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:[email protected]]
Sent: Wednesday, September 13, 2017 6:12 PM
To: [email protected]
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 <[email protected]> CTO http://webtide.com