Issue #1797 MR jar support in java8 code

This commit is contained in:
Greg Wilkins 2017-09-20 13:47:37 +10:00
parent 219b61f5b4
commit 50f861e8f2
7 changed files with 270 additions and 301 deletions

View File

@ -814,12 +814,13 @@ public class AnnotationParser
LOG.debug("Scanning jar {}", jarResource);
MultiException me = new MultiException();
JarFile jarFile = MultiReleaseJarFile.open(jarResource.getFile());
MultiReleaseJarFile.stream(jarFile).forEach(e->
// TODO do not force version 8 once ASM can scan 9
MultiReleaseJarFile jarFile = new MultiReleaseJarFile(jarResource.getFile(),8,false);
jarFile.stream().forEach(e->
{
try
{
parseJarEntry(handlers, jarResource, jarFile, e);
parseJarEntry(handlers, jarResource, e);
}
catch (Exception ex)
{
@ -836,13 +837,12 @@ public class AnnotationParser
* Parse a single entry in a jar file
*
* @param handlers the handlers to look for classes in
* @param jarFile the jar resource being parses
* @param entry the entry in the jar resource to parse
* @param entry the entry in the potentially MultiRelease jar resource to parse
* @throws Exception if unable to parse
*/
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, JarFile jarFile, JarEntry entry) throws Exception
protected void parseJarEntry (Set<? extends Handler> handlers, Resource jar, MultiReleaseJarFile.VersionedJarEntry entry) throws Exception
{
if (jarFile == null || entry == null)
if (jar == null || entry == null)
return;
//skip directories
@ -855,11 +855,10 @@ public class AnnotationParser
if (isValidClassFileName(name) && isValidClassFilePath(name))
{
String shortName = name.replace('/', '.').substring(0,name.length()-6);
Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
addParsedClass(shortName, clazz);
addParsedClass(shortName, Resource.newResource("jar:"+jar.getURI()+"!/"+entry.getNameInJar()));
if (LOG.isDebugEnabled())
LOG.debug("Scanning class from jar {}", clazz);
try (InputStream is = jarFile.getInputStream(entry))
LOG.debug("Scanning class from jar {}!/{}", jar, entry);
try (InputStream is = entry.getInputStream())
{
scanClass(handlers, jar, is);
}

View File

@ -11,8 +11,6 @@
<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>
@ -72,69 +70,4 @@
<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

@ -20,8 +20,14 @@ package org.eclipse.jetty.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Stream;
/**
@ -34,30 +40,88 @@ public class MultiReleaseJarFile
{
private static final String META_INF_VERSIONS = "META-INF/versions/";
public static JarFile open(File file) throws IOException
private final JarFile jarFile;
private final int majorVersion;
private final boolean multiRelease;
/* Map to hold unversioned name to VersionedJarEntry */
private final Map<String,VersionedJarEntry> entries;
public MultiReleaseJarFile(File file) throws IOException
{
return new JarFile(file);
this(file,JavaVersion.VERSION.getMajor(),false);
}
public static Stream<VersionedJarEntry> streamVersioned(JarFile jf)
public MultiReleaseJarFile(File file, int majorVersion, boolean includeDirectories) throws IOException
{
return jf.stream()
.map(VersionedJarEntry::new);
if (file==null || !file.exists() || !file.canRead() || file.isDirectory())
throw new IllegalArgumentException("bad jar file: "+file);
jarFile = new JarFile(file,true,JarFile.OPEN_READ);
this.majorVersion = majorVersion;
Manifest manifest = jarFile.getManifest();
if (manifest==null)
multiRelease = false;
else
multiRelease = Boolean.valueOf(String.valueOf(manifest.getMainAttributes().getValue("Multi-Release")));
Map<String,VersionedJarEntry> map = new TreeMap<>();
jarFile.stream()
.map(VersionedJarEntry::new)
.filter(e->(includeDirectories||!e.isDirectory()) && e.isApplicable())
.forEach(e->map.compute(e.name, (k, v) -> v==null || v.isReplacedBy(e) ? e : v));
for (Iterator<Map.Entry<String,VersionedJarEntry>> i = map.entrySet().iterator();i.hasNext();)
{
Map.Entry<String,VersionedJarEntry> e = i.next();
VersionedJarEntry entry = e.getValue();
if (entry.inner)
{
VersionedJarEntry outer = map.get(entry.outer);
if (entry.outer==null || outer.version!= entry.version)
i.remove();
}
}
entries = Collections.unmodifiableMap(map);
}
public static Stream<JarEntry> stream(JarFile jf)
public boolean isMultiRelease()
{
// Java 8 version of this class, ignores all versioned entries.
return streamVersioned(jf)
.filter(e->!e.isVersioned())
.map(e->e.resolve(jf));
return multiRelease;
}
public static class VersionedJarEntry
public int getVersion()
{
return majorVersion;
}
public Stream<VersionedJarEntry> stream()
{
return entries.values().stream();
}
public VersionedJarEntry getEntry(String name)
{
return entries.get(name);
}
@Override
public String toString()
{
return String.format("%s[%b,%d]",jarFile.getName(),isMultiRelease(),getVersion());
}
public class VersionedJarEntry
{
final JarEntry entry;
final String name;
final int version;
final boolean inner;
final String outer;
VersionedJarEntry(JarEntry entry)
{
@ -65,17 +129,18 @@ public class MultiReleaseJarFile
String name = entry.getName();
if (name.startsWith(META_INF_VERSIONS))
{
v=-1;
v = -1;
int index = name.indexOf('/', META_INF_VERSIONS.length());
if (index >= 0 && index < name.length())
if (index > META_INF_VERSIONS.length() && index < name.length())
{
try
{
v = Integer.parseInt(name.substring(META_INF_VERSIONS.length(), index), 10);
v = TypeUtil.parseInt(name, META_INF_VERSIONS.length(), index - META_INF_VERSIONS.length(), 10);
name = name.substring(index + 1);
}
catch (NumberFormatException x)
{
throw new RuntimeException("illegal version in "+jarFile,x);
}
}
}
@ -83,9 +148,21 @@ public class MultiReleaseJarFile
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()
public String getName()
{
return name;
}
public String getNameInJar()
{
return entry.getName();
}
public int getVersion()
{
return version;
}
@ -95,15 +172,34 @@ public class MultiReleaseJarFile
return version > 0;
}
public boolean isDirectory()
{
return entry.isDirectory();
}
public InputStream getInputStream() throws IOException
{
return jarFile.getInputStream(entry);
}
boolean isApplicable()
{
if (multiRelease)
return this.version>=0 && this.version <= majorVersion && name.length()>0;
return this.version==0;
}
boolean isReplacedBy(VersionedJarEntry entry)
{
if (isDirectory())
return entry.version==0;
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)
{
return entry;
return String.format("%s->%s[%d]",name,entry.getName(),version);
}
}
}

View File

@ -1,143 +0,0 @@
//
// ========================================================================
// 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;
}
}
}

View File

@ -0,0 +1,92 @@
//
// ========================================================================
// 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.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@RunWith(AdvancedRunner.class)
public class MultiReleaseJarFileTest
{
private File testResources = MavenTestingUtils.getTestResourcesDir().getAbsoluteFile();
private File example = new File(testResources,"example.jar");
@Test
public void testExampleJarIsMR() throws Exception
{
MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example);
assertTrue(jarFile.isMultiRelease());
}
@Test
public void testBase() throws Exception
{
MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,8,false);
assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/InBoth$InnerBase.class").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/InBoth$InnerBoth.class").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(0));
assertThat(jarFile.stream().count(), is(5L));
}
@Test
public void test9() throws Exception
{
MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,9,false);
assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/InBoth$InnerBoth.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/OnlyIn9.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/onlyIn9/OnlyIn9.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/InBoth$Inner9.class").getVersion(), is(9));
assertThat(jarFile.stream().count(), is(7L));
}
@Test
public void test10() throws Exception
{
MultiReleaseJarFile jarFile = new MultiReleaseJarFile(example,10,false);
assertThat(jarFile.getEntry("META-INF/MANIFEST.MF").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/OnlyInBase.class").getVersion(), is(0));
assertThat(jarFile.getEntry("org/example/InBoth.class").getVersion(), is(10));
assertThat(jarFile.getEntry("org/example/OnlyIn9.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/onlyIn9/OnlyIn9.class").getVersion(), is(9));
assertThat(jarFile.getEntry("org/example/In10Only.class").getVersion(), is(10));
assertThat(jarFile.stream().count(), is(6L));
}
}

