diff options
Diffstat (limited to 'src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java')
-rw-r--r-- | src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java | 895 |
1 files changed, 534 insertions, 361 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java index 3a504cbb..a4ed1f50 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/BaseConstructor.java @@ -1,21 +1,20 @@ /** - * Copyright (c) 2008, http://www.snakeyaml.org + * Copyright (c) 2008, SnakeYAML * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package org.yaml.snakeyaml.constructor; import java.lang.reflect.Array; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; @@ -25,12 +24,19 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; - +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; import org.yaml.snakeyaml.composer.ComposerException; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.introspector.PropertyUtils; +import org.yaml.snakeyaml.nodes.CollectionNode; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; @@ -40,411 +46,578 @@ 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: <br/> - * 1) explicit tag - if present. <br/> - * 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; - private 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; - - public BaseConstructor() { - 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>>(); - rootTag = null; - explicitPropertyUtils = false; - } - public void setComposer(Composer composer) { - this.composer = composer; + /** + * An instance returned by newInstance methods when instantiation has not been performed. + */ + protected static final Object NOT_INSTANTIATED_OBJECT = new Object(); + + /** + * 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."); } - - /** - * 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(); + Node node = composer.getNode(); + if (rootTag != null) { + node.setTag(rootTag); } - - /** - * Construct and return the next document - * - * @return constructed instance - */ - public Object getData() { - // Construct and return the next document. - composer.checkNode(); - Node node = composer.getNode(); - if (rootTag != null) { - node.setTag(rootTag); - } - return constructDocument(node); + 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); } - - /** - * Ensure that the stream contains a single document and construct it - * - * @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 - Node node = composer.getSingleNode(); - if (node != null) { - if (Object.class != type) { - node.setTag(new Tag(type)); - } else if (rootTag != null) { - node.setTag(rootTag); - } - return constructDocument(node); - } - return null; + } + + /** + * 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(); } - - /** - * 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) { - Object data = constructObject(node); - fillRecursive(); - constructedObjects.clear(); - recursiveObjects.clear(); - return data; + } + + /** + * 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(); } - - 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(); - } + 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) { - if (constructedObjects.containsKey(node)) { - return constructedObjects.get(node); - } - if (recursiveObjects.contains(node)) { - throw new ConstructorException(null, null, "found unconstructable recursive node", - node.getStartMark()); - } - recursiveObjects.add(node); - Construct constructor = getConstructor(node); - Object data = constructor.construct(node); - constructedObjects.put(node, data); - recursiveObjects.remove(node); - if (node.isTwoStepsConstruction()) { - constructor.construct2ndStep(node, data); - } - return data; + } + + /** + * 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) { + if (constructedObjects.containsKey(node)) { + return constructedObjects.get(node); } + return constructObjectNoCheck(node); + } - /** - * 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 - * Node to be constructed - * @return 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 Object constructObjectNoCheck(Node node) { + if (recursiveObjects.contains(node)) { + throw new ConstructorException(null, null, "found unconstructable recursive node", + node.getStartMark()); + } + 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); + recursiveObjects.remove(node); + if (node.isTwoStepsConstruction()) { + constructor.construct2ndStep(node, data); + } + 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 Object constructScalar(ScalarNode node) { - return node.getValue(); - } + protected String constructScalar(ScalarNode node) { + return node.getValue(); + } - protected List<Object> createDefaultList(int initSize) { - return new ArrayList<Object>(initSize); - } + // >>>> DEFAULTS >>>> + protected List<Object> createDefaultList(int initSize) { + return new ArrayList<Object>(initSize); + } - protected Set<Object> createDefaultSet(int initSize) { - return new LinkedHashSet<Object>(initSize); - } + protected Set<Object> createDefaultSet(int initSize) { + return new LinkedHashSet<Object>(initSize); + } - protected Object createArray(Class<?> type, int size) { - return Array.newInstance(type.getComponentType(), size); - } + protected Map<Object, Object> createDefaultMap(int initSize) { + // respect order from YAML document + return new LinkedHashMap<Object, Object>(initSize); + } - @SuppressWarnings("unchecked") - protected List<? extends Object> constructSequence(SequenceNode node) { - List<Object> result; - if (List.class.isAssignableFrom(node.getType()) && !node.getType().isInterface()) { - // the root class may be defined (Vector for instance) - try { - result = (List<Object>) node.getType().newInstance(); - } catch (Exception e) { - throw new YAMLException(e); - } - } else { - result = createDefaultList(node.getValue().size()); - } - constructSequenceStep2(node, result); - return result; + protected Object createArray(Class<?> type, int size) { + return Array.newInstance(type.getComponentType(), size); + } - } + // <<<< DEFAULTS <<<< - @SuppressWarnings("unchecked") - protected Set<? extends Object> constructSet(SequenceNode node) { - Set<Object> result; - if (!node.getType().isInterface()) { - // the root class may be defined - try { - result = (Set<Object>) node.getType().newInstance(); - } catch (Exception e) { - throw new YAMLException(e); - } - } else { - result = createDefaultSet(node.getValue().size()); + 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) { + return newInstance(Object.class, node); + } + + protected final Object newInstance(Class<?> ancestor, Node node) { + return newInstance(ancestor, node, true); + } + + /** + * Tries to create a new object for the node. + * + * @param ancestor expected ancestor of the {@code node.getType()} + * @param node for which to create a corresponding java object + * @param tryDefault should default constructor to be tried when there is no corresponding + * {@code TypeDescription} or {@code TypeDescription.newInstance(node)} returns + * {@code null}. + * + * @return - a new object created for {@code node.getType()} by using corresponding + * TypeDescription.newInstance or default constructor. - {@code NOT_INSTANTIATED_OBJECT} + * in case no object has been created + */ + protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) { + try { + 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; } - constructSequenceStep2(node, result); - return result; - + } + + if (tryDefault) { + /* + * Removed <code> have InstantiationException in case of abstract type + */ + if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) { + java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor(); + c.setAccessible(true); + return c.newInstance(); + } + } + } catch (Exception e) { + throw new YAMLException(e); } - protected Object constructArray(SequenceNode node) { - return constructArrayStep2(node, createArray(node.getType(), node.getValue().size())); - } + return NOT_INSTANTIATED_OBJECT; + } - protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) { - for (Node child : node.getValue()) { - collection.add(constructObject(child)); - } + @SuppressWarnings("unchecked") + protected Set<Object> newSet(CollectionNode<?> node) { + Object instance = newInstance(Set.class, node); + if (instance != NOT_INSTANTIATED_OBJECT) { + return (Set<Object>) instance; + } else { + return createDefaultSet(node.getValue().size()); + } + } + + @SuppressWarnings("unchecked") + protected List<Object> newList(SequenceNode node) { + Object instance = newInstance(List.class, node); + if (instance != NOT_INSTANTIATED_OBJECT) { + return (List<Object>) instance; + } else { + return createDefaultList(node.getValue().size()); } + } + + @SuppressWarnings("unchecked") + protected Map<Object, Object> newMap(MappingNode node) { + Object instance = newInstance(Map.class, node); + if (instance != NOT_INSTANTIATED_OBJECT) { + return (Map<Object, Object>) instance; + } else { + 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(); + 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); - } + 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); + 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); - } + 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; + ++index; + } + return array; + } + + 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 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(); + 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); } - return array; + } + 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); + } } - - protected Map<Object, Object> createDefaultMap() { - // respect order from YAML document - return new LinkedHashMap<Object, Object>(); + } + + /* + * 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); + } } - - protected Set<Object> createDefaultSet() { - // respect order from YAML document - return new LinkedHashSet<Object>(); + } + + /* + * 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); } + } - protected Set<Object> constructSet(MappingNode node) { - Set<Object> set = createDefaultSet(); - constructSet2ndStep(node, set); - return set; + public final PropertyUtils getPropertyUtils() { + if (propertyUtils == null) { + propertyUtils = new PropertyUtils(); } - - protected Map<Object, Object> constructMapping(MappingNode node) { - Map<Object, Object> mapping = createDefaultMap(); - constructMapping2ndStep(node, mapping); - return mapping; + 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); + } - protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { - List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue(); - for (NodeTuple tuple : nodeValue) { - Node keyNode = tuple.getKeyNode(); - Node valueNode = tuple.getValueNode(); - 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 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. - */ - maps2fill.add(0, - new RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>( - mapping, new RecursiveTuple<Object, Object>(key, value))); - } else { - mapping.put(key, value); - } - } - } + private static class RecursiveTuple<T, K> { - protected void constructSet2ndStep(MappingNode node, Set<Object> set) { - List<NodeTuple> nodeValue = (List<NodeTuple>) 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()) { - /* - * 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. - */ - sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key)); - } else { - set.add(key); - } - } + private final T _1; + private final K _2; + + public RecursiveTuple(T _1, K _2) { + this._1 = _1; + this._2 = _2; } - public void setPropertyUtils(PropertyUtils propertyUtils) { - this.propertyUtils = propertyUtils; - explicitPropertyUtils = true; + public K _2() { + return _2; } - public final PropertyUtils getPropertyUtils() { - if (propertyUtils == null) { - propertyUtils = new PropertyUtils(); - } - return propertyUtils; + public T _1() { + return _1; } + } - private static class RecursiveTuple<T, K> { - private final T _1; - private final K _2; + public final boolean isExplicitPropertyUtils() { + return explicitPropertyUtils; + } - public RecursiveTuple(T _1, K _2) { - this._1 = _1; - this._2 = _2; - } + public boolean isAllowDuplicateKeys() { + return allowDuplicateKeys; + } - public K _2() { - return _2; - } + public void setAllowDuplicateKeys(boolean allowDuplicateKeys) { + this.allowDuplicateKeys = allowDuplicateKeys; + } - public T _1() { - return _1; - } - } + public boolean isWrappedToRootException() { + return wrappedToRootException; + } - public final boolean isExplicitPropertyUtils() { - return explicitPropertyUtils; - } + public void setWrappedToRootException(boolean wrappedToRootException) { + this.wrappedToRootException = wrappedToRootException; + } + + public boolean isEnumCaseSensitive() { + return enumCaseSensitive; + } + + public void setEnumCaseSensitive(boolean enumCaseSensitive) { + this.enumCaseSensitive = enumCaseSensitive; + } } |