aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/yaml/snakeyaml/representer
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/yaml/snakeyaml/representer')
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java161
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/Represent.java10
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/Representer.java143
-rw-r--r--src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java219
4 files changed, 533 insertions, 0 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java b/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java
new file mode 100644
index 00000000..b1c94292
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/representer/BaseRepresenter.java
@@ -0,0 +1,161 @@
+/*
+ * See LICENSE file in distribution for copyright and licensing information.
+ */
+package org.yaml.snakeyaml.representer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.SequenceNode;
+import org.yaml.snakeyaml.serializer.Serializer;
+
+public abstract class BaseRepresenter {
+ @SuppressWarnings("unchecked")
+ protected final Map<Class, Represent> representers = new HashMap<Class, Represent>();
+ /**
+ * in Java 'null' is not a type. So we have to keep the null representer
+ * separately otherwise it will coincide with the default representer which
+ * is stored with the key null.
+ */
+ protected Represent nullRepresenter;
+ @SuppressWarnings("unchecked")
+ protected final Map<Class, Represent> multiRepresenters = new HashMap<Class, Represent>();
+ private Character defaultStyle;
+ protected Boolean defaultFlowStyle;
+ protected final Map<Integer, Node> representedObjects = new HashMap<Integer, Node>();
+ private final Set<Object> objectKeeper = new HashSet<Object>();
+ protected Integer aliasKey;// internal memory address
+ protected String rootTag = null;
+
+ public BaseRepresenter(Character default_style, Boolean default_flow_style) {
+ this.defaultStyle = default_style;
+ this.defaultFlowStyle = default_flow_style;
+ }
+
+ public void represent(Serializer serializer, Object data) throws IOException {
+ Node node = representData(data);
+ serializer.serialize(node);
+ representedObjects.clear();
+ objectKeeper.clear();
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Node representData(Object data) {
+ aliasKey = System.identityHashCode(data);// take memory address
+ if (!ignoreAliases(data)) {
+ // check for identity
+ if (representedObjects.containsKey(aliasKey)) {
+ Node node = representedObjects.get(aliasKey);
+ return node;
+ }
+ }
+ // check for null first
+ if (data == null) {
+ Node node = nullRepresenter.representData(data);
+ return node;
+ }
+ // check the same class
+ Node node;
+ Class clazz = data.getClass();
+ if (representers.containsKey(clazz)) {
+ Represent representer = representers.get(clazz);
+ node = representer.representData(data);
+ } else {
+ // check the parents
+ for (Class repr : multiRepresenters.keySet()) {
+ if (repr.isInstance(data)) {
+ Represent representer = multiRepresenters.get(repr);
+ node = representer.representData(data);
+ return node;
+ }
+ }
+ // check array of primitives
+ if (clazz.isArray()) {
+ throw new YAMLException("Arrays of primitives are not fully supported.");
+ }
+ // check defaults
+ if (multiRepresenters.containsKey(null)) {
+ Represent representer = multiRepresenters.get(null);
+ node = representer.representData(data);
+ } else {
+ Represent representer = representers.get(null);
+ node = representer.representData(data);
+ }
+ }
+ return node;
+ }
+
+ protected Node representScalar(String tag, String value, Character style) {
+ if (style == null) {
+ style = this.defaultStyle;
+ }
+ Node node = new ScalarNode(tag, value, null, null, style);
+ representedObjects.put(aliasKey, node);
+ return node;
+ }
+
+ protected Node representScalar(String tag, String value) {
+ return representScalar(tag, value, null);
+ }
+
+ protected Node representSequence(String tag, List<? extends Object> sequence, Boolean flowStyle) {
+ List<Node> value = new LinkedList<Node>();
+ SequenceNode node = new SequenceNode(tag, value, flowStyle);
+ representedObjects.put(aliasKey, node);
+ boolean bestStyle = true;
+ for (Object item : sequence) {
+ Node nodeItem = representData(item);
+ if (!((nodeItem instanceof ScalarNode && ((ScalarNode) nodeItem).getStyle() == null))) {
+ bestStyle = false;
+ }
+ value.add(nodeItem);
+ }
+ if (flowStyle == null) {
+ if (defaultFlowStyle != null) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
+ }
+ return node;
+ }
+
+ protected Node representMapping(String tag, Map<? extends Object, Object> mapping,
+ Boolean flowStyle) {
+ List<Node[]> value = new LinkedList<Node[]>();
+ MappingNode node = new MappingNode(tag, value, flowStyle);
+ representedObjects.put(aliasKey, node);
+ boolean bestStyle = true;
+ for (Object itemKey : mapping.keySet()) {
+ Object itemValue = mapping.get(itemKey);
+ Node nodeKey = representData(itemKey);
+ Node nodeValue = representData(itemValue);
+ if (!((nodeKey instanceof ScalarNode && ((ScalarNode) nodeKey).getStyle() == null))) {
+ bestStyle = false;
+ }
+ if (!((nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).getStyle() == null))) {
+ bestStyle = false;
+ }
+ value.add(new Node[] { nodeKey, nodeValue });
+ }
+ if (flowStyle == null) {
+ if (defaultFlowStyle != null) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
+ }
+ return node;
+ }
+
+ protected abstract boolean ignoreAliases(Object data);
+}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/Represent.java b/src/main/java/org/yaml/snakeyaml/representer/Represent.java
new file mode 100644
index 00000000..e51e7bea
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/representer/Represent.java
@@ -0,0 +1,10 @@
+/*
+ * See LICENSE file in distribution for copyright and licensing information.
+ */
+package org.yaml.snakeyaml.representer;
+
+import org.yaml.snakeyaml.nodes.Node;
+
+public interface Represent {
+ public Node representData(Object data);
+}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/Representer.java b/src/main/java/org/yaml/snakeyaml/representer/Representer.java
new file mode 100644
index 00000000..baa4e3e5
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/representer/Representer.java
@@ -0,0 +1,143 @@
+/*
+ * See LICENSE file in distribution for copyright and licensing information.
+ */
+package org.yaml.snakeyaml.representer;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.yaml.snakeyaml.TypeDescription;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.introspector.FieldProperty;
+import org.yaml.snakeyaml.introspector.MethodProperty;
+import org.yaml.snakeyaml.introspector.Property;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+
+/**
+ * @see <a href="http://pyyaml.org/wiki/PyYAML">PyYAML</a> for more information
+ */
+public class Representer extends SafeRepresenter {
+ private Map<Class<? extends Object>, String> classTags;
+ private Map<Class<? extends Object>, TypeDescription> classDefinitions;
+
+ public Representer(Character default_style, Boolean default_flow_style) {
+ super(default_style, default_flow_style);
+ classTags = new HashMap<Class<? extends Object>, String>();
+ classDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
+ this.representers.put(null, new RepresentJavaBean());
+ }
+
+ public Representer() {
+ this(null, null);
+ }
+
+ /**
+ * Make YAML aware how to represent a custom Class. If there is no root
+ * Class assigned in constructor then the 'root' property of this definition
+ * is respected.
+ *
+ * @param definition
+ * to be added to the Constructor
+ * @return the previous value associated with <tt>definition</tt>, or
+ * <tt>null</tt> if there was no mapping for <tt>definition</tt>.
+ */
+ public TypeDescription addTypeDescription(TypeDescription definition) {
+ if (definition == null) {
+ throw new NullPointerException("ClassDescription is required.");
+ }
+ String tag = definition.getTag();
+ classTags.put(definition.getType(), tag);
+ return classDefinitions.put(definition.getType(), definition);
+ }
+
+ private class RepresentJavaBean implements Represent {
+ public Node representData(Object data) {
+ Set<Property> properties;
+ try {
+ properties = getProperties(data.getClass());
+ } catch (IntrospectionException e) {
+ throw new YAMLException(e);
+ }
+ Node node = representMapping(properties, data);
+ return node;
+ }
+ }
+
+ private Node representMapping(Set<Property> properties, Object javaBean) {
+ List<Node[]> value = new LinkedList<Node[]>();
+ String tag;
+ String customTag = classTags.get(javaBean.getClass());
+ if (customTag == null) {
+ if (rootTag == null) {
+ tag = "tag:yaml.org,2002:" + javaBean.getClass().getName();
+ } else {
+ tag = "tag:yaml.org,2002:map";
+ }
+ } else {
+ tag = customTag;
+ }
+ if (rootTag == null) {
+ rootTag = tag;
+ }
+ // flow style will be chosen by BaseRepresenter
+ MappingNode node = new MappingNode(tag, value, null);
+ representedObjects.put(aliasKey, node);
+ boolean bestStyle = true;
+ for (Property property : properties) {
+ Node nodeKey = representData(property.getName());
+ Object memberValue = property.get(javaBean);
+ Node nodeValue = representData(memberValue);
+ if (nodeValue instanceof MappingNode) {
+ if (!Map.class.isAssignableFrom(memberValue.getClass())) {
+ if (property.getType() != memberValue.getClass()) {
+ String memberTag = "tag:yaml.org,2002:" + memberValue.getClass().getName();
+ nodeValue.setTag(memberTag);
+ }
+ }
+ } else if (memberValue != null && Enum.class.isAssignableFrom(memberValue.getClass())) {
+ nodeValue.setTag("tag:yaml.org,2002:str");
+ }
+ if (!((nodeKey instanceof ScalarNode && ((ScalarNode) nodeKey).getStyle() == null))) {
+ bestStyle = false;
+ }
+ if (!((nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).getStyle() == null))) {
+ bestStyle = false;
+ }
+ value.add(new Node[] { nodeKey, nodeValue });
+ }
+ if (defaultFlowStyle != null) {
+ node.setFlowStyle(defaultFlowStyle);
+ } else {
+ node.setFlowStyle(bestStyle);
+ }
+ return node;
+ }
+
+ private Set<Property> getProperties(Class<? extends Object> type) throws IntrospectionException {
+ Set<Property> properties = new TreeSet<Property>();
+ for (PropertyDescriptor property : Introspector.getBeanInfo(type).getPropertyDescriptors())
+ if (property.getReadMethod() != null
+ && !property.getReadMethod().getName().equals("getClass")) {
+ properties.add(new MethodProperty(property));
+ }
+ for (Field field : type.getFields()) {
+ int modifiers = field.getModifiers();
+ if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)
+ || Modifier.isTransient(modifiers))
+ continue;
+ properties.add(new FieldProperty(field));
+ }
+ return properties;
+ }
+}
diff --git a/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java b/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java
new file mode 100644
index 00000000..77c4a8b2
--- /dev/null
+++ b/src/main/java/org/yaml/snakeyaml/representer/SafeRepresenter.java
@@ -0,0 +1,219 @@
+/*
+ * See LICENSE file in distribution for copyright and licensing information.
+ */
+package org.yaml.snakeyaml.representer;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.regex.Pattern;
+
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.util.Base64Coder;
+
+/**
+ * @see <a href="http://pyyaml.org/wiki/PyYAML">PyYAML</a> for more information
+ */
+class SafeRepresenter extends BaseRepresenter {
+
+ public SafeRepresenter(Character default_style, Boolean default_flow_style) {
+ super(default_style, default_flow_style);
+ this.nullRepresenter = new RepresentNull();
+ this.representers.put(String.class, new RepresentString());
+ this.representers.put(Boolean.class, new RepresentBoolean());
+ this.representers.put(Character.class, new RepresentString());
+ this.representers.put(byte[].class, new RepresentByteArray());
+ this.multiRepresenters.put(Number.class, new RepresentNumber());
+ this.multiRepresenters.put(List.class, new RepresentList());
+ this.multiRepresenters.put(Map.class, new RepresentMap());
+ this.multiRepresenters.put(Set.class, new RepresentSet());
+ this.multiRepresenters.put(new Object[0].getClass(), new RepresentArray());
+ this.multiRepresenters.put(Date.class, new RepresentDate());
+ this.multiRepresenters.put(Enum.class, new RepresentEnum());
+ }
+
+ @Override
+ protected boolean ignoreAliases(Object data) {
+ if (data == null) {
+ return true;
+ }
+ if (data instanceof Object[]) {
+ Object[] array = (Object[]) data;
+ if (array.length == 0) {
+ return true;
+ }
+ }
+ return data instanceof String || data instanceof Boolean || data instanceof Integer
+ || data instanceof Long || data instanceof Float || data instanceof Double
+ || data instanceof Enum;
+ }
+
+ private class RepresentNull implements Represent {
+ public Node representData(Object data) {
+ return representScalar("tag:yaml.org,2002:null", "null");
+ }
+ }
+
+ public static Pattern BINARY_PATTERN = Pattern.compile("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]");
+
+ private class RepresentString implements Represent {
+ public Node representData(Object data) {
+ String tag = "tag:yaml.org,2002:str";
+ Character style = null;
+ String value = data.toString();
+ if (BINARY_PATTERN.matcher(value).find()) {
+ tag = "tag:yaml.org,2002:binary";
+ char[] binary;
+ binary = Base64Coder.encode(value.getBytes());
+ value = String.valueOf(binary);
+ style = '|';
+ }
+ return representScalar(tag, value, style);
+ }
+ }
+
+ private class RepresentBoolean implements Represent {
+ public Node representData(Object data) {
+ String value;
+ if (Boolean.TRUE.equals(data)) {
+ value = "true";
+ } else {
+ value = "false";
+ }
+ return representScalar("tag:yaml.org,2002:bool", value);
+ }
+ }
+
+ private class RepresentNumber implements Represent {
+ public Node representData(Object data) {
+ String tag;
+ String value;
+ if (data instanceof Byte || data instanceof Short || data instanceof Integer
+ || data instanceof Long || data instanceof BigInteger) {
+ tag = "tag:yaml.org,2002:int";
+ value = data.toString();
+ } else {
+ Number number = (Number) data;
+ tag = "tag:yaml.org,2002:float";
+ if (number.equals(Double.NaN)) {
+ value = ".NaN";
+ } else if (number.equals(Double.POSITIVE_INFINITY)) {
+ value = ".inf";
+ } else if (number.equals(Double.NEGATIVE_INFINITY)) {
+ value = "-.inf";
+ } else {
+ value = number.toString();
+ }
+ }
+ return representScalar(tag, value);
+ }
+ }
+
+ private class RepresentList implements Represent {
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ return representSequence("tag:yaml.org,2002:seq", (List<Object>) data, null);
+ }
+ }
+
+ private class RepresentArray implements Represent {
+ public Node representData(Object data) {
+ Object[] array = (Object[]) data;
+ List<Object> list = Arrays.asList(array);
+ return representSequence("tag:yaml.org,2002:seq", list, null);
+ }
+ }
+
+ private class RepresentMap implements Represent {
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ return representMapping("tag:yaml.org,2002:map", (Map<Object, Object>) data, null);
+ }
+ }
+
+ private class RepresentSet implements Represent {
+ @SuppressWarnings("unchecked")
+ public Node representData(Object data) {
+ Map<Object, Object> value = new LinkedHashMap<Object, Object>();
+ Set<Object> set = (Set<Object>) data;
+ for (Object key : set) {
+ value.put(key, null);
+ }
+ return representMapping("tag:yaml.org,2002:set", value, null);
+ }
+ }
+
+ private class RepresentDate implements Represent {
+ public Node representData(Object data) {
+ // because SimpleDateFormat ignores timezone we have to use Calendar
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.setTime((Date) data);
+ int years = calendar.get(Calendar.YEAR);
+ int months = calendar.get(Calendar.MONTH) + 1; // 0..12
+ int days = calendar.get(Calendar.DAY_OF_MONTH); // 1..31
+ int hour24 = calendar.get(Calendar.HOUR_OF_DAY); // 0..24
+ int minutes = calendar.get(Calendar.MINUTE); // 0..59
+ int seconds = calendar.get(Calendar.SECOND); // 0..59
+ int millis = calendar.get(Calendar.MILLISECOND);
+ StringBuffer buffer = new StringBuffer(String.valueOf(years));
+ buffer.append("-");
+ if (months < 10) {
+ buffer.append("0");
+ }
+ buffer.append(String.valueOf(months));
+ buffer.append("-");
+ if (days < 10) {
+ buffer.append("0");
+ }
+ buffer.append(String.valueOf(days));
+ buffer.append("T");
+ if (hour24 < 10) {
+ buffer.append("0");
+ }
+ buffer.append(String.valueOf(hour24));
+ buffer.append(":");
+ if (minutes < 10) {
+ buffer.append("0");
+ }
+ buffer.append(String.valueOf(minutes));
+ buffer.append(":");
+ if (seconds < 10) {
+ buffer.append("0");
+ }
+ buffer.append(String.valueOf(seconds));
+ if (millis > 0) {
+ if (millis < 10) {
+ buffer.append(".00");
+ } else if (millis < 100) {
+ buffer.append(".0");
+ } else {
+ buffer.append(".");
+ }
+ buffer.append(String.valueOf(millis));
+ }
+ buffer.append("Z");
+ return representScalar("tag:yaml.org,2002:timestamp", buffer.toString(), null);
+ }
+ }
+
+ private class RepresentEnum implements Represent {
+ public Node representData(Object data) {
+ String tag = "tag:yaml.org,2002:" + data.getClass().getName();
+ return representScalar(tag, data.toString());
+ }
+ }
+
+ private class RepresentByteArray implements Represent {
+ public Node representData(Object data) {
+ String tag = "tag:yaml.org,2002:binary";
+ char[] binary = Base64Coder.encode((byte[]) data);
+ return representScalar(tag, String.valueOf(binary), '|');
+ }
+ }
+} \ No newline at end of file