KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > classycle > Parser


1 /*
2  * Copyright (c) 2003-2006, Franz-Josef Elmer, All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * - Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  * - Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
15  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */

25 package classycle;
26
27 import java.io.DataInputStream JavaDoc;
28 import java.io.File JavaDoc;
29 import java.io.FileInputStream JavaDoc;
30 import java.io.IOException JavaDoc;
31 import java.io.InputStream JavaDoc;
32 import java.util.ArrayList JavaDoc;
33 import java.util.Arrays JavaDoc;
34 import java.util.Enumeration JavaDoc;
35 import java.util.HashMap JavaDoc;
36 import java.util.Iterator JavaDoc;
37 import java.util.List JavaDoc;
38 import java.util.Map JavaDoc;
39 import java.util.Set JavaDoc;
40 import java.util.zip.ZipEntry JavaDoc;
41 import java.util.zip.ZipFile JavaDoc;
42
43 import classycle.classfile.ClassConstant;
44 import classycle.classfile.Constant;
45 import classycle.classfile.StringConstant;
46 import classycle.classfile.UTF8Constant;
47 import classycle.graph.AtomicVertex;
48 import classycle.util.StringPattern;
49 import classycle.util.TrueStringPattern;
50
51 /**
52  * Utility methods for parsing class files and creating directed graphs.
53  * The nodes of the graph are classes. The initial vertex of an edge is the
54  * class which uses the class specified by the terminal vertex.
55  *
56  * @author Franz-Josef Elmer
57  */

