OSDN Git Service

add listener in order to notify birthmarking process start/end
[stigmata/stigmata-core.git] / src / main / java / jp / naist / se / stigmata / Stigmata.java
1 package jp.naist.se.stigmata;\r
2 \r
3 /*\r
4  * $Id$\r
5  */\r
6 \r
7 import java.io.ByteArrayInputStream;\r
8 import java.io.ByteArrayOutputStream;\r
9 import java.io.File;\r
10 import java.io.FileInputStream;\r
11 import java.io.FileNotFoundException;\r
12 import java.io.IOException;\r
13 import java.io.InputStream;\r
14 import java.lang.reflect.InvocationTargetException;\r
15 import java.net.MalformedURLException;\r
16 import java.util.ArrayList;\r
17 import java.util.HashMap;\r
18 import java.util.Iterator;\r
19 import java.util.List;\r
20 import java.util.Map;\r
21 import java.util.Stack;\r
22 import java.util.logging.Logger;\r
23 \r
24 import javax.imageio.spi.ServiceRegistry;\r
25 \r
26 import jp.naist.se.stigmata.event.OperationEvent;\r
27 import jp.naist.se.stigmata.event.OperationListener;\r
28 import jp.naist.se.stigmata.event.OperationStage;\r
29 import jp.naist.se.stigmata.event.OperationType;\r
30 import jp.naist.se.stigmata.event.WarningMessages;\r
31 import jp.naist.se.stigmata.filter.ComparisonPairFilterManager;\r
32 import jp.naist.se.stigmata.filter.FilteredComparisonResultSet;\r
33 import jp.naist.se.stigmata.reader.ClassFileArchive;\r
34 import jp.naist.se.stigmata.reader.ClassFileEntry;\r
35 import jp.naist.se.stigmata.reader.ClasspathContext;\r
36 import jp.naist.se.stigmata.reader.DefaultClassFileArchive;\r
37 import jp.naist.se.stigmata.reader.JarClassFileArchive;\r
38 import jp.naist.se.stigmata.reader.WarClassFileArchive;\r
39 import jp.naist.se.stigmata.spi.BirthmarkSpi;\r
40 import jp.naist.se.stigmata.utils.ConfigFileImporter;\r
41 \r
42 import org.apache.commons.beanutils.BeanUtils;\r
43 \r
44 /**\r
45  * Birthmarking engine.\r
46  *\r
47  * @author  Haruaki TAMADA\r
48  * @version  $Revision$ $Date$\r
49  */\r
50 public final class Stigmata{\r
51     private static final Stigmata instance = new Stigmata();\r
52 \r
53     private BirthmarkContext defaultContext = BirthmarkContext.getDefaultContext();\r
54     private boolean configDone = false;\r
55     private Stack<WarningMessages> stack = new Stack<WarningMessages>();\r
56     private WarningMessages warnings;\r
57     private List<OperationListener> listeners = new ArrayList<OperationListener>();\r
58 \r
59     private Stigmata(){\r
60     }\r
61 \r
62     public static Stigmata getInstance(){\r
63         return instance;\r
64     }\r
65 \r
66     public void addOperationListener(OperationListener listener){\r
67         listeners.add(listener);\r
68     }\r
69 \r
70     public void removeOperationListener(OperationListener listener){\r
71         listeners.remove(listener);\r
72     }\r
73 \r
74     public WarningMessages getWarnings(){\r
75         return warnings;\r
76     }\r
77 \r
78     public void configuration(){\r
79         operationStart(OperationType.CONFIGURATION);\r
80         configuration(null);\r
81         operationDone(OperationType.CONFIGURATION);\r
82     }\r
83 \r
84     public void configuration(String filePath){\r
85         InputStream target = null;\r
86         if(filePath != null){\r
87             try{\r
88                 target = new FileInputStream(filePath);\r
89             } catch(FileNotFoundException e){\r
90                 filePath = null;\r
91             }\r
92         }\r
93 \r
94         if(filePath == null){\r
95             File file = new File("stigmata.xml");\r
96             if(!file.exists()){\r
97                 file = new File(System.getProperty("user.home"), ".stigmata.xml");\r
98                 if(!file.exists()){\r
99                     file = null;\r
100                 }\r
101             }\r
102             if(file != null){\r
103                 try {\r
104                     target = new FileInputStream(file);\r
105                 } catch (FileNotFoundException ex) {\r
106                     // never throwed this exception;\r
107                     throw new InternalError(ex.getMessage());\r
108                 }\r
109             }\r
110         }\r
111         if(target == null){\r
112             target = getClass().getResourceAsStream("/resources/stigmata.xml");\r
113         }\r
114         initConfiguration(target);\r
115     }\r
116 \r
117     /**\r
118      * create a new {@link BirthmarkContext <code>BirthmarkContext</code>}.\r
119      */\r
120     public BirthmarkContext createContext(){\r
121         operationStart(OperationType.CREATE_CONTEXT);\r
122         BirthmarkContext context = new BirthmarkContext();\r
123         operationDone(OperationType.EXTRACT_BIRTHMARKS);\r
124         return context;\r
125     }\r
126 \r
127     public BirthmarkSet[] extract(String[] birthmarks, String[] files) throws BirthmarkExtractionException{\r
128         operationStart(OperationType.EXTRACT_BIRTHMARKS);\r
129         BirthmarkSet[] set = extract(birthmarks, files, createContext());\r
130         operationDone(OperationType.EXTRACT_BIRTHMARKS);\r
131 \r
132         return set;\r
133     }\r
134 \r
135     public BirthmarkSet[] extract(String[] birthmarks, String[] files,\r
136                                   BirthmarkContext context) throws BirthmarkExtractionException{\r
137         operationStart(OperationType.EXTRACT_BIRTHMARKS);\r
138         try{\r
139             return extractImpl(birthmarks, files, context);\r
140         } catch(IOException e){\r
141             throw new BirthmarkExtractionException(e);\r
142         } finally{\r
143             operationDone(OperationType.EXTRACT_BIRTHMARKS);\r
144         }\r
145     }\r
146 \r
147     public ComparisonResultSet compare(BirthmarkSet[] holders) throws IOException{\r
148         operationStart(OperationType.COMPARE_BIRTHMARKS);\r
149         ComparisonResultSet crs = compare(holders, createContext());\r
150         operationDone(OperationType.COMPARE_BIRTHMARKS);\r
151 \r
152         return crs;\r
153     }\r
154 \r
155     public ComparisonResultSet compare(BirthmarkSet[] holders, BirthmarkContext context) throws IOException{\r
156         operationStart(OperationType.COMPARE_BIRTHMARKS);\r
157         ComparisonResultSet result = new RoundRobinComparisonResultSet(holders, context, true);\r
158         operationDone(OperationType.COMPARE_BIRTHMARKS);\r
159 \r
160         return result;\r
161     }\r
162 \r
163     public ComparisonResultSet compare(BirthmarkSet[] holders1, BirthmarkSet[] holders2) throws IOException{\r
164         operationStart(OperationType.COMPARE_BIRTHMARKS);\r
165         ComparisonResultSet crs = compare(holders1, holders2, createContext());\r
166         operationDone(OperationType.COMPARE_BIRTHMARKS);\r
167         return crs;\r
168     }\r
169 \r
170     public ComparisonResultSet compare(BirthmarkSet[] holders1, BirthmarkSet[] holders2, BirthmarkContext context) throws IOException{\r
171         operationStart(OperationType.COMPARE_BIRTHMARKS);\r
172         ComparisonResultSet result = new RoundRobinComparisonResultSet(holders1, holders2, context);\r
173         operationDone(OperationType.COMPARE_BIRTHMARKS);\r
174 \r
175         return result;\r
176     }\r
177 \r
178     public ComparisonResultSet filter(ComparisonResultSet resultset, String[] filters){\r
179         operationStart(OperationType.FILTER_BIRTHMARKS);\r
180         ComparisonResultSet crs = filter(resultset, filters, createContext());\r
181         operationDone(OperationType.FILTER_BIRTHMARKS);\r
182 \r
183         return crs;\r
184     }\r
185 \r
186     public ComparisonResultSet filter(ComparisonResultSet resultset, String[] filters, BirthmarkContext context){\r
187         operationStart(OperationType.FILTER_BIRTHMARKS);\r
188         if(filters != null){\r
189             List<ComparisonPairFilterSet> filterList = new ArrayList<ComparisonPairFilterSet>();\r
190             ComparisonPairFilterManager manager = context.getFilterManager();\r
191             for(int i = 0; i < filters.length; i++){\r
192                 ComparisonPairFilterSet fset = manager.getFilterSet(filters[i]);\r
193                 if(fset != null){\r
194                     filterList.add(fset);\r
195                 }\r
196                 else{\r
197                     Logger logger = Logger.getLogger(getClass().getName());\r
198                     logger.warning(filters[i] + ": filter not found");\r
199                 }\r
200             }\r
201 \r
202             return filter(resultset, filterList.toArray(new ComparisonPairFilterSet[filterList.size()]));\r
203         }\r
204         operationDone(OperationType.FILTER_BIRTHMARKS);\r
205         return resultset;\r
206     }\r
207 \r
208     public ComparisonResultSet filter(ComparisonResultSet resultset, ComparisonPairFilterSet[] filters){\r
209         operationStart(OperationType.FILTER_BIRTHMARKS);\r
210         ComparisonResultSet crs = filter(resultset, filters, createContext());\r
211         operationDone(OperationType.FILTER_BIRTHMARKS);\r
212 \r
213         return crs;\r
214     }\r
215 \r
216     public ComparisonResultSet filter(ComparisonResultSet resultset, ComparisonPairFilterSet[] filters, BirthmarkContext context){\r
217         operationStart(OperationType.FILTER_BIRTHMARKS);\r
218         FilteredComparisonResultSet filterResultSet = new FilteredComparisonResultSet(resultset);\r
219         operationDone(OperationType.FILTER_BIRTHMARKS);\r
220 \r
221         return filterResultSet;\r
222     }\r
223 \r
224     public double compareDetails(BirthmarkSet h1, BirthmarkSet h2){\r
225         operationStart(OperationType.COMPARE_DETAIL_BIRTHMARKS);\r
226         double similarity = compareDetails(h1, h2, createContext());\r
227         operationDone(OperationType.COMPARE_DETAIL_BIRTHMARKS);\r
228         return similarity;\r
229     }\r
230 \r
231     public double compareDetails(BirthmarkSet h1, BirthmarkSet h2, BirthmarkContext context){\r
232         operationStart(OperationType.COMPARE_DETAIL_BIRTHMARKS);\r
233 \r
234         List<Double> list = new ArrayList<Double>();\r
235         int count = 0;\r
236         for(Iterator<String> i = h1.birthmarkTypes(); i.hasNext(); ){\r
237             String type = i.next();\r
238             Birthmark b1 = h1.getBirthmark(type);\r
239             Birthmark b2 = h2.getBirthmark(type);\r
240 \r
241             double similarity = Double.NaN;\r
242             if(b1 != null && b2 != null){\r
243                 BirthmarkSpi spi = context.getService(type);\r
244                 BirthmarkComparator comparator = spi.getComparator();\r
245 \r
246                 similarity = comparator.compare(b1, b2);\r
247                 count++;\r
248             }\r
249             list.add(similarity);\r
250         }\r
251 \r
252         double similarity = 0d;\r
253         for(Double d: list){\r
254             if(d.doubleValue() != Double.NaN){\r
255                 similarity += d.doubleValue();\r
256             }\r
257         }\r
258         operationDone(OperationType.COMPARE_DETAIL_BIRTHMARKS);\r
259         return similarity / count;\r
260     }\r
261 \r
262     private BirthmarkExtractor[] createExtractors(String[] birthmarkTypes, BirthmarkContext context){\r
263         List<BirthmarkExtractor> list = new ArrayList<BirthmarkExtractor>();\r
264         for(String type: birthmarkTypes){\r
265             BirthmarkExtractor extractor = createExtractor(type, context);\r
266             list.add(extractor);\r
267         }\r
268         return list.toArray(new BirthmarkExtractor[list.size()]);\r
269     }\r
270 \r
271     private BirthmarkExtractor createExtractor(String birthmarkType, BirthmarkContext context){\r
272         BirthmarkSpi spi = context.getService(birthmarkType);\r
273         if(spi != null){\r
274             BirthmarkExtractor extractor = spi.getExtractor();\r
275             try{\r
276                 Map props = BeanUtils.describe(extractor);\r
277                 props.remove("class");\r
278                 props.remove("provider");\r
279                 for(Object keyObject: props.keySet()){\r
280                     String key = "extractor." + spi.getType() + "." + String.valueOf(keyObject);\r
281                     if(context.getProperty(key) != null){\r
282                         BeanUtils.setProperty(extractor, (String)keyObject, context.getProperty(key));\r
283                     }\r
284                 }\r
285             } catch(InvocationTargetException e){\r
286                 throw new InternalError(e.getMessage());\r
287             } catch(NoSuchMethodException e){\r
288                 throw new InternalError(e.getMessage());\r
289             } catch(IllegalAccessException e){\r
290                 throw new InternalError(e.getMessage());\r
291             }\r
292             return extractor;\r
293         }\r
294         return null;\r
295     }\r
296 \r
297     private byte[] inputStreamToByteArray(InputStream in) throws IOException{\r
298         ByteArrayOutputStream bout = new ByteArrayOutputStream();\r
299         int read;\r
300         byte[] dataBuffer = new byte[512];\r
301         while((read = in.read(dataBuffer, 0, dataBuffer.length)) != -1){\r
302             bout.write(dataBuffer, 0, read);\r
303         }\r
304         byte[] data = bout.toByteArray();\r
305 \r
306         bout.close();\r
307         return data;\r
308     }\r
309 \r
310     private void initConfiguration(InputStream in){\r
311         try {\r
312             ConfigFileImporter parser = new ConfigFileImporter(defaultContext);\r
313             parser.parse(in);\r
314         } catch(IOException e){\r
315             throw new ApplicationInitializationError(e);\r
316         }\r
317         for(Iterator<BirthmarkSpi> i = ServiceRegistry.lookupProviders(BirthmarkSpi.class); i.hasNext(); ){\r
318             BirthmarkSpi service = i.next();\r
319             defaultContext.addService(service);\r
320         }\r
321         configDone = true;\r
322     }\r
323 \r
324     private void operationStart(OperationType type){\r
325         if(type != OperationType.CONFIGURATION && !configDone){\r
326             configuration();\r
327         }\r
328         if(warnings == null){\r
329             warnings = new WarningMessages(type);\r
330             fireEvent(new OperationEvent(OperationStage.OPERATION_START, type, warnings));\r
331         }\r
332         stack.push(warnings);\r
333         fireEvent(new OperationEvent(OperationStage.SUB_OPERATION_START, type, warnings));\r
334     }\r
335 \r
336     private void operationDone(OperationType type){\r
337         fireEvent(new OperationEvent(OperationStage.SUB_OPERATION_DONE, type, warnings));\r
338         stack.pop();\r
339         if(stack.size() == 0){\r
340             fireEvent(new OperationEvent(OperationStage.OPERATION_DONE, type, warnings));\r
341             warnings = null;\r
342         }\r
343     }\r
344 \r
345     private void fireEvent(OperationEvent e){\r
346         for(OperationListener listener: listeners){\r
347             switch(e.getStage()){\r
348             case OPERATION_START:\r
349                 listener.operationStart(e);\r
350                 break;\r
351             case SUB_OPERATION_START:\r
352                 listener.subOperationStart(e);\r
353                 break;\r
354             case SUB_OPERATION_DONE:\r
355                 listener.subOperationDone(e);\r
356                 break;\r
357             case OPERATION_DONE:\r
358                 listener.operationDone(e);\r
359                 break;\r
360             default:\r
361                 throw new InternalError("unknown stage: " + e.getStage());\r
362             }\r
363         }\r
364     }\r
365 \r
366     private BirthmarkSet[] extractImpl(String[] birthmarks, String[] files, BirthmarkContext context) throws IOException, BirthmarkExtractionException{\r
367         ClassFileArchive[] archives = createArchives(files, context);\r
368         BirthmarkExtractor[] extractors = createExtractors(birthmarks, context);\r
369         ExtractionUnit unit = context.getExtractionUnit();\r
370 \r
371         if(unit == ExtractionUnit.CLASS){\r
372             return extractFromClass(archives, extractors, context);\r
373         }\r
374         else if(unit == ExtractionUnit.PACKAGE){\r
375             return extractFromPackage(archives, extractors, context);\r
376         }\r
377         else if(unit == ExtractionUnit.ARCHIVE){\r
378             return extractFromProduct(archives, extractors, context);\r
379         }\r
380         return null;\r
381     }\r
382 \r
383     private BirthmarkSet[] extractFromPackage(ClassFileArchive[] archives, BirthmarkExtractor[] extractors, BirthmarkContext context) throws IOException, BirthmarkExtractionException{\r
384         Map<String, BirthmarkSet> list = new HashMap<String, BirthmarkSet>();\r
385 \r
386         for(ClassFileArchive archive: archives){\r
387             for(ClassFileEntry entry: archive){\r
388                 try{\r
389                     String name = entry.getClassName();\r
390                     String packageName = parsePackageName(name);\r
391                     BirthmarkSet bs = list.get(packageName);\r
392                     if(bs == null){\r
393                         bs = new BirthmarkSet(packageName, archive.getLocation());\r
394                         list.put(packageName, bs);\r
395                     }\r
396 \r
397                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());\r
398                     for(BirthmarkExtractor extractor: extractors){\r
399                         if(extractor.isAcceptable(ExtractionUnit.PACKAGE)){\r
400                             Birthmark b = bs.getBirthmark(extractor.getProvider().getType());\r
401                             if(b == null){\r
402                                 b = extractor.createBirthmark();\r
403                                 bs.addBirthmark(b);\r
404                             }\r
405                             extractor.extract(b, new ByteArrayInputStream(data), context);\r
406                         }\r
407                     }\r
408                 } catch(IOException e){\r
409                     warnings.addMessage(e, archive.getName());\r
410                 }\r
411             }\r
412         }\r
413 \r
414         return list.values().toArray(new BirthmarkSet[list.size()]);\r
415     }\r
416 \r
417     private String parsePackageName(String name){\r
418         String n = name.replace('/', '.');\r
419         int index = n.lastIndexOf('.');\r
420         if(index > 0){\r
421             n = n.substring(0, index - 1);\r
422         }\r
423 \r
424         return n;\r
425     }\r
426 \r
427     private BirthmarkSet[] extractFromClass(ClassFileArchive[] archives, BirthmarkExtractor[] extractors, BirthmarkContext context) throws IOException, BirthmarkExtractionException{\r
428         List<BirthmarkSet> list = new ArrayList<BirthmarkSet>();\r
429 \r
430         for(ClassFileArchive archive: archives){\r
431             for(ClassFileEntry entry: archive){\r
432                 try{\r
433                     BirthmarkSet birthmarkset = new BirthmarkSet(entry.getClassName(), entry.getLocation());\r
434                     list.add(birthmarkset);\r
435                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());\r
436                     for(BirthmarkExtractor extractor: extractors){\r
437                         if(extractor.isAcceptable(ExtractionUnit.CLASS)){\r
438                             Birthmark b = extractor.extract(new ByteArrayInputStream(data), context);\r
439                             birthmarkset.addBirthmark(b);\r
440                         }\r
441                     }\r
442                 } catch(IOException e){\r
443                     warnings.addMessage(e, entry.getClassName());\r
444                 }\r
445             }\r
446         }\r
447         return list.toArray(new BirthmarkSet[list.size()]);\r
448     }\r
449 \r
450     private BirthmarkSet[] extractFromProduct(ClassFileArchive[] archives, BirthmarkExtractor[] extractors, BirthmarkContext context) throws IOException, BirthmarkExtractionException{\r
451         List<BirthmarkSet> list = new ArrayList<BirthmarkSet>();\r
452 \r
453         for(ClassFileArchive archive: archives){\r
454             BirthmarkSet birthmarkset = new BirthmarkSet(archive.getName(), archive.getLocation());\r
455             list.add(birthmarkset);\r
456 \r
457             for(ClassFileEntry entry: archive){\r
458                 try{\r
459                     byte[] data = inputStreamToByteArray(entry.getLocation().openStream());\r
460                     for(BirthmarkExtractor extractor: extractors){\r
461                         if(extractor.isAcceptable(ExtractionUnit.ARCHIVE)){\r
462                             Birthmark b = birthmarkset.getBirthmark(extractor.getProvider().getType());\r
463                             if(b == null){\r
464                                 b = extractor.createBirthmark();\r
465                                 birthmarkset.addBirthmark(b);\r
466                             }\r
467                             extractor.extract(b, new ByteArrayInputStream(data), context);\r
468                         }\r
469                     }\r
470                 } catch(IOException e){\r
471                     warnings.addMessage(e, entry.getClassName());\r
472                 }\r
473             }\r
474         }\r
475         for(Iterator<BirthmarkSet> i = list.iterator(); i.hasNext(); ){\r
476             BirthmarkSet set = i.next();\r
477             if(set.getBirthmarksCount() == 0){\r
478                 i.remove();\r
479             }\r
480         }\r
481 \r
482         return list.toArray(new BirthmarkSet[list.size()]);\r
483     }\r
484 \r
485     private ClassFileArchive[] createArchives(String[] files, BirthmarkContext context) throws IOException, MalformedURLException{\r
486         ClasspathContext bytecode = context.getClasspathContext();\r
487         List<ClassFileArchive> archives = new ArrayList<ClassFileArchive>();\r
488         for(int i = 0; i < files.length; i++){\r
489             try{\r
490                 if(files[i].endsWith(".class")){\r
491                     archives.add(new DefaultClassFileArchive(files[i]));\r
492                 }\r
493                 else if(files[i].endsWith(".jar") || files[i].endsWith(".zip")){\r
494                     archives.add(new JarClassFileArchive(files[i]));\r
495                     bytecode.addClasspath(new File(files[i]).toURI().toURL());\r
496                 }\r
497                 else if(files[i].endsWith(".war")){\r
498                     archives.add(new WarClassFileArchive(files[i]));\r
499                 }\r
500             } catch(IOException e){\r
501                 warnings.addMessage(e, files[i]);\r
502             }\r
503         }\r
504         return archives.toArray(new ClassFileArchive[archives.size()]);\r
505     }\r
506 }\r