OSDN Git Service

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