| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.bcel.classfile; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.util.Enumeration; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.bcel.util.SyntheticRepository; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.Strings; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.ValueSource; |
| |
| /** |
| * Tests {@code module-info.class} files. |
| */ |
| class ConstantPoolModuleToStringTest { |
| |
| static class ToStringVisitor extends EmptyVisitor { |
| |
| private final StringBuilder stringBuilder = new StringBuilder(); |
| private final ConstantPool pool; |
| private int count; |
| |
| ToStringVisitor(final ConstantPool pool) { |
| this.pool = pool; |
| } |
| |
| private void append(final Object obj) { |
| if (!(stringBuilder.length() == 0)) { |
| stringBuilder.append('\n'); |
| } |
| stringBuilder.append(obj); |
| } |
| |
| @Override |
| public String toString() { |
| return "ToStringVisitor [count=" + count + ", stringBuilder=" + stringBuilder + ", pool=" + pool + "]"; |
| } |
| |
| @Override |
| public void visitAnnotation(final Annotations obj) { |
| super.visitAnnotation(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitAnnotationDefault(final AnnotationDefault obj) { |
| super.visitAnnotationDefault(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitAnnotationEntry(final AnnotationEntry obj) { |
| super.visitAnnotationEntry(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitBootstrapMethods(final BootstrapMethods obj) { |
| super.visitBootstrapMethods(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitCode(final Code obj) { |
| super.visitCode(obj); |
| append(obj.toString(true)); |
| } |
| |
| @Override |
| public void visitCodeException(final CodeException obj) { |
| super.visitCodeException(obj); |
| append(obj.toString(pool, true)); |
| } |
| |
| @Override |
| public void visitConstantClass(final ConstantClass obj) { |
| super.visitConstantClass(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantDouble(final ConstantDouble obj) { |
| super.visitConstantDouble(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantDynamic(final ConstantDynamic constantDynamic) { |
| super.visitConstantDynamic(constantDynamic); |
| append(constantDynamic); |
| } |
| |
| @Override |
| public void visitConstantFieldref(final ConstantFieldref obj) { |
| super.visitConstantFieldref(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantFloat(final ConstantFloat obj) { |
| super.visitConstantFloat(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantInteger(final ConstantInteger obj) { |
| super.visitConstantInteger(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj) { |
| super.visitConstantInterfaceMethodref(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantInvokeDynamic(final ConstantInvokeDynamic obj) { |
| super.visitConstantInvokeDynamic(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantLong(final ConstantLong obj) { |
| super.visitConstantLong(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantMethodHandle(final ConstantMethodHandle obj) { |
| super.visitConstantMethodHandle(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantMethodref(final ConstantMethodref obj) { |
| super.visitConstantMethodref(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantMethodType(final ConstantMethodType obj) { |
| super.visitConstantMethodType(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantModule(final ConstantModule constantModule) { |
| super.visitConstantModule(constantModule); |
| append(constantModule); |
| } |
| |
| @Override |
| public void visitConstantNameAndType(final ConstantNameAndType obj) { |
| super.visitConstantNameAndType(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantPackage(final ConstantPackage constantPackage) { |
| super.visitConstantPackage(constantPackage); |
| append(constantPackage); |
| } |
| |
| @Override |
| public void visitConstantPool(final ConstantPool obj) { |
| super.visitConstantPool(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantString(final ConstantString obj) { |
| super.visitConstantString(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantUtf8(final ConstantUtf8 obj) { |
| super.visitConstantUtf8(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitConstantValue(final ConstantValue obj) { |
| super.visitConstantValue(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitDeprecated(final Deprecated obj) { |
| super.visitDeprecated(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitEnclosingMethod(final EnclosingMethod obj) { |
| super.visitEnclosingMethod(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitExceptionTable(final ExceptionTable obj) { |
| super.visitExceptionTable(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitField(final Field obj) { |
| super.visitField(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitInnerClass(final InnerClass obj) { |
| super.visitInnerClass(obj); |
| append(obj.toString(pool)); |
| } |
| |
| @Override |
| public void visitInnerClasses(final InnerClasses obj) { |
| super.visitInnerClasses(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitJavaClass(final JavaClass obj) { |
| super.visitJavaClass(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitLineNumber(final LineNumber obj) { |
| super.visitLineNumber(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitLineNumberTable(final LineNumberTable obj) { |
| super.visitLineNumberTable(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitLocalVariable(final LocalVariable obj) { |
| super.visitLocalVariable(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitLocalVariableTable(final LocalVariableTable obj) { |
| super.visitLocalVariableTable(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitLocalVariableTypeTable(final LocalVariableTypeTable obj) { |
| super.visitLocalVariableTypeTable(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitMethod(final Method obj) { |
| super.visitMethod(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitMethodParameter(final MethodParameter obj) { |
| super.visitMethodParameter(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitMethodParameters(final MethodParameters obj) { |
| super.visitMethodParameters(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitModule(final Module constantModule) { |
| super.visitModule(constantModule); |
| final String s = constantModule.toString(); |
| final Matcher matcher = Pattern.compile(" (\\w+)([:(])").matcher(s); |
| while (matcher.find()) { |
| switch (matcher.group(2)) { |
| case ":": |
| assertTrue(Strings.CS.containsAny(matcher.group(1), "name", "flags", "version")); |
| break; |
| case "(": |
| assertTrue(Strings.CS.containsAny(matcher.group(1), "requires", "exports", "opens", "uses", "provides")); |
| break; |
| default: |
| break; |
| } |
| } |
| append(constantModule); |
| } |
| |
| @Override |
| public void visitModuleExports(final ModuleExports constantModule) { |
| super.visitModuleExports(constantModule); |
| append(constantModule); |
| final String s = constantModule.toString(pool); |
| final String[] tokens = s.split(", "); |
| assertNotNull(tokens); |
| assertEquals(3, tokens.length); |
| assertEquals("0000", tokens[1]); |
| final Matcher matcher = Pattern.compile("to\\((\\d+)\\):").matcher(tokens[2]); |
| assertTrue(matcher.find()); |
| assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n')); |
| } |
| |
| @Override |
| public void visitModuleMainClass(final ModuleMainClass obj) { |
| super.visitModuleMainClass(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitModuleOpens(final ModuleOpens constantModule) { |
| super.visitModuleOpens(constantModule); |
| append(constantModule); |
| final String s = constantModule.toString(pool); |
| final String[] tokens = s.split(", "); |
| assertNotNull(tokens); |
| assertEquals(3, tokens.length); |
| assertEquals("0000", tokens[1]); |
| final Matcher matcher = Pattern.compile("to\\((\\d+)\\):").matcher(tokens[2]); |
| assertTrue(matcher.find()); |
| assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n')); |
| } |
| |
| @Override |
| public void visitModulePackages(final ModulePackages constantModule) { |
| super.visitModulePackages(constantModule); |
| append(constantModule); |
| final String s = constantModule.toString(); |
| assertEquals(constantModule.getNumberOfPackages(), StringUtils.countMatches(s, '\n')); |
| } |
| |
| @Override |
| public void visitModuleProvides(final ModuleProvides constantModule) { |
| super.visitModuleProvides(constantModule); |
| append(constantModule); |
| final String s = constantModule.toString(pool); |
| final String[] tokens = s.split(", "); |
| assertNotNull(tokens); |
| assertEquals(2, tokens.length); |
| final Matcher matcher = Pattern.compile("with\\((\\d+)\\):").matcher(tokens[1]); |
| assertTrue(matcher.find()); |
| assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n')); |
| append(s); |
| } |
| |
| @Override |
| public void visitModuleRequires(final ModuleRequires constantModule) { |
| super.visitModuleRequires(constantModule); |
| append(constantModule); |
| append(constantModule.toString(pool)); |
| final String s = constantModule.toString(pool).trim(); |
| final boolean condition = Strings.CS.startsWithAny(s, |
| "jdk.", |
| "java.", |
| "org.junit", |
| "org.apiguardian.api", |
| "org.opentest4j", |
| "net.bytebuddy", |
| "com.sun.jna", |
| "junit", |
| // com.azul.* for reproducibility with Azul JDK |
| "com.azul."); |
| assertTrue(condition, s); |
| } |
| |
| @Override |
| public void visitNestHost(final NestHost obj) { |
| super.visitNestHost(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitNestMembers(final NestMembers obj) { |
| super.visitNestMembers(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitParameterAnnotation(final ParameterAnnotations obj) { |
| super.visitParameterAnnotation(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitParameterAnnotationEntry(final ParameterAnnotationEntry obj) { |
| super.visitParameterAnnotationEntry(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitSignature(final Signature obj) { |
| super.visitSignature(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitSourceFile(final SourceFile obj) { |
| super.visitSourceFile(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitStackMap(final StackMap obj) { |
| super.visitStackMap(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitStackMapEntry(final StackMapEntry obj) { |
| super.visitStackMapEntry(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitSynthetic(final Synthetic obj) { |
| super.visitSynthetic(obj); |
| append(obj); |
| } |
| |
| @Override |
| public void visitUnknown(final Unknown obj) { |
| super.visitUnknown(obj); |
| append(obj); |
| } |
| } |
| |
| private static void test(final InputStream inputStream) throws IOException { |
| final ClassParser classParser = new ClassParser(inputStream, "module-info.class"); |
| final JavaClass javaClass = classParser.parse(); |
| testJavaClass(javaClass); |
| } |
| |
| private static void testJavaClass(final JavaClass javaClass) { |
| final ConstantPool constantPool = javaClass.getConstantPool(); |
| final ToStringVisitor visitor = new ToStringVisitor(constantPool); |
| final DescendingVisitor descendingVisitor = new DescendingVisitor(javaClass, visitor); |
| try { |
| javaClass.accept(descendingVisitor); |
| assertNotNull(visitor.toString()); |
| } catch (Exception | Error e) { |
| fail(visitor.toString(), e); |
| } |
| } |
| |
| @Test |
| void test() throws Exception { |
| final Enumeration<URL> moduleURLs = getClass().getClassLoader().getResources("module-info.class"); |
| while (moduleURLs.hasMoreElements()) { |
| final URL url = moduleURLs.nextElement(); |
| try (InputStream inputStream = url.openStream()) { |
| test(inputStream); |
| } |
| } |
| } |
| |
| @ParameterizedTest |
| @ValueSource(strings = { |
| // @formatter:off |
| "src/test/resources/jpms/java11/commons-io/module-info.class", |
| "src/test/resources/jpms/java17/commons-io/module-info.class", |
| "src/test/resources/jpms/java18/commons-io/module-info.class", |
| "src/test/resources/jpms/java19-ea/commons-io/module-info.class"}) |
| // @formatter:on |
| void test(final String first) throws Exception { |
| try (InputStream inputStream = Files.newInputStream(Paths.get(first))) { |
| test(inputStream); |
| } |
| } |
| |
| @ParameterizedTest |
| @ValueSource(strings = { |
| // @formatter:off |
| "java.lang.CharSequence$1CharIterator", // contains attribute EnclosingMethod |
| "org.apache.commons.lang3.function.TriFunction", // contains attributes BootstrapMethods, InnerClasses, LineNumberTable, LocalVariableTable, |
| // LocalVariableTypeTable, RuntimeVisibleAnnotations, Signature, SourceFile |
| "org.apache.commons.lang3.math.NumberUtils", // contains attribute ConstantFloat, ConstantDouble |
| "org.apache.bcel.Const", // contains attributes MethodParameters |
| "java.io.StringBufferInputStream", // contains attributes Deprecated, StackMap |
| "java.nio.file.Files", // contains attributes ConstantValue, ExceptionTable, NestMembers |
| "org.junit.jupiter.api.AssertionsKt", // contains attribute ParameterAnnotation |
| "javax.annotation.ManagedBean", // contains attribute AnnotationDefault |
| "javax.management.remote.rmi.RMIConnectionImpl_Stub"}) // contains attribute Synthetic |
| // @formatter:on |
| void testClass(final String className) throws Exception { |
| testJavaClass(SyntheticRepository.getInstance().loadClass(className)); |
| } |
| } |