View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.classfile;
20  
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.net.URL;
29  import java.nio.file.Files;
30  import java.nio.file.Paths;
31  import java.util.Enumeration;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.apache.bcel.util.SyntheticRepository;
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.commons.lang3.Strings;
38  import org.junit.jupiter.api.Test;
39  import org.junit.jupiter.params.ParameterizedTest;
40  import org.junit.jupiter.params.provider.ValueSource;
41  
42  /**
43   * Tests {@code module-info.class} files.
44   */
45  class ConstantPoolModuleToStringTest {
46  
47      static class ToStringVisitor extends EmptyVisitor {
48  
49          private final StringBuilder stringBuilder = new StringBuilder();
50          private final ConstantPool pool;
51          private int count;
52  
53          ToStringVisitor(final ConstantPool pool) {
54              this.pool = pool;
55          }
56  
57          private void append(final Object obj) {
58              if (!(stringBuilder.length() == 0)) {
59                  stringBuilder.append('\n');
60              }
61              stringBuilder.append(obj);
62          }
63  
64          @Override
65          public String toString() {
66              return "ToStringVisitor [count=" + count + ", stringBuilder=" + stringBuilder + ", pool=" + pool + "]";
67          }
68  
69          @Override
70          public void visitAnnotation(final Annotations obj) {
71              super.visitAnnotation(obj);
72              append(obj);
73          }
74  
75          @Override
76          public void visitAnnotationDefault(final AnnotationDefault obj) {
77              super.visitAnnotationDefault(obj);
78              append(obj);
79          }
80  
81          @Override
82          public void visitAnnotationEntry(final AnnotationEntry obj) {
83              super.visitAnnotationEntry(obj);
84              append(obj);
85          }
86  
87          @Override
88          public void visitBootstrapMethods(final BootstrapMethods obj) {
89              super.visitBootstrapMethods(obj);
90              append(obj);
91          }
92  
93          @Override
94          public void visitCode(final Code obj) {
95              super.visitCode(obj);
96              append(obj.toString(true));
97          }
98  
99          @Override
100         public void visitCodeException(final CodeException obj) {
101             super.visitCodeException(obj);
102             append(obj.toString(pool, true));
103         }
104 
105         @Override
106         public void visitConstantClass(final ConstantClass obj) {
107             super.visitConstantClass(obj);
108             append(obj);
109         }
110 
111         @Override
112         public void visitConstantDouble(final ConstantDouble obj) {
113             super.visitConstantDouble(obj);
114             append(obj);
115         }
116 
117         @Override
118         public void visitConstantDynamic(final ConstantDynamic constantDynamic) {
119             super.visitConstantDynamic(constantDynamic);
120             append(constantDynamic);
121         }
122 
123         @Override
124         public void visitConstantFieldref(final ConstantFieldref obj) {
125             super.visitConstantFieldref(obj);
126             append(obj);
127         }
128 
129         @Override
130         public void visitConstantFloat(final ConstantFloat obj) {
131             super.visitConstantFloat(obj);
132             append(obj);
133         }
134 
135         @Override
136         public void visitConstantInteger(final ConstantInteger obj) {
137             super.visitConstantInteger(obj);
138             append(obj);
139         }
140 
141         @Override
142         public void visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj) {
143             super.visitConstantInterfaceMethodref(obj);
144             append(obj);
145         }
146 
147         @Override
148         public void visitConstantInvokeDynamic(final ConstantInvokeDynamic obj) {
149             super.visitConstantInvokeDynamic(obj);
150             append(obj);
151         }
152 
153         @Override
154         public void visitConstantLong(final ConstantLong obj) {
155             super.visitConstantLong(obj);
156             append(obj);
157         }
158 
159         @Override
160         public void visitConstantMethodHandle(final ConstantMethodHandle obj) {
161             super.visitConstantMethodHandle(obj);
162             append(obj);
163         }
164 
165         @Override
166         public void visitConstantMethodref(final ConstantMethodref obj) {
167             super.visitConstantMethodref(obj);
168             append(obj);
169         }
170 
171         @Override
172         public void visitConstantMethodType(final ConstantMethodType obj) {
173             super.visitConstantMethodType(obj);
174             append(obj);
175         }
176 
177         @Override
178         public void visitConstantModule(final ConstantModule constantModule) {
179             super.visitConstantModule(constantModule);
180             append(constantModule);
181         }
182 
183         @Override
184         public void visitConstantNameAndType(final ConstantNameAndType obj) {
185             super.visitConstantNameAndType(obj);
186             append(obj);
187         }
188 
189         @Override
190         public void visitConstantPackage(final ConstantPackage constantPackage) {
191             super.visitConstantPackage(constantPackage);
192             append(constantPackage);
193         }
194 
195         @Override
196         public void visitConstantPool(final ConstantPool obj) {
197             super.visitConstantPool(obj);
198             append(obj);
199         }
200 
201         @Override
202         public void visitConstantString(final ConstantString obj) {
203             super.visitConstantString(obj);
204             append(obj);
205         }
206 
207         @Override
208         public void visitConstantUtf8(final ConstantUtf8 obj) {
209             super.visitConstantUtf8(obj);
210             append(obj);
211         }
212 
213         @Override
214         public void visitConstantValue(final ConstantValue obj) {
215             super.visitConstantValue(obj);
216             append(obj);
217         }
218 
219         @Override
220         public void visitDeprecated(final Deprecated obj) {
221             super.visitDeprecated(obj);
222             append(obj);
223         }
224 
225         @Override
226         public void visitEnclosingMethod(final EnclosingMethod obj) {
227             super.visitEnclosingMethod(obj);
228             append(obj);
229         }
230 
231         @Override
232         public void visitExceptionTable(final ExceptionTable obj) {
233             super.visitExceptionTable(obj);
234             append(obj);
235         }
236 
237         @Override
238         public void visitField(final Field obj) {
239             super.visitField(obj);
240             append(obj);
241         }
242 
243         @Override
244         public void visitInnerClass(final InnerClass obj) {
245             super.visitInnerClass(obj);
246             append(obj.toString(pool));
247         }
248 
249         @Override
250         public void visitInnerClasses(final InnerClasses obj) {
251             super.visitInnerClasses(obj);
252             append(obj);
253         }
254 
255         @Override
256         public void visitJavaClass(final JavaClass obj) {
257             super.visitJavaClass(obj);
258             append(obj);
259         }
260 
261         @Override
262         public void visitLineNumber(final LineNumber obj) {
263             super.visitLineNumber(obj);
264             append(obj);
265         }
266 
267         @Override
268         public void visitLineNumberTable(final LineNumberTable obj) {
269             super.visitLineNumberTable(obj);
270             append(obj);
271         }
272 
273         @Override
274         public void visitLocalVariable(final LocalVariable obj) {
275             super.visitLocalVariable(obj);
276             append(obj);
277         }
278 
279         @Override
280         public void visitLocalVariableTable(final LocalVariableTable obj) {
281             super.visitLocalVariableTable(obj);
282             append(obj);
283         }
284 
285         @Override
286         public void visitLocalVariableTypeTable(final LocalVariableTypeTable obj) {
287             super.visitLocalVariableTypeTable(obj);
288             append(obj);
289         }
290 
291         @Override
292         public void visitMethod(final Method obj) {
293             super.visitMethod(obj);
294             append(obj);
295         }
296 
297         @Override
298         public void visitMethodParameter(final MethodParameter obj) {
299             super.visitMethodParameter(obj);
300             append(obj);
301         }
302 
303         @Override
304         public void visitMethodParameters(final MethodParameters obj) {
305             super.visitMethodParameters(obj);
306             append(obj);
307         }
308 
309         @Override
310         public void visitModule(final Module constantModule) {
311             super.visitModule(constantModule);
312             final String s = constantModule.toString();
313             final Matcher matcher = Pattern.compile("  (\\w+)([:(])").matcher(s);
314             while (matcher.find()) {
315                 switch (matcher.group(2)) {
316                     case ":":
317                         assertTrue(Strings.CS.containsAny(matcher.group(1), "name", "flags", "version"));
318                         break;
319                     case "(":
320                         assertTrue(Strings.CS.containsAny(matcher.group(1), "requires", "exports", "opens", "uses", "provides"));
321                         break;
322                     default:
323                         break;
324                 }
325             }
326             append(constantModule);
327         }
328 
329         @Override
330         public void visitModuleExports(final ModuleExports constantModule) {
331             super.visitModuleExports(constantModule);
332             append(constantModule);
333             final String s = constantModule.toString(pool);
334             final String[] tokens = s.split(", ");
335             assertNotNull(tokens);
336             assertEquals(3, tokens.length);
337             assertEquals("0000", tokens[1]);
338             final Matcher matcher = Pattern.compile("to\\((\\d+)\\):").matcher(tokens[2]);
339             assertTrue(matcher.find());
340             assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n'));
341         }
342 
343         @Override
344         public void visitModuleMainClass(final ModuleMainClass obj) {
345             super.visitModuleMainClass(obj);
346             append(obj);
347         }
348 
349         @Override
350         public void visitModuleOpens(final ModuleOpens constantModule) {
351             super.visitModuleOpens(constantModule);
352             append(constantModule);
353             final String s = constantModule.toString(pool);
354             final String[] tokens = s.split(", ");
355             assertNotNull(tokens);
356             assertEquals(3, tokens.length);
357             assertEquals("0000", tokens[1]);
358             final Matcher matcher = Pattern.compile("to\\((\\d+)\\):").matcher(tokens[2]);
359             assertTrue(matcher.find());
360             assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n'));
361         }
362 
363         @Override
364         public void visitModulePackages(final ModulePackages constantModule) {
365             super.visitModulePackages(constantModule);
366             append(constantModule);
367             final String s = constantModule.toString();
368             assertEquals(constantModule.getNumberOfPackages(), StringUtils.countMatches(s, '\n'));
369         }
370 
371         @Override
372         public void visitModuleProvides(final ModuleProvides constantModule) {
373             super.visitModuleProvides(constantModule);
374             append(constantModule);
375             final String s = constantModule.toString(pool);
376             final String[] tokens = s.split(", ");
377             assertNotNull(tokens);
378             assertEquals(2, tokens.length);
379             final Matcher matcher = Pattern.compile("with\\((\\d+)\\):").matcher(tokens[1]);
380             assertTrue(matcher.find());
381             assertEquals(Integer.parseInt(matcher.group(1)), StringUtils.countMatches(s, '\n'));
382             append(s);
383         }
384 
385         @Override
386         public void visitModuleRequires(final ModuleRequires constantModule) {
387             super.visitModuleRequires(constantModule);
388             append(constantModule);
389             append(constantModule.toString(pool));
390             final String s = constantModule.toString(pool).trim();
391             final boolean condition = Strings.CS.startsWithAny(s,
392                     "jdk.",
393                     "java.",
394                     "org.junit",
395                     "org.apiguardian.api",
396                     "org.opentest4j",
397                     "net.bytebuddy",
398                     "com.sun.jna",
399                     "junit",
400                     // com.azul.* for reproducibility with Azul JDK
401                     "com.azul.");
402             assertTrue(condition, s);
403         }
404 
405         @Override
406         public void visitNestHost(final NestHost obj) {
407             super.visitNestHost(obj);
408             append(obj);
409         }
410 
411         @Override
412         public void visitNestMembers(final NestMembers obj) {
413             super.visitNestMembers(obj);
414             append(obj);
415         }
416 
417         @Override
418         public void visitParameterAnnotation(final ParameterAnnotations obj) {
419             super.visitParameterAnnotation(obj);
420             append(obj);
421         }
422 
423         @Override
424         public void visitParameterAnnotationEntry(final ParameterAnnotationEntry obj) {
425             super.visitParameterAnnotationEntry(obj);
426             append(obj);
427         }
428 
429         @Override
430         public void visitSignature(final Signature obj) {
431             super.visitSignature(obj);
432             append(obj);
433         }
434 
435         @Override
436         public void visitSourceFile(final SourceFile obj) {
437             super.visitSourceFile(obj);
438             append(obj);
439         }
440 
441         @Override
442         public void visitStackMap(final StackMap obj) {
443             super.visitStackMap(obj);
444             append(obj);
445         }
446 
447         @Override
448         public void visitStackMapEntry(final StackMapEntry obj) {
449             super.visitStackMapEntry(obj);
450             append(obj);
451         }
452 
453         @Override
454         public void visitSynthetic(final Synthetic obj) {
455             super.visitSynthetic(obj);
456             append(obj);
457         }
458 
459         @Override
460         public void visitUnknown(final Unknown obj) {
461             super.visitUnknown(obj);
462             append(obj);
463         }
464     }
465 
466     private static void test(final InputStream inputStream) throws IOException {
467         final ClassParser classParser = new ClassParser(inputStream, "module-info.class");
468         final JavaClass javaClass = classParser.parse();
469         testJavaClass(javaClass);
470     }
471 
472     private static void testJavaClass(final JavaClass javaClass) {
473         final ConstantPool constantPool = javaClass.getConstantPool();
474         final ToStringVisitor visitor = new ToStringVisitor(constantPool);
475         final DescendingVisitor descendingVisitor = new DescendingVisitor(javaClass, visitor);
476         try {
477             javaClass.accept(descendingVisitor);
478             assertNotNull(visitor.toString());
479         } catch (Exception | Error e) {
480             fail(visitor.toString(), e);
481         }
482     }
483 
484     @Test
485     void test() throws Exception {
486         final Enumeration<URL> moduleURLs = getClass().getClassLoader().getResources("module-info.class");
487         while (moduleURLs.hasMoreElements()) {
488             final URL url = moduleURLs.nextElement();
489             try (InputStream inputStream = url.openStream()) {
490                 test(inputStream);
491             }
492         }
493     }
494 
495     @ParameterizedTest
496     @ValueSource(strings = {
497     // @formatter:off
498         "src/test/resources/jpms/java11/commons-io/module-info.class",
499         "src/test/resources/jpms/java17/commons-io/module-info.class",
500         "src/test/resources/jpms/java18/commons-io/module-info.class",
501         "src/test/resources/jpms/java19-ea/commons-io/module-info.class"})
502     // @formatter:on
503     void test(final String first) throws Exception {
504         try (InputStream inputStream = Files.newInputStream(Paths.get(first))) {
505             test(inputStream);
506         }
507     }
508 
509     @ParameterizedTest
510     @ValueSource(strings = {
511     // @formatter:off
512         "java.lang.CharSequence$1CharIterator",                 // contains attribute EnclosingMethod
513         "org.apache.commons.lang3.function.TriFunction",        // contains attributes BootstrapMethods, InnerClasses, LineNumberTable, LocalVariableTable,
514                                                                 // LocalVariableTypeTable, RuntimeVisibleAnnotations, Signature, SourceFile
515         "org.apache.commons.lang3.math.NumberUtils",            // contains attribute ConstantFloat, ConstantDouble
516         "org.apache.bcel.Const",                                // contains attributes MethodParameters
517         "java.io.StringBufferInputStream",                      // contains attributes Deprecated, StackMap
518         "java.nio.file.Files",                                  // contains attributes ConstantValue, ExceptionTable, NestMembers
519         "org.junit.jupiter.api.AssertionsKt",                   // contains attribute ParameterAnnotation
520         "javax.annotation.ManagedBean",                         // contains attribute AnnotationDefault
521         "javax.management.remote.rmi.RMIConnectionImpl_Stub"})  // contains attribute Synthetic
522     // @formatter:on
523     void testClass(final String className) throws Exception {
524         testJavaClass(SyntheticRepository.getInstance().loadClass(className));
525     }
526 }