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