View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.beanutils;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.function.Function;
27  
28  /**
29   * <p>
30   * A special implementation of the {@code BeanDeclaration} interface which allows combining multiple
31   * {@code BeanDeclaration} objects.
32   * </p>
33   * <p>
34   * An instance of this class can be used if a bean is defined using multiple sources. For instance, there can be one
35   * definition with default values and one with actual values; if actual values are provided, they are used; otherwise,
36   * the default values apply.
37   * </p>
38   * <p>
39   * When constructing an instance an arbitrary number of child {@code BeanDeclaration} objects can be specified. The
40   * implementations of the {@code BeanDeclaration} methods implement a logical combination of the data returned by these
41   * child declarations. The order in which child declarations are added is relevant; first entries take precedence over
42   * later ones. The comments of the single methods explain in which way a combination of the child declarations is built.
43   * </p>
44   *
45   * @since 2.0
46   */
47  public class CombinedBeanDeclaration implements BeanDeclaration {
48  
49      /** A list with the child declarations. */
50      private final ArrayList<BeanDeclaration> childDeclarations;
51  
52      /**
53       * Constructs a new instance of {@code CombinedBeanDeclaration} and initializes it with the given child declarations.
54       *
55       * @param decl the child declarations
56       * @throws NullPointerException if the array with child declarations is <b>null</b>
57       */
58      public CombinedBeanDeclaration(final BeanDeclaration... decl) {
59          childDeclarations = new ArrayList<>(Arrays.asList(decl));
60      }
61  
62      /**
63       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory name.
64       * The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean factory name,
65       * result is <b>null</b>.
66       */
67      @Override
68      public String getBeanFactoryName() {
69          return findFirst(BeanDeclaration::getBeanFactoryName);
70      }
71  
72      private <T> T findFirst(final Function<? super BeanDeclaration, ? extends T> mapper) {
73          return childDeclarations.stream().map(mapper).filter(Objects::nonNull).findFirst().orElse(null);
74      }
75  
76      /**
77       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory
78       * parameter. The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean factory
79       * parameter, result is <b>null</b>.
80       */
81      @Override
82      public Object getBeanFactoryParameter() {
83          return findFirst(BeanDeclaration::getBeanFactoryParameter);
84      }
85  
86      /**
87       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for the bean class name.
88       * The first non-<b>null</b> value is returned. If none of the child declarations have a defined bean class, result is
89       * <b>null</b>.
90       */
91      @Override
92      public String getBeanClassName() {
93          return findFirst(BeanDeclaration::getBeanClassName);
94      }
95  
96      /**
97       * {@inheritDoc} This implementation creates a union of the properties returned by all child declarations. If a property
98       * is defined in multiple child declarations, the declaration that comes before in the list of children takes
99       * precedence.
100      */
101     @Override
102     public Map<String, Object> getBeanProperties() {
103         return get(BeanDeclaration::getBeanProperties);
104     }
105 
106     private Map<String, Object> get(final Function<? super BeanDeclaration, ? extends Map<String, Object>> mapper) {
107         @SuppressWarnings("unchecked")
108         final ArrayList<BeanDeclaration> temp = (ArrayList<BeanDeclaration>) childDeclarations.clone();
109         Collections.reverse(temp);
110         return temp.stream().map(mapper).filter(Objects::nonNull).collect(HashMap::new, HashMap::putAll, HashMap::putAll);
111     }
112 
113     /**
114      * {@inheritDoc} This implementation creates a union of the nested bean declarations returned by all child declarations.
115      * If a complex property is defined in multiple child declarations, the declaration that comes before in the list of
116      * children takes precedence.
117      */
118     @Override
119     public Map<String, Object> getNestedBeanDeclarations() {
120         return get(BeanDeclaration::getNestedBeanDeclarations);
121     }
122 
123     /**
124      * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for constructor
125      * arguments. The first non-<b>null</b> and non empty collection is returned. If none of the child declarations provide
126      * constructor arguments, result is an empty collection.
127      */
128     @Override
129     public Collection<ConstructorArg> getConstructorArgs() {
130         for (final BeanDeclaration d : childDeclarations) {
131             final Collection<ConstructorArg> args = d.getConstructorArgs();
132             if (args != null && !args.isEmpty()) {
133                 return args;
134             }
135         }
136         return Collections.emptyList();
137     }
138 }