<commit_before>package org.exist.util.serializer.json;

import java.io.IOException;
import java.io.Writer;

public class JSONObject extends JSONNode {
	
	private JSONNode firstChild = null;
	
	public JSONObject(String name) {
		super(Type.OBJECT_TYPE, name);
	}
	
	public void addObject(JSONNode node) {
		JSONNode childNode = findChild(node.getName());
		if (childNode == null) {
			childNode = getLastChild();
			if (childNode == null)
				firstChild = node;
			else
				childNode.setNext(node);
		} else
			childNode.setNextOfSame(node);
	}
	
	public JSONNode findChild(String nameToFind) {
		JSONNode nextNode = firstChild;
		while (nextNode != null) {
			if (nextNode.getName().equals(nameToFind))
				return nextNode;
			nextNode = nextNode.getNext();
		}
		return null;
	}
	
	public JSONNode getLastChild() {
		JSONNode nextNode = firstChild;
		JSONNode currentNode = null;
		while (nextNode != null) {
			currentNode = nextNode;
			nextNode = currentNode.getNext();
		}
		return currentNode;
	}
	
	public int getChildCount() {
		int count = 0;
		JSONNode nextNode = firstChild;
		while (nextNode != null) {
			count++;
			nextNode = nextNode.getNext();
		}
		return count;
	}
	
	public void serialize(Writer writer, boolean isRoot) throws IOException {
		if (!isRoot) {
			writer.write('"');
			writer.write(getName());
			writer.write("\" : ");
		}
		if (getNextOfSame() != null || getSerializationType() == SerializationType.AS_ARRAY) {
			writer.write("[");
			JSONNode next = this;
			while (next != null) {
				next.serializeContent(writer);
				next = next.getNextOfSame();
				if (next != null)
					writer.write(", ");
			}
			writer.write("]");
		} else
			serializeContent(writer);
	}
	
	public void serializeContent(Writer writer) throws IOException {
		if (firstChild == null)
			// an empty node gets a null value
			writer.write("null");
		else if (firstChild.getNext() == null && firstChild.getType() == Type.VALUE_TYPE)
			// if there's only one child and if it is text, it is serialized as simple value
			firstChild.serialize(writer, false);
		else {
			// complex object
			writer.write("{ ");
			boolean allowText = false;
			JSONNode next = firstChild;
			while (next != null) {
				if (next.getType() == Type.VALUE_TYPE) {
					// if an element has attributes and text content, the text
					// node is serialized as property "#text". Text in mixed content nodes
					// is ignored though.
					if (allowText) {
						writer.write("\"#text\" : ");
						next.serialize(writer, false);
						allowText = false;
					}
				} else
					next.serialize(writer, false);
				if (next.getType() == Type.SIMPLE_PROPERTY_TYPE)
					allowText = true;
				next = next.getNext();
				if (next != null)
					writer.write(", ");
			}
			writer.write("} ");
		}
	}
}<commit_msg>[bugfix] org.exist.util.serializer.JSONObject.java - produced invalid json with preserve-whitespace-mixed-content=yes in conf.xml. Port of rev 14112.<commit_after>/*
 * eXist Open Source Native XML Database
 * Copyright (C) 2011 The eXist Project
 * http://exist-db.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *  
 *  $Id$
 */
package org.exist.util.serializer.json;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

public class JSONObject extends JSONNode {
	
	protected JSONNode firstChild = null;
	private boolean asSimpleValue = false;
	
	public JSONObject() {
		super(Type.OBJECT_TYPE, ANONYMOUS_OBJECT);
	}
	
	public JSONObject(String name) {
		super(Type.OBJECT_TYPE, name);
	}
	
	public JSONObject(String name, boolean asSimpleValue) {
		super(Type.OBJECT_TYPE, name);
		this.asSimpleValue = asSimpleValue;
	}

	public void addObject(JSONNode node) {
		JSONNode childNode = findChild(node.getName());
		if (childNode == null) {
			childNode = getLastChild();
			if (childNode == null)
				firstChild = node;
			else
				childNode.setNext(node);
		} else
			childNode.setNextOfSame(node);
	}
	
	public JSONNode findChild(String nameToFind) {
		JSONNode nextNode = firstChild;
		while (nextNode != null) {
			if (nextNode.getName().equals(nameToFind))
				return nextNode;
			nextNode = nextNode.getNext();
		}
		return null;
	}
	
	public JSONNode getLastChild() {
		JSONNode nextNode = firstChild;
		JSONNode currentNode = null;
		while (nextNode != null) {
			currentNode = nextNode;
			nextNode = currentNode.getNext();
		}
		return currentNode;
	}
	
	public int getChildCount() {
		int count = 0;
		JSONNode nextNode = firstChild;
		while (nextNode != null) {
			count++;
			nextNode = nextNode.getNext();
		}
		return count;
	}
	
	public void serialize(Writer writer, boolean isRoot) throws IOException {
		if (!isRoot && isNamed()) {
			writer.write('"');
			writer.write(getName());
			writer.write("\" : ");
		}
		if (getNextOfSame() != null || getSerializationType() == SerializationType.AS_ARRAY) {
			writer.write("[");
			JSONNode next = this;
			while (next != null) {
				next.serializeContent(writer);
				next = next.getNextOfSame();
				if (next != null)
					writer.write(", ");
			}
			writer.write("]");
		} else
			serializeContent(writer);
	}
	
	public void serializeContent(Writer writer) throws IOException {
		if (firstChild == null)
			// an empty node gets a null value
			writer.write("null");
		else if (firstChild.getNext() == null && 
					(
						firstChild.getType() == Type.VALUE_TYPE ||
						(firstChild.isArray() && !firstChild.isNamed())
					)
				)
			// if there's only one child and if it is text, it is serialized as simple value
			firstChild.serialize(writer, false);
		else {
			// complex object
			writer.write("{ ");
			
			JSONNode next = firstChild;
			boolean allowText = false;
			boolean skipMixedContentText = false;
			while (next != null) {
				if (next.getType() == Type.VALUE_TYPE) {
					// if an element has attributes and text content, the text
					// node is serialized as property "#text". 
                    // Text in mixed content nodes is ignored though.
					if (allowText) {
						writer.write("\"#text\" : ");
						next.serialize(writer, false);
						allowText = false;
					} else {
                        //writer.write("\"#mixed-content-text\" : ");
                        skipMixedContentText = true;
                    }
				} else
					next.serialize(writer, false);
				if (next.getType() == Type.SIMPLE_PROPERTY_TYPE)
					allowText = true;
				next = next.getNext();
				if (next != null && !skipMixedContentText && !isMixedContentTextLast(next, allowText)) {
					writer.write(", ");
                }
                skipMixedContentText = false;
			}
			
			writer.write(" }");
		}
	}
 
   private boolean isMixedContentTextLast(final JSONNode node, final boolean allowText) {
       if (node.getType() == Type.VALUE_TYPE && !allowText && node.equals(getLastChild()))
            return true;
        return false;
    }

	@Override
	public String toString() {
		StringWriter writer = new StringWriter();
		try {
			serialize(writer, false);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return writer.toString();
	}
	
	
}
{'fraction_non_alphanumeric': 0.12124726195077953, 'fraction_numerical': 0.0029635356268522097, 'mean_word_length': 3.104706504494976, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 1}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1080248', 'n_tokens_mistral': 2759, 'n_tokens_neox': 2323, 'n_words': 675}