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 <strong>null</strong>
57       */
58      public CombinedBeanDeclaration(final BeanDeclaration... decl) {
59          childDeclarations = new ArrayList<>(Arrays.asList(decl));
60      }
61  
62      private <T> T findFirst(final Function<? super BeanDeclaration, ? extends T> mapper) {
63          return childDeclarations.stream().map(mapper).filter(Objects::nonNull).findFirst().orElse(null);
64      }
65  
66      private Map<String, Object> get(final Function<? super BeanDeclaration, ? extends Map<String, Object>> mapper) {
67          @SuppressWarnings("unchecked")
68          final ArrayList<BeanDeclaration> temp = (ArrayList<BeanDeclaration>) childDeclarations.clone();
69          Collections.reverse(temp);
70          return temp.stream().map(mapper).filter(Objects::nonNull).collect(HashMap::new, HashMap::putAll, HashMap::putAll);
71      }
72  
73      /**
74       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for the bean class name.
75       * The first non-<strong>null</strong> value is returned. If none of the child declarations have a defined bean class, result is
76       * <strong>null</strong>.
77       */
78      @Override
79      public String getBeanClassName() {
80          return findFirst(BeanDeclaration::getBeanClassName);
81      }
82  
83      /**
84       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory name.
85       * The first non-<strong>null</strong> value is returned. If none of the child declarations have a defined bean factory name,
86       * result is <strong>null</strong>.
87       */
88      @Override
89      public String getBeanFactoryName() {
90          return findFirst(BeanDeclaration::getBeanFactoryName);
91      }
92  
93      /**
94       * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for a bean factory
95       * parameter. The first non-<strong>null</strong> value is returned. If none of the child declarations have a defined bean factory
96       * parameter, result is <strong>null</strong>.
97       */
98      @Override
99      public Object getBeanFactoryParameter() {
100         return findFirst(BeanDeclaration::getBeanFactoryParameter);
101     }
102 
103     /**
104      * {@inheritDoc} This implementation creates a union of the properties returned by all child declarations. If a property
105      * is defined in multiple child declarations, the declaration that comes before in the list of children takes
106      * precedence.
107      */
108     @Override
109     public Map<String, Object> getBeanProperties() {
110         return get(BeanDeclaration::getBeanProperties);
111     }
112 
113     /**
114      * {@inheritDoc} This implementation iterates over the list of child declarations and asks them for constructor
115      * arguments. The first non-<strong>null</strong> and non empty collection is returned. If none of the child declarations provide
116      * constructor arguments, result is an empty collection.
117      */
118     @Override
119     public Collection<ConstructorArg> getConstructorArgs() {
120         for (final BeanDeclaration d : childDeclarations) {
121             final Collection<ConstructorArg> args = d.getConstructorArgs();
122             if (args != null && !args.isEmpty()) {
123                 return args;
124             }
125         }
126         return Collections.emptyList();
127     }
128 
129     /**
130      * {@inheritDoc} This implementation creates a union of the nested bean declarations returned by all child declarations.
131      * If a complex property is defined in multiple child declarations, the declaration that comes before in the list of
132      * children takes precedence.
133      */
134     @Override
135     public Map<String, Object> getNestedBeanDeclarations() {
136         return get(BeanDeclaration::getNestedBeanDeclarations);
137     }
138 }