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;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.Serializable;
26  import java.util.AbstractCollection;
27  import java.util.AbstractList;
28  import java.util.ArrayDeque;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Set;
35  
36  import org.apache.commons.jexl3.internal.ArrayBuilder;
37  import org.apache.commons.jexl3.internal.introspection.ClassMisc;
38  import org.junit.jupiter.api.Test;
39  
40  /**
41   * Ensure ArrayBuilder types its output by finding some common ancestor class or interface (besides Object.class)
42   * from its entries when possible.
43   */
44  class ArrayTypeTest {
45    public abstract static class Class0 implements Inter0 {
46      private final int value;
47      public Class0(final int v) {
48        value = v;
49      }
50      @Override public String toString() {
51        return getClass().getSimpleName() + "{" + value + "}";
52      }
53    }
54    public static class ClassA extends Class0 implements InterA {
55      public ClassA(final int v) { super(v); }
56    }
57    public static class ClassB extends ClassA implements InterB {
58      public ClassB(final int v) { super(v); }
59    }
60    public static class ClassC extends ClassB implements InterC, InterX {
61      public ClassC(final int v) { super(v); }
62    }
63    public static class ClassD implements InterB, Inter0 {
64      @Override public String toString() {
65        return getClass().getSimpleName() + "{" + Integer.toHexString(hashCode()) + "}";
66      }
67    }
68    public static class ClassX extends Class0 implements InterX {
69      public ClassX(final int v) { super(v); }
70    }
71    // A dependency tree with some complexity follows:
72    public interface Inter0 {}
73    public interface InterA {}
74    public interface InterB {}
75    public interface InterC {}
76    public interface InterX extends InterB {}
77  
78    @Test
79    void testArrayTypes() {
80      final ArrayBuilder ab = new ArrayBuilder(1);
81      // An engine for expressions with args
82      final JexlFeatures features = JexlFeatures.createScript().script(false);
83      final JexlEngine jexl = new JexlBuilder()
84          .features(features)
85          .create();
86      // Super for ClassC
87      final Set<Class<?>> superSet = ClassMisc.getSuperClasses(ClassC.class);
88      assertTrue(superSet.size() > 0);
89      // verify the order
90      final List<Class<?>> ordered = Arrays.asList(
91          ClassB.class, ClassA.class, Class0.class,
92          InterC.class, InterX.class, InterB.class,
93          InterA.class, Inter0.class);
94      int i = 0;
95      for(final Class<?> clazz : superSet) {
96        assertEquals(clazz, ordered.get(i++), "order " + i);
97      }
98      // intersect ClassC, ClassX -> Class0
99      Class<?> inter = ClassMisc.getCommonSuperClass(ClassC.class, ClassX.class);
100     assertEquals(Class0.class, inter);
101     assertEquals(Object.class, ClassMisc.getCommonSuperClass(ClassC.class, Object.class));
102     assertNull(ClassMisc.getCommonSuperClass(ClassC.class, null));
103     assertNull(ClassMisc.getCommonSuperClass(null, ClassC.class));
104 
105     // intersect ClassC, ClassB -> ClassB
106     inter = ClassMisc.getCommonSuperClass(ClassC.class, ClassB.class);
107     assertEquals(ClassB.class, inter);
108 
109     // intersect ArrayList, ArrayDeque -> AbstractCollection
110     final Class<?> list = ClassMisc.getCommonSuperClass(ArrayList.class, ArrayDeque.class);
111     assertEquals(list, AbstractCollection.class);
112 
113     final Set<Class<?>> sset = ClassMisc.getSuperClasses(ArrayList.class, ArrayDeque.class);
114     assertFalse(sset.isEmpty());
115     // in java 21, a SequenceCollection interface is added to the sset
116     final List<Class<?>> expected = Arrays.asList(AbstractCollection.class, Collection.class, Iterable.class, Cloneable.class, Serializable.class);
117     assertTrue(sset.containsAll(expected));
118 
119     Class<?> collection = ClassMisc.getCommonSuperClass(ArrayList.class, Collections.emptyList().getClass());
120     assertEquals(AbstractList.class, collection);
121     collection = ClassMisc.getSuperClasses(ArrayList.class, Collections.emptyList().getClass())
122                           .stream().findFirst().orElse(Object.class);
123 
124     // apply on objects
125     final Object a = new ClassA(1);
126     final Object b = new ClassB(2);
127     final Object c = new ClassC(3);
128     final Object x = new ClassX(4);
129     JexlScript script;
130     Object result;
131 
132     script = jexl.createScript("[ a, b, c, d ]", "a", "b", "c", "d");
133     // intersect a, b, c, c -> classA
134     result = script.execute(null, a, b, c, c);
135     assertTrue(result.getClass().isArray()
136         && result.getClass().getComponentType().equals(ClassA.class));
137 
138     // intersect a, b, c, x -> class0
139     result = script.execute(null, a, b, c, x);
140     assertTrue(result.getClass().isArray()
141         && result.getClass().getComponentType().equals(Class0.class));
142 
143     // intersect x, c, b, a -> class0
144     result = script.execute(null, x, c, b, a);
145     assertTrue(result.getClass().isArray()
146         & result.getClass().getComponentType().equals(Class0.class));
147 
148     // intersect a, b, c, d -> inter0
149     final Object d = new ClassD();
150     result = script.execute(null, a, b, c, d);
151     assertTrue(result.getClass().isArray()
152         && result.getClass().getComponentType().equals(Inter0.class));
153 
154     script = jexl.createScript("[ a, b, c, d, ... ]", "a", "b", "c", "d");
155     // intersect a, b, c, c -> classA
156     result = script.execute(null, a, b, c, c);
157     assertInstanceOf(List.class, result);
158   }
159 }