    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 * @version $Id: XMLBeanDeclaration.java 1842268 2018-09-28 16:25:25Z ggregory $
    114 */
    115public class XMLBeanDeclaration implements BeanDeclaration
    116{
    117    /** Constant for the prefix of reserved attributes. */
    118    public static final String RESERVED_PREFIX = "config-";
    119
    120    /** Constant for the prefix for reserved attributes.*/
    121    public static final String ATTR_PREFIX = "[@" + RESERVED_PREFIX;
    122
    123    /** Constant for the bean class attribute. */
    124    public static final String ATTR_BEAN_CLASS = ATTR_PREFIX + "class]";
    125
    126    /** Constant for the bean factory attribute. */
    127    public static final String ATTR_BEAN_FACTORY = ATTR_PREFIX + "factory]";
    128
    129    /** Constant for the bean factory parameter attribute. */
    130    public static final String ATTR_FACTORY_PARAM = ATTR_PREFIX
    131            + "factoryParam]";
    132
    133    /** Constant for the name of the bean class attribute. */
    134    private static final String ATTR_BEAN_CLASS_NAME = RESERVED_PREFIX + "class";
    135
    136    /** Constant for the name of the element for constructor arguments. */
    137    private static final String ELEM_CTOR_ARG = RESERVED_PREFIX + "constrarg";
    138
    139    /**
    140     * Constant for the name of the attribute with the value of a constructor
    141     * argument.
    142     */
    143    private static final String ATTR_CTOR_VALUE = RESERVED_PREFIX + "value";
    144
    145    /**
    146     * Constant for the name of the attribute with the data type of a
    147     * constructor argument.
    148     */
    149    private static final String ATTR_CTOR_TYPE = RESERVED_PREFIX + "type";
    150
    151    /** Stores the associated configuration. */
    152    private final HierarchicalConfiguration<?> configuration;
    153
    154    /** Stores the configuration node that contains the bean declaration. */
    155    private final NodeData<?> node;
    156
    157    /** The name of the default bean class. */
    158    private final String defaultBeanClassName;
    159
    160    /**
    161     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    162     * from the given configuration. The passed in key points to the bean
    163     * declaration.
    164     *
    165     * @param config the configuration (must not be <b>null</b>)
    166     * @param key the key to the bean declaration (this key must point to
    167     *        exactly one bean declaration or a {@code IllegalArgumentException}
    168     *        exception will be thrown)
    169     * @param <T> the node type of the configuration
    170     * @throws IllegalArgumentException if required information is missing to
    171     *         construct the bean declaration
    172     */
    173    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key)
    174    {
    175        this(config, key, false);
    176    }
    177
    178    /**
    179     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    180     * from the given configuration supporting optional declarations.
    181     *
    182     * @param config the configuration (must not be <b>null</b>)
    183     * @param key the key to the bean declaration
    184     * @param optional a flag whether this declaration is optional; if set to
    185     *        <b>true</b>, no exception will be thrown if the passed in key is
    186     *        undefined
    187     * @param <T> the node type of the configuration
    188     * @throws IllegalArgumentException if required information is missing to
    189     *         construct the bean declaration
    190     */
    191    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key,
    192            final boolean optional)
    193    {
    194        this(config, key, optional, null);
    195    }
    196
    197    /**
    198     * Creates a new instance of {@code XMLBeanDeclaration} and initializes it
    199     * from the given configuration supporting optional declarations and a
    200     * default bean class name. The passed in key points to the bean
    201     * declaration. If the key does not exist and the boolean argument is
    202     * <b>true</b>, the declaration is initialized with an empty configuration.
    203     * It is possible to create objects from such an empty declaration if a
    204     * default class is provided. If the key on the other hand has multiple
    205     * values or is undefined and the boolean argument is <b>false</b>, a
    206     * {@code IllegalArgumentException} exception will be thrown. It is possible
    207     * to set a default bean class name; this name is used if the configuration
    208     * does not contain a bean class.
    209     *
    210     * @param config the configuration (must not be <b>null</b>)
    211     * @param key the key to the bean declaration
    212     * @param optional a flag whether this declaration is optional; if set to
    213     *        <b>true</b>, no exception will be thrown if the passed in key is
    214     *        undefined
    215     * @param defBeanClsName a default bean class name
    216     * @param <T> the node type of the configuration
    217     * @throws IllegalArgumentException if required information is missing to
    218     *         construct the bean declaration
    219     * @since 2.0
    220     */
    221    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config, final String key,
    222            final boolean optional, final String defBeanClsName)
    223    {
    224        if (config == null)
    225        {
    226            throw new IllegalArgumentException(
    227                    "Configuration must not be null!");
    228        }
    229
    230        HierarchicalConfiguration<?> tmpconfiguration;
    231        try
    232        {
    233            tmpconfiguration = config.configurationAt(key);
    234        }
    235        catch (final ConfigurationRuntimeException iex)
    236        {
    237            // If we reach this block, the key does not have exactly one value
    238            if (!optional || config.getMaxIndex(key) > 0)
    239            {
    240                throw iex;
    241            }
    242            tmpconfiguration = new BaseHierarchicalConfiguration();
    243        }
    244        this.node = createNodeDataFromConfiguration(tmpconfiguration);
    245        this.configuration = tmpconfiguration;
    246        defaultBeanClassName = defBeanClsName;
    247        initSubnodeConfiguration(getConfiguration());
    248    }
    249
    250    /**
    251     * Creates a new instance of {@code XMLBeanDeclaration} and
    252     * initializes it from the given configuration. The configuration's root
    253     * node must contain the bean declaration.
    254     *
    255     * @param config the configuration with the bean declaration
    256     * @param <T> the node type of the configuration
    257     */
    258    public <T> XMLBeanDeclaration(final HierarchicalConfiguration<T> config)
    259    {
    260        this(config, (String) null);
    261    }
    262
    263    /**
    264     * Creates a new instance of {@code XMLBeanDeclaration} and
    265     * initializes it with the configuration node that contains the bean
    266     * declaration. This constructor is used internally.
    267     *
    268     * @param config the configuration
    269     * @param node the node with the bean declaration.
    270     */
    271    XMLBeanDeclaration(final HierarchicalConfiguration<?> config,
    272            final NodeData<?> node)
    273    {
    274        this.node = node;
    275        configuration = config;
    276        defaultBeanClassName = null;
    277        initSubnodeConfiguration(config);
    278    }
    279
    280    /**
    281     * Returns the configuration object this bean declaration is based on.
    282     *
    283     * @return the associated configuration
    284     */
    285    public HierarchicalConfiguration<?> getConfiguration()
    286    {
    287        return configuration;
    288    }
    289
    290    /**
    291     * Returns the name of the default bean class. This class is used if no bean
    292     * class is specified in the configuration. It may be <b>null</b> if no
    293     * default class was set.
    294     *
    295     * @return the default bean class name
    296     * @since 2.0
    297     */
    298    public String getDefaultBeanClassName()
    299    {
    300        return defaultBeanClassName;
    301    }
    302
    303    /**
    304     * Returns the name of the bean factory. This information is fetched from
    305     * the {@code config-factory} attribute.
    306     *
    307     * @return the name of the bean factory
    308     */
    309    @Override
    310    public String getBeanFactoryName()
    311    {
    312        return getConfiguration().getString(ATTR_BEAN_FACTORY, null);
    313    }
    314
    315    /**
    316     * Returns a parameter for the bean factory. This information is fetched
    317     * from the {@code config-factoryParam} attribute.
    318     *
    319     * @return the parameter for the bean factory
    320     */
    321    @Override
    322    public Object getBeanFactoryParameter()
    323    {
    324        return getConfiguration().getProperty(ATTR_FACTORY_PARAM);
    325    }
    326
    327    /**
    328     * Returns the name of the class of the bean to be created. This information
    329     * is obtained from the {@code config-class} attribute.
    330     *
    331     * @return the name of the bean's class
    332     */
    333    @Override
    334    public String getBeanClassName()
    335    {
    336        return getConfiguration().getString(ATTR_BEAN_CLASS, getDefaultBeanClassName());
    337    }
    338
    339    /**
    340     * Returns a map with the bean's (simple) properties. The properties are
    341     * collected from all attribute nodes, which are not reserved.
    342     *
    343     * @return a map with the bean's properties
    344     */
    345    @Override
    346    public Map<String, Object> getBeanProperties()
    347    {
    348        final Map<String, Object> props = new HashMap<>();
    349        for (final String key : getAttributeNames())
    350        {
    351            if (!isReservedAttributeName(key))
    352            {
    353                props.put(key, interpolate(getNode().getAttribute(key)));
    354            }
    355        }
    356
    357        return props;
    358    }
    359
    360    /**
    361     * Returns a map with bean declarations for the complex properties of the
    362     * bean to be created. These declarations are obtained from the child nodes
    363     * of this declaration's root node.
    364     *
    365     * @return a map with bean declarations for complex properties
    366     */
    367    @Override
    368    public Map<String, Object> getNestedBeanDeclarations()
    369    {
    370        final Map<String, Object> nested = new HashMap<>();
    371        for (final NodeData<?> child : getNode().getChildren())
    372        {
    373            if (!isReservedChildName(child.nodeName()))
    374            {
    375                if (nested.containsKey(child.nodeName()))
    376                {
    377                    final Object obj = nested.get(child.nodeName());
    378                    List<BeanDeclaration> list;
    379                    if (obj instanceof List)
    380                    {
    381                        // Safe because we created the lists ourselves.
    382                        @SuppressWarnings("unchecked")
    383                        final
    384                        List<BeanDeclaration> tmpList = (List<BeanDeclaration>) obj;
    385                        list = tmpList;
    386                    }
    387                    else
    388                    {
    389                        list = new ArrayList<>();
    390                        list.add((BeanDeclaration) obj);
    391                        nested.put(child.nodeName(), list);
    392                    }
    393                    list.add(createBeanDeclaration(child));
    394                }
    395                else
    396                {
    397                    nested.put(child.nodeName(), createBeanDeclaration(child));
    398                }
    399            }
    400        }
    401
    402        return nested;
    403    }
    404
    405    /**
    406     * {@inheritDoc} This implementation processes all child nodes with the name
    407     * {@code config-constrarg}. If such a node has a {@code config-class}
    408     * attribute, it is considered a nested bean declaration; otherwise it is
    409     * interpreted as a simple value. If no nested constructor argument
    410     * declarations are found, result is an empty collection.
    411     */
    412    @Override
    413    public Collection<ConstructorArg> getConstructorArgs()
    414    {
    415        final Collection<ConstructorArg> args = new LinkedList<>();
    416        for (final NodeData<?> child : getNode().getChildren(ELEM_CTOR_ARG))
    417        {
    418            args.add(createConstructorArg(child));
    419        }
    420        return args;
    421    }
    422
    423    /**
    424     * Performs interpolation for the specified value. This implementation will
    425     * interpolate against the current subnode configuration's parent. If sub
    426     * classes need a different interpolation mechanism, they should override
    427     * this method.
    428     *
    429     * @param value the value that is to be interpolated
    430     * @return the interpolated value
    431     */
    432    protected Object interpolate(final Object value)
    433    {
    434        final ConfigurationInterpolator interpolator =
    435                getConfiguration().getInterpolator();
    436        return interpolator != null ? interpolator.interpolate(value) : value;
    437    }
    438
    439    /**
    440     * Checks if the specified child node name is reserved and thus should be
    441     * ignored. This method is called when processing child nodes of this bean
    442     * declaration. It is then possible to ignore some nodes with a specific
    443     * meaning. This implementation delegates to {@link #isReservedName(String)}
    444     * .
    445     *
    446     * @param name the name of the child node to be checked
    447     * @return a flag whether this name is reserved
    448     * @since 2.0
    449     */
    450    protected boolean isReservedChildName(final String name)
    451    {
    452        return isReservedName(name);
    453    }
    454
    455    /**
    456     * Checks if the specified attribute name is reserved and thus does not
    457     * point to a property of the bean to be created. This method is called when
    458     * processing the attributes of this bean declaration. It is then possible
    459     * to ignore some attributes with a specific meaning. This implementation
    460     * delegates to {@link #isReservedName(String)}.
    461     *
    462     * @param name the name of the attribute to be checked
    463     * @return a flag whether this name is reserved
    464     * @since 2.0
    465     */
    466    protected boolean isReservedAttributeName(final String name)
    467    {
    468        return isReservedName(name);
    469    }
    470
    471    /**
    472     * Checks if the specified name of a node or attribute is reserved and thus
    473     * should be ignored. This method is called per default by the methods for
    474     * checking attribute and child node names. It checks whether the passed in
    475     * name starts with the reserved prefix.
    476     *
    477     * @param name the name to be checked
    478     * @return a flag whether this name is reserved
    479     */
    480    protected boolean isReservedName(final String name)
    481    {
    482        return name == null || name.startsWith(RESERVED_PREFIX);
    483    }
    484
    485    /**
    486     * Returns a set with the names of the attributes of the configuration node
    487     * holding the data of this bean declaration.
    488     *
    489     * @return the attribute names of the underlying configuration node
    490     */
    491    protected Set<String> getAttributeNames()
    492    {
    493        return getNode().getAttributes();
    494    }
    495
    496    /**
    497     * Returns the data about the associated node.
    498     *
    499     * @return the node with the bean declaration
    500     */
    501    NodeData<?> getNode()
    502    {
    503        return node;
    504    }
    505
    506    /**
    507     * Creates a new {@code BeanDeclaration} for a child node of the
    508     * current configuration node. This method is called by
    509     * {@code getNestedBeanDeclarations()} for all complex sub properties
    510     * detected by this method. Derived classes can hook in if they need a
    511     * specific initialization. This base implementation creates a
    512     * {@code XMLBeanDeclaration} that is properly initialized from the
    513     * passed in node.
    514     *
    515     * @param node the child node, for which a {@code BeanDeclaration} is
    516     *        to be created
    517     * @return the {@code BeanDeclaration} for this child node
    518     */
    519    BeanDeclaration createBeanDeclaration(final NodeData<?> node)
    520    {
    521        for (final HierarchicalConfiguration<?> config : getConfiguration()
    522                .configurationsAt(node.escapedNodeName(getConfiguration())))
    523        {
    524            if (node.matchesConfigRootNode(config))
    525            {
    526                return new XMLBeanDeclaration(config, node);
    527            }
    528        }
    529        throw new ConfigurationRuntimeException("Unable to match node for "
    530                + node.nodeName());
    531    }
    532
    533    /**
    534     * Initializes the internally managed sub configuration. This method
    535     * will set some default values for some properties.
    536     *
    537     * @param conf the configuration to initialize
    538     */
    539    private void initSubnodeConfiguration(final HierarchicalConfiguration<?> conf)
    540    {
    541        conf.setExpressionEngine(null);
    542    }
    543
    544    /**
    545     * Creates a {@code ConstructorArg} object for the specified configuration
    546     * node.
    547     *
    548     * @param child the configuration node
    549     * @return the corresponding {@code ConstructorArg} object
    550     */
    551    private ConstructorArg createConstructorArg(final NodeData<?> child)
    552    {
    553        final String type = getAttribute(child, ATTR_CTOR_TYPE);
    554        if (isBeanDeclarationArgument(child))
    555        {
    556            return ConstructorArg.forValue(
    557                    getAttribute(child, ATTR_CTOR_VALUE), type);
    558        }
    559        return ConstructorArg.forBeanDeclaration(
    560                createBeanDeclaration(child), type);
    561    }
    562
    563    /**
    564     * Helper method for obtaining an attribute of a configuration node.
    565     * This method also takes interpolation into account.
    566     *
    567     * @param nd the node
    568     * @param attr the name of the attribute
    569     * @return the string value of this attribute (can be <b>null</b>)
    570     */
    571    private String getAttribute(final NodeData<?> nd, final String attr)
    572    {
    573        final Object value = nd.getAttribute(attr);
    574        return value == null ? null : String.valueOf(interpolate(value));
    575    }
    576
    577    /**
    578     * Checks whether the constructor argument represented by the given
    579     * configuration node is a bean declaration.
    580     *
    581     * @param nd the configuration node in question
    582     * @return a flag whether this constructor argument is a bean declaration
    583     */
    584    private static boolean isBeanDeclarationArgument(final NodeData<?> nd)
    585    {
    586        return !nd.getAttributes().contains(ATTR_BEAN_CLASS_NAME);
    587    }
    588
    589    /**
    590     * Creates a {@code NodeData} object from the root node of the given
    591     * configuration.
    592     *
    593     * @param config the configuration
    594     * @param <T> the type of the nodes
    595     * @return the {@code NodeData} object
    596     */
    597    private static <T> NodeData<T> createNodeDataFromConfiguration(
    598            final HierarchicalConfiguration<T> config)
    599    {
    600        final NodeHandler<T> handler = config.getNodeModel().getNodeHandler();
    601        return new NodeData<>(handler.getRootNode(), handler);
    602    }
    603
    604    /**
    605     * An internally used helper class which wraps the node with the bean
    606     * declaration and the corresponding node handler.
    607     *
    608     * @param <T> the type of the node
    609     */
    610    static class NodeData<T>
    611    {
    612        /** The wrapped node. */
    613        private final T node;
    614
    615        /** The node handler for interacting with this node. */
    616        private final NodeHandler<T> handler;
    617
    618        /**
    619         * Creates a new instance of {@code NodeData}.
    620         *
    621         * @param nd the node
    622         * @param hndlr the handler
    623         */
    624        public NodeData(final T nd, final NodeHandler<T> hndlr)
    625        {
    626            node = nd;
    627            handler = hndlr;
    628        }
    629
    630        /**
    631         * Returns the name of the wrapped node.
    632         *
    633         * @return the node name
    634         */
    635        public String nodeName()
    636        {
    637            return handler.nodeName(node);
    638        }
    639
    640        /**
    641         * Returns the unescaped name of the node stored in this data object.
    642         * This method handles the case that the node name may contain reserved
    643         * characters with a special meaning for the current expression engine.
    644         * In this case, the characters affected have to be escaped accordingly.
    645         *
    646         * @param config the configuration
    647         * @return the escaped node name
    648         */
    649        public String escapedNodeName(final HierarchicalConfiguration<?> config)
    650        {
    651            return config.getExpressionEngine().nodeKey(node,
    652                    StringUtils.EMPTY, handler);
    653        }
    654
    655        /**
    656         * Returns a list with the children of the wrapped node, again wrapped
    657         * into {@code NodeData} objects.
    658         *
    659         * @return a list with the children
    660         */
    661        public List<NodeData<T>> getChildren()
    662        {
    663            return wrapInNodeData(handler.getChildren(node));
    664        }
    665
    666        /**
    667         * Returns a list with the children of the wrapped node with the given
    668         * name, again wrapped into {@code NodeData} objects.
    669         *
    670         * @param name the name of the desired child nodes
    671         * @return a list with the children with this name
    672         */
    673        public List<NodeData<T>> getChildren(final String name)
    674        {
    675            return wrapInNodeData(handler.getChildren(node, name));
    676        }
    677
    678        /**
    679         * Returns a set with the names of the attributes of the wrapped node.
    680         *
    681         * @return the attribute names of this node
    682         */
    683        public Set<String> getAttributes()
    684        {
    685            return handler.getAttributes(node);
    686        }
    687
    688        /**
    689         * Returns the value of the attribute with the given name of the wrapped
    690         * node.
    691         *
    692         * @param key the key of the attribute
    693         * @return the value of this attribute
    694         */
    695        public Object getAttribute(final String key)
    696        {
    697            return handler.getAttributeValue(node, key);
    698        }
    699
    700        /**
    701         * Returns a flag whether the wrapped node is the root node of the
    702         * passed in configuration.
    703         *
    704         * @param config the configuration
    705         * @return a flag whether this node is the configuration's root node
    706         */
    707        public boolean matchesConfigRootNode(final HierarchicalConfiguration<?> config)
    708        {
    709            return config.getNodeModel().getNodeHandler().getRootNode()
    710                    .equals(node);
    711        }
    712
    713        /**
    714         * Wraps the passed in list of nodes in {@code NodeData} objects.
    715         *
    716         * @param nodes the list with nodes
    717         * @return the wrapped nodes
    718         */
    719        private List<NodeData<T>> wrapInNodeData(final List<T> nodes)
    720        {
    721            final List<NodeData<T>> result = new ArrayList<>(nodes.size());
    722            for (final T node : nodes)
    723            {
    724                result.add(new NodeData<>(node, handler));
    725            }
    726            return result;
    727        }
    728    }
    729}