1 /***
2 * Analyzer.java
3 *
4 * Project: Dependency Tool
5 *
6 * WHEN WHO WHAT
7 * 06.06.2003 pko initial public release
8 * 22.01.2002 ctr modification
9 * 08.01.2002 ctr creation
10 *
11 * Copyright 2003 ELCA Informatique SA
12 * Av. de la Harpe 22-24, 1000 Lausanne 13, Switzerland
13 * www.elca.ch
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public License
17 * as published by the Free Software Foundation; either version 2.1 of
18 * the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
28 * USA
29 */
30
31 package ch.elca.dependency.core;
32
33 import org.apache.log4j.Logger;
34 import org.apache.regexp.RE;
35 import org.apache.regexp.RESyntaxException;
36
37 import java.io.*;
38 import java.util.*;
39
40 import ch.elca.dependency.core.classinfo.ClassInfo;
41 import ch.elca.dependency.core.classinfo.Reader;
42 import ch.elca.dependency.exception.AnalyseException;
43 import ch.elca.dependency.exception.ClassInfoException;
44 import ch.elca.dependency.exception.InvalidClassFileException;
45 import ch.elca.dependency.util.IOManager;
46 import ch.elca.dependency.util.UnZip;
47
48 /***
49 * This class is the main responsible one for parsing the whole class structure
50 * in the directory tree. It uses the <code>Reader</code> class for reading the
51 * class files contained by the class structure.
52 * Every file in the directory structure is parsed recursively if it is a java
53 * class file. This is determined by looking at the file extension of the
54 * corresponding file. If a file has no file extension the type cannot be
55 * determined and so it will not be parsed as a java class file.
56 * Because sometimes in a large project there are many files with partially no
57 * file extension, there are two possibilities for handling this case.<p>
58 *
59 * The files with no file extension can be skipped and are not included in the
60 * analyse. Or the whole analyse can be canceled with a corresponding message
61 * to the user.
62 * This behaviour is specified by the third argument (boolean) in
63 * the constructor of this class. If the argument is true, file extensions are
64 * required and if there is a file without any, the application is stopped. If
65 * this argument is false all the files with no file extension are just skipped
66 * and the analyse is performed whithout these files.
67 *
68 * @see ch.elca.dependency.core.classinfo.Reader
69 * @see ch.elca.dependency.core.RawModel
70 * @see ch.elca.dependency.core.Filter
71 *
72 * @author Christoph Trutmann
73 * @version 1.0-beta
74 */
75 public class Analyzer {
76
77 /***
78 * Log4j Logger
79 */
80 private static final Logger LOG = Logger.getLogger(Analyzer.class);
81
82 /***
83 * To read the directory structure
84 */
85 private Reader m_reader;
86
87 /***
88 * <code>File</code> that points on the root of the directory
89 * structure with the java .class files.
90 */
91 private File m_root;
92
93 /***
94 * Load filter that specifies which packages are excluded at the
95 * beginning.
96 */
97 private Filter m_filter;
98
99 /***
100 * <code>HashMap</code> containing all the dependencies got from
101 * the ananlyse.
102 */
103 private HashMap m_dependencies = new HashMap();
104
105 /***
106 * HashMap containing all the <code>Classinfo</code> objects of
107 * the analyzed project. They have all passed the load filter. The
108 * key of the map is the package name (String).
109 */
110 private HashMap m_classInfos = new HashMap();
111
112 /***
113 * Relations classes / packages of all the artifacts of the project.
114 */
115 private HashMap m_projClassPackageRel = new HashMap();
116
117 /***
118 * Relations classes / packages of all the artifacts which were analyzed.
119 */
120 private HashMap m_analyzedClassPackageRel = new HashMap();
121
122 /***
123 * Statistic object containing all the statistical data gained from the
124 * analyse.
125 */
126 private Statistic m_statistic;
127
128 /***
129 * Variables for holding the statistical values during the analyse.
130 */
131 private int m_projClass, m_projPackage, m_analyzedClass, m_usesDep = 0;
132
133 /***
134 * This flag specifies whether the analyse was performed at least one time.
135 */
136 private boolean m_analysed = false;
137
138 /***
139 * Contains all the jars which were unpacked and analyzed.
140 */
141 private ArrayList m_analysedJars = new ArrayList();
142
143 /***
144 * Helper class for unpacking jar and zip files.
145 */
146 private UnZip m_unzipper = new UnZip();
147
148 /***
149 * Creates a new <code>Analyzer</code> instance.
150 *
151 * @param root a <code>File</code> value
152 * @param filter a <code>Filter</code> value
153 * @exception AnalyseException if an error occurs
154 */
155 public Analyzer(File root, Filter filter) throws AnalyseException {
156
157 if( root == null ) {
158 throw new AnalyseException("There is no root specified.");
159 }
160 m_root = root;
161
162 try {
163 LOG.info("Root File to be analyzed: " + m_root.getCanonicalPath().toString());
164 } catch (Exception e) {
165 ;
166 }
167
168
169 if ( filter == null) {
170 throw new AnalyseException("There is no filter specified.");
171 }
172 m_filter = filter;
173
174 LOG.debug("Filtering with filter: " + m_filter.getValue());
175
176 m_reader = new Reader();
177 m_statistic = new Statistic();
178 }
179
180 /***
181 * Performs the analyse on the specified directory root in the constructor.
182 */
183 public void analyse() throws AnalyseException {
184 m_analysed = true; // shows that the analyse is performed
185
186 try{
187 analyze(m_root);
188 } catch ( AnalyseException ae ) {
189 throw ae;
190 } catch ( Exception e ) {
191 e.printStackTrace();
192 throw new AnalyseException(e);
193 }
194 }
195
196 /***
197 * Gets the dependencies encountered by the analyse.
198 *
199 * @return A <code>HashTable</code> containing all the packages
200 * dependencies.
201 * @thorws AnalyseException If the 'analyse()' method was not called
202 * before.
203 */
204 public HashMap getDependencies() throws AnalyseException {
205 if ( ! m_analysed ) {
206 throw new AnalyseException("The 'analyse()' method must be called before!");
207 }
208 if ( m_dependencies.isEmpty() ) {
209 throw new AnalyseException("There is no .class file in the spec. directory structure.\n"
210 + "Specify the right \"root\" as argument.");
211 } else {
212 return m_dependencies;
213 }
214 }
215
216
217 /***
218 * Gets all the <code>ClassInfo</code> objects which have passed the load
219 * filter.
220 *
221 * @return List containing all the <code>ClassInfo</code> objects of this
222 * project which have passed the load filter.
223 */
224 public HashMap getClassInfos() {
225 return m_classInfos;
226 }
227
228
229 /***
230 * Gets the Statistic object containing all the statistical data gained from
231 * the analyse.
232 *
233 * @return Statistic object contining all the statistcal data gained from
234 * the analyse.
235 * @exception AnalyseException If the 'analyse()' method was not called
236 * before.
237 */
238 public Statistic getStatistics() throws AnalyseException {
239 if ( ! m_analysed ) {
240 throw new AnalyseException(
241 "The 'analyse()' method must be called before!");
242 }
243 evaluateClassPackageRelations();
244 return m_statistic;
245 }
246
247
248 /***
249 * Helpermethod for performing the analyse of the directory structure.
250 */
251 private void analyze(File file)
252 throws FileNotFoundException, IOException, InvalidClassFileException,
253 RESyntaxException, ClassInfoException, AnalyseException {
254
255 // it is a directory
256 //
257 if ( file.isDirectory() ) {
258 File[] fileArr = file.listFiles();
259 int elems = fileArr.length, i=0;
260 boolean firstFileInDir = true, skipDir = false;
261 String fileName = file.getName();
262
263 // if there is an extracted jar yet
264 //
265 if (file.equals(IOManager.getTempDir())) {
266
267 // directory with the extracted content is included yet
268 //
269 if ( m_analysedJars.contains(fileName) ) {
270 skipDir = true;
271 } else {
272
273 // mark the directory with the extracted content
274 //
275 m_analysedJars.add(fileName);
276 }
277
278 // mark the jar already extracted
279 //
280 // m_analysedJars.add(fileName.substring(10, fileName.length()));
281 }
282
283 // recursive calls for each abstract file in the current directory
284 //
285 while ( (elems > 0 && i<elems) && ! skipDir) {
286
287 // inquire whether it is a package
288 //
289 if ( isClassFile(fileArr[i]) && firstFileInDir ) {
290 m_projPackage++;
291 firstFileInDir = false;
292 }
293 analyze(fileArr[i]);
294 i++;
295 }
296 }
297
298 // it is a file
299 //
300 else {
301
302 // test if the file is a class file
303
304 //
305 // do not create so many readers
306 // one should be enough
307 //
308
309 if ( isClassFile(file) ) {
310 m_projClass++; // increment total class counter
311 FileInputStream fi = new FileInputStream(file);
312 Reader reader = new Reader();
313
314 if ( ! reader.read(fi) ) {
315 LOG.error("Unable to read class file.");
316 System.exit(0);
317 }
318
319 // add the package to the hash table
320 registerClass(reader.getClassInfo());
321 // test if the file is a jar file
322 //
323 } else if ( isJarFile(file) &&
324 ! m_analysedJars.contains(file.getName()) ) {
325
326 // mark it as an analyzed jar file
327 //
328 m_analysedJars.add(file.getName());
329
330 // unzip the jar
331 //
332 File out = IOManager.getTempDir();
333
334 m_unzipper.extractFiles(file, out);
335
336 // recusive analyse the extracted files
337 //
338 analyze(out);
339 }
340 }
341 }
342
343 /***
344 * Helper method for testing whether the specified abstract file is a class
345 * file.
346 *
347 * @param file Abstract file object pointing to the location where the
348 * physical location of the file to inqure is.
349 * @return True if it is a java .class file.<br>
350 * If the file has no file extension, false is returned.
351 */
352 private boolean isClassFile(File file){
353 String fileName = file.getName();
354 int indexOfPoint = fileName.lastIndexOf(".");
355
356 // no file extension
357 if ( indexOfPoint == -1 ) {
358 return false;
359 }
360
361 String fileExt = fileName.substring(indexOfPoint);
362 if ( fileExt.equals( ".class" ) ) {
363 return true;
364 }
365 return false;
366 }
367
368
369 /***
370 * Helper method for testing whether the specified abstract file is jar
371 * file.
372 *
373 * @param file Abstract file object pointing to the location where the
374 * physical location of the file to inqure is.
375 * @return True if it is a jar file.<br>
376 * If the file has no file extension, false is returned.
377 */
378 private boolean isJarFile(File file) {
379 String fileName = file.getName();
380 int indexOfPoint = fileName.lastIndexOf(".");
381
382 // no file extension
383 if ( indexOfPoint == -1 ) {
384 return false;
385 }
386
387 String fileExt = fileName.substring(indexOfPoint);
388 if ( fileExt.equals( ".jar" ) ) {
389 return true;
390 }
391 return false;
392 }
393
394
395 /***
396 * Appends the package information to the hash maps.<br>
397 * On one side the informations for the graph construction on the other side
398 * the informations for the statistics.
399 *
400 * @param info <code>ClassInfo</code> object storing the dependency
401 * information of a .class file.
402 * @throws RESyntaxException If there is a problem with the regular expression
403 * used to determine the excluded packages.
404 * @throws ClassInfoException If the class informations are invalid.
405 *
406 * @tbd improve this method
407 */
408 private void registerClass(ClassInfo info)
409 throws RESyntaxException, ClassInfoException {
410
411 ArrayList packages; // list of Strings
412 ArrayList classInfos; // list of ClassInfo objects
413
414 // get the regular expression with the load filter
415 //
416 RE regExp = new RE(m_filter.getValue());
417
418 String packageName = info.getPackage();
419
420 // register all the relations of the project
421 // use hashtable
422 // Integer classNum = null;
423 // if ( (classNum = (Integer)m_projClassPackageRel.get(packageName)) != null ) {
424 //
425 //
426 <b>if ( m_projClassPackageRel.containsKey(packageName) ) {
427 int numberOfClasses = ((Integer)m_projClassPackageRel
428 .get(packageName)).intValue();
429
430 numberOfClasses++;
431
432 m_projClassPackageRel.put(packageName,
433 new Integer(numberOfClasses));
434 // the first occurance of this package
435 //
436 } else {
437 m_projClassPackageRel.put(packageName, new Integer(1));
438 }
439
440 // apply the load filter
441 //
442 <b>if ( regExp.match(packageName)) {
443 return;
444 }
445 m_analyzedClass++; // increments the analyzed class counter
446
447 // register all the relations of the analyse
448 <b>if ( m_analyzedClassPackageRel.containsKey(packageName) ) {
449 int numberOfClasses = ((Integer)m_analyzedClassPackageRel
450 .get(packageName)).intValue();
451
452 numberOfClasses++;
453
454 m_analyzedClassPackageRel.put(packageName,
455 new Integer(numberOfClasses));
456 // the first occurance of this package
457 //
458 } else {
459 m_analyzedClassPackageRel.put(packageName, new Integer(1));
460 }
461
462 // gets the value (list) if it exists otherwise creates it
463 //
464 // use ternary operator here
465 // a = (map.get(key) != null ? map.get(key) : new ArrayList());
466 //
467 <b>if ( m_dependencies.containsKey(packageName) ) {
468 packages = (ArrayList)m_dependencies.get(packageName);
469 classInfos = (ArrayList)m_classInfos.get(packageName);
470 } else {
471 packages = new ArrayList();
472 classInfos = new ArrayList();
473 }
474
475 ArrayList list = info.getNeededPackages();
476 ListIterator iterator = list.listIterator();
477
478 while ( iterator.hasNext() ) {
479 String tmp = (String)iterator.next();
480
481 if ( ! packages.contains(tmp) && !regExp.match(tmp) ) {
482 packages.add(tmp);
483
484 if ( ! packageName.equals(tmp) ) {
485 m_usesDep++; // dont count reflexive dependencies
486 }
487 }
488 }
489
490 // add the package to the hash map
491 //
492 m_dependencies.put(packageName, packages);
493
494 // add the ClassInfo to the hash map
495 //
496 classInfos.add(info);
497 m_classInfos.put(packageName, classInfos);
498 }
499
500 /***
501 * Helper method for evaluating the biggest package of the project and the
502 * biggest package which passed the load filter.
503 */
504 private void evaluateClassPackageRelations(){
505
506 // evaluate the biggest package which passed the load filter
507 Set keySet = m_analyzedClassPackageRel.keySet();
508 Iterator keyIter = keySet.iterator();
509 int maxVal = 0, totalVal = 0;
510 String keyOfMaxVal = "";
511
512 // get the biggest package (max # classes)
513 while ( keyIter.hasNext() ) {
514 String keyTmp = (String)keyIter.next();
515
516 int valTmp = ((Integer)m_analyzedClassPackageRel
517 .get(keyTmp)).intValue();
518 totalVal += valTmp;
519
520 if ( valTmp > maxVal ) {
521 maxVal = valTmp;
522 keyOfMaxVal = keyTmp;
523 } else if ( valTmp == maxVal ) {
524 keyOfMaxVal.concat(", " + keyTmp);
525 }
526 }
527 m_statistic.setBiggestAnalyzedPackage(keyOfMaxVal);
528
529 // evaluate the biggest package of the project
530 //
531 maxVal = 0;
532 totalVal = 0;
533 keyOfMaxVal = "";
534 keySet = m_projClassPackageRel.keySet();
535 keyIter = keySet.iterator();
536
537 // get the biggest package (max # classes)
538 while ( keyIter.hasNext() ) {
539 String keyTmp = (String)keyIter.next();
540
541 int valTmp = ((Integer)m_projClassPackageRel
542 .get(keyTmp)).intValue();
543 totalVal += valTmp;
544
545 if ( valTmp > maxVal ) {
546 maxVal = valTmp;
547 keyOfMaxVal = keyTmp;
548 }
549 else if ( valTmp == maxVal ) {
550 keyOfMaxVal.concat(", " + keyTmp);
551 }
552 }
553 m_statistic.setBiggestProjPackage(keyOfMaxVal);
554
555 // set up the rest of the statistic object
556 m_statistic.setAnalyzedClasses(m_analyzedClass);
557 m_statistic.setProjClasses(m_projClass);
558 m_statistic.setAnalyzedPackages(m_dependencies.keySet().size());
559 m_statistic.setProjPackages(m_projPackage);
560 m_statistic.setNumberOfUsesDep(m_usesDep);
561 }
562
563 /***
564 * Get the project root file.
565 *
566 * @return a <code>File</code> value
567 */
568 public File getProjectRoot() {
569 return m_root;
570 }
571 }
This page was automatically generated by Maven