We ran into this problem, where we have a closed-set class checker and it has a 
problem processing MR jar files.

I recommend replacing all inner classes if the ordinary class is versioned.  If 
the inner class goes away, you would need to stub it so a versioned copy 
exists.  That is the convention we have adopted for our project but  that is 
not currently a universal convention (and I believe that we saw one MR jar that 
didn't follow this convention - maybe the new JAXB jar?).

If you use the JDK9 API to scan classes in a jar file for version 9, you will 
get META-INF/versions/9/org/example/Foo.class  and org/example/Foo$Bar.class. 

This is the code that we use to read the classes.

>         // the JDK9 mode.  This is accomplished by supplying the "base 
> version" to the constructor.
>         Method baseVersionMethod = null;
>         try {
>           baseVersionMethod = JarFile.class.getMethod("baseVersion");
>         } catch (NoSuchMethodException nsme) {}
>
>         Object baseVersion = null;
>         if (baseVersionMethod != null) {
>           try {
>             baseVersion = baseVersionMethod.invoke(null);
>           } catch (IllegalAccessException | IllegalArgumentException | 
> InvocationTargetException e) {
>             throw new RuntimeException(e);
>           }
>         }
>
>         Constructor<?> jarFileConstructor = null;
>         if (baseVersion != null) {
>           try {
>             jarFileConstructor = JarFile.class.getConstructor(File.class, 
> boolean.class, int.class, baseVersion.getClass());
>           } catch (NoSuchMethodException | SecurityException e) {
>             throw new RuntimeException(e);
>           }
>         }
>
>         JarFile jarFile;
>         if (jarFileConstructor != null) {
>           try {
>             jarFile = (JarFile) jarFileConstructor.newInstance(new 
> File(filePath), Boolean.TRUE, ZipFile.OPEN_READ, baseVersion);
>           } catch (InvocationTargetException i) {
>             Throwable t = i.getCause();
>             if (t instanceof IOException)
>               throw (IOException) t;
>             throw new RuntimeException(t);
>           } catch (InstantiationException | IllegalAccessException | 
> IllegalArgumentException e) {
>             throw new RuntimeException(e);
>           }
>         } else {
>           jarFile = new JarFile(filePath);
>         }

In our case when processing a MR jar, we assume that if a class is versioned, 
we will ignore all related classes (ordinary and inner).
We need to keep a list of all classes and not process any of them until we get 
the full list.
That is, we post process the class list so that we drop ordinary classes and 
inner classes if there is versioned replacement of any of them.
We avoid false failures by taking this approach (we ignore inner classes for 
older releases that reference a class that might no longer exist).

We know that this is not purely correct.  It's possible that the versioned 
replacement class references the non-versioned inner class. 
I think that the correct way would be to see if the inner class is referenced 
by any class file but that isn't doable so you will need to choose a heuristic.

In the case of annotation processing, it seems the heuristic should be to 
include the non-versioned inner class since it could be referenced by another 
class somewhere. That should be simple since you don't need to wait to process 
files.




-----Original Message-----
From: Greg Wilkins [mailto:gr...@webtide.com] 
Sent: Wednesday, September 13, 2017 6:12 PM
To: jigsaw-dev@openjdk.java.net
Subject: Scanning multi version jars?

I hope this is the right group for this question. please redirect me if not.

The Jetty project is trying to implement annotation scanning for multi version 
jars and have some concerns with some edge cases, specifically with inner 
classes.

A multi versioned jar might contain something like:

   - org/example/Foo.class
   - org/example/Foo$Bar.class
   - META-INF/versions/9/org/example/Foo.class

It is clear that there is a java 9 version of Foo.  But what is unclear is the 
inner class Foo$Bar?  Is that only used by the base Foo version? or does the 
java 9 version also use the Foo$Bar inner class, but it didn't use any java 9 
features, so the base version is able to be used??

It is unclear from just an index of the jar if we should be scanning Foo$Bar 
for annotations.  So currently it appears that we have to actually scan the Foo 
class to see if Foo$Bar is referenced and only then scan Foo$Bar for 
annotations (and recursive analysis for any Foo$Bar$Bob class )!

An alternative would be if there was an enforced convention that any versioned 
class would also version all of it's inner classes - which may be a reasonable 
assumption given that they would be compiled together, but we see nothing in 
the specifications that force a jar to be assembled that way.

Any guidance anybody can give would be helpful.

cheers
Greg Wilkins

PS. The JarFile.getEntry method does not appear to respect it's javadoc with 
respect to multiversioned jars: it says it will do a search for the most recent 
version, however the code indicates that the search is only done if the base 
version does not exist.  This is kind of separate issue, but makes it difficult 
to defer the behaviour of what to scan to the implementation in JarFile






--
Greg Wilkins <gr...@webtide.com> CTO http://webtide.com

Reply via email to