58 public class Parser
59 {
60   private static final int ACC_INTERFACE = 0x200, ACC_ABSTRACT = 0x400;
61   private static final String JavaDoc[] ZIP_FILE_TYPES
62       = new String JavaDoc[] {".zip", ".jar", ".war", ".ear"};
63   
64   private static class UnresolvedNode implements Comparable JavaDoc
65   {
66     ClassAttributes attributes;
67     ArrayList JavaDoc nodes = new ArrayList JavaDoc();
68
69     public UnresolvedNode() {}
70
71     public int compareTo(Object JavaDoc obj)
72     {
73       return attributes.getName().compareTo(
74                 ((UnresolvedNode) obj).attributes.getName());
75     }
76     
77     public boolean isMatchedBy(StringPattern pattern)
78     {
79       return pattern.matches(attributes.getName());
80     }
81   }
82
83   /** Private constructor to prohibit instanciation. */
84   private Parser() {
85   }
86   
87   /**
88    * Reads and parses class files and creates a direct graph. Short-cut of
89    * <tt>readClassFiles(classFiles, new {@link TrueStringPattern}(),
90    * null, false);</tt>
91    */

92   public static AtomicVertex[] readClassFiles(String JavaDoc[] classFiles)
93                                throws IOException JavaDoc
94   {
95     return readClassFiles(classFiles, new TrueStringPattern(), null, false);
96   }
97
98   /**
99    * Reads the specified class files and creates a directed graph where each
100    * vertex represents a class. The head vertex of an arc is a class which is
101    * used by the tail vertex of the arc.
102    * The elements of <tt>classFiles</tt> are file names (relative to the
103    * working directory) which are interpreted depending on its file type as
104    * <ul>
105    * <li>name of a class file (file type <tt>.class</tt>)
106    * <li>name of a folder containing class files
107    * <li>name of a file of type <code>.zip</code>, <code>.jar</code>,
108    * <code>.war</code>, or <code>.ear</code>
109    * containing class file
110    * </ul>
111    * Folders and zip/jar/war/ear files are searched recursively
112    * for class files.
113    * @param classFiles Array of file names.
114    * @param pattern Pattern fully qualified class names have to match in order
115    * to be added to the graph. Otherwise they count as
116    * 'external'.
117    * @param reflectionPattern Pattern ordinary string constants of a class
118    * file have to fullfill in order to be handled as
119    * class references. In addition they have to be
120    * syntactically valid fully qualified class names. If
121    * <tt>null</tt> ordinary string constants will not be
122    * checked.
123    * @param mergeInnerClasses If <code>true</code>
124    * merge inner classes with its outer class
125    * @return directed graph.
126    */

127   public static AtomicVertex[] readClassFiles(String JavaDoc[] classFiles,
128                                               StringPattern pattern,
129                                               StringPattern reflectionPattern,
130                                               boolean mergeInnerClasses)
131                                throws IOException JavaDoc
132   {
133     ArrayList JavaDoc unresolvedNodes = new ArrayList JavaDoc();
134     for (int i = 0; i < classFiles.length; i++)
135     {
136       File JavaDoc file = new File JavaDoc(classFiles[i]);
137       if (file.isDirectory() || file.getName().endsWith(".class"))
138       {
139         analyseClassFile(file, unresolvedNodes, reflectionPattern);
140       } else if (isZipFile(file))
141       {
142         analyseClassFiles(new ZipFile JavaDoc(file.getAbsoluteFile()),
143                           unresolvedNodes,
144                           reflectionPattern);
145       } else
146       {
147         throw new IOException JavaDoc(classFiles[i] + " is an invalid file.");
148       }
149     }
150     ArrayList JavaDoc filteredNodes = new ArrayList JavaDoc();
151     for (int i = 0, n = unresolvedNodes.size(); i < n; i++)
152     {
153       UnresolvedNode node = (UnresolvedNode) unresolvedNodes.get(i);
154       if (node.isMatchedBy(pattern))
155       {
156         filteredNodes.add(node);
157       }
158     }
159     UnresolvedNode[] nodes = new UnresolvedNode[filteredNodes.size()];
160     nodes = (UnresolvedNode[]) filteredNodes.toArray(nodes);
161     Arrays.sort(nodes);
162     return createGraph(nodes, mergeInnerClasses);
163   }
164
165   private static boolean isZipFile(File JavaDoc file)
166   {
167     boolean result = false;
168     String JavaDoc name = file.getName();
169     for (int i = 0; i < ZIP_FILE_TYPES.length; i++)
170     {
171       if (name.endsWith(ZIP_FILE_TYPES[i]))
172       {
173         result = true;
174         break;
175       }
176     }
177     return result;
178   }
179
180   private static void analyseClassFile(File JavaDoc file, ArrayList JavaDoc unresolvedNodes,
181                                        StringPattern reflectionPattern)
182                       throws IOException JavaDoc
183   {
184     if (file.isDirectory())
185     {
186       String JavaDoc[] files = file.list();
187       for (int i = 0; i < files.length; i++)
188       {
189         File JavaDoc child = new File JavaDoc(file, files[i]);
190         if (child.isDirectory() || files[i].endsWith(".class"))
191         {
192           analyseClassFile(child, unresolvedNodes, reflectionPattern);
193         }
194       }
195     } else
196     {
197       unresolvedNodes.add(extractNode(file, reflectionPattern));
198     }
199   }
200
201   private static UnresolvedNode extractNode(File JavaDoc file,
202                                             StringPattern reflectionPattern)
203                                 throws IOException JavaDoc
204   {
205     InputStream JavaDoc stream = null;
206     UnresolvedNode result = null;
207     try
208     {
209       stream = new FileInputStream JavaDoc(file);
210       result = Parser.createNode(stream, (int) file.length(),
211                                  reflectionPattern);
212     } finally
213     {
214       try
215       {
216         stream.close();
217       } catch (IOException JavaDoc e) {}
218     }
219     return result;
220   }
221
222   private static void analyseClassFiles(ZipFile JavaDoc zipFile,
223                                         ArrayList JavaDoc unresolvedNodes,
224                                         StringPattern reflectionPattern)
225                       throws IOException JavaDoc
226   {
227     Enumeration JavaDoc entries = zipFile.entries();
228     while (entries.hasMoreElements())
229     {
230       ZipEntry JavaDoc entry = (ZipEntry JavaDoc) entries.nextElement();
231       if (!entry.isDirectory() && entry.getName().endsWith(".class"))
232       {
233         InputStream JavaDoc stream = zipFile.getInputStream(entry);
234         unresolvedNodes.add(Parser.createNode(stream, (int) entry.getSize(),
235                                               reflectionPattern));
236       }
237     }
238   }
239
240   /**
241    * Creates a new node with unresolved references.
242    * @param stream A just opended byte stream of a class file.
243    * If this method finishes succefully the internal pointer of the
244    * stream will point onto the superclass index.
245    * @param size Number of bytes of the class file.
246    * @param reflectionPattern Pattern used to check whether a
247    * {@link StringConstant} refer to a class. Can be <tt>null</tt>.
248    * @return a node with unresolved link of all classes used by the analysed
249    * class.
250    */

251   private static UnresolvedNode createNode(InputStream JavaDoc stream, int size,
252                                            StringPattern reflectionPattern)
253                                 throws IOException JavaDoc
254   {
255     // Reads constant pool, accessFlags, and class name
256
DataInputStream JavaDoc dataStream = new DataInputStream JavaDoc(stream);
257     Constant[] pool = Constant.extractConstantPool(dataStream);
258     int accessFlags = dataStream.readUnsignedShort();
259     String JavaDoc name =
260         ((ClassConstant) pool[dataStream.readUnsignedShort()]).getName();
261     ClassAttributes attributes = null;
262     if ((accessFlags & ACC_INTERFACE) != 0)
263     {
264       attributes = ClassAttributes.createInterface(name, size);
265     } else
266     {
267       if ((accessFlags & ACC_ABSTRACT) != 0)
268       {
269         attributes = ClassAttributes.createAbstractClass(name, size);
270       } else
271       {
272         attributes = ClassAttributes.createClass(name, size);
273       }
274     }
275
276     // Creates a new node with unresolved references
277
UnresolvedNode node = new UnresolvedNode();
278     node.attributes = attributes;
279     for (int i = 0; i < pool.length; i++)
280     {
281       Constant constant = pool[i];
282       if (constant instanceof ClassConstant)
283       {
284         ClassConstant cc = (ClassConstant) constant;
285         if (!cc.getName().startsWith(("[")) && !cc.getName().equals(name))
286         {
287           node.nodes.add(cc.getName());
288         }
289       } else if (constant instanceof UTF8Constant)
290       {
291         parseUTF8Constant((UTF8Constant) constant, node.nodes, name);
292       } else if (reflectionPattern != null
293                  && constant instanceof StringConstant)
294       {
295         String JavaDoc str = ((StringConstant) constant).getString();
296         if (ClassNameExtractor.isValid(str) && reflectionPattern.matches(str))
297         {
298           node.nodes.add(str);
299         }
300       }
301     }
302     return node;
303   }
304
305   /**
306    * Parses an UFT8Constant and picks class names if it has the correct syntax
307    * of a field or method descirptor.
308    */

309   static void parseUTF8Constant(UTF8Constant constant, List JavaDoc nodes,
310                                 String JavaDoc className)
311   {
312     Set JavaDoc classNames = new ClassNameExtractor(constant).extract();
313     for (Iterator JavaDoc iter = classNames.iterator(); iter.hasNext();)
314     {
315       String JavaDoc element = (String JavaDoc) iter.next();
316       if (className.equals(element) == false)
317       {
318         nodes.add(element);
319       }
320     }
321   }
322
323   /**
324    * Creates a graph from the bunch of unresolved nodes.
325    * @param unresolvedNodes All nodes with unresolved references.
326    * @param mergeInnerClasses TODO
327    * @return an array of length <tt>unresolvedNodes.size()</tt> with all
328    * unresolved nodes transformed into <tt>Node</tt> objects
329    * with appropriated links. External nodes are created and linked
330    * but not added to the result array.
331    */

332   private static AtomicVertex[] createGraph(UnresolvedNode[] unresolvedNodes,
333                                             boolean mergeInnerClasses)
334   {
335     Map JavaDoc vertices = createVertices(unresolvedNodes, mergeInnerClasses);
336     AtomicVertex[] result
337         = (AtomicVertex[]) vertices.values().toArray(new AtomicVertex[0]);
338     
339     // Add arces to vertices
340
for (int i = 0; i < unresolvedNodes.length; i++)
341     {
342       UnresolvedNode node = unresolvedNodes[i];
343       String JavaDoc name = normalize(node.attributes.getName(), mergeInnerClasses);
344       AtomicVertex vertex = (AtomicVertex) vertices.get(name);
345       for (int j = 0, m = node.nodes.size(); j < m; j++)
346       {
347         name = normalize((String JavaDoc) node.nodes.get(j), mergeInnerClasses);
348         AtomicVertex head = (AtomicVertex) vertices.get(name);
349         if (head == null)
350         {
351           // external node created and added to the map but not to the result
352
head = new AtomicVertex(ClassAttributes.createUnknownClass(name, 0));
353           vertices.put(name, head);
354         }
355         if (vertex != head)
356         {
357           vertex.addOutgoingArcTo(head);
358         }
359       }
360     }
361     
362     return result;
363   }
364
365   private static Map JavaDoc createVertices(UnresolvedNode[] unresolvedNodes,
366                                     boolean mergeInnerClasses)
367   {
368     Map JavaDoc vertices = new HashMap JavaDoc();
369     for (int i = 0; i < unresolvedNodes.length; i++)
370     {
371       ClassAttributes attributes = unresolvedNodes[i].attributes;
372       String JavaDoc type = attributes.getType();
373       String JavaDoc originalName = attributes.getName();
374       int size = attributes.getSize();
375       String JavaDoc name = normalize(originalName, mergeInnerClasses);
376       AtomicVertex vertex = (AtomicVertex) vertices.get(name);
377       if (vertex != null)
378       {
379         ClassAttributes vertexAttributes
380                               = (ClassAttributes) vertex.getAttributes();
381         size += vertexAttributes.getSize();
382         if (name.equals(originalName) == false)
383         {
384           type = vertexAttributes.getType();
385         }
386       }
387       attributes = new ClassAttributes(name, type, size);
388       vertex = new AtomicVertex(attributes);
389       vertices.put(name, vertex);
390     }
391     return vertices;
392   }
393
394   private static String JavaDoc normalize(String JavaDoc name, boolean mergeInnerClasses)
395   {
396     if (mergeInnerClasses)
397     {
398       int index = name.indexOf('$');
399       if (index >= 0)
400       {
401         name = name.substring(0, index);
402       }
403     }
404     return name;
405   }
406
407 } //class
Popular Tags