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.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027/**
028 * <p>
029 * A special implementation of the {@code BeanDeclaration} interface which allows combining multiple
030 * {@code BeanDeclaration} objects.
031 * </p>
032 * <p>
033 * An instance of this class can be used if a bean is defined using multiple sources. For instance, there can be one
034 * definition with default values and one with actual values; if actual values are provided, they are used; otherwise,
035 * the default values apply.
036 * </p>
037 * <p>
038 * When constructing an instance an arbitrary number of child {@code BeanDeclaration} objects can be specified. The
039 * implementations of the {@code BeanDeclaration} methods implement a logical combination of the data returned by these
040 * child declarations. The order in which child declarations are added is relevant; first entries take precedence over
041 * later ones. The comments of the single methods explain in which way a combination of the child declarations is built.
042 * </p>
043 *
044 * @since 2.0
045 */
046public class CombinedBeanDeclaration implements BeanDeclaration {
047
048    /** A list with the child declarations. */
049    private final List<BeanDeclaration> childDeclarations;
050
051    /**
052     * Constructs a new instance of {@code CombinedBeanDeclaration} and initializes it with the given child declarations.
053     *
054     * @param decl the child declarations
055     * @throws NullPointerException if the array with child declarations is <b>null</b>
056     */
057    public CombinedBeanDeclaration(final BeanDeclaration... decl) {
058        childDeclarations = new ArrayList<>(Arrays.asList(decl));
059    }
060
061    /**
062     * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory name.
063     * The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean factory name,
064     * result is <b>null</b>.
065     */
066    @Override
067    public String getBeanFactoryName() {
068        for (final BeanDeclaration d : childDeclarations) {
069            final String factoryName = d.getBeanFactoryName();
070            if (factoryName != null) {
071                return factoryName;
072            }
073        }
074        return null;
075    }
076
077    /**
078     * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory
079     * parameter. The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean factory
080     * parameter, result is <b>null</b>.
081     */
082    @Override
083    public Object getBeanFactoryParameter() {
084        for (final BeanDeclaration d : childDeclarations) {
085            final Object factoryParam = d.getBeanFactoryParameter();
086            if (factoryParam != null) {
087                return factoryParam;
088            }
089        }
090        return null;
091    }
092
093    /**
094     * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for the bean class name.
095     * The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean class, result is
096     * <b>null</b>.
097     */
098    @Override
099    public String getBeanClassName() {
100        for (final BeanDeclaration d : childDeclarations) {
101            final String beanClassName = d.getBeanClassName();
102            if (beanClassName != null) {
103                return beanClassName;
104            }
105        }
106        return null;
107    }
108
109    /**
110     * {@inheritDoc} This implementation creates a union of the properties returned by all child declarations. If a property
111     * is defined in multiple child declarations, the declaration that comes before in the list of children takes
112     * precedence.
113     */
114    @Override
115    public Map<String, Object> getBeanProperties() {
116        final Map<String, Object> result = new HashMap<>();
117        for (int i = childDeclarations.size() - 1; i >= 0; i--) {
118            final Map<String, Object> props = childDeclarations.get(i).getBeanProperties();
119            if (props != null) {
120                result.putAll(props);
121            }
122        }
123        return result;
124    }
125
126    /**
127     * {@inheritDoc} This implementation creates a union of the nested bean declarations returned by all child declarations.
128     * If a complex property is defined in multiple child declarations, the declaration that comes before in the list of
129     * children takes precedence.
130     */
131    @Override
132    public Map<String, Object> getNestedBeanDeclarations() {
133        final Map<String, Object> result = new HashMap<>();
134        for (int i = childDeclarations.size() - 1; i >= 0; i--) {
135            final Map<String, Object> decls = childDeclarations.get(i).getNestedBeanDeclarations();
136            if (decls != null) {
137                result.putAll(decls);
138            }
139        }
140        return result;
141    }
142
143    /**
144     * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for constructor
145     * arguments. The first non-<b>null</b> and non empty collection is returned. If none of the child declarations provide
146     * constructor arguments, result is an empty collection.
147     */
148    @Override
149    public Collection<ConstructorArg> getConstructorArgs() {
150        for (final BeanDeclaration d : childDeclarations) {
151            final Collection<ConstructorArg> args = d.getConstructorArgs();
152            if (args != null && !args.isEmpty()) {
153                return args;
154            }
155        }
156        return Collections.emptyList();
157    }
158}