All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.esotericsoftware.yamlbeans.YamlWriter Maven / Gradle / Ivy

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright (c) 2008 Nathan Sweet
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.esotericsoftware.yamlbeans;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.esotericsoftware.yamlbeans.Beans.Property;
import com.esotericsoftware.yamlbeans.YamlConfig.WriteClassName;
import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig;
import com.esotericsoftware.yamlbeans.document.YamlElement;
import com.esotericsoftware.yamlbeans.emitter.Emitter;
import com.esotericsoftware.yamlbeans.emitter.EmitterException;
import com.esotericsoftware.yamlbeans.parser.AliasEvent;
import com.esotericsoftware.yamlbeans.parser.DocumentEndEvent;
import com.esotericsoftware.yamlbeans.parser.DocumentStartEvent;
import com.esotericsoftware.yamlbeans.parser.Event;
import com.esotericsoftware.yamlbeans.parser.MappingStartEvent;
import com.esotericsoftware.yamlbeans.parser.ScalarEvent;
import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent;
import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer;

/** Serializes Java objects as YAML.
 * @author Nathan Sweet */
public class YamlWriter implements AutoCloseable {
	private final YamlConfig config;
	private final Emitter emitter;
	private boolean started;

	private Map defaultValuePrototypes = new IdentityHashMap();

	private final List queuedObjects = new ArrayList();
	private final Map referenceCount = new IdentityHashMap();
	private final Map anchoredObjects = new HashMap();
	private int nextAnchor = 1;
	private boolean isRoot;

	public YamlWriter (Writer writer) {
		this(writer, new YamlConfig());
	}

	public YamlWriter (Writer writer, YamlConfig config) {
		this.config = config;
		emitter = new Emitter(writer, config.writeConfig.emitterConfig);
	}

	public void setAlias (Object object, String alias) {
		anchoredObjects.put(object, alias);
	}

	public void write (Object object) throws YamlException {
		if (config.writeConfig.autoAnchor) {
			countObjectReferences(object);
			queuedObjects.add(object);
			return;
		}
		writeInternal(object);
	}

	public YamlConfig getConfig () {
		return config;
	}

	private void writeInternal (Object object) throws YamlException {
		try {
			if (!started) {
				emitter.emit(Event.STREAM_START);
				started = true;
			}
			emitter.emit(new DocumentStartEvent(config.writeConfig.explicitFirstDocument, config.writeConfig.version,
					config.writeConfig.tags));
			isRoot = true;
			writeValue(object, config.writeConfig.writeRootTags ? null : object.getClass(), null, null);
			emitter.emit(new DocumentEndEvent(config.writeConfig.explicitEndDocument));
		} catch (EmitterException ex) {
			throw new YamlException("Error writing YAML.", ex);
		} catch (IOException ex) {
			throw new YamlException("Error writing YAML.", ex);
		}
	}

	/** Writes any buffered objects, then resets the list of anchored objects.
	 * @see WriteConfig#setAutoAnchor(boolean) */
	public void clearAnchors () throws YamlException {
		for (Object object : queuedObjects)
			writeInternal(object);
		queuedObjects.clear();
		referenceCount.clear();
		nextAnchor = 1;
	}

	/** Finishes writing any buffered output and releases all resources.
	 * @throws YamlException If the buffered output could not be written or the writer could not be closed. */
	public void close () throws YamlException {
		clearAnchors();
		defaultValuePrototypes.clear();
		try {
			emitter.emit(Event.STREAM_END);
			emitter.close();
		} catch (EmitterException ex) {
			throw new YamlException(ex);
		} catch (IOException ex) {
			throw new YamlException(ex);
		}
	}

	private void writeValue (Object object, Class fieldClass, Class elementType, Class defaultType)
		throws EmitterException, IOException, YamlException {
		boolean isRoot = this.isRoot;
		this.isRoot = false;

		if (object instanceof YamlElement) {
			((YamlElement)object).emitEvent(emitter, config.writeConfig);
			return;
		} else if (object == null) {
			emitter.emit(
					new ScalarEvent(null, null, new boolean[] { true, true }, null, this.config.writeConfig.quote.c));
			return;
		}

		Class valueClass = object.getClass();
		boolean unknownType = fieldClass == null;
		if (unknownType) fieldClass = valueClass;

		String anchor = null;
		if (!Beans.isScalar(valueClass) && !(object instanceof Enum)) {
			anchor = anchoredObjects.get(object);
			if (config.writeConfig.autoAnchor) {
				Integer count = referenceCount.get(object);
				if (count == null) {
					emitter.emit(new AliasEvent(anchor));
					return;
				}
				if (count > 1) {
					referenceCount.remove(object);
					if (anchor == null) {
						anchor = String.valueOf(nextAnchor++);
						anchoredObjects.put(object, anchor);
					}
				}
			}
		}

		String tag = null;
		boolean showTag = false;
		if ((unknownType || valueClass != fieldClass || config.writeConfig.writeClassName == WriteClassName.ALWAYS)
			&& config.writeConfig.writeClassName != WriteClassName.NEVER) {
			showTag = true;
			if ((unknownType || fieldClass == List.class) && valueClass == ArrayList.class) showTag = false;
			if ((unknownType || fieldClass == Map.class) && valueClass == HashMap.class) showTag = false;
			if (fieldClass == Set.class && valueClass == HashSet.class) showTag = false;
			if (valueClass == defaultType) showTag = false;
			if (showTag) {
				tag = config.classNameToTag.get(valueClass.getName());
				if (tag == null) tag = valueClass.getName();
			}
		}

		for (Entry entry : config.scalarSerializers.entrySet()) {
			if (entry.getKey().isAssignableFrom(valueClass)) {
				ScalarSerializer serializer = entry.getValue();
				emitter.emit(new ScalarEvent(null, tag, new boolean[] { tag == null, tag == null },
						serializer.write(object), this.config.writeConfig.quote.c));
				return;
			}
		}

		if (Beans.isScalar(valueClass)) {
			emitter.emit(new ScalarEvent(null, tag, new boolean[] { true, true }, String.valueOf(object),
					this.config.writeConfig.quote.c));
			return;
		}

		if (object instanceof Enum) {
			emitter.emit(new ScalarEvent(null, object.getClass().getName(),
					new boolean[] { object.getClass().equals(fieldClass), object.getClass().equals(fieldClass) },
					((Enum) object).name(), this.config.writeConfig.quote.c));
			return;
		}

		if (object instanceof Collection) {
			emitter.emit(new SequenceStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle()));
			for (Object item : (Collection)object) {
				if (isRoot && !config.writeConfig.writeRootElementTags) elementType = item.getClass();
				writeValue(item, elementType, null, null);
			}
			emitter.emit(Event.SEQUENCE_END);
			return;
		}

