| /* | |
| * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.constant.ClassDesc; | |
| import java.lang.constant.ConstantDescs; | |
| import java.lang.reflect.Array; | |
| import java.lang.reflect.Field; | |
| import java.lang.reflect.Modifier; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import java.util.Map; | |
| import org.testng.annotations.Test; | |
| import static org.testng.Assert.assertEquals; | |
| import static org.testng.Assert.assertFalse; | |
| import static org.testng.Assert.assertNotEquals; | |
| import static org.testng.Assert.assertNull; | |
| import static org.testng.Assert.assertTrue; | |
| import static org.testng.Assert.fail; | |
| /* | |
| * @test | |
| * @bug 8215510 8283075 8338544 | |
| * @compile ClassDescTest.java | |
| * @run testng ClassDescTest | |
| * @summary unit tests for java.lang.constant.ClassDesc | |
| */ | |
| @Test | |
| public class ClassDescTest extends SymbolicDescTest { | |
| private void testClassDesc(ClassDesc r) throws ReflectiveOperationException { | |
| testSymbolicDesc(r); | |
| // Test descriptor accessor, factory, equals | |
| assertEquals(r, ClassDesc.ofDescriptor(r.descriptorString())); | |
| if (!r.descriptorString().equals("V")) { | |
| assertEquals(r, r.arrayType().componentType()); | |
| // Commutativity: array -> resolve -> componentType -> toSymbolic | |
| assertEquals(r, r.arrayType().resolveConstantDesc(LOOKUP).getComponentType().describeConstable().orElseThrow()); | |
| // Commutativity: resolve -> array -> toSymbolic -> component type | |
| assertEquals(r, Array.newInstance(r.resolveConstantDesc(LOOKUP), 0).getClass().describeConstable().orElseThrow().componentType()); | |
| } | |
| if (r.isArray()) { | |
| assertEquals(r, r.componentType().arrayType()); | |
| assertEquals(r, r.resolveConstantDesc(LOOKUP).getComponentType().describeConstable().orElseThrow().arrayType()); | |
| assertEquals(r, Array.newInstance(r.componentType().resolveConstantDesc(LOOKUP), 0).getClass().describeConstable().orElseThrow()); | |
| } else { | |
| assertNull(r.componentType()); | |
| } | |
| if (!r.isClassOrInterface()) { | |
| assertEquals(r.packageName(), ""); | |
| } | |
| } | |
| private static String classDisplayName(Class<?> c) { | |
| int arrayLevel = 0; | |
| while (c.isArray()) { | |
| arrayLevel++; | |
| c = c.componentType(); | |
| } | |
| String name = c.getName(); | |
| String simpleClassName; | |
| if (c.isPrimitive()) { | |
| simpleClassName = name; | |
| } else { | |
| int lastDot = name.lastIndexOf('.'); | |
| simpleClassName = lastDot == -1 ? name : name.substring(lastDot + 1); | |
| } | |
| return simpleClassName + "[]".repeat(arrayLevel); | |
| } | |
| private void testClassDesc(ClassDesc r, Class<?> c) throws ReflectiveOperationException { | |
| testClassDesc(r); | |
| assertEquals(r.resolveConstantDesc(LOOKUP), c); | |
| assertEquals(c.describeConstable().orElseThrow(), r); | |
| assertEquals(ClassDesc.ofDescriptor(c.descriptorString()), r); | |
| if (r.isArray()) { | |
| testClassDesc(r.componentType(), c.componentType()); | |
| } | |
| if (r.isClassOrInterface()) { | |
| assertEquals(r.packageName(), c.getPackageName()); | |
| } | |
| assertEquals(r.displayName(), classDisplayName(c)); | |
| } | |
| public void testSymbolicDescsConstants() throws ReflectiveOperationException { | |
| int tested = 0; | |
| Field[] fields = ConstantDescs.class.getDeclaredFields(); | |
| for (Field f : fields) { | |
| try { | |
| if (f.getType().equals(ClassDesc.class) | |
| && ((f.getModifiers() & Modifier.STATIC) != 0) | |
| && ((f.getModifiers() & Modifier.PUBLIC) != 0)) { | |
| ClassDesc cr = (ClassDesc) f.get(null); | |
| Class<?> c = cr.resolveConstantDesc(MethodHandles.lookup()); | |
| testClassDesc(cr, c); | |
| ++tested; | |
| } | |
| } | |
| catch (Throwable e) { | |
| System.out.println(e.getMessage()); | |
| fail("Error testing field " + f.getName(), e); | |
| } | |
| } | |
| assertTrue(tested > 0); | |
| } | |
| public void testPrimitiveClassDesc() throws ReflectiveOperationException { | |
| for (Primitives p : Primitives.values()) { | |
| List<ClassDesc> descs = List.of(ClassDesc.ofDescriptor(p.descriptor), | |
| p.classDesc, | |
| p.clazz.describeConstable().orElseThrow()); | |
| for (ClassDesc c : descs) { | |
| testClassDesc(c, p.clazz); | |
| assertTrue(c.isPrimitive()); | |
| assertEquals(p.descriptor, c.descriptorString()); | |
| assertEquals(p.name, c.displayName()); | |
| descs.forEach(cc -> assertEquals(c, cc)); | |
| if (p != Primitives.VOID) { | |
| testClassDesc(c.arrayType(), p.arrayClass); | |
| assertEquals(c, p.arrayClass.describeConstable().orElseThrow().componentType()); | |
| assertEquals(c, p.classDesc.arrayType().componentType()); | |
| } | |
| } | |
| for (Primitives other : Primitives.values()) { | |
| ClassDesc otherDescr = ClassDesc.ofDescriptor(other.descriptor); | |
| if (p != other) | |
| descs.forEach(c -> assertNotEquals(c, otherDescr)); | |
| else | |
| descs.forEach(c -> assertEquals(c, otherDescr)); | |
| } | |
| } | |
| } | |
| public void testSimpleClassDesc() throws ReflectiveOperationException { | |
| List<ClassDesc> stringClassDescs = Arrays.asList(ClassDesc.ofDescriptor("Ljava/lang/String;"), | |
| ClassDesc.ofInternalName("java/lang/String"), | |
| ClassDesc.of("java.lang", "String"), | |
| ClassDesc.of("java.lang.String"), | |
| ClassDesc.of("java.lang.String").arrayType().componentType(), | |
| String.class.describeConstable().orElseThrow()); | |
| for (ClassDesc r : stringClassDescs) { | |
| testClassDesc(r, String.class); | |
| assertFalse(r.isPrimitive()); | |
| assertEquals("Ljava/lang/String;", r.descriptorString()); | |
| assertEquals("String", r.displayName()); | |
| testClassDesc(r.arrayType(), String[].class); | |
| testClassDesc(r.arrayType(3), String[][][].class); | |
| stringClassDescs.forEach(rr -> assertEquals(r, rr)); | |
| } | |
| testClassDesc(ClassDesc.of("java.lang.String").arrayType(), String[].class); | |
| testClassDesc(ClassDesc.of("java.util.Map").nested("Entry"), Map.Entry.class); | |
| assertEquals(ClassDesc.of("java.lang.String"), ClassDesc.ofDescriptor("Ljava/lang/String;")); | |
| assertEquals(ClassDesc.of("java.lang.String"), ClassDesc.ofInternalName("java/lang/String")); | |
| ClassDesc thisClassDesc = ClassDesc.ofDescriptor("LClassDescTest;"); | |
| assertEquals(thisClassDesc, ClassDesc.of("", "ClassDescTest")); | |
| assertEquals(thisClassDesc, ClassDesc.of("ClassDescTest")); | |
| assertEquals(thisClassDesc.displayName(), "ClassDescTest"); | |
| testClassDesc(thisClassDesc, ClassDescTest.class); | |
| } | |
| public void testPackageName() { | |
| assertEquals("com.foo", ClassDesc.of("com.foo.Bar").packageName()); | |
| assertEquals("com.foo", ClassDesc.of("com.foo.Bar").nested("Baz").packageName()); | |
| assertEquals("", ClassDesc.of("Bar").packageName()); | |
| assertEquals("", ClassDesc.of("Bar").nested("Baz").packageName()); | |
| assertEquals("", ClassDesc.of("Bar").nested("Baz", "Foo").packageName()); | |
| assertEquals("", ConstantDescs.CD_int.packageName()); | |
| assertEquals("", ConstantDescs.CD_int.arrayType().packageName()); | |
| assertEquals("", ConstantDescs.CD_String.arrayType().packageName()); | |
| assertEquals("", ClassDesc.of("Bar").arrayType().packageName()); | |
| } | |
| private void testBadArrayRank(ClassDesc cr) { | |
| try { | |
| cr.arrayType(-1); | |
| fail(""); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| try { | |
| cr.arrayType(0); | |
| fail(""); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| } | |
| private void testArrayRankOverflow() { | |
| ClassDesc TwoDArrayDesc = | |
| String.class.describeConstable().get().arrayType().arrayType(); | |
| try { | |
| TwoDArrayDesc.arrayType(Integer.MAX_VALUE); | |
| fail(""); | |
| } catch (IllegalArgumentException iae) { | |
| // Expected | |
| } | |
| } | |
| public void testArrayClassDesc() throws ReflectiveOperationException { | |
| for (String d : basicDescs) { | |
| ClassDesc a0 = ClassDesc.ofDescriptor(d); | |
| ClassDesc a1 = a0.arrayType(); | |
| ClassDesc a2 = a1.arrayType(); | |
| testClassDesc(a0); | |
| testClassDesc(a1); | |
| testClassDesc(a2); | |
| assertFalse(a0.isArray()); | |
| assertTrue(a1.isArray()); | |
| assertTrue(a2.isArray()); | |
| assertFalse(a1.isPrimitive()); | |
| assertFalse(a2.isPrimitive()); | |
| assertEquals(a0.descriptorString(), d); | |
| assertEquals(a1.descriptorString(), "[" + a0.descriptorString()); | |
| assertEquals(a2.descriptorString(), "[[" + a0.descriptorString()); | |
| assertNull(a0.componentType()); | |
| assertEquals(a0, a1.componentType()); | |
| assertEquals(a1, a2.componentType()); | |
| assertNotEquals(a0, a1); | |
| assertNotEquals(a1, a2); | |
| assertEquals(a1, ClassDesc.ofDescriptor("[" + d)); | |
| assertEquals(a2, ClassDesc.ofDescriptor("[[" + d)); | |
| assertEquals(classToDescriptor(a0.resolveConstantDesc(LOOKUP)), a0.descriptorString()); | |
| assertEquals(classToDescriptor(a1.resolveConstantDesc(LOOKUP)), a1.descriptorString()); | |
| assertEquals(classToDescriptor(a2.resolveConstantDesc(LOOKUP)), a2.descriptorString()); | |
| testBadArrayRank(ConstantDescs.CD_int); | |
| testBadArrayRank(ConstantDescs.CD_String); | |
| testBadArrayRank(ClassDesc.of("Bar")); | |
| testArrayRankOverflow(); | |
| } | |
| try { | |
| ConstantDescs.CD_void.arrayType(); | |
| fail("Should throw IAE"); | |
| } catch (IllegalArgumentException iae) { | |
| // Expected | |
| } | |
| } | |
| public void testBadClassDescs() { | |
| List<String> badDescriptors = List.of("II", "I;", "Q", "L", "", | |
| "java.lang.String", "[]", "Ljava/lang/String", | |
| "Ljava.lang.String;", "java/lang/String", "L;", | |
| "La//b;", "L/a;", "La/;"); | |
| for (String d : badDescriptors) { | |
| try { | |
| ClassDesc constant = ClassDesc.ofDescriptor(d); | |
| fail(d); | |
| } | |
| catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| } | |
| List<String> badBinaryNames = List.of("I;", "[]", "Ljava/lang/String", | |
| "Ljava.lang.String;", "java/lang/String", ""); | |
| for (String d : badBinaryNames) { | |
| try { | |
| ClassDesc constant = ClassDesc.of(d); | |
| fail(d); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| } | |
| List<String> badInternalNames = List.of("I;", "[]", "[Ljava/lang/String;", | |
| "Ljava.lang.String;", "java.lang.String", ""); | |
| for (String d : badInternalNames) { | |
| try { | |
| ClassDesc constant = ClassDesc.ofInternalName(d); | |
| fail(d); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| } | |
| for (Primitives p : Primitives.values()) { | |
| testBadNestedClasses(ClassDesc.ofDescriptor(p.descriptor), "any"); | |
| testBadNestedClasses(ClassDesc.ofDescriptor(p.descriptor), "any", "other"); | |
| } | |
| ClassDesc stringDesc = ClassDesc.ofDescriptor("Ljava/lang/String;"); | |
| ClassDesc stringArrDesc = stringDesc.arrayType(255); | |
| try { | |
| ClassDesc arrGreaterThan255 = stringArrDesc.arrayType(); | |
| fail("can't create an array type descriptor with more than 255 dimensions"); | |
| } catch (IllegalStateException e) { | |
| // good | |
| } | |
| String descWith255ArrayDims = new String(new char[255]).replace('\0', '['); | |
| try { | |
| ClassDesc arrGreaterThan255 = ClassDesc.ofDescriptor(descWith255ArrayDims + "[Ljava/lang/String;"); | |
| fail("can't create an array type descriptor with more than 255 dimensions"); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| try { | |
| ClassDesc arrWith255Dims = ClassDesc.ofDescriptor(descWith255ArrayDims + "Ljava/lang/String;"); | |
| arrWith255Dims.arrayType(1); | |
| fail("can't create an array type descriptor with more than 255 dimensions"); | |
| } catch (IllegalArgumentException e) { | |
| // good | |
| } | |
| } | |
| private void testBadNestedClasses(ClassDesc cr, String firstNestedName, String... moreNestedNames) { | |
| try { | |
| cr.nested(firstNestedName, moreNestedNames); | |
| fail(""); | |
| } catch (IllegalStateException e) { | |
| // good | |
| } | |
| } | |
| public void testLangClasses() { | |
| Double d = 1.0; | |
| assertEquals(d.resolveConstantDesc(LOOKUP), d); | |
| assertEquals(d.describeConstable().get(), d); | |
| Integer i = 1; | |
| assertEquals(i.resolveConstantDesc(LOOKUP), i); | |
| assertEquals(i.describeConstable().get(), i); | |
| Float f = 1.0f; | |
| assertEquals(f.resolveConstantDesc(LOOKUP), f); | |
| assertEquals(f.describeConstable().get(), f); | |
| Long l = 1L; | |
| assertEquals(l.resolveConstantDesc(LOOKUP), l); | |
| assertEquals(l.describeConstable().get(), l); | |
| String s = ""; | |
| assertEquals(s.resolveConstantDesc(LOOKUP), s); | |
| assertEquals(s.describeConstable().get(), s); | |
| } | |
| public void testNullNestedClasses() { | |
| ClassDesc cd = ClassDesc.of("Bar"); | |
| try { | |
| cd.nested(null); | |
| fail(""); | |
| } catch (NullPointerException e) { | |
| // good | |
| } | |
| try { | |
| cd.nested("good", null); | |
| fail(""); | |
| } catch (NullPointerException e) { | |
| // good | |
| } | |
| try { | |
| cd.nested("good", "goodToo", null); | |
| fail(""); | |
| } catch (NullPointerException e) { | |
| // good | |
| } | |
| } | |
| } |