13
13
import japicmp .model .JavaObjectSerializationCompatibility ;
14
14
import japicmp .output .OutputFilter ;
15
15
import japicmp .util .AnnotationHelper ;
16
+ import java .util .function .Supplier ;
16
17
import javassist .ClassPool ;
17
18
import javassist .CtClass ;
18
19
import javassist .NotFoundException ;
19
20
20
21
import java .io .File ;
21
22
import java .io .IOException ;
22
- import java .util . ArrayList ;
23
+ import java .io . InputStream ;
23
24
import java .util .Collections ;
24
25
import java .util .Enumeration ;
26
+ import java .util .Iterator ;
25
27
import java .util .LinkedList ;
26
28
import java .util .List ;
27
29
import java .util .jar .JarEntry ;
36
38
*/
37
39
public class JarArchiveComparator {
38
40
private static final Logger LOGGER = Logger .getLogger (JarArchiveComparator .class .getName ());
39
- private ClassPool commonClassPool ;
40
- private ClassPool oldClassPool ;
41
- private ClassPool newClassPool ;
41
+ private ReducibleClassPool commonClassPool ;
42
+ private ReducibleClassPool oldClassPool ;
43
+ private ReducibleClassPool newClassPool ;
42
44
private String commonClassPathAsString = "" ;
43
45
private String oldClassPathAsString = "" ;
44
46
private String newClassPathAsString = "" ;
@@ -102,12 +104,12 @@ private void checkJavaObjectSerializationCompatibility(List<JApiClass> jApiClass
102
104
103
105
private void setupClasspaths () {
104
106
if (this .options .getClassPathMode () == JarArchiveComparatorOptions .ClassPathMode .ONE_COMMON_CLASSPATH ) {
105
- commonClassPool = new ClassPool ();
107
+ commonClassPool = new ReducibleClassPool ();
106
108
commonClassPathAsString = setupClasspath (commonClassPool , this .options .getClassPathEntries ());
107
109
} else if (this .options .getClassPathMode () == JarArchiveComparatorOptions .ClassPathMode .TWO_SEPARATE_CLASSPATHS ) {
108
- oldClassPool = new ClassPool ();
110
+ oldClassPool = new ReducibleClassPool ();
109
111
oldClassPathAsString = setupClasspath (oldClassPool , this .options .getOldClassPath ());
110
- newClassPool = new ClassPool ();
112
+ newClassPool = new ReducibleClassPool ();
111
113
newClassPathAsString = setupClasspath (newClassPool , this .options .getNewClassPath ());
112
114
} else {
113
115
throw new JApiCmpException (Reason .IllegalState , "Unknown classpath mode: " + this .options .getClassPathMode ());
@@ -201,10 +203,8 @@ private List<JApiClass> createAndCompareClassLists(List<File> oldArchives, List<
201
203
* @return a list of {@link japicmp.model.JApiClass} that represent the changes
202
204
*/
203
205
List <JApiClass > compareClassLists (JarArchiveComparatorOptions options , List <CtClass > oldClasses , List <CtClass > newClasses ) {
204
- List <CtClass > oldClassesFiltered = applyFilter (options , oldClasses );
205
- List <CtClass > newClassesFiltered = applyFilter (options , newClasses );
206
206
ClassesComparator classesComparator = new ClassesComparator (this , options );
207
- classesComparator .compare (oldClassesFiltered , newClassesFiltered );
207
+ classesComparator .compare (oldClasses , newClasses );
208
208
List <JApiClass > classList = classesComparator .getClasses ();
209
209
if (LOGGER .isLoggable (Level .FINE )) {
210
210
for (JApiClass jApiClass : classList ) {
@@ -217,55 +217,53 @@ List<JApiClass> compareClassLists(JarArchiveComparatorOptions options, List<CtCl
217
217
return classList ;
218
218
}
219
219
220
- private List <CtClass > applyFilter (JarArchiveComparatorOptions options , List <CtClass > ctClasses ) {
221
- List <CtClass > newList = new ArrayList <>(ctClasses .size ());
222
- for (CtClass ctClass : ctClasses ) {
223
- if (options .getFilters ().includeClass (ctClass )) {
224
- newList .add (ctClass );
225
- }
226
- }
227
- return newList ;
220
+ private List <CtClass > createListOfCtClasses (List <File > archives , ReducibleClassPool classPool ) {
221
+ return createListOfCtClasses (() -> new JarsCtClassIterable (archives , classPool ), classPool );
222
+ }
223
+
224
+ List <CtClass > createListOfCtClasses (Supplier <Iterable <CtClass >> ctClasses , ReducibleClassPool classPool ) {
225
+ return loadAndFilterClasses (ctClasses , classPool , false );
228
226
}
229
227
230
- private List <CtClass > createListOfCtClasses (List <File > archives , ClassPool classPool ) {
228
+ private List <CtClass > loadAndFilterClasses (Supplier <Iterable <CtClass >> ctClasses , ReducibleClassPool classPool , boolean ignorePackageFilters ) {
229
+ // marks whether any package was found
230
+ // if so we need to go over _all_ classes again
231
+ boolean packageFilterEncountered = false ;
232
+
231
233
List <CtClass > classes = new LinkedList <>();
232
- for (File archive : archives ) {
233
- if (LOGGER .isLoggable (Level .FINE )) {
234
- LOGGER .fine ("Loading classes from jar file '" + archive .getAbsolutePath () + "'" );
235
- }
236
- try (JarFile jarFile = new JarFile (archive )) {
237
- Enumeration <JarEntry > entryEnumeration = jarFile .entries ();
238
- while (entryEnumeration .hasMoreElements ()) {
239
- JarEntry jarEntry = entryEnumeration .nextElement ();
240
- String name = jarEntry .getName ();
241
- if (name .endsWith (".class" )) {
242
- CtClass ctClass ;
243
- try {
244
- ctClass = classPool .makeClass (jarFile .getInputStream (jarEntry ));
245
- } catch (Exception e ) {
246
- throw new JApiCmpException (Reason .IoException , String .format ("Failed to load file from jar '%s' as class file: %s." , name , e .getMessage ()), e );
247
- }
248
- classes .add (ctClass );
249
- if (LOGGER .isLoggable (Level .FINE )) {
250
- LOGGER .fine (String .format ("Adding class '%s' with jar name '%s' to list." , ctClass .getName (), name ));
251
- }
252
- if (name .endsWith ("package-info.class" )) {
253
- updatePackageFilter (ctClass );
254
- }
255
- } else {
256
- if (LOGGER .isLoggable (Level .FINE )) {
257
- LOGGER .fine (String .format ("Skipping file '%s' because filename does not end with '.class'." , name ));
258
- }
234
+ for (CtClass ctClass : ctClasses .get ()) {
235
+ if (!packageFilterEncountered ) {
236
+ if (options .getFilters ().includeClass (ctClass )) {
237
+ classes .add (ctClass );
238
+ if (LOGGER .isLoggable (Level .FINE )) {
239
+ LOGGER .fine (String .format ("Adding class '%s' with jar name '%s' to list." , ctClass .getName (), ctClass .getName ()));
240
+ }
241
+ } else {
242
+ classPool .remove (ctClass );
243
+ if (LOGGER .isLoggable (Level .FINE )) {
244
+ LOGGER .fine (String .format ("Ignoring class '%s' with jar name '%s'." , ctClass .getName (), ctClass .getName ()));
259
245
}
260
246
}
261
- } catch (IOException e ) {
262
- throw new JApiCmpException (Reason .IoException , String .format ("Processing of jar file %s failed: %s" , archive .getAbsolutePath (), e .getMessage ()), e );
247
+ }
248
+ if (!ignorePackageFilters && ctClass .getName ().endsWith ("package-info" )) {
249
+ packageFilterEncountered |= updatePackageFilter (ctClass );
250
+ if (packageFilterEncountered ) {
251
+ // we found a package filter, so any filtering we did so far may be invalid
252
+ // reset everything and restart after having read all remaining filters
253
+ classes .forEach (classPool ::remove );
254
+ classes .clear ();
255
+ }
263
256
}
264
257
}
265
- return classes ;
258
+
259
+ return packageFilterEncountered
260
+ ? loadAndFilterClasses (ctClasses , classPool , true )
261
+ : classes ;
266
262
}
267
263
268
- private void updatePackageFilter (CtClass ctClass ) {
264
+
265
+ private boolean updatePackageFilter (CtClass ctClass ) {
266
+ boolean filtersUpdated = false ;
269
267
Filters filters = options .getFilters ();
270
268
List <Filter > newFilters = new LinkedList <>();
271
269
for (Filter filter : filters .getIncludes ()) {
@@ -279,6 +277,7 @@ private void updatePackageFilter(CtClass ctClass) {
279
277
if (newFilters .size () > 0 ) {
280
278
filters .getIncludes ().addAll (newFilters );
281
279
newFilters .clear ();
280
+ filtersUpdated = true ;
282
281
}
283
282
for (Filter filter : filters .getExcludes ()) {
284
283
if (filter instanceof AnnotationFilterBase ) {
@@ -291,7 +290,9 @@ private void updatePackageFilter(CtClass ctClass) {
291
290
if (newFilters .size () > 0 ) {
292
291
filters .getExcludes ().addAll (newFilters );
293
292
newFilters .clear ();
293
+ filtersUpdated = true ;
294
294
}
295
+ return filtersUpdated ;
295
296
}
296
297
297
298
/**
@@ -309,7 +310,7 @@ public JarArchiveComparatorOptions getJarArchiveComparatorOptions() {
309
310
*
310
311
* @return an instance of ClassPool
311
312
*/
312
- public ClassPool getCommonClassPool () {
313
+ public ReducibleClassPool getCommonClassPool () {
313
314
return commonClassPool ;
314
315
}
315
316
@@ -377,4 +378,108 @@ public Optional<CtClass> loadClass(ArchiveType archiveType, String name) {
377
378
}
378
379
return loadedClass ;
379
380
}
381
+
382
+ private static class JarsCtClassIterable implements Iterable <CtClass >, Iterator <CtClass > {
383
+ private final Iterator <File > archives ;
384
+ private final ClassPool classPool ;
385
+
386
+ private Iterator <CtClass > currentIterator = null ;
387
+
388
+ public JarsCtClassIterable (List <File > archives , ClassPool classPool ) {
389
+ this .archives = archives .iterator ();
390
+ this .classPool = classPool ;
391
+ }
392
+
393
+ @ Override
394
+ public boolean hasNext () {
395
+ if (currentIterator != null ) {
396
+ if (currentIterator .hasNext ()) {
397
+ return true ;
398
+ } else {
399
+ currentIterator = null ;
400
+ }
401
+ }
402
+ if (archives .hasNext ()) {
403
+ final File archive = archives .next ();
404
+ currentIterator = new JarCtClassIterator (archive , classPool );
405
+ return hasNext ();
406
+ }
407
+ return false ;
408
+ }
409
+
410
+ @ Override
411
+ public CtClass next () {
412
+ return currentIterator .next ();
413
+ }
414
+
415
+ @ Override
416
+ public Iterator <CtClass > iterator () {
417
+ return this ;
418
+ }
419
+ }
420
+
421
+ private static class JarCtClassIterator implements Iterator <CtClass > {
422
+
423
+ private final File archive ;
424
+ private final JarFile jarFile ;
425
+ private final Enumeration <JarEntry > entryEnumeration ;
426
+ private final ClassPool classPool ;
427
+
428
+ private CtClass next = null ;
429
+
430
+ public JarCtClassIterator (File archive , ClassPool classPool ) {
431
+ this .archive = archive ;
432
+ try {
433
+ this .jarFile = new JarFile (archive );
434
+ } catch (IOException e ) {
435
+ throw new JApiCmpException (Reason .IoException , String .format ("Processing of jar file %s failed: %s" , archive .getAbsolutePath (), e .getMessage ()), e );
436
+ }
437
+ if (LOGGER .isLoggable (Level .FINE )) {
438
+ LOGGER .fine ("Loading classes from jar file '" + archive .getAbsolutePath () + "'" );
439
+ }
440
+ this .entryEnumeration = jarFile .entries ();
441
+ this .classPool = classPool ;
442
+ }
443
+
444
+ @ Override
445
+ public boolean hasNext () {
446
+ if (next != null ) {
447
+ return true ;
448
+ }
449
+
450
+ while (entryEnumeration .hasMoreElements ()) {
451
+ JarEntry jarEntry = entryEnumeration .nextElement ();
452
+ String name = jarEntry .getName ();
453
+ if (name .endsWith (".class" )) {
454
+ CtClass ctClass ;
455
+ try (InputStream classFile = jarFile .getInputStream (jarEntry )) {
456
+ ctClass = classPool .makeClass (classFile );
457
+ } catch (Exception e ) {
458
+ throw new JApiCmpException (Reason .IoException , String .format ("Failed to load file from jar '%s' as class file: %s." , name , e .getMessage ()), e );
459
+ }
460
+ next = ctClass ;
461
+ return true ;
462
+ } else {
463
+ if (LOGGER .isLoggable (Level .FINE )) {
464
+ LOGGER .fine (String .format ("Skipping file '%s' because filename does not end with '.class'." , name ));
465
+ }
466
+ }
467
+ }
468
+ try {
469
+ jarFile .close ();
470
+ } catch (IOException e ) {
471
+ throw new JApiCmpException (Reason .IoException , String .format ("Processing of jar file %s failed: %s" , archive .getAbsolutePath (), e .getMessage ()), e );
472
+ }
473
+ return false ;
474
+ }
475
+
476
+ @ Override
477
+ public CtClass next () {
478
+ try {
479
+ return next ;
480
+ } finally {
481
+ next = null ;
482
+ }
483
+ }
484
+ }
380
485
}
0 commit comments