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   *   http://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.maven.shared.jar.classes;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.apache.bcel.classfile.ConstantClass;
27  import org.apache.bcel.classfile.ConstantUtf8;
28  import org.apache.bcel.classfile.EmptyVisitor;
29  import org.apache.bcel.classfile.JavaClass;
30  import org.apache.commons.collections4.list.SetUniqueList;
31  
32  /**
33   * Implementation of a BCEL class visitor that analyzes a class and collects imports.
34   */
35  public class ImportVisitor extends EmptyVisitor {
36      /**
37       * The list of imports discovered.
38       */
39      private final List<String> imports;
40  
41      /**
42       * The Java class that is being analyzed.
43       */
44      private final JavaClass javaClass;
45  
46      /**
47       * Pattern to detect if the import is qualified and allows retrieval of the actual import name from the string via
48       * the group 1.
49       */
50      private static final Pattern QUALIFIED_IMPORT_PATTERN = Pattern.compile("L([a-zA-Z][a-zA-Z0-9\\.]+);");
51  
52      /**
53       * Pattern that checks whether a string is valid UTF-8. Imports that are not are ignored.
54       */
55      private static final Pattern VALID_UTF8_PATTERN = Pattern.compile("^[\\(\\)\\[A-Za-z0-9;/]+$");
56  
57      /**
58       * Create an Import visitor.
59       *
60       * @param javaClass the javaclass to work from
61       */
62      public ImportVisitor(JavaClass javaClass) {
63          this.javaClass = javaClass;
64  
65          // Create a list that is guaranteed to be unique while retaining it's list qualities (LinkedHashSet does not
66          // expose the list interface even if natural ordering is retained)
67          this.imports = SetUniqueList.setUniqueList(new ArrayList<>());
68      }
69  
70      /**
71       * Get the list of discovered imports.
72       *
73       * @return Returns the imports.
74       */
75      public List<String> getImports() {
76          return imports;
77      }
78  
79      /**
80       * Find any formally declared import in the Constant Pool.
81       *
82       * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantClass(org.apache.bcel.classfile.ConstantClass)
83       */
84      @Override
85      public void visitConstantClass(ConstantClass constantClass) {
86          String name = constantClass.getBytes(javaClass.getConstantPool());
87  
88          // only strings with '/' character are to be considered.
89          if (name.indexOf('/') == -1) {
90              return;
91          }
92  
93          name = name.replace('/', '.');
94  
95          if (name.endsWith(".class")) {
96              name = name.substring(0, name.length() - 6);
97          }
98  
99          Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher(name);
100         if (mat.find()) {
101             this.imports.add(mat.group(1));
102         } else {
103             this.imports.add(name);
104         }
105     }
106 
107     /**
108      * Find any package class Strings in the UTF8 String Pool.
109      *
110      * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantUtf8(org.apache.bcel.classfile.ConstantUtf8)
111      */
112     @Override
113     public void visitConstantUtf8(ConstantUtf8 constantUtf8) {
114         String ret = constantUtf8.getBytes().trim();
115 
116         // empty strings are not class names.
117         if (ret.length() <= 0) {
118             return;
119         }
120 
121         // Only valid characters please.
122         if (!VALID_UTF8_PATTERN.matcher(ret).matches()) {
123             return;
124         }
125 
126         // only strings with '/' character are to be considered.
127         if (ret.indexOf('/') == -1) {
128             return;
129         }
130 
131         // Strings that start with '/' are bad too
132         // Seen when Pool has regex patterns.
133         if (ret.charAt(0) == '/') {
134             return;
135         }
136 
137         // Make string more class-like.
138         ret = ret.replace('/', '.');
139 
140         // Double ".." indicates a bad class fail-fast.
141         // Seen when ConstantUTF8 Pool has regex patterns.
142         if (ret.contains("..")) {
143             return;
144         }
145 
146         Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher(ret);
147         char prefix = ret.charAt(0);
148 
149         if (prefix == '(') {
150             // A Method Declaration.
151 
152             // Loop for each Qualified Class found.
153             while (mat.find()) {
154                 this.imports.add(mat.group(1));
155             }
156         } else {
157             // A Variable Declaration.
158             if (mat.find()) {
159                 // Add a UTF8 Qualified Class reference.
160                 this.imports.add(mat.group(1));
161             } else {
162                 // Add a simple Class reference.
163                 this.imports.add(ret);
164             }
165         }
166     }
167 }