View Javadoc
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
51       * the group 1.
52       */
53      private static final Pattern QUALIFIED_IMPORT_PATTERN = Pattern.compile( "L([a-zA-Z][a-zA-Z0-9\\.]+);" );
54  
55      /**
56       * Pattern that checks whether a string is valid UTF-8. Imports that are not are ignored.
57       */
58      private static final Pattern VALID_UTF8_PATTERN = Pattern.compile( "^[\\(\\)\\[A-Za-z0-9;/]+$" );
59  
60      /**
61       * Create an Import visitor.
62       *
63       * @param javaClass the javaclass to work from
64       */
65      public ImportVisitor( JavaClass javaClass )
66      {
67          this.javaClass = javaClass;
68  
69          // Create a list that is guaranteed to be unique while retaining it's list qualities (LinkedHashSet does not
70          // expose the list interface even if natural ordering is retained)  
71          this.imports = SetUniqueList.decorate( new ArrayList() );
72      }
73  
74      /**
75       * Get the list of discovered imports.
76       *
77       * @return Returns the imports.
78       */
79      public List getImports()
80      {
81          return imports;
82      }
83  
84      /**
85       * Find any formally declared import in the Constant Pool.
86       *
87       * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantClass(org.apache.bcel.classfile.ConstantClass)
88       */
89      public void visitConstantClass( ConstantClass constantClass )
90      {
91          String name = constantClass.getBytes( javaClass.getConstantPool() );
92  
93          // only strings with '/' character are to be considered.
94          if ( name.indexOf( '/' ) == -1 )
95          {
96              return;
97          }
98  
99          name = name.replace( '/', '.' );
100 
101         if ( name.endsWith( ".class" ) )
102         {
103             name = name.substring( 0, name.length() - 6 );
104         }
105 
106         Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( name );
107         if ( mat.find() )
108         {
109             this.imports.add( mat.group( 1 ) );
110         }
111         else
112         {
113             this.imports.add( name );
114         }
115     }
116 
117     /**
118      * Find any package class Strings in the UTF8 String Pool.
119      *
120      * @see org.apache.bcel.classfile.EmptyVisitor#visitConstantUtf8(org.apache.bcel.classfile.ConstantUtf8)
121      */
122     public void visitConstantUtf8( ConstantUtf8 constantUtf8 )
123     {
124         String ret = constantUtf8.getBytes().trim();
125 
126         // empty strings are not class names.
127         if ( ret.length() <= 0 )
128         {
129             return;
130         }
131 
132         // Only valid characters please.
133         if ( !VALID_UTF8_PATTERN.matcher( ret ).matches() )
134         {
135             return;
136         }
137 
138         // only strings with '/' character are to be considered.
139         if ( ret.indexOf( '/' ) == -1 )
140         {
141             return;
142         }
143 
144         // Strings that start with '/' are bad too
145         // Seen when Pool has regex patterns.
146         if ( ret.charAt( 0 ) == '/' )
147         {
148             return;
149         }
150 
151         // Make string more class-like.
152         ret = ret.replace( '/', '.' );
153 
154         // Double ".." indicates a bad class fail-fast.
155         // Seen when ConstantUTF8 Pool has regex patterns.
156         if ( ret.indexOf( ".." ) != -1 )
157         {
158             return;
159         }
160 
161         Matcher mat = QUALIFIED_IMPORT_PATTERN.matcher( ret );
162         char prefix = ret.charAt( 0 );
163 
164         if ( prefix == '(' )
165         {
166             // A Method Declaration.
167 
168             // Loop for each Qualified Class found.
169             while ( mat.find() )
170             {
171                 this.imports.add( mat.group( 1 ) );
172             }
173         }
174         else
175         {
176             // A Variable Declaration.
177             if ( mat.find() )
178             {
179                 // Add a UTF8 Qualified Class reference.
180                 this.imports.add( mat.group( 1 ) );
181             }
182             else
183             {
184                 // Add a simple Class reference.
185                 this.imports.add( ret );
186             }
187         }
188     }
189 }