diff options
author | Andrey Somov <public.somov@gmail.com> | 2022-09-10 12:54:02 +0300 |
---|---|---|
committer | Andrey Somov <public.somov@gmail.com> | 2022-09-10 12:54:02 +0300 |
commit | 1c934251f9de51ce1e86bb80a86f46d235744274 (patch) | |
tree | 7f76ebc000d1d2d9a9582660d333079f053367b0 | |
parent | 5056a448f09c46250346c338e821386caa751182 (diff) | |
download | snakeyaml-1c934251f9de51ce1e86bb80a86f46d235744274.tar.gz |
Reformat constructor package
8 files changed, 1727 insertions, 1699 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java b/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java index d302bd89..6f4a1183 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/AbstractConstruct.java @@ -24,18 +24,18 @@ import org.yaml.snakeyaml.nodes.Node; */ public abstract class AbstractConstruct implements Construct { - /** - * Fail with a reminder to provide the seconds step for a recursive - * structure - * - * @see org.yaml.snakeyaml.constructor.Construct#construct2ndStep(org.yaml.snakeyaml.nodes.Node, - * java.lang.Object) - */ - public void construct2ndStep(Node node, Object data) { - if (node.isTwoStepsConstruction()) { - throw new IllegalStateException("Not Implemented in " + getClass().getName()); - } else { - throw new YAMLException("Unexpected recursive structure for Node: " + node); - } + /** + * Fail with a reminder to provide the seconds step for a recursive + * structure + * + * @see org.yaml.snakeyaml.constructor.Construct#construct2ndStep(org.yaml.snakeyaml.nodes.Node, + * java.lang.Object) + */ + public void construct2ndStep(Node node, Object data) { + if (node.isTwoStepsConstruction()) { + throw new IllegalStateException("Not Implemented in " + getClass().getName()); + } else { + throw new YAMLException("Unexpected recursive structure for Node: " + node); } + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java index 454ea4c9..485b6f81 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java @@ -32,7 +32,6 @@ import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; - import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.TypeDescription; import org.yaml.snakeyaml.composer.Composer; @@ -49,568 +48,573 @@ import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; public abstract class BaseConstructor { - /** - * It maps the node kind to the the Construct implementation. When the - * runtime class is known then the implicit tag is ignored. - */ - protected final Map<NodeId, Construct> yamlClassConstructors = new EnumMap<NodeId, Construct>( - NodeId.class); - /** - * It maps the (explicit or implicit) tag to the Construct implementation. - * It is used: - * 1) explicit tag - if present. - * 2) implicit tag - when the runtime class of the instance is unknown (the - * node has the Object.class) - */ - protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>(); - /** - * It maps the (explicit or implicit) tag to the Construct implementation. - * It is used when no exact match found. - */ - protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>(); - - protected Composer composer; - final Map<Node, Object> constructedObjects; - private final Set<Node> recursiveObjects; - private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill; - private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill; - - protected Tag rootTag; - private PropertyUtils propertyUtils; - private boolean explicitPropertyUtils; - private boolean allowDuplicateKeys = true; - private boolean wrappedToRootException = false; - - private boolean enumCaseSensitive = false; - - protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions; - protected final Map<Tag, Class<? extends Object>> typeTags; - - protected LoaderOptions loadingConfig; - - public BaseConstructor() { - this(new LoaderOptions()); - } - - public BaseConstructor(LoaderOptions loadingConfig) { - constructedObjects = new HashMap<Node, Object>(); - recursiveObjects = new HashSet<Node>(); - maps2fill = new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>(); - sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>(); - typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>(); - typeTags = new HashMap<Tag, Class<? extends Object>>(); - - rootTag = null; - explicitPropertyUtils = false; - - typeDefinitions.put(SortedMap.class, new TypeDescription(SortedMap.class, Tag.OMAP, - TreeMap.class)); - typeDefinitions.put(SortedSet.class, new TypeDescription(SortedSet.class, Tag.SET, - TreeSet.class)); - this.loadingConfig = loadingConfig; - } - - public void setComposer(Composer composer) { - this.composer = composer; - } - - /** - * Check if more documents available - * - * @return true when there are more YAML documents in the stream - */ - public boolean checkData() { - // If there are more documents available? - return composer.checkNode(); - } - /** - * Construct and return the next document - * - * @return constructed instance - */ - public Object getData() throws NoSuchElementException { - // Construct and return the next document. - if (!composer.checkNode()) throw new NoSuchElementException("No document is available."); - Node node = composer.getNode(); - if (rootTag != null) { - node.setTag(rootTag); - } - return constructDocument(node); - } - - /** - * Ensure that the stream contains a single document and construct it - * - * @param type the class of the instance being created - * @return constructed instance - * @throws ComposerException in case there are more documents in the stream - */ - public Object getSingleData(Class<?> type) { - // Ensure that the stream contains a single document and construct it - final Node node = composer.getSingleNode(); - if (node != null && !Tag.NULL.equals(node.getTag())) { - if (Object.class != type) { - node.setTag(new Tag(type)); - } else if (rootTag != null) { - node.setTag(rootTag); - } - return constructDocument(node); - } else { - Construct construct = yamlConstructors.get(Tag.NULL); - return construct.construct(node); - } - } - - /** - * Construct complete YAML document. Call the second step in case of - * recursive structures. At the end cleans all the state. - * - * @param node root Node - * @return Java instance - */ - protected final Object constructDocument(Node node) { - try { - Object data = constructObject(node); - fillRecursive(); - return data; - } catch (RuntimeException e) { - if (wrappedToRootException && !(e instanceof YAMLException)) { - throw new YAMLException(e); - } else { - throw e; - } - } finally { - //clean up resources - constructedObjects.clear(); - recursiveObjects.clear(); - } - } - - /** - * Fill the recursive structures and clean the internal collections - */ - private void fillRecursive() { - if (!maps2fill.isEmpty()) { - for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) { - RecursiveTuple<Object, Object> key_value = entry._2(); - entry._1().put(key_value._1(), key_value._2()); - } - maps2fill.clear(); - } - if (!sets2fill.isEmpty()) { - for (RecursiveTuple<Set<Object>, Object> value : sets2fill) { - value._1().add(value._2()); - } - sets2fill.clear(); - } - } - - /** - * Construct object from the specified Node. Return existing instance if the - * node is already constructed. - * - * @param node Node to be constructed - * @return Java instance - */ - protected Object constructObject(Node node) { + /** + * It maps the node kind to the the Construct implementation. When the + * runtime class is known then the implicit tag is ignored. + */ + protected final Map<NodeId, Construct> yamlClassConstructors = new EnumMap<NodeId, Construct>( + NodeId.class); + /** + * It maps the (explicit or implicit) tag to the Construct implementation. + * It is used: + * 1) explicit tag - if present. + * 2) implicit tag - when the runtime class of the instance is unknown (the + * node has the Object.class) + */ + protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>(); + /** + * It maps the (explicit or implicit) tag to the Construct implementation. + * It is used when no exact match found. + */ + protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>(); + + protected Composer composer; + final Map<Node, Object> constructedObjects; + private final Set<Node> recursiveObjects; + private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill; + private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill; + + protected Tag rootTag; + private PropertyUtils propertyUtils; + private boolean explicitPropertyUtils; + private boolean allowDuplicateKeys = true; + private boolean wrappedToRootException = false; + + private boolean enumCaseSensitive = false; + + protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions; + protected final Map<Tag, Class<? extends Object>> typeTags; + + protected LoaderOptions loadingConfig; + + public BaseConstructor() { + this(new LoaderOptions()); + } + + public BaseConstructor(LoaderOptions loadingConfig) { + constructedObjects = new HashMap<Node, Object>(); + recursiveObjects = new HashSet<Node>(); + maps2fill = new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>(); + sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>(); + typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>(); + typeTags = new HashMap<Tag, Class<? extends Object>>(); + + rootTag = null; + explicitPropertyUtils = false; + + typeDefinitions.put(SortedMap.class, new TypeDescription(SortedMap.class, Tag.OMAP, + TreeMap.class)); + typeDefinitions.put(SortedSet.class, new TypeDescription(SortedSet.class, Tag.SET, + TreeSet.class)); + this.loadingConfig = loadingConfig; + } + + public void setComposer(Composer composer) { + this.composer = composer; + } + + /** + * Check if more documents available + * + * @return true when there are more YAML documents in the stream + */ + public boolean checkData() { + // If there are more documents available? + return composer.checkNode(); + } + + /** + * Construct and return the next document + * + * @return constructed instance + */ + public Object getData() throws NoSuchElementException { + // Construct and return the next document. + if (!composer.checkNode()) { + throw new NoSuchElementException("No document is available."); + } + Node node = composer.getNode(); + if (rootTag != null) { + node.setTag(rootTag); + } + return constructDocument(node); + } + + /** + * Ensure that the stream contains a single document and construct it + * + * @param type the class of the instance being created + * @return constructed instance + * @throws ComposerException in case there are more documents in the stream + */ + public Object getSingleData(Class<?> type) { + // Ensure that the stream contains a single document and construct it + final Node node = composer.getSingleNode(); + if (node != null && !Tag.NULL.equals(node.getTag())) { + if (Object.class != type) { + node.setTag(new Tag(type)); + } else if (rootTag != null) { + node.setTag(rootTag); + } + return constructDocument(node); + } else { + Construct construct = yamlConstructors.get(Tag.NULL); + return construct.construct(node); + } + } + + /** + * Construct complete YAML document. Call the second step in case of + * recursive structures. At the end cleans all the state. + * + * @param node root Node + * @return Java instance + */ + protected final Object constructDocument(Node node) { + try { + Object data = constructObject(node); + fillRecursive(); + return data; + } catch (RuntimeException e) { + if (wrappedToRootException && !(e instanceof YAMLException)) { + throw new YAMLException(e); + } else { + throw e; + } + } finally { + //clean up resources + constructedObjects.clear(); + recursiveObjects.clear(); + } + } + + /** + * Fill the recursive structures and clean the internal collections + */ + private void fillRecursive() { + if (!maps2fill.isEmpty()) { + for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) { + RecursiveTuple<Object, Object> key_value = entry._2(); + entry._1().put(key_value._1(), key_value._2()); + } + maps2fill.clear(); + } + if (!sets2fill.isEmpty()) { + for (RecursiveTuple<Set<Object>, Object> value : sets2fill) { + value._1().add(value._2()); + } + sets2fill.clear(); + } + } + + /** + * Construct object from the specified Node. Return existing instance if the + * node is already constructed. + * + * @param node Node to be constructed + * @return Java instance + */ + protected Object constructObject(Node node) { // System.out.println(" <<<< " + node.getAnchor() + " : " + node.getTag() + " : " // + System.identityHashCode(node)); - if (constructedObjects.containsKey(node)) { - return constructedObjects.get(node); - } - return constructObjectNoCheck(node); - } - - protected Object constructObjectNoCheck(Node node) { - recursiveObjects.add(node); - Construct constructor = getConstructor(node); - Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node) - : constructor.construct(node); - - finalizeConstruction(node, data); - constructedObjects.put(node, data); - if (node.isTwoStepsConstruction()) { - constructor.construct2ndStep(node, data); - } - recursiveObjects.remove(node); - return data; - } - - /** - * Get the constructor to construct the Node. For implicit tags if the - * runtime class is known a dedicated Construct implementation is used. - * Otherwise the constructor is chosen by the tag. - * - * @param node {@link Node} to construct an instance from - * @return {@link Construct} implementation for the specified node - */ - protected Construct getConstructor(Node node) { - if (node.useClassConstructor()) { - return yamlClassConstructors.get(node.getNodeId()); - } else { - Construct constructor = yamlConstructors.get(node.getTag()); - if (constructor == null) { - for (String prefix : yamlMultiConstructors.keySet()) { - if (node.getTag().startsWith(prefix)) { - return yamlMultiConstructors.get(prefix); - } - } - return yamlConstructors.get(null); - } - return constructor; - } - } - - protected String constructScalar(ScalarNode node) { - return node.getValue(); - } - - // >>>> DEFAULTS >>>> - protected List<Object> createDefaultList(int initSize) { - return new ArrayList<Object>(initSize); - } - - protected Set<Object> createDefaultSet(int initSize) { - return new LinkedHashSet<Object>(initSize); - } - - protected Map<Object, Object> createDefaultMap(int initSize) { - // respect order from YAML document - return new LinkedHashMap<Object, Object>(initSize); - } - - protected Object createArray(Class<?> type, int size) { - return Array.newInstance(type.getComponentType(), size); - } - - // <<<< DEFAULTS <<<< - - protected Object finalizeConstruction(Node node, Object data) { - final Class<? extends Object> type = node.getType(); - if (typeDefinitions.containsKey(type)) { - return typeDefinitions.get(type).finalizeConstruction(data); - } - return data; - } - - // >>>> NEW instance - protected Object newInstance(Node node) { - try { - return newInstance(Object.class, node); - } catch (InstantiationException e) { - throw new YAMLException(e); - } - } - - final protected Object newInstance(Class<?> ancestor, Node node) throws InstantiationException { - return newInstance(ancestor, node, true); - } - - protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) - throws InstantiationException { - final Class<? extends Object> type = node.getType(); - if (typeDefinitions.containsKey(type)) { - TypeDescription td = typeDefinitions.get(type); - final Object instance = td.newInstance(node); - if (instance != null) { - return instance; - } - } - if (tryDefault) { - /* - * Removed <code> have InstantiationException in case of abstract - * type - */ - if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) { - try { - java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor(); - c.setAccessible(true); - return c.newInstance(); - } catch (NoSuchMethodException e) { - throw new InstantiationException("NoSuchMethodException:" - + e.getLocalizedMessage()); - } catch (Exception e) { - throw new YAMLException(e); - } - } - } - throw new InstantiationException(); - } - - @SuppressWarnings("unchecked") - protected Set<Object> newSet(CollectionNode<?> node) { - try { - return (Set<Object>) newInstance(Set.class, node); - } catch (InstantiationException e) { - return createDefaultSet(node.getValue().size()); - } - } - - @SuppressWarnings("unchecked") - protected List<Object> newList(SequenceNode node) { - try { - return (List<Object>) newInstance(List.class, node); - } catch (InstantiationException e) { - return createDefaultList(node.getValue().size()); + if (constructedObjects.containsKey(node)) { + return constructedObjects.get(node); + } + return constructObjectNoCheck(node); + } + + protected Object constructObjectNoCheck(Node node) { + recursiveObjects.add(node); + Construct constructor = getConstructor(node); + Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node) + : constructor.construct(node); + + finalizeConstruction(node, data); + constructedObjects.put(node, data); + if (node.isTwoStepsConstruction()) { + constructor.construct2ndStep(node, data); + } + recursiveObjects.remove(node); + return data; + } + + /** + * Get the constructor to construct the Node. For implicit tags if the + * runtime class is known a dedicated Construct implementation is used. + * Otherwise the constructor is chosen by the tag. + * + * @param node {@link Node} to construct an instance from + * @return {@link Construct} implementation for the specified node + */ + protected Construct getConstructor(Node node) { + if (node.useClassConstructor()) { + return yamlClassConstructors.get(node.getNodeId()); + } else { + Construct constructor = yamlConstructors.get(node.getTag()); + if (constructor == null) { + for (String prefix : yamlMultiConstructors.keySet()) { + if (node.getTag().startsWith(prefix)) { + return yamlMultiConstructors.get(prefix); + } } - } - - @SuppressWarnings("unchecked") - protected Map<Object, Object> newMap(MappingNode node) { + return yamlConstructors.get(null); + } + return constructor; + } + } + + protected String constructScalar(ScalarNode node) { + return node.getValue(); + } + + // >>>> DEFAULTS >>>> + protected List<Object> createDefaultList(int initSize) { + return new ArrayList<Object>(initSize); + } + + protected Set<Object> createDefaultSet(int initSize) { + return new LinkedHashSet<Object>(initSize); + } + + protected Map<Object, Object> createDefaultMap(int initSize) { + // respect order from YAML document + return new LinkedHashMap<Object, Object>(initSize); + } + + protected Object createArray(Class<?> type, int size) { + return Array.newInstance(type.getComponentType(), size); + } + + // <<<< DEFAULTS <<<< + + protected Object finalizeConstruction(Node node, Object data) { + final Class<? extends Object> type = node.getType(); + if (typeDefinitions.containsKey(type)) { + return typeDefinitions.get(type).finalizeConstruction(data); + } + return data; + } + + // >>>> NEW instance + protected Object newInstance(Node node) { + try { + return newInstance(Object.class, node); + } catch (InstantiationException e) { + throw new YAMLException(e); + } + } + + final protected Object newInstance(Class<?> ancestor, Node node) throws InstantiationException { + return newInstance(ancestor, node, true); + } + + protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) + throws InstantiationException { + final Class<? extends Object> type = node.getType(); + if (typeDefinitions.containsKey(type)) { + TypeDescription td = typeDefinitions.get(type); + final Object instance = td.newInstance(node); + if (instance != null) { + return instance; + } + } + if (tryDefault) { + /* + * Removed <code> have InstantiationException in case of abstract + * type + */ + if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) { try { - return (Map<Object, Object>) newInstance(Map.class, node); - } catch (InstantiationException e) { - return createDefaultMap(node.getValue().size()); + java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor(); + c.setAccessible(true); + return c.newInstance(); + } catch (NoSuchMethodException e) { + throw new InstantiationException("NoSuchMethodException:" + + e.getLocalizedMessage()); + } catch (Exception e) { + throw new YAMLException(e); } - } - - // <<<< NEW instance - - // >>>> Construct => NEW, 2ndStep(filling) - protected List<? extends Object> constructSequence(SequenceNode node) { - List<Object> result = newList(node); - constructSequenceStep2(node, result); - return result; - } - - protected Set<? extends Object> constructSet(SequenceNode node) { - Set<Object> result = newSet(node); - constructSequenceStep2(node, result); - return result; - } - - protected Object constructArray(SequenceNode node) { - return constructArrayStep2(node, createArray(node.getType(), node.getValue().size())); - } - - protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) { - for (Node child : node.getValue()) { - collection.add(constructObject(child)); + } + } + throw new InstantiationException(); + } + + @SuppressWarnings("unchecked") + protected Set<Object> newSet(CollectionNode<?> node) { + try { + return (Set<Object>) newInstance(Set.class, node); + } catch (InstantiationException e) { + return createDefaultSet(node.getValue().size()); + } + } + + @SuppressWarnings("unchecked") + protected List<Object> newList(SequenceNode node) { + try { + return (List<Object>) newInstance(List.class, node); + } catch (InstantiationException e) { + return createDefaultList(node.getValue().size()); + } + } + + @SuppressWarnings("unchecked") + protected Map<Object, Object> newMap(MappingNode node) { + try { + return (Map<Object, Object>) newInstance(Map.class, node); + } catch (InstantiationException e) { + return createDefaultMap(node.getValue().size()); + } + } + + // <<<< NEW instance + + // >>>> Construct => NEW, 2ndStep(filling) + protected List<? extends Object> constructSequence(SequenceNode node) { + List<Object> result = newList(node); + constructSequenceStep2(node, result); + return result; + } + + protected Set<? extends Object> constructSet(SequenceNode node) { + Set<Object> result = newSet(node); + constructSequenceStep2(node, result); + return result; + } + + protected Object constructArray(SequenceNode node) { + return constructArrayStep2(node, createArray(node.getType(), node.getValue().size())); + } + + protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) { + for (Node child : node.getValue()) { + collection.add(constructObject(child)); + } + } + + protected Object constructArrayStep2(SequenceNode node, Object array) { + final Class<?> componentType = node.getType().getComponentType(); + + int index = 0; + for (Node child : node.getValue()) { + // Handle multi-dimensional arrays... + if (child.getType() == Object.class) { + child.setType(componentType); + } + + final Object value = constructObject(child); + + if (componentType.isPrimitive()) { + // Null values are disallowed for primitives + if (value == null) { + throw new NullPointerException( + "Unable to construct element value for " + child); } - } - - protected Object constructArrayStep2(SequenceNode node, Object array) { - final Class<?> componentType = node.getType().getComponentType(); - - int index = 0; - for (Node child : node.getValue()) { - // Handle multi-dimensional arrays... - if (child.getType() == Object.class) { - child.setType(componentType); - } - - final Object value = constructObject(child); - if (componentType.isPrimitive()) { - // Null values are disallowed for primitives - if (value == null) { - throw new NullPointerException( - "Unable to construct element value for " + child); - } + // Primitive arrays require quite a lot of work. + if (byte.class.equals(componentType)) { + Array.setByte(array, index, ((Number) value).byteValue()); - // Primitive arrays require quite a lot of work. - if (byte.class.equals(componentType)) { - Array.setByte(array, index, ((Number) value).byteValue()); + } else if (short.class.equals(componentType)) { + Array.setShort(array, index, ((Number) value).shortValue()); - } else if (short.class.equals(componentType)) { - Array.setShort(array, index, ((Number) value).shortValue()); + } else if (int.class.equals(componentType)) { + Array.setInt(array, index, ((Number) value).intValue()); - } else if (int.class.equals(componentType)) { - Array.setInt(array, index, ((Number) value).intValue()); + } else if (long.class.equals(componentType)) { + Array.setLong(array, index, ((Number) value).longValue()); - } else if (long.class.equals(componentType)) { - Array.setLong(array, index, ((Number) value).longValue()); + } else if (float.class.equals(componentType)) { + Array.setFloat(array, index, ((Number) value).floatValue()); - } else if (float.class.equals(componentType)) { - Array.setFloat(array, index, ((Number) value).floatValue()); + } else if (double.class.equals(componentType)) { + Array.setDouble(array, index, ((Number) value).doubleValue()); - } else if (double.class.equals(componentType)) { - Array.setDouble(array, index, ((Number) value).doubleValue()); + } else if (char.class.equals(componentType)) { + Array.setChar(array, index, ((Character) value).charValue()); - } else if (char.class.equals(componentType)) { - Array.setChar(array, index, ((Character) value).charValue()); + } else if (boolean.class.equals(componentType)) { + Array.setBoolean(array, index, ((Boolean) value).booleanValue()); - } else if (boolean.class.equals(componentType)) { - Array.setBoolean(array, index, ((Boolean) value).booleanValue()); - - } else { - throw new YAMLException("unexpected primitive type"); - } + } else { + throw new YAMLException("unexpected primitive type"); + } - } else { - // Non-primitive arrays can simply be assigned: - Array.set(array, index, value); - } + } else { + // Non-primitive arrays can simply be assigned: + Array.set(array, index, value); + } - ++index; - } - return array; + ++index; } + return array; + } - protected Set<Object> constructSet(MappingNode node) { - final Set<Object> set = newSet(node); - constructSet2ndStep(node, set); - return set; - } + protected Set<Object> constructSet(MappingNode node) { + final Set<Object> set = newSet(node); + constructSet2ndStep(node, set); + return set; + } - protected Map<Object, Object> constructMapping(MappingNode node) { - final Map<Object, Object> mapping = newMap(node); - constructMapping2ndStep(node, mapping); - return mapping; - } + protected Map<Object, Object> constructMapping(MappingNode node) { + final Map<Object, Object> mapping = newMap(node); + constructMapping2ndStep(node, mapping); + return mapping; + } - protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { - List<NodeTuple> nodeValue = node.getValue(); - for (NodeTuple tuple : nodeValue) { - Node keyNode = tuple.getKeyNode(); - Node valueNode = tuple.getValueNode(); + protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { + List<NodeTuple> nodeValue = node.getValue(); + for (NodeTuple tuple : nodeValue) { + Node keyNode = tuple.getKeyNode(); + Node valueNode = tuple.getValueNode(); // System.out.println( // " >>>> " + keyNode.isTwoStepsConstruction() + " : " + keyNode.getStartMark()); - Object key = constructObject(keyNode); - if (key != null) { - try { - key.hashCode();// check circular dependencies - } catch (Exception e) { - throw new ConstructorException("while constructing a mapping", - node.getStartMark(), "found unacceptable key " + key, - tuple.getKeyNode().getStartMark(), e); - } - } - Object value = constructObject(valueNode); - if (keyNode.isTwoStepsConstruction()) { - if (loadingConfig.getAllowRecursiveKeys()) { - postponeMapFilling(mapping, key, value); - } else { - throw new YAMLException("Recursive key for mapping is detected but it is not configured to be allowed."); - } - } else { - mapping.put(key, value); - } - } - } - - /* - * if keyObject is created it 2 steps we should postpone putting - * it in map because it may have different hash after - * initialization compared to clean just created one. And map of - * course does not observe key hashCode changes. - */ - protected void postponeMapFilling(Map<Object, Object> mapping, Object key, Object value) { - maps2fill.add(0, new RecursiveTuple(mapping, new RecursiveTuple(key, value))); - } - - protected void constructSet2ndStep(MappingNode node, Set<Object> set) { - List<NodeTuple> nodeValue = node.getValue(); - for (NodeTuple tuple : nodeValue) { - Node keyNode = tuple.getKeyNode(); - Object key = constructObject(keyNode); - if (key != null) { - try { - key.hashCode();// check circular dependencies - } catch (Exception e) { - throw new ConstructorException("while constructing a Set", node.getStartMark(), - "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e); - } - } - if (keyNode.isTwoStepsConstruction()) { - postponeSetFilling(set, key); - } else { - set.add(key); - } - } - } - - /* - * if keyObject is created it 2 steps we should postpone putting - * it into the set because it may have different hash after - * initialization compared to clean just created one. And set of - * course does not observe value hashCode changes. - */ - protected void postponeSetFilling(Set<Object> set, Object key) { - sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key)); - } - - public void setPropertyUtils(PropertyUtils propertyUtils) { - this.propertyUtils = propertyUtils; - explicitPropertyUtils = true; - Collection<TypeDescription> tds = typeDefinitions.values(); - for (TypeDescription typeDescription : tds) { - typeDescription.setPropertyUtils(propertyUtils); - } - } - - public final PropertyUtils getPropertyUtils() { - if (propertyUtils == null) { - propertyUtils = new PropertyUtils(); - } - return propertyUtils; - } - - /** - * Make YAML aware how to parse 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 <code>definition</code>, or - * <code>null</code> if there was no mapping for <code>definition</code>. - */ - public TypeDescription addTypeDescription(TypeDescription definition) { - if (definition == null) { - throw new NullPointerException("TypeDescription is required."); - } - Tag tag = definition.getTag(); - typeTags.put(tag, definition.getType()); - definition.setPropertyUtils(getPropertyUtils()); - return typeDefinitions.put(definition.getType(), definition); - } - - private static class RecursiveTuple<T, K> { - private final T _1; - private final K _2; - - public RecursiveTuple(T _1, K _2) { - this._1 = _1; - this._2 = _2; + Object key = constructObject(keyNode); + if (key != null) { + try { + key.hashCode();// check circular dependencies + } catch (Exception e) { + throw new ConstructorException("while constructing a mapping", + node.getStartMark(), "found unacceptable key " + key, + tuple.getKeyNode().getStartMark(), e); } - - public K _2() { - return _2; + } + Object value = constructObject(valueNode); + if (keyNode.isTwoStepsConstruction()) { + if (loadingConfig.getAllowRecursiveKeys()) { + postponeMapFilling(mapping, key, value); + } else { + throw new YAMLException( + "Recursive key for mapping is detected but it is not configured to be allowed."); } - - public T _1() { - return _1; + } else { + mapping.put(key, value); + } + } + } + + /* + * if keyObject is created it 2 steps we should postpone putting + * it in map because it may have different hash after + * initialization compared to clean just created one. And map of + * course does not observe key hashCode changes. + */ + protected void postponeMapFilling(Map<Object, Object> mapping, Object key, Object value) { + maps2fill.add(0, new RecursiveTuple(mapping, new RecursiveTuple(key, value))); + } + + protected void constructSet2ndStep(MappingNode node, Set<Object> set) { + List<NodeTuple> nodeValue = node.getValue(); + for (NodeTuple tuple : nodeValue) { + Node keyNode = tuple.getKeyNode(); + Object key = constructObject(keyNode); + if (key != null) { + try { + key.hashCode();// check circular dependencies + } catch (Exception e) { + throw new ConstructorException("while constructing a Set", node.getStartMark(), + "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e); } - } - - public final boolean isExplicitPropertyUtils() { - return explicitPropertyUtils; - } - - public boolean isAllowDuplicateKeys() { - return allowDuplicateKeys; - } - - public void setAllowDuplicateKeys(boolean allowDuplicateKeys) { - this.allowDuplicateKeys = allowDuplicateKeys; - } - - public boolean isWrappedToRootException() { - return wrappedToRootException; - } - - public void setWrappedToRootException(boolean wrappedToRootException) { - this.wrappedToRootException = wrappedToRootException; - } - - public boolean isEnumCaseSensitive() { - return enumCaseSensitive; - } - - public void setEnumCaseSensitive(boolean enumCaseSensitive) { - this.enumCaseSensitive = enumCaseSensitive; - } + } + if (keyNode.isTwoStepsConstruction()) { + postponeSetFilling(set, key); + } else { + set.add(key); + } + } + } + + /* + * if keyObject is created it 2 steps we should postpone putting + * it into the set because it may have different hash after + * initialization compared to clean just created one. And set of + * course does not observe value hashCode changes. + */ + protected void postponeSetFilling(Set<Object> set, Object key) { + sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key)); + } + + public void setPropertyUtils(PropertyUtils propertyUtils) { + this.propertyUtils = propertyUtils; + explicitPropertyUtils = true; + Collection<TypeDescription> tds = typeDefinitions.values(); + for (TypeDescription typeDescription : tds) { + typeDescription.setPropertyUtils(propertyUtils); + } + } + + public final PropertyUtils getPropertyUtils() { + if (propertyUtils == null) { + propertyUtils = new PropertyUtils(); + } + return propertyUtils; + } + + /** + * Make YAML aware how to parse 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 <code>definition</code>, or + * <code>null</code> if there was no mapping for <code>definition</code>. + */ + public TypeDescription addTypeDescription(TypeDescription definition) { + if (definition == null) { + throw new NullPointerException("TypeDescription is required."); + } + Tag tag = definition.getTag(); + typeTags.put(tag, definition.getType()); + definition.setPropertyUtils(getPropertyUtils()); + return typeDefinitions.put(definition.getType(), definition); + } + + private static class RecursiveTuple<T, K> { + + private final T _1; + private final K _2; + + public RecursiveTuple(T _1, K _2) { + this._1 = _1; + this._2 = _2; + } + + public K _2() { + return _2; + } + + public T _1() { + return _1; + } + } + + public final boolean isExplicitPropertyUtils() { + return explicitPropertyUtils; + } + + public boolean isAllowDuplicateKeys() { + return allowDuplicateKeys; + } + + public void setAllowDuplicateKeys(boolean allowDuplicateKeys) { + this.allowDuplicateKeys = allowDuplicateKeys; + } + + public boolean isWrappedToRootException() { + return wrappedToRootException; + } + + public void setWrappedToRootException(boolean wrappedToRootException) { + this.wrappedToRootException = wrappedToRootException; + } + + public boolean isEnumCaseSensitive() { + return enumCaseSensitive; + } + + public void setEnumCaseSensitive(boolean enumCaseSensitive) { + this.enumCaseSensitive = enumCaseSensitive; + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/Construct.java b/src/main/java/org/yaml/snakeyaml/constructor/Construct.java index 0fd46141..bf116520 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/Construct.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/Construct.java @@ -21,30 +21,31 @@ import org.yaml.snakeyaml.nodes.Node; * Provide a way to construct a Java instance out of the composed Node. Support * recursive objects if it is required. (create Native Data Structure out of * Node Graph) - * + * * @see <a href="http://yaml.org/spec/1.1/#id859109">Chapter 3. Processing YAML * Information</a> */ public interface Construct { - /** - * Construct a Java instance with all the properties injected when it is - * possible. - * - * @param node - * composed Node - * @return a complete Java instance - */ - Object construct(Node node); - /** - * Apply the second step when constructing recursive structures. Because the - * instance is already created it can assign a reference to itself. - * - * @param node - * composed Node - * @param object - * the instance constructed earlier by - * <code>construct(Node node)</code> for the provided Node - */ - void construct2ndStep(Node node, Object object); + /** + * Construct a Java instance with all the properties injected when it is + * possible. + * + * @param node + * composed Node + * @return a complete Java instance + */ + Object construct(Node node); + + /** + * Apply the second step when constructing recursive structures. Because the + * instance is already created it can assign a reference to itself. + * + * @param node + * composed Node + * @param object + * the instance constructed earlier by + * <code>construct(Node node)</code> for the provided Node + */ + void construct2ndStep(Node node, Object object); } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java index 7ef83854..48004fc2 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; - import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.TypeDescription; import org.yaml.snakeyaml.error.YAMLException; @@ -44,640 +43,643 @@ import org.yaml.snakeyaml.util.EnumUtils; */ public class Constructor extends SafeConstructor { - public Constructor() { - this(Object.class); + public Constructor() { + this(Object.class); + } + + public Constructor(LoaderOptions loadingConfig) { + this(Object.class, loadingConfig); + } + + /** + * Create Constructor for the specified class as the root. + * + * @param theRoot + * - the class (usually JavaBean) to be constructed + */ + public Constructor(Class<? extends Object> theRoot) { + this(new TypeDescription(checkRoot(theRoot))); + } + + public Constructor(Class<? extends Object> theRoot, LoaderOptions loadingConfig) { + this(new TypeDescription(checkRoot(theRoot)), loadingConfig); + } + + /** + * Ugly Java way to check the argument in the constructor + */ + private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) { + if (theRoot == null) { + throw new NullPointerException("Root class must be provided."); + } else { + return theRoot; + } + } + + public Constructor(TypeDescription theRoot) { + this(theRoot, null, new LoaderOptions()); + } + + public Constructor(TypeDescription theRoot, LoaderOptions loadingConfig) { + this(theRoot, null, loadingConfig); + } + + public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs) { + this(theRoot, moreTDs, new LoaderOptions()); + } + + /** + * Create with all possible arguments + * @param theRoot - the class (usually JavaBean) to be constructed + * @param moreTDs - collection of classes used by the root class + * @param loadingConfig - configuration + */ + public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs, + LoaderOptions loadingConfig) { + super(loadingConfig); + if (theRoot == null) { + throw new NullPointerException("Root type must be provided."); } - - public Constructor(LoaderOptions loadingConfig) { - this(Object.class, loadingConfig); + this.yamlConstructors.put(null, new ConstructYamlObject()); + if (!Object.class.equals(theRoot.getType())) { + rootTag = new Tag(theRoot.getType()); } - - /** - * Create Constructor for the specified class as the root. - * - * @param theRoot - * - the class (usually JavaBean) to be constructed - */ - public Constructor(Class<? extends Object> theRoot) { - this(new TypeDescription(checkRoot(theRoot))); + yamlClassConstructors.put(NodeId.scalar, new ConstructScalar()); + yamlClassConstructors.put(NodeId.mapping, new ConstructMapping()); + yamlClassConstructors.put(NodeId.sequence, new ConstructSequence()); + addTypeDescription(theRoot); + if (moreTDs != null) { + for (TypeDescription td : moreTDs) { + addTypeDescription(td); + } } - - public Constructor(Class<? extends Object> theRoot, LoaderOptions loadingConfig) { - this(new TypeDescription(checkRoot(theRoot)), loadingConfig); + } + + /** + * Create Constructor for a class which does not have to be in the classpath + * or for a definition from a Spring ApplicationContext. + * + * @param theRoot + * fully qualified class name of the root class (usually + * JavaBean) + * @throws ClassNotFoundException if cannot be loaded by the classloader + */ + public Constructor(String theRoot) throws ClassNotFoundException { + this(Class.forName(check(theRoot))); + } + + public Constructor(String theRoot, LoaderOptions loadingConfig) throws ClassNotFoundException { + this(Class.forName(check(theRoot)), loadingConfig); + } + + private static final String check(String s) { + if (s == null) { + throw new NullPointerException("Root type must be provided."); } + if (s.trim().length() == 0) { + throw new YAMLException("Root type must be provided."); + } + return s; + } + + /** + * Construct mapping instance (Map, JavaBean) when the runtime class is + * known. + */ + protected class ConstructMapping implements Construct { /** - * Ugly Java way to check the argument in the constructor + * Construct JavaBean. If type safe collections are used please look at + * <code>TypeDescription</code>. + * + * @param node + * node where the keys are property names (they can only be + * <code>String</code>s) and values are objects to be created + * @return constructed JavaBean */ - private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) { - if (theRoot == null) { - throw new NullPointerException("Root class must be provided."); - } else - return theRoot; + public Object construct(Node node) { + MappingNode mnode = (MappingNode) node; + if (Map.class.isAssignableFrom(node.getType())) { + if (node.isTwoStepsConstruction()) { + return newMap(mnode); + } else { + return constructMapping(mnode); + } + } else if (Collection.class.isAssignableFrom(node.getType())) { + if (node.isTwoStepsConstruction()) { + return newSet(mnode); + } else { + return constructSet(mnode); + } + } else { + Object obj = Constructor.this.newInstance(mnode); + if (node.isTwoStepsConstruction()) { + return obj; + } else { + return constructJavaBean2ndStep(mnode, obj); + } + } } - public Constructor(TypeDescription theRoot) { - this(theRoot, null, new LoaderOptions()); + @SuppressWarnings("unchecked") + public void construct2ndStep(Node node, Object object) { + if (Map.class.isAssignableFrom(node.getType())) { + constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object); + } else if (Set.class.isAssignableFrom(node.getType())) { + constructSet2ndStep((MappingNode) node, (Set<Object>) object); + } else { + constructJavaBean2ndStep((MappingNode) node, object); + } } - public Constructor(TypeDescription theRoot, LoaderOptions loadingConfig) { - this(theRoot, null, loadingConfig); + // protected Object createEmptyJavaBean(MappingNode node) { + // try { + // Object instance = Constructor.this.newInstance(node); + // if (instance != null) { + // return instance; + // } + // + // /** + // * Using only default constructor. Everything else will be + // * initialized on 2nd step. If we do here some partial + // * initialization, how do we then track what need to be done on + // * 2nd step? I think it is better to get only object here (to + // * have it as reference for recursion) and do all other thing on + // * 2nd step. + // */ + // java.lang.reflect.Constructor<?> c = + // node.getType().getDeclaredConstructor(); + // c.setAccessible(true); + // return c.newInstance(); + // } catch (Exception e) { + // throw new YAMLException(e); + // } + // } + + protected Object constructJavaBean2ndStep(MappingNode node, Object object) { + flattenMapping(node, true); + Class<? extends Object> beanType = node.getType(); + List<NodeTuple> nodeValue = node.getValue(); + for (NodeTuple tuple : nodeValue) { + Node valueNode = tuple.getValueNode(); + // flattenMapping enforces keys to be Strings + String key = (String) constructObject(tuple.getKeyNode()); + try { + TypeDescription memberDescription = typeDefinitions.get(beanType); + Property property = memberDescription == null ? getProperty(beanType, key) + : memberDescription.getProperty(key); + + if (!property.isWritable()) { + throw new YAMLException("No writable property '" + key + "' on class: " + + beanType.getName()); + } + + valueNode.setType(property.getType()); + final boolean typeDetected = + memberDescription != null && memberDescription.setupPropertyType(key, valueNode); + if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) { + // only if there is no explicit TypeDescription + Class<?>[] arguments = property.getActualTypeArguments(); + if (arguments != null && arguments.length > 0) { + // type safe (generic) collection may contain the + // proper class + if (valueNode.getNodeId() == NodeId.sequence) { + Class<?> t = arguments[0]; + SequenceNode snode = (SequenceNode) valueNode; + snode.setListType(t); + } else if (Map.class.isAssignableFrom(valueNode.getType())) { + Class<?> keyType = arguments[0]; + Class<?> valueType = arguments[1]; + MappingNode mnode = (MappingNode) valueNode; + mnode.setTypes(keyType, valueType); + mnode.setUseClassConstructor(true); + } else if (Collection.class.isAssignableFrom(valueNode.getType())) { + Class<?> t = arguments[0]; + MappingNode mnode = (MappingNode) valueNode; + mnode.setOnlyKeyType(t); + mnode.setUseClassConstructor(true); + } + } + } + + Object value = (memberDescription != null) + ? newInstance(memberDescription, key, valueNode) + : constructObject(valueNode); + // Correct when the property expects float but double was + // constructed + if (property.getType() == Float.TYPE || property.getType() == Float.class) { + if (value instanceof Double) { + value = ((Double) value).floatValue(); + } + } + // Correct when the property a String but the value is binary + if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag()) + && value instanceof byte[]) { + value = new String((byte[]) value); + } + + if (memberDescription == null + || !memberDescription.setProperty(object, key, value)) { + property.set(object, value); + } + } catch (DuplicateKeyException e) { + throw e; + } catch (Exception e) { + throw new ConstructorException( + "Cannot create property=" + key + " for JavaBean=" + object, + node.getStartMark(), e.getMessage(), valueNode.getStartMark(), e); + } + } + return object; } - public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs) { - this(theRoot, moreTDs, new LoaderOptions()); + private Object newInstance(TypeDescription memberDescription, String propertyName, + Node node) { + Object newInstance = memberDescription.newInstance(propertyName, node); + if (newInstance != null) { + constructedObjects.put(node, newInstance); + return + constructObjectNoCheck(node); + } + return constructObject(node); } - /** - * Create with all possible arguments - * @param theRoot - the class (usually JavaBean) to be constructed - * @param moreTDs - collection of classes used by the root class - * @param loadingConfig - configuration - */ - public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs, LoaderOptions loadingConfig) { - super(loadingConfig); - if (theRoot == null) { - throw new NullPointerException("Root type must be provided."); - } - this.yamlConstructors.put(null, new ConstructYamlObject()); - if (!Object.class.equals(theRoot.getType())) { - rootTag = new Tag(theRoot.getType()); - } - yamlClassConstructors.put(NodeId.scalar, new ConstructScalar()); - yamlClassConstructors.put(NodeId.mapping, new ConstructMapping()); - yamlClassConstructors.put(NodeId.sequence, new ConstructSequence()); - addTypeDescription(theRoot); - if (moreTDs != null) { - for (TypeDescription td : moreTDs) { - addTypeDescription(td); - } - } + protected Property getProperty(Class<? extends Object> type, String name) { + return getPropertyUtils().getProperty(type, name); } - - /** - * Create Constructor for a class which does not have to be in the classpath - * or for a definition from a Spring ApplicationContext. - * - * @param theRoot - * fully qualified class name of the root class (usually - * JavaBean) - * @throws ClassNotFoundException if cannot be loaded by the classloader - */ - public Constructor(String theRoot) throws ClassNotFoundException { - this(Class.forName(check(theRoot))); + } + + /** + * Construct an instance when the runtime class is not known but a global + * tag with a class name is defined. It delegates the construction to the + * appropriate constructor based on the node kind (scalar, sequence, + * mapping) + */ + protected class ConstructYamlObject implements Construct { + + private Construct getConstructor(Node node) { + Class<?> cl = getClassForNode(node); + node.setType(cl); + // call the constructor as if the runtime class is defined + Construct constructor = yamlClassConstructors.get(node.getNodeId()); + return constructor; } - public Constructor(String theRoot, LoaderOptions loadingConfig) throws ClassNotFoundException { - this(Class.forName(check(theRoot)), loadingConfig); + public Object construct(Node node) { + try { + return getConstructor(node).construct(node); + } catch (ConstructorException e) { + throw e; + } catch (Exception e) { + throw new ConstructorException(null, null, "Can't construct a java object for " + + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e); + } } - private static final String check(String s) { - if (s == null) { - throw new NullPointerException("Root type must be provided."); + public void construct2ndStep(Node node, Object object) { + try { + getConstructor(node).construct2ndStep(node, object); + } catch (Exception e) { + throw new ConstructorException( + null, null, "Can't construct a second step for a java object for " + + node.getTag() + "; exception=" + e.getMessage(), + node.getStartMark(), e); + } + } + } + + /** + * Construct scalar instance when the runtime class is known. Recursive + * structures are not supported. + */ + protected class ConstructScalar extends AbstractConstruct { + + public Object construct(Node nnode) { + ScalarNode node = (ScalarNode) nnode; + Class<?> type = node.getType(); + + try { + return newInstance(type, node, false); + } catch (InstantiationException e1) { + } + + Object result; + if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type) + || type == Boolean.class || Date.class.isAssignableFrom(type) + || type == Character.class || type == BigInteger.class + || type == BigDecimal.class || Enum.class.isAssignableFrom(type) + || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type) + || type == UUID.class) { + // standard classes created directly + result = constructStandardJavaInstance(type, node); + } else { + // there must be only 1 constructor with 1 argument + java.lang.reflect.Constructor<?>[] javaConstructors = type + .getDeclaredConstructors(); + int oneArgCount = 0; + java.lang.reflect.Constructor<?> javaConstructor = null; + for (java.lang.reflect.Constructor<?> c : javaConstructors) { + if (c.getParameterTypes().length == 1) { + oneArgCount++; + javaConstructor = c; + } } - if (s.trim().length() == 0) { - throw new YAMLException("Root type must be provided."); + Object argument; + if (javaConstructor == null) { + try { + return newInstance(type, node, false); + } catch (InstantiationException ie) { + throw new YAMLException("No single argument constructor found for " + type + + " : " + ie.getMessage()); + } + } else if (oneArgCount == 1) { + argument = constructStandardJavaInstance(javaConstructor.getParameterTypes()[0], + node); + } else { + // TODO it should be possible to use implicit types instead + // of forcing String. Resolver must be available here to + // obtain the implicit tag. Then we can set the tag and call + // callConstructor(node) to create the argument instance. + // On the other hand it may be safer to require a custom + // constructor to avoid guessing the argument class + argument = constructScalar(node); + try { + javaConstructor = type.getDeclaredConstructor(String.class); + } catch (Exception e) { + throw new YAMLException("Can't construct a java object for scalar " + + node.getTag() + "; No String constructor found. Exception=" + + e.getMessage(), e); + } } - return s; + try { + javaConstructor.setAccessible(true); + result = javaConstructor.newInstance(argument); + } catch (Exception e) { + throw new ConstructorException(null, null, + "Can't construct a java object for scalar " + node.getTag() + + "; exception=" + e.getMessage(), + node.getStartMark(), e); + } + } + return result; } - /** - * Construct mapping instance (Map, JavaBean) when the runtime class is - * known. - */ - protected class ConstructMapping implements Construct { - - /** - * Construct JavaBean. If type safe collections are used please look at - * <code>TypeDescription</code>. - * - * @param node - * node where the keys are property names (they can only be - * <code>String</code>s) and values are objects to be created - * @return constructed JavaBean - */ - public Object construct(Node node) { - MappingNode mnode = (MappingNode) node; - if (Map.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - return newMap(mnode); - } else { - return constructMapping(mnode); - } - } else if (Collection.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - return newSet(mnode); - } else { - return constructSet(mnode); - } - } else { - Object obj = Constructor.this.newInstance(mnode); - if (node.isTwoStepsConstruction()) { - return obj; - } else { - return constructJavaBean2ndStep(mnode, obj); - } - } + @SuppressWarnings("unchecked") + private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type, + ScalarNode node) { + Object result; + if (type == String.class) { + Construct stringConstructor = yamlConstructors.get(Tag.STR); + result = stringConstructor.construct(node); + } else if (type == Boolean.class || type == Boolean.TYPE) { + Construct boolConstructor = yamlConstructors.get(Tag.BOOL); + result = boolConstructor.construct(node); + } else if (type == Character.class || type == Character.TYPE) { + Construct charConstructor = yamlConstructors.get(Tag.STR); + String ch = (String) charConstructor.construct(node); + if (ch.length() == 0) { + result = null; + } else if (ch.length() != 1) { + throw new YAMLException( + "Invalid node Character: '" + ch + "'; length: " + ch.length()); + } else { + result = Character.valueOf(ch.charAt(0)); } - - @SuppressWarnings("unchecked") - public void construct2ndStep(Node node, Object object) { - if (Map.class.isAssignableFrom(node.getType())) { - constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object); - } else if (Set.class.isAssignableFrom(node.getType())) { - constructSet2ndStep((MappingNode) node, (Set<Object>) object); - } else { - constructJavaBean2ndStep((MappingNode) node, object); - } + } else if (Date.class.isAssignableFrom(type)) { + Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP); + Date date = (Date) dateConstructor.construct(node); + if (type == Date.class) { + result = date; + } else { + try { + java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class); + result = constr.newInstance(date.getTime()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new YAMLException("Cannot construct: '" + type + "'"); + } } - - // protected Object createEmptyJavaBean(MappingNode node) { - // try { - // Object instance = Constructor.this.newInstance(node); - // if (instance != null) { - // return instance; - // } - // - // /** - // * Using only default constructor. Everything else will be - // * initialized on 2nd step. If we do here some partial - // * initialization, how do we then track what need to be done on - // * 2nd step? I think it is better to get only object here (to - // * have it as reference for recursion) and do all other thing on - // * 2nd step. - // */ - // java.lang.reflect.Constructor<?> c = - // node.getType().getDeclaredConstructor(); - // c.setAccessible(true); - // return c.newInstance(); - // } catch (Exception e) { - // throw new YAMLException(e); - // } - // } - - protected Object constructJavaBean2ndStep(MappingNode node, Object object) { - flattenMapping(node, true); - Class<? extends Object> beanType = node.getType(); - List<NodeTuple> nodeValue = node.getValue(); - for (NodeTuple tuple : nodeValue) { - Node valueNode = tuple.getValueNode(); - // flattenMapping enforces keys to be Strings - String key = (String) constructObject(tuple.getKeyNode()); - try { - TypeDescription memberDescription = typeDefinitions.get(beanType); - Property property = memberDescription == null ? getProperty(beanType, key) - : memberDescription.getProperty(key); - - if (!property.isWritable()) { - throw new YAMLException("No writable property '" + key + "' on class: " - + beanType.getName()); - } - - valueNode.setType(property.getType()); - final boolean typeDetected = (memberDescription != null) - ? memberDescription.setupPropertyType(key, valueNode) - : false; - if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) { - // only if there is no explicit TypeDescription - Class<?>[] arguments = property.getActualTypeArguments(); - if (arguments != null && arguments.length > 0) { - // type safe (generic) collection may contain the - // proper class - if (valueNode.getNodeId() == NodeId.sequence) { - Class<?> t = arguments[0]; - SequenceNode snode = (SequenceNode) valueNode; - snode.setListType(t); - } else if (Map.class.isAssignableFrom(valueNode.getType())) { - Class<?> keyType = arguments[0]; - Class<?> valueType = arguments[1]; - MappingNode mnode = (MappingNode) valueNode; - mnode.setTypes(keyType, valueType); - mnode.setUseClassConstructor(true); - } else if (Collection.class.isAssignableFrom(valueNode.getType())) { - Class<?> t = arguments[0]; - MappingNode mnode = (MappingNode) valueNode; - mnode.setOnlyKeyType(t); - mnode.setUseClassConstructor(true); - } - } - } - - Object value = (memberDescription != null) - ? newInstance(memberDescription, key, valueNode) - : constructObject(valueNode); - // Correct when the property expects float but double was - // constructed - if (property.getType() == Float.TYPE || property.getType() == Float.class) { - if (value instanceof Double) { - value = ((Double) value).floatValue(); - } - } - // Correct when the property a String but the value is binary - if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag()) - && value instanceof byte[]) { - value = new String((byte[]) value); - } - - if (memberDescription == null - || !memberDescription.setProperty(object, key, value)) { - property.set(object, value); - } - } catch (DuplicateKeyException e) { - throw e; - } catch (Exception e) { - throw new ConstructorException( - "Cannot create property=" + key + " for JavaBean=" + object, - node.getStartMark(), e.getMessage(), valueNode.getStartMark(), e); - } - } - return object; + } else if (type == Float.class || type == Double.class || type == Float.TYPE + || type == Double.TYPE || type == BigDecimal.class) { + if (type == BigDecimal.class) { + result = new BigDecimal(node.getValue()); + } else { + Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT); + result = doubleConstructor.construct(node); + if (type == Float.class || type == Float.TYPE) { + result = Float.valueOf(((Double) result).floatValue()); + } } - - private Object newInstance(TypeDescription memberDescription, String propertyName, - Node node) { - Object newInstance = memberDescription.newInstance(propertyName, node); - if (newInstance != null) { - constructedObjects.put(node, newInstance); - return - constructObjectNoCheck(node); - } - return constructObject(node); + } else if (type == Byte.class || type == Short.class || type == Integer.class + || type == Long.class || type == BigInteger.class || type == Byte.TYPE + || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) { + Construct intConstructor = yamlConstructors.get(Tag.INT); + result = intConstructor.construct(node); + if (type == Byte.class || type == Byte.TYPE) { + result = Integer.valueOf(result.toString()).byteValue(); + } else if (type == Short.class || type == Short.TYPE) { + result = Integer.valueOf(result.toString()).shortValue(); + } else if (type == Integer.class || type == Integer.TYPE) { + result = Integer.parseInt(result.toString()); + } else if (type == Long.class || type == Long.TYPE) { + result = Long.valueOf(result.toString()); + } else { + // only BigInteger left + result = new BigInteger(result.toString()); } - - protected Property getProperty(Class<? extends Object> type, String name) { - return getPropertyUtils().getProperty(type, name); + } else if (Enum.class.isAssignableFrom(type)) { + String enumValueName = node.getValue(); + try { + if (loadingConfig.isEnumCaseSensitive()) { + result = Enum.valueOf(type, enumValueName); + } else { + result = EnumUtils.findEnumInsensitiveCase(type, enumValueName); + } + } catch (Exception ex) { + throw new YAMLException("Unable to find enum value '" + enumValueName + + "' for enum class: " + type.getName()); + } + } else if (Calendar.class.isAssignableFrom(type)) { + ConstructYamlTimestamp contr = new ConstructYamlTimestamp(); + contr.construct(node); + result = contr.getCalendar(); + } else if (Number.class.isAssignableFrom(type)) { + //since we do not know the exact type we create Float + ConstructYamlFloat contr = new ConstructYamlFloat(); + result = contr.construct(node); + } else if (UUID.class == type) { + result = UUID.fromString(node.getValue()); + } else { + if (yamlConstructors.containsKey(node.getTag())) { + result = yamlConstructors.get(node.getTag()).construct(node); + } else { + throw new YAMLException("Unsupported class: " + type); } + } + return result; } - - /** - * Construct an instance when the runtime class is not known but a global - * tag with a class name is defined. It delegates the construction to the - * appropriate constructor based on the node kind (scalar, sequence, - * mapping) - */ - protected class ConstructYamlObject implements Construct { - - private Construct getConstructor(Node node) { - Class<?> cl = getClassForNode(node); - node.setType(cl); - // call the constructor as if the runtime class is defined - Construct constructor = yamlClassConstructors.get(node.getNodeId()); - return constructor; + } + + /** + * Construct sequence (List, Array, or immutable object) when the runtime + * class is known. + */ + protected class ConstructSequence implements Construct { + + @SuppressWarnings("unchecked") + public Object construct(Node node) { + SequenceNode snode = (SequenceNode) node; + if (Set.class.isAssignableFrom(node.getType())) { + if (node.isTwoStepsConstruction()) { + throw new YAMLException("Set cannot be recursive."); + } else { + return constructSet(snode); } - - public Object construct(Node node) { - try { - return getConstructor(node).construct(node); - } catch (ConstructorException e) { - throw e; - } catch (Exception e) { - throw new ConstructorException(null, null, "Can't construct a java object for " - + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e); - } + } else if (Collection.class.isAssignableFrom(node.getType())) { + if (node.isTwoStepsConstruction()) { + return newList(snode); + } else { + return constructSequence(snode); + } + } else if (node.getType().isArray()) { + if (node.isTwoStepsConstruction()) { + return createArray(node.getType(), snode.getValue().size()); + } else { + return constructArray(snode); + } + } else { + // create immutable object + List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>( + snode.getValue().size()); + for (java.lang.reflect.Constructor<?> constructor : node.getType() + .getDeclaredConstructors()) { + if (snode.getValue().size() == constructor.getParameterTypes().length) { + possibleConstructors.add(constructor); + } } + if (!possibleConstructors.isEmpty()) { + if (possibleConstructors.size() == 1) { + Object[] argumentList = new Object[snode.getValue().size()]; + java.lang.reflect.Constructor<?> c = possibleConstructors.get(0); + int index = 0; + for (Node argumentNode : snode.getValue()) { + Class<?> type = c.getParameterTypes()[index]; + // set runtime classes for arguments + argumentNode.setType(type); + argumentList[index++] = constructObject(argumentNode); + } - public void construct2ndStep(Node node, Object object) { try { - getConstructor(node).construct2ndStep(node, object); + c.setAccessible(true); + return c.newInstance(argumentList); } catch (Exception e) { - throw new ConstructorException( - null, null, "Can't construct a second step for a java object for " - + node.getTag() + "; exception=" + e.getMessage(), - node.getStartMark(), e); + throw new YAMLException(e); } - } - } - - /** - * Construct scalar instance when the runtime class is known. Recursive - * structures are not supported. - */ - protected class ConstructScalar extends AbstractConstruct { - public Object construct(Node nnode) { - ScalarNode node = (ScalarNode) nnode; - Class<?> type = node.getType(); - - try { - return newInstance(type, node, false); - } catch (InstantiationException e1) { + } + + // use BaseConstructor + List<Object> argumentList = (List<Object>) constructSequence(snode); + Class<?>[] parameterTypes = new Class[argumentList.size()]; + int index = 0; + for (Object parameter : argumentList) { + parameterTypes[index] = parameter.getClass(); + index++; + } + + for (java.lang.reflect.Constructor<?> c : possibleConstructors) { + Class<?>[] argTypes = c.getParameterTypes(); + boolean foundConstructor = true; + for (int i = 0; i < argTypes.length; i++) { + if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) { + foundConstructor = false; + break; + } } - - Object result; - if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type) - || type == Boolean.class || Date.class.isAssignableFrom(type) - || type == Character.class || type == BigInteger.class - || type == BigDecimal.class || Enum.class.isAssignableFrom(type) - || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type) - || type == UUID.class) { - // standard classes created directly - result = constructStandardJavaInstance(type, node); - } else { - // there must be only 1 constructor with 1 argument - java.lang.reflect.Constructor<?>[] javaConstructors = type - .getDeclaredConstructors(); - int oneArgCount = 0; - java.lang.reflect.Constructor<?> javaConstructor = null; - for (java.lang.reflect.Constructor<?> c : javaConstructors) { - if (c.getParameterTypes().length == 1) { - oneArgCount++; - javaConstructor = c; - } - } - Object argument; - if (javaConstructor == null) { - try { - return newInstance(type, node, false); - } catch (InstantiationException ie) { - throw new YAMLException("No single argument constructor found for " + type - + " : " + ie.getMessage()); - } - } else if (oneArgCount == 1) { - argument = constructStandardJavaInstance(javaConstructor.getParameterTypes()[0], - node); - } else { - // TODO it should be possible to use implicit types instead - // of forcing String. Resolver must be available here to - // obtain the implicit tag. Then we can set the tag and call - // callConstructor(node) to create the argument instance. - // On the other hand it may be safer to require a custom - // constructor to avoid guessing the argument class - argument = constructScalar(node); - try { - javaConstructor = type.getDeclaredConstructor(String.class); - } catch (Exception e) { - throw new YAMLException("Can't construct a java object for scalar " - + node.getTag() + "; No String constructor found. Exception=" - + e.getMessage(), e); - } - } - try { - javaConstructor.setAccessible(true); - result = javaConstructor.newInstance(argument); - } catch (Exception e) { - throw new ConstructorException(null, null, - "Can't construct a java object for scalar " + node.getTag() - + "; exception=" + e.getMessage(), - node.getStartMark(), e); - } + if (foundConstructor) { + try { + c.setAccessible(true); + return c.newInstance(argumentList.toArray()); + } catch (Exception e) { + throw new YAMLException(e); + } } - return result; + } } + throw new YAMLException( + "No suitable constructor with " + snode.getValue().size() + + " arguments found for " + node.getType()); - @SuppressWarnings("unchecked") - private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type, - ScalarNode node) { - Object result; - if (type == String.class) { - Construct stringConstructor = yamlConstructors.get(Tag.STR); - result = stringConstructor.construct(node); - } else if (type == Boolean.class || type == Boolean.TYPE) { - Construct boolConstructor = yamlConstructors.get(Tag.BOOL); - result = boolConstructor.construct(node); - } else if (type == Character.class || type == Character.TYPE) { - Construct charConstructor = yamlConstructors.get(Tag.STR); - String ch = (String) charConstructor.construct(node); - if (ch.length() == 0) { - result = null; - } else if (ch.length() != 1) { - throw new YAMLException( - "Invalid node Character: '" + ch + "'; length: " + ch.length()); - } else { - result = Character.valueOf(ch.charAt(0)); - } - } else if (Date.class.isAssignableFrom(type)) { - Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP); - Date date = (Date) dateConstructor.construct(node); - if (type == Date.class) { - result = date; - } else { - try { - java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class); - result = constr.newInstance(date.getTime()); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new YAMLException("Cannot construct: '" + type + "'"); - } - } - } else if (type == Float.class || type == Double.class || type == Float.TYPE - || type == Double.TYPE || type == BigDecimal.class) { - if (type == BigDecimal.class) { - result = new BigDecimal(node.getValue()); - } else { - Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT); - result = doubleConstructor.construct(node); - if (type == Float.class || type == Float.TYPE) { - result = Float.valueOf(((Double) result).floatValue()); - } - } - } else if (type == Byte.class || type == Short.class || type == Integer.class - || type == Long.class || type == BigInteger.class || type == Byte.TYPE - || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) { - Construct intConstructor = yamlConstructors.get(Tag.INT); - result = intConstructor.construct(node); - if (type == Byte.class || type == Byte.TYPE) { - result = Integer.valueOf(result.toString()).byteValue(); - } else if (type == Short.class || type == Short.TYPE) { - result = Integer.valueOf(result.toString()).shortValue(); - } else if (type == Integer.class || type == Integer.TYPE) { - result = Integer.parseInt(result.toString()); - } else if (type == Long.class || type == Long.TYPE) { - result = Long.valueOf(result.toString()); - } else { - // only BigInteger left - result = new BigInteger(result.toString()); - } - } else if (Enum.class.isAssignableFrom(type)) { - String enumValueName = node.getValue(); - try { - if(loadingConfig.isEnumCaseSensitive()) { - result = Enum.valueOf(type, enumValueName); - } else { - result = EnumUtils.findEnumInsensitiveCase(type, enumValueName); - } - } catch (Exception ex) { - throw new YAMLException("Unable to find enum value '" + enumValueName - + "' for enum class: " + type.getName()); - } - } else if (Calendar.class.isAssignableFrom(type)) { - ConstructYamlTimestamp contr = new ConstructYamlTimestamp(); - contr.construct(node); - result = contr.getCalendar(); - } else if (Number.class.isAssignableFrom(type)) { - //since we do not know the exact type we create Float - ConstructYamlFloat contr = new ConstructYamlFloat(); - result = contr.construct(node); - } else if (UUID.class == type) { - result = UUID.fromString(node.getValue()); - } else { - if (yamlConstructors.containsKey(node.getTag())) { - result = yamlConstructors.get(node.getTag()).construct(node); - } else { - throw new YAMLException("Unsupported class: " + type); - } - } - return result; - } + } } - /** - * Construct sequence (List, Array, or immutable object) when the runtime - * class is known. - */ - protected class ConstructSequence implements Construct { - @SuppressWarnings("unchecked") - public Object construct(Node node) { - SequenceNode snode = (SequenceNode) node; - if (Set.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - throw new YAMLException("Set cannot be recursive."); - } else { - return constructSet(snode); - } - } else if (Collection.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - return newList(snode); - } else { - return constructSequence(snode); - } - } else if (node.getType().isArray()) { - if (node.isTwoStepsConstruction()) { - return createArray(node.getType(), snode.getValue().size()); - } else { - return constructArray(snode); - } - } else { - // create immutable object - List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>( - snode.getValue().size()); - for (java.lang.reflect.Constructor<?> constructor : node.getType() - .getDeclaredConstructors()) { - if (snode.getValue().size() == constructor.getParameterTypes().length) { - possibleConstructors.add(constructor); - } - } - if (!possibleConstructors.isEmpty()) { - if (possibleConstructors.size() == 1) { - Object[] argumentList = new Object[snode.getValue().size()]; - java.lang.reflect.Constructor<?> c = possibleConstructors.get(0); - int index = 0; - for (Node argumentNode : snode.getValue()) { - Class<?> type = c.getParameterTypes()[index]; - // set runtime classes for arguments - argumentNode.setType(type); - argumentList[index++] = constructObject(argumentNode); - } - - try { - c.setAccessible(true); - return c.newInstance(argumentList); - } catch (Exception e) { - throw new YAMLException(e); - } - } - - // use BaseConstructor - List<Object> argumentList = (List<Object>) constructSequence(snode); - Class<?>[] parameterTypes = new Class[argumentList.size()]; - int index = 0; - for (Object parameter : argumentList) { - parameterTypes[index] = parameter.getClass(); - index++; - } - - for (java.lang.reflect.Constructor<?> c : possibleConstructors) { - Class<?>[] argTypes = c.getParameterTypes(); - boolean foundConstructor = true; - for (int i = 0; i < argTypes.length; i++) { - if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) { - foundConstructor = false; - break; - } - } - if (foundConstructor) { - try { - c.setAccessible(true); - return c.newInstance(argumentList.toArray()); - } catch (Exception e) { - throw new YAMLException(e); - } - } - } - } - throw new YAMLException( - "No suitable constructor with " + String.valueOf(snode.getValue().size()) - + " arguments found for " + node.getType()); - - } - } - - private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) { - if (!clazz.isPrimitive()) { - return clazz; - } - if (clazz == Integer.TYPE) { - return Integer.class; - } - if (clazz == Float.TYPE) { - return Float.class; - } - if (clazz == Double.TYPE) { - return Double.class; - } - if (clazz == Boolean.TYPE) { - return Boolean.class; - } - if (clazz == Long.TYPE) { - return Long.class; - } - if (clazz == Character.TYPE) { - return Character.class; - } - if (clazz == Short.TYPE) { - return Short.class; - } - if (clazz == Byte.TYPE) { - return Byte.class; - } - throw new YAMLException("Unexpected primitive " + clazz); - } - - @SuppressWarnings("unchecked") - public void construct2ndStep(Node node, Object object) { - SequenceNode snode = (SequenceNode) node; - if (List.class.isAssignableFrom(node.getType())) { - List<Object> list = (List<Object>) object; - constructSequenceStep2(snode, list); - } else if (node.getType().isArray()) { - constructArrayStep2(snode, object); - } else { - throw new YAMLException("Immutable objects cannot be recursive."); - } - } + private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) { + if (!clazz.isPrimitive()) { + return clazz; + } + if (clazz == Integer.TYPE) { + return Integer.class; + } + if (clazz == Float.TYPE) { + return Float.class; + } + if (clazz == Double.TYPE) { + return Double.class; + } + if (clazz == Boolean.TYPE) { + return Boolean.class; + } + if (clazz == Long.TYPE) { + return Long.class; + } + if (clazz == Character.TYPE) { + return Character.class; + } + if (clazz == Short.TYPE) { + return Short.class; + } + if (clazz == Byte.TYPE) { + return Byte.class; + } + throw new YAMLException("Unexpected primitive " + clazz); } - protected Class<?> getClassForNode(Node node) { - Class<? extends Object> classForTag = typeTags.get(node.getTag()); - if (classForTag == null) { - String name = node.getTag().getClassName(); - Class<?> cl; - try { - cl = getClassForName(name); - } catch (ClassNotFoundException e) { - throw new YAMLException("Class not found: " + name); - } - typeTags.put(node.getTag(), cl); - return cl; - } else { - return classForTag; - } + @SuppressWarnings("unchecked") + public void construct2ndStep(Node node, Object object) { + SequenceNode snode = (SequenceNode) node; + if (List.class.isAssignableFrom(node.getType())) { + List<Object> list = (List<Object>) object; + constructSequenceStep2(snode, list); + } else if (node.getType().isArray()) { + constructArrayStep2(snode, object); + } else { + throw new YAMLException("Immutable objects cannot be recursive."); + } } + } + + protected Class<?> getClassForNode(Node node) { + Class<? extends Object> classForTag = typeTags.get(node.getTag()); + if (classForTag == null) { + String name = node.getTag().getClassName(); + Class<?> cl; + try { + cl = getClassForName(name); + } catch (ClassNotFoundException e) { + throw new YAMLException("Class not found: " + name); + } + typeTags.put(node.getTag(), cl); + return cl; + } else { + return classForTag; + } + } - protected Class<?> getClassForName(String name) throws ClassNotFoundException { - try { - return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); - } catch (ClassNotFoundException e) { - return Class.forName(name); - } + protected Class<?> getClassForName(String name) throws ClassNotFoundException { + try { + return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + return Class.forName(name); } + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java b/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java index 00929687..a619aff5 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/ConstructorException.java @@ -19,15 +19,16 @@ import org.yaml.snakeyaml.error.Mark; import org.yaml.snakeyaml.error.MarkedYAMLException; public class ConstructorException extends MarkedYAMLException { - private static final long serialVersionUID = -8816339931365239910L; - protected ConstructorException(String context, Mark contextMark, String problem, - Mark problemMark, Throwable cause) { - super(context, contextMark, problem, problemMark, cause); - } + private static final long serialVersionUID = -8816339931365239910L; - protected ConstructorException(String context, Mark contextMark, String problem, - Mark problemMark) { - this(context, contextMark, problem, problemMark, null); - } + protected ConstructorException(String context, Mark contextMark, String problem, + Mark problemMark, Throwable cause) { + super(context, contextMark, problem, problemMark, cause); + } + + protected ConstructorException(String context, Mark contextMark, String problem, + Mark problemMark) { + this(context, contextMark, problem, problemMark, null); + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java index 21af6bdd..2bfcd3e7 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/CustomClassLoaderConstructor.java @@ -19,22 +19,23 @@ package org.yaml.snakeyaml.constructor; * Construct instances with a custom Class Loader. */ public class CustomClassLoaderConstructor extends Constructor { - private ClassLoader loader = CustomClassLoaderConstructor.class.getClassLoader(); - public CustomClassLoaderConstructor(ClassLoader cLoader) { - this(Object.class, cLoader); - } + private ClassLoader loader = CustomClassLoaderConstructor.class.getClassLoader(); - public CustomClassLoaderConstructor(Class<? extends Object> theRoot, ClassLoader theLoader) { - super(theRoot); - if (theLoader == null) { - throw new NullPointerException("Loader must be provided."); - } - this.loader = theLoader; - } + public CustomClassLoaderConstructor(ClassLoader cLoader) { + this(Object.class, cLoader); + } - @Override - protected Class<?> getClassForName(String name) throws ClassNotFoundException { - return Class.forName(name, true, loader); + public CustomClassLoaderConstructor(Class<? extends Object> theRoot, ClassLoader theLoader) { + super(theRoot); + if (theLoader == null) { + throw new NullPointerException("Loader must be provided."); } + this.loader = theLoader; + } + + @Override + protected Class<?> getClassForName(String name) throws ClassNotFoundException { + return Class.forName(name, true, loader); + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java b/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java index dc7198ee..55654ef0 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/DuplicateKeyException.java @@ -19,8 +19,9 @@ import org.yaml.snakeyaml.error.Mark; public class DuplicateKeyException extends ConstructorException { - protected DuplicateKeyException(Mark contextMark, Object key, - Mark problemMark) { - super("while constructing a mapping", contextMark, "found duplicate key " + String.valueOf(key), problemMark); - } + protected DuplicateKeyException(Mark contextMark, Object key, + Mark problemMark) { + super("while constructing a mapping", contextMark, "found duplicate key " + key, + problemMark); + } } diff --git a/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java index 190d93ad..33ca27cd 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/SafeConstructor.java @@ -28,7 +28,6 @@ import java.util.TimeZone; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; @@ -45,577 +44,596 @@ import org.yaml.snakeyaml.nodes.Tag; */ public class SafeConstructor extends BaseConstructor { - public static final ConstructUndefined undefinedConstructor = new ConstructUndefined(); - - public SafeConstructor() { - this(new LoaderOptions()); - } - - public SafeConstructor(LoaderOptions loadingConfig) { - super(loadingConfig); - this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull()); - this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool()); - this.yamlConstructors.put(Tag.INT, new ConstructYamlInt()); - this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat()); - this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary()); - this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp()); - this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap()); - this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs()); - this.yamlConstructors.put(Tag.SET, new ConstructYamlSet()); - this.yamlConstructors.put(Tag.STR, new ConstructYamlStr()); - this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq()); - this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap()); - this.yamlConstructors.put(null, undefinedConstructor); - this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor); - this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor); - this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor); + public static final ConstructUndefined undefinedConstructor = new ConstructUndefined(); + + public SafeConstructor() { + this(new LoaderOptions()); + } + + public SafeConstructor(LoaderOptions loadingConfig) { + super(loadingConfig); + this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull()); + this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool()); + this.yamlConstructors.put(Tag.INT, new ConstructYamlInt()); + this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat()); + this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary()); + this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp()); + this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap()); + this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs()); + this.yamlConstructors.put(Tag.SET, new ConstructYamlSet()); + this.yamlConstructors.put(Tag.STR, new ConstructYamlStr()); + this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq()); + this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap()); + this.yamlConstructors.put(null, undefinedConstructor); + this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor); + this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor); + this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor); + } + + protected void flattenMapping(MappingNode node) { + flattenMapping(node, false); + } + + protected void flattenMapping(MappingNode node, boolean forceStringKeys) { + // perform merging only on nodes containing merge node(s) + processDuplicateKeys(node, forceStringKeys); + if (node.isMerged()) { + node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(), + new ArrayList<NodeTuple>(), forceStringKeys)); } - - protected void flattenMapping(MappingNode node) { - flattenMapping(node, false); - } - - protected void flattenMapping(MappingNode node, boolean forceStringKeys) { - // perform merging only on nodes containing merge node(s) - processDuplicateKeys(node, forceStringKeys); - if (node.isMerged()) { - node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(), - new ArrayList<NodeTuple>(), forceStringKeys)); + } + + protected void processDuplicateKeys(MappingNode node) { + processDuplicateKeys(node, false); + } + + protected void processDuplicateKeys(MappingNode node, boolean forceStringKeys) { + List<NodeTuple> nodeValue = node.getValue(); + Map<Object, Integer> keys = new HashMap<Object, Integer>(nodeValue.size()); + TreeSet<Integer> toRemove = new TreeSet<Integer>(); + int i = 0; + for (NodeTuple tuple : nodeValue) { + Node keyNode = tuple.getKeyNode(); + if (!keyNode.getTag().equals(Tag.MERGE)) { + if (forceStringKeys) { + if (keyNode instanceof ScalarNode) { + keyNode.setType(String.class); + keyNode.setTag(Tag.STR); + } else { + throw new YAMLException( + "Keys must be scalars but found: " + keyNode); + } } - } - - protected void processDuplicateKeys(MappingNode node) { - processDuplicateKeys(node, false); - } - - protected void processDuplicateKeys(MappingNode node, boolean forceStringKeys) { - List<NodeTuple> nodeValue = node.getValue(); - Map<Object, Integer> keys = new HashMap<Object, Integer>(nodeValue.size()); - TreeSet<Integer> toRemove = new TreeSet<Integer>(); - int i = 0; - for (NodeTuple tuple : nodeValue) { - Node keyNode = tuple.getKeyNode(); - if (!keyNode.getTag().equals(Tag.MERGE)) { - if (forceStringKeys) { - if (keyNode instanceof ScalarNode) { - keyNode.setType(String.class); - keyNode.setTag(Tag.STR); - } else { - throw new YAMLException( - "Keys must be scalars but found: " + keyNode); - } - } - Object key = constructObject(keyNode); - if (key != null && !forceStringKeys) { - if (keyNode.isTwoStepsConstruction()) { - if(!loadingConfig.getAllowRecursiveKeys()) { - throw new YAMLException( - "Recursive key is detected but it is not configured to be allowed."); - } else { - try { - key.hashCode();// check circular dependencies - } catch (Exception e) { - throw new ConstructorException("while constructing a mapping", - node.getStartMark(), "found unacceptable key " + key, - tuple.getKeyNode().getStartMark(), e); - } - } - } - } - - Integer prevIndex = keys.put(key, i); - if (prevIndex != null) { - if (!isAllowDuplicateKeys()) { - throw new DuplicateKeyException(node.getStartMark(), key, - tuple.getKeyNode().getStartMark()); - } - toRemove.add(prevIndex); - } + Object key = constructObject(keyNode); + if (key != null && !forceStringKeys) { + if (keyNode.isTwoStepsConstruction()) { + if (!loadingConfig.getAllowRecursiveKeys()) { + throw new YAMLException( + "Recursive key is detected but it is not configured to be allowed."); + } else { + try { + key.hashCode();// check circular dependencies + } catch (Exception e) { + throw new ConstructorException("while constructing a mapping", + node.getStartMark(), "found unacceptable key " + key, + tuple.getKeyNode().getStartMark(), e); + } } - i = i + 1; + } } - Iterator<Integer> indices2remove = toRemove.descendingIterator(); - while (indices2remove.hasNext()) { - nodeValue.remove(indices2remove.next().intValue()); + Integer prevIndex = keys.put(key, i); + if (prevIndex != null) { + if (!isAllowDuplicateKeys()) { + throw new DuplicateKeyException(node.getStartMark(), key, + tuple.getKeyNode().getStartMark()); + } + toRemove.add(prevIndex); } + } + i = i + 1; } - /** - * Does merge for supplied mapping node. - * - * @param node - * where to merge - * @param isPreffered - * true if keys of node should take precedence over others... - * @param key2index - * maps already merged keys to index from values - * @param values - * collects merged NodeTuple - * @return list of the merged NodeTuple (to be set as value for the - * MappingNode) - */ - private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered, - Map<Object, Integer> key2index, List<NodeTuple> values, boolean forceStringKeys) { - Iterator<NodeTuple> iter = node.getValue().iterator(); - while (iter.hasNext()) { - final NodeTuple nodeTuple = iter.next(); - final Node keyNode = nodeTuple.getKeyNode(); - final Node valueNode = nodeTuple.getValueNode(); - if (keyNode.getTag().equals(Tag.MERGE)) { - iter.remove(); - switch (valueNode.getNodeId()) { - case mapping: - MappingNode mn = (MappingNode) valueNode; - mergeNode(mn, false, key2index, values, forceStringKeys); - break; - case sequence: - SequenceNode sn = (SequenceNode) valueNode; - List<Node> vals = sn.getValue(); - for (Node subnode : vals) { - if (!(subnode instanceof MappingNode)) { - throw new ConstructorException("while constructing a mapping", - node.getStartMark(), - "expected a mapping for merging, but found " - + subnode.getNodeId(), - subnode.getStartMark()); - } - MappingNode mnode = (MappingNode) subnode; - mergeNode(mnode, false, key2index, values,forceStringKeys); - } - break; - default: - throw new ConstructorException("while constructing a mapping", - node.getStartMark(), - "expected a mapping or list of mappings for merging, but found " - + valueNode.getNodeId(), - valueNode.getStartMark()); - } - } else { - // we need to construct keys to avoid duplications - if (forceStringKeys) { - if (keyNode instanceof ScalarNode) { - keyNode.setType(String.class); - keyNode.setTag(Tag.STR); - } else { - throw new YAMLException("Keys must be scalars but found: " + keyNode); - } - } - Object key = constructObject(keyNode); - if (!key2index.containsKey(key)) { // 1st time merging key - values.add(nodeTuple); - // keep track where tuple for the key is - key2index.put(key, values.size() - 1); - } else if (isPreffered) { // there is value for the key, but we - // need to override it - // change value for the key using saved position - values.set(key2index.get(key), nodeTuple); - } + Iterator<Integer> indices2remove = toRemove.descendingIterator(); + while (indices2remove.hasNext()) { + nodeValue.remove(indices2remove.next().intValue()); + } + } + + /** + * Does merge for supplied mapping node. + * + * @param node + * where to merge + * @param isPreffered + * true if keys of node should take precedence over others... + * @param key2index + * maps already merged keys to index from values + * @param values + * collects merged NodeTuple + * @return list of the merged NodeTuple (to be set as value for the + * MappingNode) + */ + private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered, + Map<Object, Integer> key2index, List<NodeTuple> values, boolean forceStringKeys) { + Iterator<NodeTuple> iter = node.getValue().iterator(); + while (iter.hasNext()) { + final NodeTuple nodeTuple = iter.next(); + final Node keyNode = nodeTuple.getKeyNode(); + final Node valueNode = nodeTuple.getValueNode(); + if (keyNode.getTag().equals(Tag.MERGE)) { + iter.remove(); + switch (valueNode.getNodeId()) { + case mapping: + MappingNode mn = (MappingNode) valueNode; + mergeNode(mn, false, key2index, values, forceStringKeys); + break; + case sequence: + SequenceNode sn = (SequenceNode) valueNode; + List<Node> vals = sn.getValue(); + for (Node subnode : vals) { + if (!(subnode instanceof MappingNode)) { + throw new ConstructorException("while constructing a mapping", + node.getStartMark(), + "expected a mapping for merging, but found " + + subnode.getNodeId(), + subnode.getStartMark()); + } + MappingNode mnode = (MappingNode) subnode; + mergeNode(mnode, false, key2index, values, forceStringKeys); } + break; + default: + throw new ConstructorException("while constructing a mapping", + node.getStartMark(), + "expected a mapping or list of mappings for merging, but found " + + valueNode.getNodeId(), + valueNode.getStartMark()); + } + } else { + // we need to construct keys to avoid duplications + if (forceStringKeys) { + if (keyNode instanceof ScalarNode) { + keyNode.setType(String.class); + keyNode.setTag(Tag.STR); + } else { + throw new YAMLException("Keys must be scalars but found: " + keyNode); + } + } + Object key = constructObject(keyNode); + if (!key2index.containsKey(key)) { // 1st time merging key + values.add(nodeTuple); + // keep track where tuple for the key is + key2index.put(key, values.size() - 1); + } else if (isPreffered) { // there is value for the key, but we + // need to override it + // change value for the key using saved position + values.set(key2index.get(key), nodeTuple); } - return values; + } } + return values; + } + + @Override + protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { + flattenMapping(node); + super.constructMapping2ndStep(node, mapping); + } + + @Override + protected void constructSet2ndStep(MappingNode node, Set<Object> set) { + flattenMapping(node); + super.constructSet2ndStep(node, set); + } + + public class ConstructYamlNull extends AbstractConstruct { @Override - protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { - flattenMapping(node); - super.constructMapping2ndStep(node, mapping); + public Object construct(Node node) { + if (node != null) { + constructScalar((ScalarNode) node); + } + return null; } + } + + private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>(); + + static { + BOOL_VALUES.put("yes", Boolean.TRUE); + BOOL_VALUES.put("no", Boolean.FALSE); + BOOL_VALUES.put("true", Boolean.TRUE); + BOOL_VALUES.put("false", Boolean.FALSE); + BOOL_VALUES.put("on", Boolean.TRUE); + BOOL_VALUES.put("off", Boolean.FALSE); + } + + public class ConstructYamlBool extends AbstractConstruct { @Override - protected void constructSet2ndStep(MappingNode node, Set<Object> set) { - flattenMapping(node); - super.constructSet2ndStep(node, set); + public Object construct(Node node) { + String val = constructScalar((ScalarNode) node); + return BOOL_VALUES.get(val.toLowerCase()); } + } - public class ConstructYamlNull extends AbstractConstruct { - @Override - public Object construct(Node node) { - if (node != null) constructScalar((ScalarNode) node); - return null; + public class ConstructYamlInt extends AbstractConstruct { + + @Override + public Object construct(Node node) { + String value = constructScalar((ScalarNode) node).replaceAll("_", ""); + if (value.isEmpty()) { + throw new ConstructorException("while constructing an int", + node.getStartMark(), "found empty value", + node.getStartMark()); + } + int sign = +1; + char first = value.charAt(0); + if (first == '-') { + sign = -1; + value = value.substring(1); + } else if (first == '+') { + value = value.substring(1); + } + int base = 10; + if ("0".equals(value)) { + return Integer.valueOf(0); + } else if (value.startsWith("0b")) { + value = value.substring(2); + base = 2; + } else if (value.startsWith("0x")) { + value = value.substring(2); + base = 16; + } else if (value.startsWith("0")) { + value = value.substring(1); + base = 8; + } else if (value.indexOf(':') != -1) { + String[] digits = value.split(":"); + int bes = 1; + int val = 0; + for (int i = 0, j = digits.length; i < j; i++) { + val += Long.parseLong(digits[j - i - 1]) * bes; + bes *= 60; } + return createNumber(sign, String.valueOf(val), 10); + } else { + return createNumber(sign, value, 10); + } + return createNumber(sign, value, base); } + } - private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>(); - static { - BOOL_VALUES.put("yes", Boolean.TRUE); - BOOL_VALUES.put("no", Boolean.FALSE); - BOOL_VALUES.put("true", Boolean.TRUE); - BOOL_VALUES.put("false", Boolean.FALSE); - BOOL_VALUES.put("on", Boolean.TRUE); - BOOL_VALUES.put("off", Boolean.FALSE); - } + private static final int[][] RADIX_MAX = new int[17][2]; - public class ConstructYamlBool extends AbstractConstruct { - @Override - public Object construct(Node node) { - String val = (String) constructScalar((ScalarNode) node); - return BOOL_VALUES.get(val.toLowerCase()); - } + static { + int[] radixList = new int[]{2, 8, 10, 16}; + for (int radix : radixList) { + RADIX_MAX[radix] = new int[]{maxLen(Integer.MAX_VALUE, radix), maxLen(Long.MAX_VALUE, radix)}; } + } - public class ConstructYamlInt extends AbstractConstruct { - @Override - public Object construct(Node node) { - String value = constructScalar((ScalarNode) node).replaceAll("_", ""); - if (value.isEmpty()) { - throw new ConstructorException("while constructing an int", - node.getStartMark(), "found empty value", - node.getStartMark()); - } - int sign = +1; - char first = value.charAt(0); - if (first == '-') { - sign = -1; - value = value.substring(1); - } else if (first == '+') { - value = value.substring(1); - } - int base = 10; - if ("0".equals(value)) { - return Integer.valueOf(0); - } else if (value.startsWith("0b")) { - value = value.substring(2); - base = 2; - } else if (value.startsWith("0x")) { - value = value.substring(2); - base = 16; - } else if (value.startsWith("0")) { - value = value.substring(1); - base = 8; - } else if (value.indexOf(':') != -1) { - String[] digits = value.split(":"); - int bes = 1; - int val = 0; - for (int i = 0, j = digits.length; i < j; i++) { - val += Long.parseLong(digits[j - i - 1]) * bes; - bes *= 60; - } - return createNumber(sign, String.valueOf(val), 10); - } else { - return createNumber(sign, value, 10); - } - return createNumber(sign, value, base); + private static int maxLen(final int max, final int radix) { + return Integer.toString(max, radix).length(); + } + + private static int maxLen(final long max, final int radix) { + return Long.toString(max, radix).length(); + } + + private Number createNumber(int sign, String number, int radix) { + final int len = number != null ? number.length() : 0; + if (sign < 0) { + number = "-" + number; + } + final int[] maxArr = radix < RADIX_MAX.length ? RADIX_MAX[radix] : null; + if (maxArr != null) { + final boolean gtInt = len > maxArr[0]; + if (gtInt) { + if (len > maxArr[1]) { + return new BigInteger(number, radix); } + return createLongOrBigInteger(number, radix); + } + } + Number result; + try { + result = Integer.valueOf(number, radix); + } catch (NumberFormatException e) { + result = createLongOrBigInteger(number, radix); + } + return result; + } + + protected static Number createLongOrBigInteger(final String number, final int radix) { + try { + return Long.valueOf(number, radix); + } catch (NumberFormatException e1) { + return new BigInteger(number, radix); } + } - private static final int[][] RADIX_MAX = new int[17][2]; - static { - int[] radixList = new int[] {2, 8, 10, 16}; - for( int radix : radixList) { - RADIX_MAX[radix] = new int[] { maxLen(Integer.MAX_VALUE,radix), maxLen(Long.MAX_VALUE,radix)}; + public class ConstructYamlFloat extends AbstractConstruct { + + @Override + public Object construct(Node node) { + String value = constructScalar((ScalarNode) node).replaceAll("_", ""); + if (value.isEmpty()) { + throw new ConstructorException("while constructing a float", + node.getStartMark(), "found empty value", + node.getStartMark()); + } + int sign = +1; + char first = value.charAt(0); + if (first == '-') { + sign = -1; + value = value.substring(1); + } else if (first == '+') { + value = value.substring(1); + } + String valLower = value.toLowerCase(); + if (".inf".equals(valLower)) { + return Double + .valueOf(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY); + } else if (".nan".equals(valLower)) { + return Double.valueOf(Double.NaN); + } else if (value.indexOf(':') != -1) { + String[] digits = value.split(":"); + int bes = 1; + double val = 0.0; + for (int i = 0, j = digits.length; i < j; i++) { + val += Double.parseDouble(digits[j - i - 1]) * bes; + bes *= 60; } + return Double.valueOf(sign * val); + } else { + Double d = Double.valueOf(value); + return Double.valueOf(d.doubleValue() * sign); + } } + } + + public class ConstructYamlBinary extends AbstractConstruct { - private static int maxLen(final int max, final int radix) { - return Integer.toString(max,radix).length(); + @Override + public Object construct(Node node) { + // Ignore white spaces for base64 encoded scalar + String noWhiteSpaces = constructScalar((ScalarNode) node).replaceAll("\\s", + ""); + byte[] decoded = Base64Coder.decode(noWhiteSpaces.toCharArray()); + return decoded; } - private static int maxLen(final long max,final int radix) { - return Long.toString(max,radix).length(); + } + + private final static Pattern TIMESTAMP_REGEXP = Pattern.compile( + "^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$"); + private final static Pattern YMD_REGEXP = Pattern + .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$"); + + public static class ConstructYamlTimestamp extends AbstractConstruct { + + private Calendar calendar; + + public Calendar getCalendar() { + return calendar; } - private Number createNumber(int sign, String number, int radix) { - final int len = number != null ? number.length() : 0; - if (sign < 0) { - number = "-" + number; + + @Override + public Object construct(Node node) { + ScalarNode scalar = (ScalarNode) node; + String nodeValue = scalar.getValue(); + Matcher match = YMD_REGEXP.matcher(nodeValue); + if (match.matches()) { + String year_s = match.group(1); + String month_s = match.group(2); + String day_s = match.group(3); + calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); + // Java's months are zero-based... + calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x + calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); + return calendar.getTime(); + } else { + match = TIMESTAMP_REGEXP.matcher(nodeValue); + if (!match.matches()) { + throw new YAMLException("Unexpected timestamp: " + nodeValue); } - final int[] maxArr = radix < RADIX_MAX.length ? RADIX_MAX[radix] : null; - if (maxArr != null) { - final boolean gtInt = len >maxArr[0]; - if (gtInt) { - if(len > maxArr[1]) { - return new BigInteger(number, radix); - } - return createLongOrBigInteger(number, radix); - } + String year_s = match.group(1); + String month_s = match.group(2); + String day_s = match.group(3); + String hour_s = match.group(4); + String min_s = match.group(5); + // seconds and milliseconds + String seconds = match.group(6); + String millis = match.group(7); + if (millis != null) { + seconds = seconds + "." + millis; } - Number result; - try { - result = Integer.valueOf(number, radix); - } catch (NumberFormatException e) { - result = createLongOrBigInteger(number, radix); + double fractions = Double.parseDouble(seconds); + int sec_s = (int) Math.round(Math.floor(fractions)); + int usec = (int) Math.round((fractions - sec_s) * 1000); + // timezone + String timezoneh_s = match.group(8); + String timezonem_s = match.group(9); + TimeZone timeZone; + if (timezoneh_s != null) { + String time = timezonem_s != null ? ":" + timezonem_s : "00"; + timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time); + } else { + // no time zone provided + timeZone = TimeZone.getTimeZone("UTC"); } - return result; + calendar = Calendar.getInstance(timeZone); + calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); + // Java's months are zero-based... + calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); + calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); + calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s)); + calendar.set(Calendar.MINUTE, Integer.parseInt(min_s)); + calendar.set(Calendar.SECOND, sec_s); + calendar.set(Calendar.MILLISECOND, usec); + return calendar.getTime(); + } } + } - protected static Number createLongOrBigInteger(final String number,final int radix) { - try { - return Long.valueOf(number, radix); - } catch (NumberFormatException e1) { - return new BigInteger(number, radix); - } - } + public class ConstructYamlOmap extends AbstractConstruct { - public class ConstructYamlFloat extends AbstractConstruct { - @Override - public Object construct(Node node) { - String value = constructScalar((ScalarNode) node).replaceAll("_", ""); - if (value.isEmpty()) { - throw new ConstructorException("while constructing a float", - node.getStartMark(), "found empty value", - node.getStartMark()); - } - int sign = +1; - char first = value.charAt(0); - if (first == '-') { - sign = -1; - value = value.substring(1); - } else if (first == '+') { - value = value.substring(1); - } - String valLower = value.toLowerCase(); - if (".inf".equals(valLower)) { - return Double - .valueOf(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY); - } else if (".nan".equals(valLower)) { - return Double.valueOf(Double.NaN); - } else if (value.indexOf(':') != -1) { - String[] digits = value.split(":"); - int bes = 1; - double val = 0.0; - for (int i = 0, j = digits.length; i < j; i++) { - val += Double.parseDouble(digits[j - i - 1]) * bes; - bes *= 60; - } - return Double.valueOf(sign * val); - } else { - Double d = Double.valueOf(value); - return Double.valueOf(d.doubleValue() * sign); - } + @Override + public Object construct(Node node) { + // Note: we do not check for duplicate keys, because it's too + // CPU-expensive. + Map<Object, Object> omap = new LinkedHashMap<Object, Object>(); + if (!(node instanceof SequenceNode)) { + throw new ConstructorException("while constructing an ordered map", + node.getStartMark(), "expected a sequence, but found " + node.getNodeId(), + node.getStartMark()); + } + SequenceNode snode = (SequenceNode) node; + for (Node subnode : snode.getValue()) { + if (!(subnode instanceof MappingNode)) { + throw new ConstructorException("while constructing an ordered map", + node.getStartMark(), + "expected a mapping of length 1, but found " + subnode.getNodeId(), + subnode.getStartMark()); } - } - - public class ConstructYamlBinary extends AbstractConstruct { - @Override - public Object construct(Node node) { - // Ignore white spaces for base64 encoded scalar - String noWhiteSpaces = constructScalar((ScalarNode) node).toString().replaceAll("\\s", - ""); - byte[] decoded = Base64Coder.decode(noWhiteSpaces.toCharArray()); - return decoded; + MappingNode mnode = (MappingNode) subnode; + if (mnode.getValue().size() != 1) { + throw new ConstructorException("while constructing an ordered map", + node.getStartMark(), "expected a single mapping item, but found " + + mnode.getValue().size() + " items", + mnode.getStartMark()); } + Node keyNode = mnode.getValue().get(0).getKeyNode(); + Node valueNode = mnode.getValue().get(0).getValueNode(); + Object key = constructObject(keyNode); + Object value = constructObject(valueNode); + omap.put(key, value); + } + return omap; } + } - private final static Pattern TIMESTAMP_REGEXP = Pattern.compile( - "^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$"); - private final static Pattern YMD_REGEXP = Pattern - .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$"); - - public static class ConstructYamlTimestamp extends AbstractConstruct { - private Calendar calendar; + public class ConstructYamlPairs extends AbstractConstruct { - public Calendar getCalendar() { - return calendar; + @Override + public Object construct(Node node) { + // Note: we do not check for duplicate keys, because it's too + // CPU-expensive. + if (!(node instanceof SequenceNode)) { + throw new ConstructorException("while constructing pairs", node.getStartMark(), + "expected a sequence, but found " + node.getNodeId(), node.getStartMark()); + } + SequenceNode snode = (SequenceNode) node; + List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size()); + for (Node subnode : snode.getValue()) { + if (!(subnode instanceof MappingNode)) { + throw new ConstructorException("while constructingpairs", node.getStartMark(), + "expected a mapping of length 1, but found " + subnode.getNodeId(), + subnode.getStartMark()); } - - @Override - public Object construct(Node node) { - ScalarNode scalar = (ScalarNode) node; - String nodeValue = scalar.getValue(); - Matcher match = YMD_REGEXP.matcher(nodeValue); - if (match.matches()) { - String year_s = match.group(1); - String month_s = match.group(2); - String day_s = match.group(3); - calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.clear(); - calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); - // Java's months are zero-based... - calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x - calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); - return calendar.getTime(); - } else { - match = TIMESTAMP_REGEXP.matcher(nodeValue); - if (!match.matches()) { - throw new YAMLException("Unexpected timestamp: " + nodeValue); - } - String year_s = match.group(1); - String month_s = match.group(2); - String day_s = match.group(3); - String hour_s = match.group(4); - String min_s = match.group(5); - // seconds and milliseconds - String seconds = match.group(6); - String millis = match.group(7); - if (millis != null) { - seconds = seconds + "." + millis; - } - double fractions = Double.parseDouble(seconds); - int sec_s = (int) Math.round(Math.floor(fractions)); - int usec = (int) Math.round((fractions - sec_s) * 1000); - // timezone - String timezoneh_s = match.group(8); - String timezonem_s = match.group(9); - TimeZone timeZone; - if (timezoneh_s != null) { - String time = timezonem_s != null ? ":" + timezonem_s : "00"; - timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time); - } else { - // no time zone provided - timeZone = TimeZone.getTimeZone("UTC"); - } - calendar = Calendar.getInstance(timeZone); - calendar.set(Calendar.YEAR, Integer.parseInt(year_s)); - // Java's months are zero-based... - calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); - calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s)); - calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s)); - calendar.set(Calendar.MINUTE, Integer.parseInt(min_s)); - calendar.set(Calendar.SECOND, sec_s); - calendar.set(Calendar.MILLISECOND, usec); - return calendar.getTime(); - } + MappingNode mnode = (MappingNode) subnode; + if (mnode.getValue().size() != 1) { + throw new ConstructorException("while constructing pairs", node.getStartMark(), + "expected a single mapping item, but found " + mnode.getValue().size() + + " items", + mnode.getStartMark()); } + Node keyNode = mnode.getValue().get(0).getKeyNode(); + Node valueNode = mnode.getValue().get(0).getValueNode(); + Object key = constructObject(keyNode); + Object value = constructObject(valueNode); + pairs.add(new Object[]{key, value}); + } + return pairs; } + } - public class ConstructYamlOmap extends AbstractConstruct { - @Override - public Object construct(Node node) { - // Note: we do not check for duplicate keys, because it's too - // CPU-expensive. - Map<Object, Object> omap = new LinkedHashMap<Object, Object>(); - if (!(node instanceof SequenceNode)) { - throw new ConstructorException("while constructing an ordered map", - node.getStartMark(), "expected a sequence, but found " + node.getNodeId(), - node.getStartMark()); - } - SequenceNode snode = (SequenceNode) node; - for (Node subnode : snode.getValue()) { - if (!(subnode instanceof MappingNode)) { - throw new ConstructorException("while constructing an ordered map", - node.getStartMark(), - "expected a mapping of length 1, but found " + subnode.getNodeId(), - subnode.getStartMark()); - } - MappingNode mnode = (MappingNode) subnode; - if (mnode.getValue().size() != 1) { - throw new ConstructorException("while constructing an ordered map", - node.getStartMark(), "expected a single mapping item, but found " - + mnode.getValue().size() + " items", - mnode.getStartMark()); - } - Node keyNode = mnode.getValue().get(0).getKeyNode(); - Node valueNode = mnode.getValue().get(0).getValueNode(); - Object key = constructObject(keyNode); - Object value = constructObject(valueNode); - omap.put(key, value); - } - return omap; - } + public class ConstructYamlSet implements Construct { + + @Override + public Object construct(Node node) { + if (node.isTwoStepsConstruction()) { + return (constructedObjects.containsKey(node) ? constructedObjects.get(node) + : createDefaultSet(((MappingNode) node).getValue().size())); + } else { + return constructSet((MappingNode) node); + } } - public class ConstructYamlPairs extends AbstractConstruct { - @Override - public Object construct(Node node) { - // Note: we do not check for duplicate keys, because it's too - // CPU-expensive. - if (!(node instanceof SequenceNode)) { - throw new ConstructorException("while constructing pairs", node.getStartMark(), - "expected a sequence, but found " + node.getNodeId(), node.getStartMark()); - } - SequenceNode snode = (SequenceNode) node; - List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size()); - for (Node subnode : snode.getValue()) { - if (!(subnode instanceof MappingNode)) { - throw new ConstructorException("while constructingpairs", node.getStartMark(), - "expected a mapping of length 1, but found " + subnode.getNodeId(), - subnode.getStartMark()); - } - MappingNode mnode = (MappingNode) subnode; - if (mnode.getValue().size() != 1) { - throw new ConstructorException("while constructing pairs", node.getStartMark(), - "expected a single mapping item, but found " + mnode.getValue().size() - + " items", - mnode.getStartMark()); - } - Node keyNode = mnode.getValue().get(0).getKeyNode(); - Node valueNode = mnode.getValue().get(0).getValueNode(); - Object key = constructObject(keyNode); - Object value = constructObject(valueNode); - pairs.add(new Object[] { key, value }); - } - return pairs; - } + @Override + @SuppressWarnings("unchecked") + public void construct2ndStep(Node node, Object object) { + if (node.isTwoStepsConstruction()) { + constructSet2ndStep((MappingNode) node, (Set<Object>) object); + } else { + throw new YAMLException("Unexpected recursive set structure. Node: " + node); + } } + } - public class ConstructYamlSet implements Construct { - @Override - public Object construct(Node node) { - if (node.isTwoStepsConstruction()) { - return (constructedObjects.containsKey(node) ? constructedObjects.get(node) - : createDefaultSet(((MappingNode) node).getValue().size())); - } else { - return constructSet((MappingNode) node); - } - } + public class ConstructYamlStr extends AbstractConstruct { - @Override - @SuppressWarnings("unchecked") - public void construct2ndStep(Node node, Object object) { - if (node.isTwoStepsConstruction()) { - constructSet2ndStep((MappingNode) node, (Set<Object>) object); - } else { - throw new YAMLException("Unexpected recursive set structure. Node: " + node); - } - } + @Override + public Object construct(Node node) { + return constructScalar((ScalarNode) node); } + } - public class ConstructYamlStr extends AbstractConstruct { - @Override - public Object construct(Node node) { - return constructScalar((ScalarNode) node); - } - } + public class ConstructYamlSeq implements Construct { - public class ConstructYamlSeq implements Construct { - @Override - public Object construct(Node node) { - SequenceNode seqNode = (SequenceNode) node; - if (node.isTwoStepsConstruction()) { - return newList(seqNode); - } else { - return constructSequence(seqNode); - } - } + @Override + public Object construct(Node node) { + SequenceNode seqNode = (SequenceNode) node; + if (node.isTwoStepsConstruction()) { + return newList(seqNode); + } else { + return constructSequence(seqNode); + } + } - @Override - @SuppressWarnings("unchecked") - public void construct2ndStep(Node node, Object data) { - if (node.isTwoStepsConstruction()) { - constructSequenceStep2((SequenceNode) node, (List<Object>) data); - } else { - throw new YAMLException("Unexpected recursive sequence structure. Node: " + node); - } - } + @Override + @SuppressWarnings("unchecked") + public void construct2ndStep(Node node, Object data) { + if (node.isTwoStepsConstruction()) { + constructSequenceStep2((SequenceNode) node, (List<Object>) data); + } else { + throw new YAMLException("Unexpected recursive sequence structure. Node: " + node); + } } + } - public class ConstructYamlMap implements Construct { - @Override - public Object construct(Node node) { - MappingNode mnode = (MappingNode) node; - if (node.isTwoStepsConstruction()) { - return createDefaultMap(mnode.getValue().size()); - } else { - return constructMapping(mnode); - } - } + public class ConstructYamlMap implements Construct { - @Override - @SuppressWarnings("unchecked") - public void construct2ndStep(Node node, Object object) { - if (node.isTwoStepsConstruction()) { - constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object); - } else { - throw new YAMLException("Unexpected recursive mapping structure. Node: " + node); - } - } + @Override + public Object construct(Node node) { + MappingNode mnode = (MappingNode) node; + if (node.isTwoStepsConstruction()) { + return createDefaultMap(mnode.getValue().size()); + } else { + return constructMapping(mnode); + } } - public static final class ConstructUndefined extends AbstractConstruct { - @Override - public Object construct(Node node) { - throw new ConstructorException(null, null, - "could not determine a constructor for the tag " + node.getTag(), - node.getStartMark()); - } + @Override + @SuppressWarnings("unchecked") + public void construct2ndStep(Node node, Object object) { + if (node.isTwoStepsConstruction()) { + constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object); + } else { + throw new YAMLException("Unexpected recursive mapping structure. Node: " + node); + } + } + } + + public static final class ConstructUndefined extends AbstractConstruct { + + @Override + public Object construct(Node node) { + throw new ConstructorException(null, null, + "could not determine a constructor for the tag " + node.getTag(), + node.getStartMark()); } + } } |