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