From 50f861e8f260fa60cba5e1f3160b14d3c48c116a Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 20 Sep 2017 13:47:37 +1000 Subject: [PATCH] Issue #1797 MR jar support in java8 code --- .../jetty/annotations/AnnotationParser.java | 21 ++- jetty-util/pom.xml | 67 -------- .../jetty/util/MultiReleaseJarFile.java | 138 ++++++++++++++--- .../jetty/util/MultiReleaseJarFile.java | 143 ------------------ .../jetty/util/MultiReleaseJarFileTest.java | 92 +++++++++++ jetty-util/src/test/resources/example.jar | Bin 3571 -> 3975 bytes pom.xml | 110 +++++++------- 7 files changed, 270 insertions(+), 301 deletions(-) delete mode 100644 jetty-util/src/main/java9/org/eclipse/jetty/util/MultiReleaseJarFile.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java index 8418c4a405b..13f9b36b77d 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java @@ -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 handlers, Resource jar, JarFile jarFile, JarEntry entry) throws Exception + protected void parseJarEntry (Set 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); } diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index 87243dd3a2d..a75e4f8c6ce 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -11,8 +11,6 @@ http://www.eclipse.org/jetty ${project.groupId}.util - ${project.basedir}/src/main/java9 - ${project.build.directory}/classes-java9 @@ -72,69 +70,4 @@ test - - - - jdk9 MR Jar - - [1.9,) - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - compile-java9 - compile - - - - - - - - run - - - - - - org.apache.maven.plugins - maven-resources-plugin - - - copy-resources - prepare-package - - copy-resources - - - ${project.build.outputDirectory}/META-INF/versions/9 - - - ${java9.build.outputDirectory} - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - - - - - - - - diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java index f98973f375e..7144bac5631 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java @@ -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 entries; + + public MultiReleaseJarFile(File file) throws IOException { - return new JarFile(file); + this(file,JavaVersion.VERSION.getMajor(),false); } - public static Stream 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 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> i = map.entrySet().iterator();i.hasNext();) + { + Map.Entry 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 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 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); } } } diff --git a/jetty-util/src/main/java9/org/eclipse/jetty/util/MultiReleaseJarFile.java b/jetty-util/src/main/java9/org/eclipse/jetty/util/MultiReleaseJarFile.java deleted file mode 100644 index b15e2a3ceaf..00000000000 --- a/jetty-util/src/main/java9/org/eclipse/jetty/util/MultiReleaseJarFile.java +++ /dev/null @@ -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; - -/** - *

Utility class to create a stream of Multi Release {@link JarEntry}s

- *

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.

- */ -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 streamVersioned(JarFile jf) - { - return jf.stream() - .map(VersionedJarEntry::new); - } - - public static Stream stream(JarFile jf) - { - if (!jf.isMultiRelease()) - return jf.stream(); - - // Map to hold unversioned name to VersionedJarEntry - Map 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; - } - } -} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java new file mode 100644 index 00000000000..01031f6296a --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiReleaseJarFileTest.java @@ -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)); + } + + +} diff --git a/jetty-util/src/test/resources/example.jar b/jetty-util/src/test/resources/example.jar index 3c3e1cdaa1d416df813419ffcaf63b1cc6953468..171a823145c6ebff3d76910f91ee769498e0562d 100644 GIT binary patch delta 625 zcmew?-7e1?;LXe;!ob17!EoKjWFoH>Gl=qayWD}We!==My0%6v12`K=5gyIJUB>yllWI+@H zy)?OpLtyeFE-9uJ2q6V-K@Bbj9ASZCju8{sZlF0Ph@e3@9uzppo&bYD959(o?qQXfe3zGlPno5Pa|5%YA7Z%EeND76R6lny7vq)MCBWS0_!a@-QLGcrdFh4-V z!dg;UiH!(?g{@7ru(H!i8{b7E*)4Wvc6OPAfseFR2_^0kRaME`ZoXj^B{ASozp=E5 zY0Yh($uAUlJk@wVl&;tF*q}GKl#@KDkM z_8q|_wzYufaAl;km5?%>=!B?*h{$1giotF|7KMKpeU^(M-KXbmR@37+HcU(yKFf0P zy2fs!F@}~g&gNnQj$(()B1*tsBkEz#%&~;rhVwB6k76_XOKoeCopd%1R;&W+RsM_) Uwz!$BgfTe^l@MJN=t7Cu4@(VP)c^nh diff --git a/pom.xml b/pom.xml index c4b333c0c7e..d8fc754385b 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,57 @@ + + org.jacoco + jacoco-maven-plugin + + + + **/org/eclipse/jetty/ant/** + **/org/eclipse/jetty/maven/** + **/org/eclipse/jetty/jspc/** + + **/org/eclipse/jetty/embedded/** + **/org/eclipse/jetty/asyncrest/** + **/org/eclipse/jetty/demo/** + + **/org/eclipse/jetty/gcloud/** + **/org/eclipse/jetty/infinispan/** + **/org/eclipse/jetty/osgi/** + **/org/eclipse/jetty/spring/** + **/org/eclipse/jetty/http/spi/** + + **/org/eclipse/jetty/tests/** + **/org/eclipse/jetty/test/** + + + + + jacoco-initialize + initialize + + prepare-agent + + + + jacoco-site + package + + report + + + + + **/org/eclipse/jetty/** + + + + + maven-pmd-plugin @@ -1085,65 +1136,6 @@
- - jacoco - - - - org.jacoco - jacoco-maven-plugin - - - - **/org/eclipse/jetty/ant/** - **/org/eclipse/jetty/maven/** - **/org/eclipse/jetty/jspc/** - - **/org/eclipse/jetty/embedded/** - **/org/eclipse/jetty/asyncrest/** - **/org/eclipse/jetty/demo/** - - **/org/eclipse/jetty/gcloud/** - **/org/eclipse/jetty/infinispan/** - **/org/eclipse/jetty/osgi/** - **/org/eclipse/jetty/spring/** - **/org/eclipse/jetty/http/spi/** - - **/org/eclipse/jetty/tests/** - **/org/eclipse/jetty/test/** - - - - - jacoco-initialize - initialize - - prepare-agent - - - - jacoco-site - package - - report - - - - - **/org/eclipse/jetty/** - - - - - - - - -