110
pom.xml
View File

@ -150,6 +150,57 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- build tools -->
<exclude>**/org/eclipse/jetty/ant/**</exclude>
<exclude>**/org/eclipse/jetty/maven/**</exclude>
<exclude>**/org/eclipse/jetty/jspc/**</exclude>
<!-- example code / documentation -->
<exclude>**/org/eclipse/jetty/embedded/**</exclude>
<exclude>**/org/eclipse/jetty/asyncrest/**</exclude>
<exclude>**/org/eclipse/jetty/demo/**</exclude>
<!-- special environments / late integrations -->
<exclude>**/org/eclipse/jetty/gcloud/**</exclude>
<exclude>**/org/eclipse/jetty/infinispan/**</exclude>
<exclude>**/org/eclipse/jetty/osgi/**</exclude>
<exclude>**/org/eclipse/jetty/spring/**</exclude>
<exclude>**/org/eclipse/jetty/http/spi/**</exclude>
<!-- test classes -->
<exclude>**/org/eclipse/jetty/tests/**</exclude>
<exclude>**/org/eclipse/jetty/test/**</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<phase>initialize</phase>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- list of classes that you want to see in the report.
Specify a narrow list of multi-module project
classes you want to see here.
This is useful to remove 3rd party library classes
from the report. -->
<includes>
<include>**/org/eclipse/jetty/**</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-pmd-plugin</artifactId>
<executions>
@ -1085,65 +1136,6 @@
</plugins>
</build>
</profile>
<profile>
<id>jacoco</id>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- build tools -->
<exclude>**/org/eclipse/jetty/ant/**</exclude>
<exclude>**/org/eclipse/jetty/maven/**</exclude>
<exclude>**/org/eclipse/jetty/jspc/**</exclude>
<!-- example code / documentation -->
<exclude>**/org/eclipse/jetty/embedded/**</exclude>
<exclude>**/org/eclipse/jetty/asyncrest/**</exclude>
<exclude>**/org/eclipse/jetty/demo/**</exclude>
<!-- special environments / late integrations -->
<exclude>**/org/eclipse/jetty/gcloud/**</exclude>
<exclude>**/org/eclipse/jetty/infinispan/**</exclude>
<exclude>**/org/eclipse/jetty/osgi/**</exclude>
<exclude>**/org/eclipse/jetty/spring/**</exclude>
<exclude>**/org/eclipse/jetty/http/spi/**</exclude>
<!-- test classes -->
<exclude>**/org/eclipse/jetty/tests/**</exclude>
<exclude>**/org/eclipse/jetty/test/**</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<phase>initialize</phase>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- list of classes that you want to see in the report.
Specify a narrow list of multi-module project
classes you want to see here.
This is useful to remove 3rd party library classes
from the report. -->
<includes>
<include>**/org/eclipse/jetty/**</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!--
Usage:
configure settings.xml for jetty.eclipse.website server entry