1 package org.apache.maven.shared.jar.classes;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import org.apache.bcel.classfile.ConstantClass;
23 import org.apache.bcel.classfile.ConstantUtf8;
24 import org.apache.bcel.classfile.EmptyVisitor;
25 import org.apache.bcel.classfile.JavaClass;
26 import org.apache.commons.collections.list.SetUniqueList;
27
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 /**
34 * Implementation of a BCEL class visitor that analyzes a class and collects imports.
35 */
36 public class ImportVisitor
37 extends EmptyVisitor
38 {
39 /**
40 * The list of imports discovered.
41 */
42 private List imports;
43
44 /**
45 * The Java class that is being analyzed.
46 */
47 private JavaClass javaClass;
48
49 /**
50 * Pattern to detect if the import is qualified and allows retrieval of the actual import name from the string via the group 1.
51 */
52 private static final Pattern QUALIFIED_IMPORT_PATTERN = Pattern.compile( "L([a-zA-Z][a-zA-Z0-9\\.]+);" );
53
54 /**
55 * Pattern that checks whether a string is valid UTF-8. Imports that are not are ignored.
56 */
57 private static final Pattern VALID_UTF8_PATTERN = Pattern.compile( "^[\\(\\)\\[A-Za-z0-9;/]+$" );
58
59 /**
60 * Create an Import visitor.
61 *
62 * @param javaClass the javaclass to work from
63 */
64 public ImportVisitor( JavaClass javaClass )
65 {
66 this.javaClass = javaClass;
67
68 // Create a list that is guaranteed to be unique while retaining it's list qualities (LinkedHashSet does not
69 // expose the list interface even if natural ordering is retained)
70 this.imports = SetUniqueList.decorate( new ArrayList() );
71 }
72
73 /**
74 * Get the list of discovered imports.
75 *
76 * @return Returns the imports.
77 */
78 public List getImports()
79 {
80 return imports;
81 }
82
83 /**
84 * Find any formally declared import in the Constant Pool.
85 *
86 * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantClass(org.apache.bcel.classfile.ConstantClass)
87 */
88 public void visitConstantClass( ConstantClass constantClass )
89 {
90 String name = constantClass.getBytes( javaClass.getConstantPool() );
91
92 // only strings with '/' character are to be considered.
93 if ( name.indexOf( '/' ) == -1 )
94 {
95 return;
96 }
97
98 name = name.replace( '/', '.' );
99
100 if ( name.endsWith( ".class" ) )
101 {
102 name = name.substring( 0, name.length() - 6 );
103 }
104
105 Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( name );
106 if ( mat.find() )
107 {
108 this.imports.add( mat.group( 1 ) );
109 }
110 else
111 {
112 this.imports.add( name );
113 }
114 }
115
116 /**
117 * Find any package class Strings in the UTF8 String Pool.
118 *
119 * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantUtf8(org.apache.bcel.classfile.ConstantUtf8)
120 */
121 public void visitConstantUtf8( ConstantUtf8 constantUtf8 )
122 {
123 String ret = constantUtf8.getBytes().trim();
124
125 // empty strings are not class names.
126 if ( ret.length() <= 0 )
127 {
128 return;
129 }
130
131 // Only valid characters please.
132 if ( !VALID_UTF8_PATTERN.matcher( ret ).matches() )
133 {
134 return;
135 }
136
137 // only strings with '/' character are to be considered.
138 if ( ret.indexOf( '/' ) == -1 )
139 {
140 return;
141 }
142
143 // Strings that start with '/' are bad too
144 // Seen when Pool has regex patterns.
145 if ( ret.charAt( 0 ) == '/' )
146 {
147 return;
148 }
149
150 // Make string more class-like.
151 ret = ret.replace( '/', '.' );
152
153 // Double ".." indicates a bad class fail-fast.
154 // Seen when ConstantUTF8 Pool has regex patterns.
155 if ( ret.indexOf( ".." ) != -1 )
156 {
157 return;
158 }
159
160 Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( ret );
161 char prefix = ret.charAt( 0 );
162
163 if ( prefix == '(' )
164 {
165 // A Method Declaration.
166
167 // Loop for each Qualified Class found.
168 while ( mat.find() )
169 {
170 this.imports.add( mat.group( 1 ) );
171 }
172 }
173 else
174 {
175 // A Variable Declaration.
176 if ( mat.find() )
177 {
178 // Add a UTF8 Qualified Class reference.
179 this.imports.add( mat.group( 1 ) );
180 }
181 else
182 {
183 // Add a simple Class reference.
184 this.imports.add( ret );
185 }
186 }
187 }
188 }