diff options
Diffstat (limited to 'src/main/java/org/yaml/snakeyaml/constructor/Constructor.java')
-rw-r--r-- | src/main/java/org/yaml/snakeyaml/constructor/Constructor.java | 1200 |
1 files changed, 593 insertions, 607 deletions
diff --git a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java index 943702f2..52dd5da8 100644 --- a/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java +++ b/src/main/java/org/yaml/snakeyaml/constructor/Constructor.java @@ -1,38 +1,29 @@ /** - * 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.beans.IntrospectionException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; import java.util.UUID; - +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.TypeDescription; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.introspector.Property; @@ -43,640 +34,635 @@ import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.util.EnumUtils; /** * Construct a custom Java instance. */ public class Constructor extends SafeConstructor { - private final Map<Tag, Class<? extends Object>> typeTags; - protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions; - 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; } - - /** - * 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(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."); } - - /** - * 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; + this.yamlConstructors.put(null, new ConstructYamlObject()); + if (!Object.class.equals(theRoot.getType())) { + rootTag = new Tag(theRoot.getType()); } - - public Constructor(TypeDescription theRoot) { - 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()); - } - typeTags = new HashMap<Tag, Class<? extends Object>>(); - typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>(); - yamlClassConstructors.put(NodeId.scalar, new ConstructScalar()); - yamlClassConstructors.put(NodeId.mapping, new ConstructMapping()); - yamlClassConstructors.put(NodeId.sequence, new ConstructSequence()); - addTypeDescription(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); + } } + } + + /** + * 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 { /** - * 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 + * 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 Constructor(String theRoot) throws ClassNotFoundException { - this(Class.forName(check(theRoot))); - } - - private static final String check(String s) { - if (s == null) { - throw new NullPointerException("Root type must be provided."); + 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); } - if (s.trim().length() == 0) { - throw new YAMLException("Root type must be provided."); + } else if (Collection.class.isAssignableFrom(node.getType())) { + if (node.isTwoStepsConstruction()) { + return newSet(mnode); + } else { + return constructSet(mnode); } - return s; + } else { + Object obj = Constructor.this.newInstance(mnode); + if (obj != NOT_INSTANTIATED_OBJECT) { + if (node.isTwoStepsConstruction()) { + return obj; + } else { + return constructJavaBean2ndStep(mnode, obj); + } + } else { + throw new ConstructorException(null, null, + "Can't create an instance for " + mnode.getTag(), node.getStartMark()); + } + } } - /** - * 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 <tt>definition</tt>, or - * <tt>null</tt> if there was no mapping for <tt>definition</tt>. - */ - public TypeDescription addTypeDescription(TypeDescription definition) { - if (definition == null) { - throw new NullPointerException("TypeDescription is required."); - } - Tag tag = definition.getTag(); - typeTags.put(tag, definition.getType()); - return typeDefinitions.put(definition.getType(), definition); + @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); + } } - /** - * 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 (Properties.class.isAssignableFrom(node.getType())) { - Properties properties = new Properties(); - if (!node.isTwoStepsConstruction()) { - constructMapping2ndStep(mnode, properties); - } else { - throw new YAMLException("Properties must not be recursive."); - } - return properties; - } else if (SortedMap.class.isAssignableFrom(node.getType())) { - SortedMap<Object, Object> map = new TreeMap<Object, Object>(); - if (!node.isTwoStepsConstruction()) { - constructMapping2ndStep(mnode, map); - } - return map; - } else if (Map.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - return createDefaultMap(); - } else { - return constructMapping(mnode); - } - } else if (SortedSet.class.isAssignableFrom(node.getType())) { - SortedSet<Object> set = new TreeSet<Object>(); - // XXX why this is not used ? - // if (!node.isTwoStepsConstruction()) { - constructSet2ndStep(mnode, set); - // } - return set; - } else if (Collection.class.isAssignableFrom(node.getType())) { - if (node.isTwoStepsConstruction()) { - return createDefaultSet(); - } else { - return constructSet(mnode); - } - } else { - if (node.isTwoStepsConstruction()) { - return createEmptyJavaBean(mnode); - } else { - return constructJavaBean2ndStep(mnode, createEmptyJavaBean(mnode)); - } + // 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); + } } - } - - @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); + } + + 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; + } - protected Object createEmptyJavaBean(MappingNode node) { - try { - /** - * 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); - } - } + 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); + } - protected Object constructJavaBean2ndStep(MappingNode node, Object object) { - flattenMapping(node); - Class<? extends Object> beanType = node.getType(); - List<NodeTuple> nodeValue = node.getValue(); - for (NodeTuple tuple : nodeValue) { - ScalarNode keyNode; - if (tuple.getKeyNode() instanceof ScalarNode) { - // key must be scalar - keyNode = (ScalarNode) tuple.getKeyNode(); - } else { - throw new YAMLException("Keys must be scalars but found: " + tuple.getKeyNode()); - } - Node valueNode = tuple.getValueNode(); - // keys can only be Strings - keyNode.setType(String.class); - String key = (String) constructObject(keyNode); - try { - Property property = getProperty(beanType, key); - valueNode.setType(property.getType()); - TypeDescription memberDescription = typeDefinitions.get(beanType); - boolean typeDetected = false; - if (memberDescription != null) { - switch (valueNode.getNodeId()) { - case sequence: - SequenceNode snode = (SequenceNode) valueNode; - Class<? extends Object> memberType = memberDescription - .getListPropertyType(key); - if (memberType != null) { - snode.setListType(memberType); - typeDetected = true; - } else if (property.getType().isArray()) { - snode.setListType(property.getType().getComponentType()); - typeDetected = true; - } - break; - case mapping: - MappingNode mnode = (MappingNode) valueNode; - Class<? extends Object> keyType = memberDescription.getMapKeyType(key); - if (keyType != null) { - mnode.setTypes(keyType, memberDescription.getMapValueType(key)); - typeDetected = true; - } - break; - default: // scalar - } - } - 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 (valueNode.getTag().equals(Tag.SET)) { - Class<?> t = arguments[0]; - MappingNode mnode = (MappingNode) valueNode; - mnode.setOnlyKeyType(t); - mnode.setUseClassConstructor(true); - } else if (property.getType().isAssignableFrom(Map.class)) { - Class<?> ketType = arguments[0]; - Class<?> valueType = arguments[1]; - MappingNode mnode = (MappingNode) valueNode; - mnode.setTypes(ketType, valueType); - mnode.setUseClassConstructor(true); - } else { - // the type for collection entries cannot be - // detected - } - } - } - - Object value = 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); - } - - property.set(object, value); - } catch (Exception e) { - throw new ConstructorException("Cannot create property=" + key - + " for JavaBean=" + object, node.getStartMark(), e.getMessage(), - valueNode.getStartMark(), e); - } - } - return object; - } + protected Property getProperty(Class<? extends Object> type, String name) { + return getPropertyUtils().getProperty(type, name); + } + } + + /** + * 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; + } - protected Property getProperty(Class<? extends Object> type, String name) - throws IntrospectionException { - return getPropertyUtils().getProperty(type, name); - } + 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); + } } - /** - * 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 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(); + + // In case there is TypeDefinition for the 'type' + Object instance = newInstance(type, node, false); + if (instance != NOT_INSTANTIATED_OBJECT) { + return instance; + } + + 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; + } } - - public Object construct(Node node) { - Object result = null; - try { - result = 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); - } - return result; + Object argument; + if (javaConstructor == null) { + throw new YAMLException("No single argument constructor found for " + type); + } 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); + } } - - 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); - } + 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 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(); - 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) { - throw new YAMLException("No single argument constructor found for " + type); - } 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); - } - } - return result; + @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") - 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 = new Float((Double) result); - } - } - } 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 = Byte.valueOf(result.toString()); - } else if (type == Short.class || type == Short.TYPE) { - result = Short.valueOf(result.toString()); - } 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 { - result = Enum.valueOf(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)) { - ConstructYamlNumber contr = new ConstructYamlNumber(); - 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; + } 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 createDefaultList(snode.getValue().size()); - } 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()); - - } + } + + /** + * 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); } - - 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; + } 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); } - if (clazz == Character.TYPE) { - return Character.class; + + try { + c.setAccessible(true); + return c.newInstance(argumentList); + } catch (Exception e) { + throw new YAMLException(e); } - if (clazz == Short.TYPE) { - return Short.class; + } + + // 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 (clazz == Byte.TYPE) { - return Byte.class; + if (foundConstructor) { + try { + c.setAccessible(true); + return c.newInstance(argumentList.toArray()); + } catch (Exception e) { + throw new YAMLException(e); + } } - throw new YAMLException("Unexpected primitive " + clazz); + } } + throw new YAMLException("No suitable constructor with " + snode.getValue().size() + + " arguments found for " + node.getType()); - @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; - } + 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<?> getClassForName(String name) throws ClassNotFoundException { - try { - return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); - } catch (ClassNotFoundException e) { - return Class.forName(name); - } + @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); } + } } |