OSDN Git Service

94140eb5ce965f1e3ee8f0e6e695a695ae133d02
[stigmata/stigmata.git] / src / main / java / jp / sourceforge / stigmata / BirthmarkEngine.java
1 package jp.sourceforge.stigmata;
2
3 /*
4  * $Id$
5  */
6
7 import java.io.ByteArrayInputStream;
8 import java.io.ByteArrayOutputStream;
9 import java.io.File;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.net.MalformedURLException;
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.Stack;
20
21 import jp.sourceforge.stigmata.birthmarks.extractors.BirthmarkExtractorFactory;
22 import jp.sourceforge.stigmata.digger.ClassFileArchive;
23 import jp.sourceforge.stigmata.digger.ClassFileEntry;
24 import jp.sourceforge.stigmata.digger.ClasspathContext;
25 import jp.sourceforge.stigmata.digger.DefaultClassFileArchive;
26 import jp.sourceforge.stigmata.digger.JarClassFileArchive;
27 import jp.sourceforge.stigmata.digger.WarClassFileArchive;
28 import jp.sourceforge.stigmata.event.BirthmarkEngineEvent;
29 import jp.sourceforge.stigmata.event.BirthmarkEngineListener;
30 import jp.sourceforge.stigmata.event.OperationStage;
31 import jp.sourceforge.stigmata.event.OperationType;
32 import jp.sourceforge.stigmata.event.WarningMessages;
33 import jp.sourceforge.stigmata.filter.ComparisonPairFilterManager;
34 import jp.sourceforge.stigmata.filter.FilteredComparisonResultSet;
35 import jp.sourceforge.stigmata.hook.Phase;
36 import jp.sourceforge.stigmata.hook.StigmataHookManager;
37 import jp.sourceforge.stigmata.result.CertainPairComparisonResultSet;
38 import jp.sourceforge.stigmata.result.RoundRobinComparisonResultSet;
39 import jp.sourceforge.stigmata.spi.BirthmarkSpi;
40
41 /**
42  * core engine for extracting/comparing/filtering birthmarks.
43  * 
44  * This class is not thread safe.
45  * 
46  * @author Haruaki Tamada
47  */
48 public class BirthmarkEngine{
49     private BirthmarkEnvironment environment;
50     private List<BirthmarkEngineListener> listeners = new ArrayList<BirthmarkEngineListener>();
51     private Stack<WarningMessages> stack = new Stack<WarningMessages>();
52     private WarningMessages warnings;
53     private OperationType latestOperationType;
54     private OperationType targetType;
55     private BirthmarkExtractorFactory factory;
56
57     /**
58      * constructor.
59      */
60     public BirthmarkEngine(BirthmarkEnvironment env){
61         this.environment = env;
62         factory = new BirthmarkExtractorFactory(env);
63     }
64
65     /**
66      * returns an environment of this object.
67      */
68     public BirthmarkEnvironment getEnvironment(){
69         return environment;
70     }
71
72     public void addBirthmarkEngineListener(BirthmarkEngineListener listener){
73         listeners.add(listener);
74     }
75
76     public void removeBirthmarkEngineListener(BirthmarkEngineListener listener){
77         listeners.remove(listener);
78     }
79
80     /**
81      * filters comparison of birthmarks from target files.
82      * @see #extract(String[], BirthmarkContext)
83      * @see #compare(String[], BirthmarkContext)
84      * @see #filter(ComparisonResultSet)
85      * @see BirthmarkContext#getFilterTypes()
86      */
87     public synchronized ComparisonResultSet filter(String[] target, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException, BirthmarkStoreException{
88         operationStart(OperationType.FILTER_BIRTHMARKS);
89
90         ComparisonResultSet crs = compare(target, context);
91         crs = filter(crs);
92
93         operationDone(OperationType.FILTER_BIRTHMARKS);
94
95         return crs;
96     }
97
98     /**
99      * filters comparison of birthmarks from given two targets targetx, and targetY
100      * @see #extract(String[], String[], BirthmarkContext)
101      * @see #compare(String[], String[], BirthmarkContext)
102      * @see #filter(ComparisonResultSet)
103      * @see BirthmarkContext#getFilterTypes()
104      */
105     public synchronized ComparisonResultSet filter(String[] targetX, String[] targetY, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException, BirthmarkStoreException{
106         operationStart(OperationType.FILTER_BIRTHMARKS);
107
108         ComparisonResultSet crs = compare(targetX, targetY, context);
109         crs = filter(crs);
110
111         operationDone(OperationType.FILTER_BIRTHMARKS);
112
113         return crs;
114     }
115
116     /**
117      * filters comparison of birthmarks from given extraction result set.
118      * @see #compare(ExtractionResultSet)
119      * @see #filter(ComparisonResultSet)
120      * @see ExtractionResultSet#getContext()
121      * @see BirthmarkContext#getFilterTypes()
122      */
123     public synchronized ComparisonResultSet filter(ExtractionResultSet er) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException{
124         operationStart(OperationType.FILTER_BIRTHMARKS);
125
126         ComparisonResultSet crs = compare(er);
127         crs = filter(crs);
128
129         operationDone(OperationType.FILTER_BIRTHMARKS);
130
131         return crs;
132     }
133
134     /**
135      * filters comparison of birthmarks.
136      * @see ExtractionResultSet#getContext()
137      * @see BirthmarkContext#getFilterTypes()
138      */
139     public synchronized ComparisonResultSet filter(ComparisonResultSet crs) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException{
140         operationStart(OperationType.FILTER_BIRTHMARKS);
141         StigmataHookManager.getInstance().runHook(Phase.BEFORE_FILTERING, crs.getContext());
142
143         String[] filterTypes = crs.getContext().getFilterTypes();
144
145         if(filterTypes != null){
146             List<ComparisonPairFilterSet> filterList = new ArrayList<ComparisonPairFilterSet>();
147             ComparisonPairFilterManager manager = environment.getFilterManager();
148             for(int i = 0; i < filterTypes.length; i++){
149                 ComparisonPairFilterSet fset = manager.getFilterSet(filterTypes[i]);
150                 if(fset != null){
151                     filterList.add(fset);
152                 }
153                 else{
154                     warnings.addMessage(new FilterNotFoundException("filter not found"), filterTypes[i]);
155                 }
156             }
157             ComparisonPairFilterSet[] cpfs = filterList.toArray(new ComparisonPairFilterSet[filterList.size()]);
158
159             crs = new FilteredComparisonResultSet(crs, cpfs);
160         }
161
162         StigmataHookManager.getInstance().runHook(Phase.AFTER_FILTERING, crs.getContext());
163         operationDone(OperationType.FILTER_BIRTHMARKS);
164
165         return crs;
166     }
167
168     /**
169      * compares two given birthmarks and returns comparison pair.
170      */
171     public synchronized ComparisonPair compareDetails(BirthmarkSet bs1, BirthmarkSet bs2, BirthmarkContext context) throws BirthmarkComparisonFailedException{
172         return new ComparisonPair(bs1, bs2, context);
173     }
174
175     public synchronized ComparisonResultSet compare(String[] target, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException, BirthmarkStoreException{
176         operationStart(OperationType.COMPARE_BIRTHMARKS);
177
178         ExtractionResultSet er = extract(target, context);
179         ComparisonResultSet crs = compare(er);
180
181         operationDone(OperationType.COMPARE_BIRTHMARKS);
182
183         return crs;
184     }
185
186     public synchronized ComparisonResultSet compare(String[] targetX, String[] targetY, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException, BirthmarkStoreException{
187         operationStart(OperationType.COMPARE_BIRTHMARKS);
188
189         ExtractionResultSet er = extract(targetX, targetY, context);
190         ComparisonResultSet crs = compare(er);
191
192         operationDone(OperationType.COMPARE_BIRTHMARKS);
193
194         return crs;
195     }
196
197     public synchronized ComparisonResultSet compare(ExtractionResultSet er) throws BirthmarkExtractionFailedException, BirthmarkComparisonFailedException{
198         operationStart(OperationType.COMPARE_BIRTHMARKS);
199         BirthmarkContext context = er.getContext();
200
201         StigmataHookManager.getInstance().runHook(Phase.BEFORE_COMPARISON, context);
202         ComparisonResultSet crs = null;
203         switch(context.getComparisonMethod()){
204         case ROUND_ROBIN_SAME_PAIR:
205             crs = new RoundRobinComparisonResultSet(er, true);
206             break;
207         case ROUND_ROBIN_WITHOUT_SAME_PAIR:
208             crs = new RoundRobinComparisonResultSet(er, false);
209             break;
210         case ROUND_ROBIN_XY:
211             crs = new RoundRobinComparisonResultSet(er, true);
212         case GUESSED_PAIR:
213             crs = new CertainPairComparisonResultSet(er);
214             break;
215         case SPECIFIED_PAIR:
216             crs = new CertainPairComparisonResultSet(er, context.getNameMappings());
217             break;
218         }
219
220         StigmataHookManager.getInstance().runHook(Phase.AFTER_COMPARISON, context);
221         operationDone(OperationType.COMPARE_BIRTHMARKS);
222
223         return crs;
224     }
225
226     public synchronized ExtractionResultSet extract(String[] target, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkStoreException{
227         operationStart(OperationType.EXTRACT_BIRTHMARKS);
228         ExtractionResultSet er = extract(target, null, context);
229         operationDone(OperationType.EXTRACT_BIRTHMARKS);
230         return er;
231     }
232
233     public synchronized ExtractionResultSet extract(String[] targetX, String[] targetY, BirthmarkContext context) throws BirthmarkExtractionFailedException, BirthmarkStoreException{
234         operationStart(OperationType.EXTRACT_BIRTHMARKS);
235         ExtractionResultSet er = context.getEnvironment().getHistoryManager().createDefaultResultSet(context);
236
237         try{
238             prepare(targetX, targetY, context);
239
240             StigmataHookManager.getInstance().runHook(Phase.BEFORE_EXTRACTION, context);
241
242             switch(context.getComparisonMethod()){
243             case ROUND_ROBIN_SAME_PAIR:
244             case ROUND_ROBIN_WITHOUT_SAME_PAIR:
245                 er.setTableType(false);
246                 String[] targetXY = mergeTarget(targetX, targetY);
247                 extractImpl(targetXY, er, ExtractionTarget.TARGET_XY);
248                 break;
249             case GUESSED_PAIR:
250             case SPECIFIED_PAIR:
251             case ROUND_ROBIN_XY:
252             default:
253                 if(targetX == null || targetY == null){
254                     throw new BirthmarkExtractionFailedException("targetX or targetY is null");
255                 }
256                 er.setTableType(true);
257                 extractImpl(targetX, er, ExtractionTarget.TARGET_X);
258                 extractImpl(targetY, er, ExtractionTarget.TARGET_Y);
259                 break;
260             }
261             return er;
262         } catch(IOException e){
263             throw new BirthmarkExtractionFailedException(e);
264         } finally{
265             StigmataHookManager.getInstance().runHook(Phase.AFTER_EXTRACTION, context);
266             operationDone(OperationType.EXTRACT_BIRTHMARKS);
267         }
268     }
269
270     public BirthmarkContext prepare(String[] targetX, String[] targetY, BirthmarkContext context) throws MalformedURLException, IOException{
271         StigmataHookManager.getInstance().runHook(Phase.BEFORE_PREPROCESS, context);
272
273         Set<String> set = new HashSet<String>();
274         if(targetX != null){
275             for(String t: targetX) set.add(t);
276         }
277         if(targetY != null){
278             for(String t: targetY) set.add(t);
279         }
280         String[] target = set.toArray(new String[set.size()]);
281         ClassFileArchive[] archives = createArchives(target, environment);
282         for(String type: context.getBirthmarkTypes()){
283             BirthmarkSpi service = context.getEnvironment().getService(type);
284             if(service != null && service.getPreprocessor() != null){
285                 BirthmarkPreprocessor preprocessor = service.getPreprocessor();
286                 preprocessor.preprocess(archives, context);
287             }
288         }
289         StigmataHookManager.getInstance().runHook(Phase.AFTER_PREPROCESS, context);
290
291         return context;
292     }
293
294     private String[] mergeTarget(String[] t1, String[] t2){
295         List<String> list = new ArrayList<String>();
296         addToList(list, t1);
297         addToList(list, t2);
298
299         return list.toArray(new String[list.size()]);
300     }
301
302     private void addToList(List<String> list, String[] target){
303         if(target != null){
304             for(String s: target){
305                 list.add(s);
306             }
307         }
308     }
309
310     private BirthmarkSet[] extractImpl(String[] target, ExtractionResultSet er, ExtractionTarget et) throws BirthmarkExtractionFailedException, IOException, BirthmarkStoreException{
311         ClassFileArchive[] archives = createArchives(target, environment);
312         BirthmarkContext context = er.getContext();
313         ExtractionUnit unit = context.getExtractionUnit();
314
315         BirthmarkSet[] extractResult = null;
316         if(unit == ExtractionUnit.CLASS){
317             extractFromClass(archives, er, et);
318         }
319         else if(unit == ExtractionUnit.PACKAGE){
320             extractFromPackage(archives, er, et);
321         }
322         else if(unit == ExtractionUnit.ARCHIVE){
323             extractFromProduct(archives, er, et);
324         }
325
326         return extractResult;
327     }
328
329     private byte[] inputStreamToByteArray(InputStream in) throws IOException{
330         ByteArrayOutputStream bout = new ByteArrayOutputStream();
331         int read;
332         byte[] dataBuffer = new byte[512];
333         while((read = in.read(dataBuffer, 0, dataBuffer.length)) != -1){
334             bout.write(dataBuffer, 0, read);
335         }
336         byte[] data = bout.toByteArray();
337
338         bout.close();
339         return data;
340     }
341
342     private void extractFromPackage(ClassFileArchive[] archives, ExtractionResultSet er, ExtractionTarget et) throws IOException, BirthmarkExtractionFailedException{
343         Map<String, BirthmarkSet> map = new HashMap<String, BirthmarkSet>();
344         BirthmarkContext context = er.getContext();
345
346         for(ClassFileArchive archive: archives){
347             for(ClassFileEntry entry: archive){
348                 try{
349                     String name = entry.getClassName();
350                     String packageName = parsePackageName(name);
351                     BirthmarkSet bs = map.get(packageName);
352                     if(bs == null){
353                         bs = new BirthmarkSet(packageName, archive.getLocation());
354                         map.put(packageName, bs);
355                     }
356
357                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());
358                     for(String birthmarkType: context.getBirthmarkTypes()){
359                         try{
360                             BirthmarkExtractor extractor = factory.getExtractor(birthmarkType);
361                             if(extractor.isAcceptable(ExtractionUnit.PACKAGE)){
362                                 Birthmark b = bs.getBirthmark(extractor.getProvider().getType());
363                                 if(b == null){
364                                     b = extractor.createBirthmark();
365                                     bs.addBirthmark(b);
366                                 }
367                                 extractor.extract(b, new ByteArrayInputStream(data), er.getContext());
368                             }
369                         } catch(ExtractorNotFoundException e){
370                             warnings.addMessage(e, birthmarkType);
371                         }
372                     }
373                 } catch(IOException e){
374                     warnings.addMessage(e, archive.getName());
375                 }
376             }
377         }
378         try{
379             for(BirthmarkSet bs: map.values()){
380                 er.addBirthmarkSet(et, bs);
381             }
382         }catch(BirthmarkStoreException e){
383         }
384     }
385
386     private String parsePackageName(String name){
387         String n = name.replace('/', '.');
388         int index = n.lastIndexOf('.');
389         if(index > 0){
390             n = n.substring(0, index - 1);
391         }
392
393         return n;
394     }
395
396     private void extractFromClass(ClassFileArchive[] archives, ExtractionResultSet er,
397             ExtractionTarget et) throws IOException, BirthmarkExtractionFailedException, BirthmarkStoreException{
398         BirthmarkContext context = er.getContext();
399
400         for(ClassFileArchive archive: archives){
401             for(ClassFileEntry entry: archive){
402                 try{
403                     BirthmarkSet birthmarkset = new BirthmarkSet(entry.getClassName(), entry.getLocation());
404                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());
405
406                     for(String birthmarkType: context.getBirthmarkTypes()){
407                         try{
408                             BirthmarkExtractor extractor = factory.getExtractor(birthmarkType);
409                             if(extractor.isAcceptable(ExtractionUnit.CLASS)){
410                                 Birthmark b = extractor.extract(new ByteArrayInputStream(data), er.getContext());
411                                 birthmarkset.addBirthmark(b);
412                             }
413                         } catch(ExtractorNotFoundException e){
414                             warnings.addMessage(e, birthmarkType);
415                         }
416                     }
417                     er.addBirthmarkSet(et, birthmarkset);
418                 } catch(IOException e){
419                     warnings.addMessage(e, entry.getClassName());
420                 }
421             }
422         }
423     }
424
425     private void extractFromProduct(ClassFileArchive[] archives, ExtractionResultSet er, ExtractionTarget et) throws IOException, BirthmarkExtractionFailedException, BirthmarkStoreException{
426         BirthmarkContext context = er.getContext();
427
428         for(ClassFileArchive archive: archives){
429             BirthmarkSet birthmarkset = new BirthmarkSet(archive.getName(), archive.getLocation());
430
431             for(ClassFileEntry entry: archive){
432                 try{
433                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());
434                     for(String birthmarkType: context.getBirthmarkTypes()){
435                         try{
436                             BirthmarkExtractor extractor = factory.getExtractor(birthmarkType);
437                             if(extractor.isAcceptable(ExtractionUnit.ARCHIVE)){
438                                 Birthmark b = birthmarkset.getBirthmark(birthmarkType);
439                                 if(b == null){
440                                     b = extractor.createBirthmark();
441                                     birthmarkset.addBirthmark(b);
442                                 }
443                                 extractor.extract(b, new ByteArrayInputStream(data), er.getContext());
444                             }
445                         } catch(ExtractorNotFoundException e){
446                             warnings.addMessage(e, birthmarkType);
447                         } 
448                     }
449                 } catch(IOException e){
450                     warnings.addMessage(e, entry.getClassName());
451                 }
452             }
453             if(birthmarkset.getBirthmarksCount() != 0){
454                 er.addBirthmarkSet(et, birthmarkset);
455             }
456         }
457     }
458
459     private ClassFileArchive[] createArchives(String[] files, BirthmarkEnvironment environment) throws IOException, MalformedURLException{
460         ClasspathContext bytecode = environment.getClasspathContext();
461         List<ClassFileArchive> archives = new ArrayList<ClassFileArchive>();
462         for(int i = 0; i < files.length; i++){
463             try{
464                 if(files[i].endsWith(".class")){
465                     archives.add(new DefaultClassFileArchive(files[i]));
466                 }
467                 else if(files[i].endsWith(".jar") || files[i].endsWith(".zip")){
468                     archives.add(new JarClassFileArchive(files[i]));
469                     bytecode.addClasspath(new File(files[i]).toURI().toURL());
470                 }
471                 else if(files[i].endsWith(".war")){
472                     archives.add(new WarClassFileArchive(files[i]));
473                 }
474             } catch(IOException e){
475                 warnings.addMessage(e, files[i]);
476             }
477         }
478         return archives.toArray(new ClassFileArchive[archives.size()]);
479     }
480
481     private void operationStart(OperationType type){
482         if(warnings == null){
483             warnings = new WarningMessages(type);
484             fireEvent(new BirthmarkEngineEvent(OperationStage.OPERATION_START, type, warnings));
485             latestOperationType = type;
486             targetType = type;
487         }
488         stack.push(warnings);
489         /*
490          * call subOperationStart method only once when some operation is occured.
491          * Ex. extraction, comparison, filtering
492          */
493         if(latestOperationType != type){
494             fireEvent(new BirthmarkEngineEvent(OperationStage.SUB_OPERATION_START, type, warnings));
495             latestOperationType = type;
496         }
497     }
498
499     private void operationDone(OperationType type){
500         if(latestOperationType != type && targetType != type){
501             fireEvent(new BirthmarkEngineEvent(OperationStage.SUB_OPERATION_DONE, type, warnings));
502             latestOperationType = type;
503         }
504         stack.pop();
505         if(stack.size() == 0){
506             fireEvent(new BirthmarkEngineEvent(OperationStage.OPERATION_DONE, type, warnings));
507             warnings = null;
508             latestOperationType = null;
509         }
510     }
511
512     private void fireEvent(BirthmarkEngineEvent e){
513         for(BirthmarkEngineListener listener: listeners){
514             switch(e.getStage()){
515             case OPERATION_START:
516                 listener.operationStart(e);
517                 break;
518             case SUB_OPERATION_START:
519                 listener.subOperationStart(e);
520                 break;
521             case SUB_OPERATION_DONE:
522                 listener.subOperationDone(e);
523                 break;
524             case OPERATION_DONE:
525                 listener.operationDone(e);
526                 break;
527             default:
528                 throw new InternalError("unknown stage: " + e.getStage());
529             }
530         }
531     }
532 }