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 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 }