		if (object instanceof Map) {
			emitter.emit(new MappingStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle()));
			Map map = (Map)object;
			for (Object item : map.entrySet()) {
				Entry entry = (Entry)item;
				Object key = entry.getKey(), value = entry.getValue();
				if (isRoot && !config.writeConfig.writeRootElementTags) elementType = value.getClass();
				if (config.tagSuffix != null && key instanceof String) {
					// Skip tag keys.
					if (((String)key).endsWith(config.tagSuffix)) continue;

					// Write value with tag, if found.
					if (value instanceof String) {
						Object valueTag = map.get(key + config.tagSuffix);
						if (valueTag instanceof String) {
							writeValue(key, null, null, null);
							emitter.emit(new ScalarEvent(null, (String) valueTag, new boolean[] { false, false },
									(String) value, this.config.writeConfig.quote.c));
							continue;
						}
					}
				}
				writeValue(key, null, null, null);
				writeValue(value, elementType, null, null);
			}
			emitter.emit(Event.MAPPING_END);
			return;
		}

		if (fieldClass.isArray()) {
			elementType = fieldClass.getComponentType();
			emitter.emit(new SequenceStartEvent(anchor, null, true, config.writeConfig.isFlowStyle()));
			for (int i = 0, n = Array.getLength(object); i < n; i++)
				writeValue(Array.get(object, i), elementType, null, null);
			emitter.emit(Event.SEQUENCE_END);
			return;
		}

		// Value must be a bean.

		Object prototype = null;
		if (!config.writeConfig.writeDefaultValues && valueClass != Class.class) {
			prototype = defaultValuePrototypes.get(valueClass);
			if (prototype == null && Beans.getDeferredConstruction(valueClass, config) == null) {
				try {
					prototype = Beans.createObject(valueClass, config.privateConstructors);
				} catch (InvocationTargetException ex) {
					throw new YamlException("Error creating object prototype to determine default values.", ex);
				}
				defaultValuePrototypes.put(valueClass, prototype);
			}
		}

		Set properties = Beans.getProperties(valueClass, config.beanProperties, config.privateFields, config);
		emitter.emit(new MappingStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle()));
		for (Property property : properties) {
			try {
				Object propertyValue = property.get(object);
				if (prototype != null) {
					// Don't output properties that have the default value for the prototype.
					Object prototypeValue = property.get(prototype);
					if (propertyValue == null && prototypeValue == null) continue;
					if (propertyValue != null && prototypeValue != null && prototypeValue.equals(propertyValue)) continue;
				}
				emitter.emit(
					new ScalarEvent(null, null, new boolean[] {true, true}, property.getName(), this.config.writeConfig.quote.c));
				Class propertyElementType = config.propertyToElementType.get(property);
				Class propertyDefaultType = config.propertyToDefaultType.get(property);
				writeValue(propertyValue, property.getType(), propertyElementType, propertyDefaultType);
			} catch (Exception ex) {
				throw new YamlException("Error getting property '" + property + "' on class: " + valueClass.getName(), ex);
			}
		}
		emitter.emit(Event.MAPPING_END);
	}

	private void countObjectReferences (Object object) throws YamlException {
		if (object == null || Beans.isScalar(object.getClass())) return;

		// Count every reference to the object, but follow its own references the first time it is encountered.
		Integer count = referenceCount.get(object);
		if (count != null) {
			referenceCount.put(object, count + 1);
			return;
		}
		referenceCount.put(object, 1);

		if (object instanceof Collection) {
			for (Object item : (Collection)object)
				countObjectReferences(item);
			return;
		}

		if (object instanceof Map) {
			for (Object value : ((Map)object).values())
				countObjectReferences(value);
			return;
		}

		if (object.getClass().isArray()) {
			for (int i = 0, n = Array.getLength(object); i < n; i++)
				countObjectReferences(Array.get(object, i));
			return;
		}

		// Value must be an object.

		Set properties = Beans.getProperties(object.getClass(), config.beanProperties, config.privateFields, config);
		for (Property property : properties) {
			if (Beans.isScalar(property.getType())) continue;
			Object propertyValue;
			try {
				propertyValue = property.get(object);
			} catch (Exception ex) {
				throw new YamlException("Error getting property '" + property + "' on class: " + object.getClass().getName(), ex);
			}
			countObjectReferences(propertyValue);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy