Converted jetty-util to be a multi release jar
Added org/eclipse/jetty/util/MultiReleaseJarFile as botha  java8 and java9 version
deprecated jarScanner
updated AnnotationParser to use MultiReleaseJarFile
This commit is contained in:
Greg Wilkins 2017-09-19 13:40:59 +10:00
parent e00182323b
commit 5de895af88
6 changed files with 342 additions and 110 deletions

View File

@ -29,11 +29,13 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiReleaseJarFile;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@ -764,36 +766,10 @@ public class AnnotationParser
* @param resolver the class name resolver
* @throws Exception if unable to parse
*/
public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
throws Exception
@Deprecated
public void parse (final Set<? extends Handler> handlers, ClassLoader loader, boolean visitParents, boolean nullInclusive) throws Exception
{
if (loader==null)
return;
if (!(loader instanceof URLClassLoader))
return; //can't extract classes?
final MultiException me = new MultiException();
JarScanner scanner = new JarScanner()
{
@Override
public void processEntry(URI jarUri, JarEntry entry)
{
try
{
parseJarEntry(handlers, Resource.newResource(jarUri), entry, resolver);
}
catch (Exception e)
{
me.add(new RuntimeException("Error parsing entry "+entry.getName()+" from jar "+ jarUri, e));
}
}
};
scanner.scan(null, loader, nullInclusive, visitParents);
me.ifExceptionThrow();
throw new UnsupportedOperationException();
}
@ -905,33 +881,20 @@ public class AnnotationParser
{
if (LOG.isDebugEnabled()) {LOG.debug("Scanning jar {}", jarResource);};
//treat it as a jar that we need to open and scan all entries from
InputStream in = jarResource.getInputStream();
if (in==null)
return;
MultiException me = new MultiException();
JarInputStream jar_in = new JarInputStream(in);
try
{
JarEntry entry = jar_in.getNextJarEntry();
while (entry!=null)
{
try
{
parseJarEntry(handlers, jarResource, entry, resolver);
}
catch (Exception e)
{
me.add(new RuntimeException("Error scanning entry "+entry.getName()+" from jar "+jarResource, e));
}
entry = jar_in.getNextJarEntry();
}
}
catch (Exception e)
JarFile jarFile = MultiReleaseJarFile.open(jarResource.getFile());
MultiReleaseJarFile.stream(jarFile).forEach(e->
{
me.add(new RuntimeException("Error scanning jar "+jarResource, e));
}
try
{
parseJarEntry(handlers, jarResource, jarFile, e,resolver);
}
catch (Exception ex)
{
me.add(new RuntimeException("Error scanning entry " + e.getName() + " from jar " + jarResource, ex));
}
});
finally
{
jar_in.close();
@ -945,15 +908,14 @@ public class AnnotationParser
* Parse a single entry in a jar file
*
* @param handlers the handlers to look for classes in
* @param jar the jar resource to parse
* @param jarFile the jar resource being parses
* @param entry the entry in the jar resource to parse
* @param resolver the class name resolver
* @throws Exception if unable to parse
*/
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarEntry entry, final ClassNameResolver resolver)
throws Exception
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarFile jarFile, JarEntry entry,final ClassNameResolver resolver) throws Exception
{
if (jar == null || entry == null)
if (jarFile == null || entry == null)
return;
//skip directories
@ -973,7 +935,7 @@ public class AnnotationParser
{
Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
try (InputStream is = clazz.getInputStream())
try (InputStream is = jarFile.getInputStream(entry))
{
scanClass(handlers, jar, is);
}
@ -1055,57 +1017,6 @@ public class AnnotationParser
if (path == null || path.length()==0)
return false;
if (path.startsWith("META-INF/versions/"))
{
// Handle JEP 238 - Multi-Release Jars
if (JVM_MAJOR_VER < 9)
{
if (LOG.isDebugEnabled())
{
LOG.debug("JEP-238 Multi-Release JAR not supported on Java " +
System.getProperty("java.version") + ": " + path);
}
return false;
}
// Safety check for ASM bytecode support level.
// When ASM 6.0 is integrated, the below will start to work.
if (ASM_OPCODE_VERSION <= Opcodes.ASM5)
{
// Cannot scan Java 9 classes with ASM version 5
if (LOG.isDebugEnabled())
{
LOG.debug("Unable to scan newer Java bytecode (Java 9?) with ASM 5 (skipping): " + path);
}
return false;
}
int idxStart = "META-INF/versions/".length();
int idxEnd = path.indexOf('/', idxStart + 1);
try
{
int pathVersion = Integer.parseInt(path.substring(idxStart, idxEnd));
if (pathVersion < JVM_MAJOR_VER)
{
if (LOG.isDebugEnabled())
{
LOG.debug("JEP-238 Multi-Release JAR version " + pathVersion +
" not supported on Java " + System.getProperty("java.version") +
": " + path);
}
return false;
}
}
catch (NumberFormatException e)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Not a valid JEP-238 Multi-Release path: " + path);
}
return false;
}
}
// skip any classfiles that are in a hidden directory
if (path.startsWith(".") || path.contains("/."))
{

View File

@ -11,6 +11,8 @@
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.util</bundle-symbolic-name>
<java9.sourceDirectory>${project.basedir}/src/main/java9</java9.sourceDirectory>
<java9.build.outputDirectory>${project.build.directory}/classes-java9</java9.build.outputDirectory>
</properties>
<build>
<plugins>
@ -57,4 +59,69 @@
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>jdk9 MR Jar</id>
<activation>
<jdk>[1.9,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>compile-java9</id>
<phase>compile</phase>
<configuration>
<tasks>
<mkdir dir="${java9.build.outputDirectory}" />
<javac target="1.9" srcdir="${java9.sourceDirectory}" destdir="${java9.build.outputDirectory}"
classpath="${project.build.directory}/classes" includeantruntime="false" />
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
<resources>
<resource>
<directory>${java9.build.outputDirectory}</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,109 @@
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import java.io.File;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/**
* <p>Utility class to create a stream of Multi Release {@link JarEntry}s</p>
* <p>This is the java 8 version of this class.
* A java 9 version of this class is included as a Multi Release class in the
* jetty-util jar, that uses java 9 APIs to correctly handle Multi Release jars.</p>
*/
public class MultiReleaseJarFile
{
private static final String META_INF_VERSIONS = "META-INF/versions/";
public static JarFile open(File file) throws IOException
{
return new JarFile(file);
}
public static Stream<VersionedJarEntry> streamVersioned(JarFile jf)
{
return jf.stream()
.map(VersionedJarEntry::new);
}
public static Stream<JarEntry> stream(JarFile jf)
{
// Java 8 version of this class, ignores all versioned entries.
return streamVersioned(jf)
.filter(e->!e.isVersioned())
.map(e->e.resolve(jf));
}
public static class VersionedJarEntry
{
final JarEntry entry;
final String name;
final int version;
VersionedJarEntry(JarEntry entry)
{
int v = 0;
String name = entry.getName();
if (name.startsWith(META_INF_VERSIONS))
{
v=-1;
int index = name.indexOf('/', META_INF_VERSIONS.length());
if (index >= 0 && index < name.length())
{
try
{
v = Integer.parseInt(name.substring(META_INF_VERSIONS.length(), index), 10);
name = name.substring(index + 1);
}
catch (NumberFormatException x)
{
}
}
}
this.entry = entry;
this.name = name;
this.version = v;
}
public int version()
{
return version;
}
public boolean isVersioned()
{
return version > 0;
}
@Override
public String toString()
{
return entry.toString() + (version==0?"[base]":("["+version+"]"));
}
public JarEntry resolve(JarFile jf)
{
return entry;
}
}
}

View File

@ -0,0 +1,143 @@
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/**
* <p>Utility class to create a stream of Multi Release {@link JarEntry}s</p>
* <p>This is the java 9 version of this class.
* A java 8 version of this class is included as the base version in a Multi Release
* jetty-util jar, that uses does not use java 9 APIs.</p>
*/
public class MultiReleaseJarFile
{
private static final String META_INF_VERSIONS = "META-INF/versions/";
public static JarFile open(File file) throws IOException
{
return new JarFile(file,true,JarFile.OPEN_READ,Runtime.version());
}
public static Stream<VersionedJarEntry> streamVersioned(JarFile jf)
{
return jf.stream()
.map(VersionedJarEntry::new);
}
public static Stream<JarEntry> stream(JarFile jf)
{
if (!jf.isMultiRelease())
return jf.stream();
// Map to hold unversioned name to VersionedJarEntry
Map<String,VersionedJarEntry> entries = new TreeMap<>();
// Fill the map, removing non applicable entries and replacing versions with later versions.
streamVersioned(jf)
.filter(e->e.isApplicable(jf.getVersion().major()))
.forEach(e->entries.compute(e.name, (k, v) -> v==null || v.isReplacedBy(e) ? e : v));
// filter the values to remove non applicable inner classes and map to versioned entry
return entries.values().stream()
.filter(e-> {
if (!e.inner)
return true;
VersionedJarEntry outer = entries.get(e.outer);
return outer != null && outer.version == e.version;
})
.map(e->e.resolve(jf));
}
public static class VersionedJarEntry
{
final JarEntry entry;
final String name;
final int version;
final boolean inner;
final String outer;
VersionedJarEntry(JarEntry entry)
{
int v = 0;
String name = entry.getName();
if (name.startsWith(META_INF_VERSIONS))
{
v=-1;
int index = name.indexOf('/', META_INF_VERSIONS.length());
if (index >= 0 && index < name.length())
{
try
{
v = Integer.parseInt(name, META_INF_VERSIONS.length(), index, 10);
name = name.substring(index + 1);
}
catch (NumberFormatException x)
{
}
}
}
this.entry = entry;
this.name = name;
this.version = v;
this.inner = name.contains("$") && name.toLowerCase().endsWith(".class");
this.outer = inner?name.substring(0,name.indexOf('$'))+name.substring(name.length()-6,name.length()):null;
}
public int version()
{
return version;
}
public boolean isVersioned()
{
return version > 0;
}
boolean isApplicable(int version)
{
return this.version>=0 && this.version <= version;
}
boolean isReplacedBy(VersionedJarEntry entry)
{
return this.name.equals(entry.name) && entry.version>version;
}
@Override
public String toString()
{
return entry.toString() + (version==0?"[base]":("["+version+"]"));
}
public JarEntry resolve(JarFile jf)
{
if (version>0)
return jf.getJarEntry(name);
return entry;
}
}
}

Binary file not shown.

View File

@ -43,7 +43,9 @@ import org.eclipse.jetty.util.resource.Resource;
* Subclasses should implement the processEntry(URL jarUrl, JarEntry entry)
* method to handle entries in jar files whose names match the supplied
* pattern.
* @deprecated Does not handle MR Jars
*/
@Deprecated()
public abstract class JarScanner extends org.eclipse.jetty.util.PatternMatcher
{
private static final Logger LOG = Log.getLogger(JarScanner.class);