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    *      https://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.jexl3.internal;
18  
19  import java.lang.reflect.Array;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.IdentityHashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.jexl3.JexlArithmetic;
27  import org.apache.commons.jexl3.internal.introspection.ClassMisc;
28  
29  /**
30   * Helper class to create typed arrays.
31   */
32  public class ArrayBuilder implements JexlArithmetic.ArrayBuilder {
33      /** The number of primitive types. */
34      private static final int PRIMITIVE_SIZE = 8;
35      /** The boxing types to primitive conversion map. */
36      private static final Map<Class<?>, Class<?>> BOXING_CLASSES;
37      static {
38          BOXING_CLASSES = new IdentityHashMap<>(PRIMITIVE_SIZE);
39          BOXING_CLASSES.put(Boolean.class, Boolean.TYPE);
40          BOXING_CLASSES.put(Byte.class, Byte.TYPE);
41          BOXING_CLASSES.put(Character.class, Character.TYPE);
42          BOXING_CLASSES.put(Double.class, Double.TYPE);
43          BOXING_CLASSES.put(Float.class, Float.TYPE);
44          BOXING_CLASSES.put(Integer.class, Integer.TYPE);
45          BOXING_CLASSES.put(Long.class, Long.TYPE);
46          BOXING_CLASSES.put(Short.class, Short.TYPE);
47      }
48  
49      /**
50       * Gets the primitive type of given class (when it exists).
51       * @param parm a class
52       * @return the primitive type or null it the argument is not unboxable
53       */
54      protected static Class<?> unboxingClass(final Class<?> parm) {
55          return BOXING_CLASSES.getOrDefault(parm, parm);
56      }
57  
58      /** The intended class array. */
59      protected Class<?> commonClass;
60      /** Whether the array stores numbers. */
61      protected boolean isNumber = true;
62      /** Whether we can try unboxing. */
63      protected boolean unboxing = true;
64      /** The untyped list of items being added. */
65      protected final Object[] untyped;
66      /** Number of added items. */
67      protected int added;
68      /** Extended? */
69      protected final boolean extended;
70  
71      /**
72       * Creates a new builder.
73       * @param size the exact array size
74       */
75      public ArrayBuilder(final int size) {
76          this(size, false);
77      }
78  
79      /**
80       * Creates a new builder.
81       * @param size the exact array size
82       * @param extended whether the array is extended
83       */
84      public ArrayBuilder(final int size, final boolean extended) {
85          this.untyped = new Object[size];
86          this.extended = extended;
87      }
88      @Override
89      public void add(final Object value) {
90          // for all children after first...
91          if (!Object.class.equals(commonClass)) {
92              if (value == null) {
93                  isNumber = false;
94                  unboxing = false;
95              } else {
96                  final Class<?> eclass = value.getClass();
97                  // base common class on first non-null entry
98                  if (commonClass == null) {
99                      commonClass = eclass;
100                     isNumber = isNumber && Number.class.isAssignableFrom(commonClass);
101                 } else if (!commonClass.isAssignableFrom(eclass)) {
102                     // if both are numbers...
103                     if (isNumber && Number.class.isAssignableFrom(eclass)) {
104                         commonClass = Number.class;
105                     } else {
106                         isNumber = false;
107                         commonClass = getCommonSuperClass(commonClass, eclass);
108                     }
109                 }
110             }
111         }
112         if (added >= untyped.length) {
113             throw new IllegalArgumentException("add() over size");
114         }
115         untyped[added++] = value;
116     }
117 
118     @Override
119     public Object create(final boolean e) {
120         if (untyped == null) {
121             return new Object[0];
122         }
123         final int size = added;
124         if (extended || e) {
125             final List<Object> list = newList(commonClass, size);
126             list.addAll(Arrays.asList(untyped).subList(0, size));
127             return list;
128         }
129         // convert untyped array to the common class if not Object.class
130         if (commonClass == null || Object.class.equals(commonClass)) {
131             return untyped.clone();
132         }
133         // if the commonClass is a number, it has an equivalent primitive type, get it
134         if (unboxing) {
135             commonClass = unboxingClass(commonClass);
136         }
137         // allocate and fill up the typed array
138         final Object typed = Array.newInstance(commonClass, size);
139         for (int i = 0; i < size; ++i) {
140             Array.set(typed, i, untyped[i]);
141         }
142         return typed;
143     }
144 
145     /**
146      * Computes the best super class/super interface.
147      * <p>Used to try and maintain type safe arrays.</p>
148      * @param baseClass the baseClass
149      * @param other another class
150      * @return a common ancestor, class or interface, worst case being class Object
151      */
152     protected Class<?> getCommonSuperClass(final Class<?> baseClass, final Class<?> other) {
153         return ClassMisc.getCommonSuperClass(baseClass, other);
154     }
155 
156     /**
157      * Creates a new list (aka extended array)/
158      * @param clazz the class
159      * @param size the size
160      * @return the instance
161      * @param <T> the type
162      */
163     protected <T> List<T> newList(final Class<? extends T> clazz, final int size) {
164         return new ArrayList<>(size);
165     }
166 }