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 }