    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.beanutils;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.HashMap;
    022import java.util.LinkedList;
    023import java.util.List;
    024import java.util.Map;
    025import java.util.Set;
    026
    027import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
    028import org.apache.commons.configuration2.HierarchicalConfiguration;
    029import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    030import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    031import org.apache.commons.configuration2.tree.NodeHandler;
    032import org.apache.commons.lang3.StringUtils;
    033
    034/**
    035 * <p>
    036 * An implementation of the {@code BeanDeclaration} interface that is
    037 * suitable for XML configuration files.
    038 * </p>
    039 * <p>
    040 * This class defines the standard layout of a bean declaration in an XML
    041 * configuration file. Such a declaration must look like the following example
    042 * fragment:
    043 * </p>
    044 *
    045 * <pre>
    046 *   ...
    047 *   &lt;personBean config-class=&quot;my.model.PersonBean&quot;
    048 *       lastName=&quot;Doe&quot; firstName=&quot;John&quot;&gt;
    049 *       &lt;config-constrarg config-value=&quot;ID03493&quot; config-type=&quot;java.lang.String&quot;/&gt;
    050 *       &lt;address config-class=&quot;my.model.AddressBean&quot;
    051 *           street=&quot;21st street 11&quot; zip=&quot;1234&quot;
    052 *           city=&quot;TestCity&quot;/&gt;
    053 *   &lt;/personBean&gt;
    054 * </pre>
    055 *
    056 * <p>
    057 * The bean declaration can be contained in an arbitrary element. Here it is the
    058 * {@code personBean} element. In the attributes of this element
    059 * there can occur some reserved attributes, which have the following meaning:
    060 * </p>
    061 * <dl>
    062 * <dt>{@code config-class}</dt>
    063 * <dd>Here the full qualified name of the bean's class can be specified. An
    064 * instance of this class will be created. If this attribute is not specified,
    065 * the bean class must be provided in another way, e.g. as the
    066 * {@code defaultClass} passed to the {@code BeanHelper} class.</dd>
    067 * <dt>{@code config-factory}</dt>
    068 * <dd>This attribute can contain the name of the
    069 * {@link BeanFactory} that should be used for creating the bean.
    070 * If it is defined, a factory with this name must have been registered at the
    071 * {@code BeanHelper} class. If this attribute is missing, the default
    072 * bean factory will be used.</dd>
    073 * <dt>{@code config-factoryParam}</dt>
    074 * <dd>With this attribute a parameter can be specified that will be passed to
    075 * the bean factory. This may be useful for custom bean factories.</dd>
    076 * </dl>
    077 * <p>
    078 * All further attributes starting with the {@code config-} prefix are
    079 * considered as meta data and will be ignored. All other attributes are treated
    080 * as properties of the bean to be created, i.e. corresponding setter methods of
    081 * the bean will be invoked with the values specified here.
    082 * </p>
    083 * <p>
    084 * If the bean to be created has also some complex properties (which are itself
    085 * beans), their values cannot be initialized from attributes. For this purpose
    086 * nested elements can be used. The example listing shows how an address bean
    087 * can be initialized. This is done in a nested element whose name must match
    088 * the name of a property of the enclosing bean declaration. The format of this
    089 * nested element is exactly the same as for the bean declaration itself, i.e.
    090 * it can have attributes defining meta data or bean properties and even further
    091 * nested elements for complex bean properties.
    092 * </p>
    093 * <p>
    094 * If the bean should be created using a specific constructor, the constructor
    095 * arguments have to be specified. This is done by an arbitrary number of
    096 * nested {@code <config-constrarg>} elements. Each element can either have the
    097 * {@code config-value} attribute - then it defines a simple value - or must be
    098 * again a bean declaration (conforming to the format defined here) defining
    099 * the complex value of this constructor argument.
    100 * </p>
    101 * <p>
    102 * A {@code XMLBeanDeclaration} object is usually created from a
    103 * {@code HierarchicalConfiguration}. From this it will derive a
    104 * {@code SubnodeConfiguration}, which is used to access the needed
    105 * properties. This subnode configuration can be obtained using the
    106 * {@link #getConfiguration()} method. All of its properties can
    107 * be accessed in the usual way. To ensure that the property keys used by this
    108 * class are understood by the configuration, the default expression engine will
    109 * be set.
    110 * </p>
    111 *
    112 * @since 1.3
    113 */
    114public class XMLBeanDeclaration implements BeanDeclaration
    115{
    116    /** Constant for the prefix of reserved attributes. */
    117    public static final String RESERVED_PREFIX = "config-";
    118
    119    /** Constant for the prefix for reserved attributes.*/
    120    public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
    121
    122    /** Constant for the bean class attribute. */
    123    public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
    124
    125    /** Constant for the bean factory attribute. */
    126    public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
    127
    128    /** Constant for the bean factory parameter attribute. */
    129    public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
    130            + "factoryParam]";
    131
    132    /** Constant for the name of the bean class attribute. */
    133    private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class";
    134
    135    /** Constant for the name of the element for constructor arguments. */
    136    private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg";
    137
    138    /**
    139     * Constant for the name of the attribute with the value of a constructor
    140     * argument.
    141     */
    142    private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value";
    143
    144    /**
    145     * Constant for the name of the attribute with the data type of a
    146     * constructor argument.
    147     */
    148    private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type";
    149
    150    /** Stores the associated configuration. */
    151    private final HierarchicalConfiguration<?> configuration;
    152
    153    /** Stores the configuration node that contains the bean declaration. */
    154    private final NodeData<?> node;
    155
    156    /** The name of the default bean class. */
    157    private final String defaultBeanClassName;
    158
    159    /**
    160     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    161     * from the given configuration. The passed in key points to the bean
    162     * declaration.
    163     *
    164     * @param config the configuration (must not be <b>null</b>)
    165     * @param key the key to the bean declaration (this key must point to
    166     *        exactly one bean declaration or a {@code IllegalArgumentException}
    167     *        exception will be thrown)
    168     * @param <T> the node type of the configuration
    169     * @throws IllegalArgumentException if required information is missing to
    170     *         construct the bean declaration
    171     */
    172    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key)
    173    {
    174        this(config, key, false);
    175    }
    176
    177    /**
    178     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    179     * from the given configuration supporting optional declarations.
    180     *
    181     * @param config the configuration (must not be <b>null</b>)
    182     * @param key the key to the bean declaration
    183     * @param optional a flag whether this declaration is optional; if set to
    184     *        <b>true</b>, no exception will be thrown if the passed in key is
    185     *        undefined
    186     * @param <T> the node type of the configuration
    187     * @throws IllegalArgumentException if required information is missing to
    188     *         construct the bean declaration
    189     */
    190    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key,
    191            final boolean optional)
    192    {
    193        this(config, key, optional, null);
    194    }
    195
    196    /**
    197     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    198     * from the given configuration supporting optional declarations and a
    199     * default bean class name. The passed in key points to the bean
    200     * declaration. If the key does not exist and the boolean argument is
    201     * <b>true</b>, the declaration is initialized with an empty configuration.
    202     * It is possible to create objects from such an empty declaration if a
    203     * default class is provided. If the key on the other hand has multiple
    204     * values or is undefined and the boolean argument is <b>false</b>, a
    205     * {@code IllegalArgumentException} exception will be thrown. It is possible
    206     * to set a default bean class name; this name is used if the configuration
    207     * does not contain a bean class.
    208     *
    209     * @param config the configuration (must not be <b>null</b>)
    210     * @param key the key to the bean declaration
    211     * @param optional a flag whether this declaration is optional; if set to
    212     *        <b>true</b>, no exception will be thrown if the passed in key is
    213     *        undefined
    214     * @param defBeanClsName a default bean class name
    215     * @param <T> the node type of the configuration
    216     * @throws IllegalArgumentException if required information is missing to
    217     *         construct the bean declaration
    218     * @since 2.0
    219     */
    220    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key,
    221            final boolean optional, final String defBeanClsName)
    222    {
    223        if (config == null)
    224        {
    225            throw new IllegalArgumentException(
    226                    "Configuration must not be null!");
    227        }
    228
    229        HierarchicalConfiguration<?> tmpconfiguration;
    230        try
    231        {
    232            tmpconfiguration = config.configurationAt(key);
    233        }
    234        catch (final ConfigurationRuntimeException iex)
    235        {
    236            // If we reach this block, the key does not have exactly one value
    237            if (!optional || config.getMaxIndex(key) > 0)
    238            {
    239                throw iex;
    240            }
    241            tmpconfiguration = new BaseHierarchicalConfiguration();
    242        }
    243        this.node = createNodeDataFromConfiguration(tmpconfiguration);
    244        this.configuration = tmpconfiguration;
    245        defaultBeanClassName = defBeanClsName;
    246        initSubnodeConfiguration(getConfiguration());
    247    }
    248
    249    /**
    250     * Creates a new instance of {@code XMLBeanDeclaration} and
    251     * initializes it from the given configuration. The configuration's root
    252     * node must contain the bean declaration.
    253     *
    254     * @param config the configuration with the bean declaration
    255     * @param <T> the node type of the configuration
    256     */
    257    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config)
    258    {
    259        this(config, (String) null);
    260    }
    261
    262    /**
    263     * Creates a new instance of {@code XMLBeanDeclaration} and
    264     * initializes it with the configuration node that contains the bean
    265     * declaration. This constructor is used internally.
    266     *
    267     * @param config the configuration
    268     * @param node the node with the bean declaration.
    269     */
    270    XMLBeanDeclaration(final HierarchicalConfiguration<?> config,
    271            final NodeData<?> node)
    272    {
    273        this.node = node;
    274        configuration = config;
    275        defaultBeanClassName = null;
    276        initSubnodeConfiguration(config);
    277    }
    278
    279    /**
    280     * Returns the configuration object this bean declaration is based on.
    281     *
    282     * @return the associated configuration
    283     */
    284    public HierarchicalConfiguration<?> getConfiguration()
    285    {
    286        return configuration;
    287    }
    288
    289    /**
    290     * Returns the name of the default bean class. This class is used if no bean
    291     * class is specified in the configuration. It may be <b>null</b> if no
    292     * default class was set.
    293     *
    294     * @return the default bean class name
    295     * @since 2.0
    296     */
    297    public String getDefaultBeanClassName()
    298    {
    299        return defaultBeanClassName;
    300    }
    301
    302    /**
    303     * Returns the name of the bean factory. This information is fetched from
    304     * the {@code config-factory} attribute.
    305     *
    306     * @return the name of the bean factory
    307     */
    308    @Override
    309    public String getBeanFactoryName()
    310    {
    311        return getConfiguration().getString(ATTR_BEAN_FACTORY, null);
    312    }
    313
    314    /**
    315     * Returns a parameter for the bean factory. This information is fetched
    316     * from the {@code config-factoryParam} attribute.
    317     *
    318     * @return the parameter for the bean factory
    319     */
    320    @Override
    321    public Object getBeanFactoryParameter()
    322    {
    323        return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
    324    }
    325
    326    /**
    327     * Returns the name of the class of the bean to be created. This information
    328     * is obtained from the {@code config-class} attribute.
    329     *
    330     * @return the name of the bean's class
    331     */
    332    @Override
    333    public String getBeanClassName()
    334    {
    335        return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName());
    336    }
    337
    338    /**
    339     * Returns a map with the bean's (simple) properties. The properties are
    340     * collected from all attribute nodes, which are not reserved.
    341     *
    342     * @return a map with the bean's properties
    343     */
    344    @Override
    345    public Map<String, Object> getBeanProperties()
    346    {
    347        final Map<String, Object> props = new HashMap<>();
    348        for (final String key : getAttributeNames())
    349        {
    350            if (!isReservedAttributeName(key))
    351            {
    352                props.put(key, interpolate(getNode().getAttribute(key)));
    353            }
    354        }
    355
    356        return props;
    357    }
    358
    359    /**
    360     * Returns a map with bean declarations for the complex properties of the
    361     * bean to be created. These declarations are obtained from the child nodes
    362     * of this declaration's root node.
    363     *
    364     * @return a map with bean declarations for complex properties
    365     */
    366    @Override
    367    public Map<String, Object> getNestedBeanDeclarations()
    368    {
    369        final Map<String, Object> nested = new HashMap<>();
    370        for (final NodeData<?> child : getNode().getChildren())
    371        {
    372            if (!isReservedChildName(child.nodeName()))
    373            {
    374                if (nested.containsKey(child.nodeName()))
    375                {
    376                    final Object obj = nested.get(child.nodeName());
    377                    List<BeanDeclaration> list;
    378                    if (obj instanceof List)
    379                    {
    380                        // Safe because we created the lists ourselves.
    381                        @SuppressWarnings("unchecked")
    382                        final
    383                        List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
    384                        list = tmpList;
    385                    }
    386                    else
    387                    {
    388                        list = new ArrayList<>();
    389                        list.add((BeanDeclaration) obj);
    390                        nested.put(child.nodeName(), list);
    391                    }
    392                    list.add(createBeanDeclaration(child));
    393                }
    394                else
    395                {
    396                    nested.put(child.nodeName(), createBeanDeclaration(child));
    397                }
    398            }
    399        }
    400
    401        return nested;
    402    }
    403
    404    /**
    405     * {@inheritDoc} This implementation processes all child nodes with the name
    406     * {@code config-constrarg}. If such a node has a {@code config-class}
    407     * attribute, it is considered a nested bean declaration; otherwise it is
    408     * interpreted as a simple value. If no nested constructor argument
    409     * declarations are found, result is an empty collection.
    410     */
    411    @Override
    412    public Collection<ConstructorArg> getConstructorArgs()
    413    {
    414        final Collection<ConstructorArg> args = new LinkedList<>();
    415        for (final NodeData<?> child : getNode().getChildren(ELEM_CTOR_ARG))
    416        {
    417            args.add(createConstructorArg(child));
    418        }
    419        return args;
    420    }
    421
    422    /**
    423     * Performs interpolation for the specified value. This implementation will
    424     * interpolate against the current subnode configuration's parent. If sub
    425     * classes need a different interpolation mechanism, they should override
    426     * this method.
    427     *
    428     * @param value the value that is to be interpolated
    429     * @return the interpolated value
    430     */
    431    protected Object interpolate(final Object value)
    432    {
    433        final ConfigurationInterpolator interpolator =
    434                getConfiguration().getInterpolator();
    435        return interpolator != null ? interpolator.interpolate(value) : value;
    436    }
    437
    438    /**
    439     * Checks if the specified child node name is reserved and thus should be
    440     * ignored. This method is called when processing child nodes of this bean
    441     * declaration. It is then possible to ignore some nodes with a specific
    442     * meaning. This implementation delegates to {@link #isReservedName(String)}
    443     * .
    444     *
    445     * @param name the name of the child node to be checked
    446     * @return a flag whether this name is reserved
    447     * @since 2.0
    448     */
    449    protected boolean isReservedChildName(final String name)
    450    {
    451        return isReservedName(name);
    452    }
    453
    454    /**
    455     * Checks if the specified attribute name is reserved and thus does not
    456     * point to a property of the bean to be created. This method is called when
    457     * processing the attributes of this bean declaration. It is then possible
    458     * to ignore some attributes with a specific meaning. This implementation
    459     * delegates to {@link #isReservedName(String)}.
    460     *
    461     * @param name the name of the attribute to be checked
    462     * @return a flag whether this name is reserved
    463     * @since 2.0
    464     */
    465    protected boolean isReservedAttributeName(final String name)
    466    {
    467        return isReservedName(name);
    468    }
    469
    470    /**
    471     * Checks if the specified name of a node or attribute is reserved and thus
    472     * should be ignored. This method is called per default by the methods for
    473     * checking attribute and child node names. It checks whether the passed in
    474     * name starts with the reserved prefix.
    475     *
    476     * @param name the name to be checked
    477     * @return a flag whether this name is reserved
    478     */
    479    protected boolean isReservedName(final String name)
    480    {
    481        return name == null || name.startsWith(RESERVED_PREFIX);
    482    }
    483
    484    /**
    485     * Returns a set with the names of the attributes of the configuration node
    486     * holding the data of this bean declaration.
    487     *
    488     * @return the attribute names of the underlying configuration node
    489     */
    490    protected Set<String> getAttributeNames()
    491    {
    492        return getNode().getAttributes();
    493    }
    494
    495    /**
    496     * Returns the data about the associated node.
    497     *
    498     * @return the node with the bean declaration
    499     */
    500    NodeData<?> getNode()
    501    {
    502        return node;
    503    }
    504
    505    /**
    506     * Creates a new {@code BeanDeclaration} for a child node of the
    507     * current configuration node. This method is called by
    508     * {@code getNestedBeanDeclarations()} for all complex sub properties
    509     * detected by this method. Derived classes can hook in if they need a
    510     * specific initialization. This base implementation creates a
    511     * {@code XMLBeanDeclaration} that is properly initialized from the
    512     * passed in node.
    513     *
    514     * @param node the child node, for which a {@code BeanDeclaration} is
    515     *        to be created
    516     * @return the {@code BeanDeclaration} for this child node
    517     */
    518    BeanDeclaration createBeanDeclaration(final NodeData<?> node)
    519    {
    520        for (final HierarchicalConfiguration<?> config : getConfiguration()
    521                .configurationsAt(node.escapedNodeName(getConfiguration())))
    522        {
    523            if (node.matchesConfigRootNode(config))
    524            {
    525                return new XMLBeanDeclaration(config, node);
    526            }
    527        }
    528        throw new ConfigurationRuntimeException("Unable to match node for "
    529                + node.nodeName());
    530    }
    531
    532    /**
    533     * Initializes the internally managed sub configuration. This method
    534     * will set some default values for some properties.
    535     *
    536     * @param conf the configuration to initialize
    537     */
    538    private void initSubnodeConfiguration(final HierarchicalConfiguration<?> conf)
    539    {
    540        conf.setExpressionEngine(null);
    541    }
    542
    543    /**
    544     * Creates a {@code ConstructorArg} object for the specified configuration
    545     * node.
    546     *
    547     * @param child the configuration node
    548     * @return the corresponding {@code ConstructorArg} object
    549     */
    550    private ConstructorArg createConstructorArg(final NodeData<?> child)
    551    {
    552        final String type = getAttribute(child, ATTR_CTOR_TYPE);
    553        if (isBeanDeclarationArgument(child))
    554        {
    555            return ConstructorArg.forValue(
    556                    getAttribute(child, ATTR_CTOR_VALUE), type);
    557        }
    558        return ConstructorArg.forBeanDeclaration(
    559                createBeanDeclaration(child), type);
    560    }
    561
    562    /**
    563     * Helper method for obtaining an attribute of a configuration node.
    564     * This method also takes interpolation into account.
    565     *
    566     * @param nd the node
    567     * @param attr the name of the attribute
    568     * @return the string value of this attribute (can be <b>null</b>)
    569     */
    570    private String getAttribute(final NodeData<?> nd, final String attr)
    571    {
    572        final Object value = nd.getAttribute(attr);
    573        return value == null ? null : String.valueOf(interpolate(value));
    574    }
    575
    576    /**
    577     * Checks whether the constructor argument represented by the given
    578     * configuration node is a bean declaration.
    579     *
    580     * @param nd the configuration node in question
    581     * @return a flag whether this constructor argument is a bean declaration
    582     */
    583    private static boolean isBeanDeclarationArgument(final NodeData<?> nd)
    584    {
    585        return !nd.getAttributes().contains(ATTR_BEAN_CLASS_NAME);
    586    }
    587
    588    /**
    589     * Creates a {@code NodeData} object from the root node of the given
    590     * configuration.
    591     *
    592     * @param config the configuration
    593     * @param <T> the type of the nodes
    594     * @return the {@code NodeData} object
    595     */
    596    private static <T> NodeData<T> createNodeDataFromConfiguration(
    597            final HierarchicalConfiguration<T> config)
    598    {
    599        final NodeHandler<T> handler = config.getNodeModel().getNodeHandler();
    600        return new NodeData<>(handler.getRootNode(), handler);
    601    }
    602
    603    /**
    604     * An internally used helper class which wraps the node with the bean
    605     * declaration and the corresponding node handler.
    606     *
    607     * @param <T> the type of the node
    608     */
    609    static class NodeData<T>
    610    {
    611        /** The wrapped node. */
    612        private final T node;
    613
    614        /** The node handler for interacting with this node. */
    615        private final NodeHandler<T> handler;
    616
    617        /**
    618         * Creates a new instance of {@code NodeData}.
    619         *
    620         * @param nd the node
    621         * @param hndlr the handler
    622         */
    623        public NodeData(final T nd, final NodeHandler<T> hndlr)
    624        {
    625            node = nd;
    626            handler = hndlr;
    627        }
    628
    629        /**
    630         * Returns the name of the wrapped node.
    631         *
    632         * @return the node name
    633         */
    634        public String nodeName()
    635        {
    636            return handler.nodeName(node);
    637        }
    638
    639        /**
    640         * Returns the unescaped name of the node stored in this data object.
    641         * This method handles the case that the node name may contain reserved
    642         * characters with a special meaning for the current expression engine.
    643         * In this case, the characters affected have to be escaped accordingly.
    644         *
    645         * @param config the configuration
    646         * @return the escaped node name
    647         */
    648        public String escapedNodeName(final HierarchicalConfiguration<?> config)
    649        {
    650            return config.getExpressionEngine().nodeKey(node,
    651                    StringUtils.EMPTY, handler);
    652        }
    653
    654        /**
    655         * Returns a list with the children of the wrapped node, again wrapped
    656         * into {@code NodeData} objects.
    657         *
    658         * @return a list with the children
    659         */
    660        public List<NodeData<T>> getChildren()
    661        {
    662            return wrapInNodeData(handler.getChildren(node));
    663        }
    664
    665        /**
    666         * Returns a list with the children of the wrapped node with the given
    667         * name, again wrapped into {@code NodeData} objects.
    668         *
    669         * @param name the name of the desired child nodes
    670         * @return a list with the children with this name
    671         */
    672        public List<NodeData<T>> getChildren(final String name)
    673        {
    674            return wrapInNodeData(handler.getChildren(node, name));
    675        }
    676
    677        /**
    678         * Returns a set with the names of the attributes of the wrapped node.
    679         *
    680         * @return the attribute names of this node
    681         */
    682        public Set<String> getAttributes()
    683        {
    684            return handler.getAttributes(node);
    685        }
    686
    687        /**
    688         * Returns the value of the attribute with the given name of the wrapped
    689         * node.
    690         *
    691         * @param key the key of the attribute
    692         * @return the value of this attribute
    693         */
    694        public Object getAttribute(final String key)
    695        {
    696            return handler.getAttributeValue(node, key);
    697        }
    698
    699        /**
    700         * Returns a flag whether the wrapped node is the root node of the
    701         * passed in configuration.
    702         *
    703         * @param config the configuration
    704         * @return a flag whether this node is the configuration's root node
    705         */
    706        public boolean matchesConfigRootNode(final HierarchicalConfiguration<?> config)
    707        {
    708            return config.getNodeModel().getNodeHandler().getRootNode()
    709                    .equals(node);
    710        }
    711
    712        /**
    713         * Wraps the passed in list of nodes in {@code NodeData} objects.
    714         *
    715         * @param nodes the list with nodes
    716         * @return the wrapped nodes
    717         */
    718        private List<NodeData<T>> wrapInNodeData(final List<T> nodes)
    719        {
    720            final List<NodeData<T>> result = new ArrayList<>(nodes.size());
    721            for (final T node : nodes)
    722            {
    723                result.add(new NodeData<>(node, handler));
    724            }
    725            return result;
    726        }
    727    }
    728}