+ final String trimmed = this.name.substring(VERSIONS_PREFIX.length());
+ final int divider = trimmed.indexOf('/');
+ if (divider == -1) { // malformed, ignore
+ return this.unversionedName = this.name;
+ }
+
+ final String version = trimmed.substring(0, divider);
+ final String unversioned = trimmed.substring(divider + 1);
+ try {
+ if (!unversioned.startsWith(META_INF)) { // Files already within META-INF cannot be versioned
+ final int parsedVersion = Integer.parseInt(version);
+ if (parsedVersion >= 0) {
+ this.version = parsedVersion;
+ return this.unversionedName = unversioned;
+ }
+ }
+ }
+ catch (final NumberFormatException ignored) { // invalid integer, treat as unversioned
+ // fall through
+ }
+ return this.unversionedName = this.name;
+ }
+
/**
* Gets the package that contains the jar entry, an empty
* string if in the root package.
@@ -78,9 +158,10 @@ public final long getTime() {
*/
public final String getPackage() {
if (this.packageName != null) return this.packageName;
- final int index = this.name.lastIndexOf('/');
+ final String name = this.getUnversionedName();
+ final int index = name.lastIndexOf('/');
if (index == -1) return this.packageName = "";
- return this.packageName = this.name.substring(0, index);
+ return this.packageName = name.substring(0, index);
}
/**
@@ -92,12 +173,28 @@ public final String getSimpleName() {
if (this.simpleName != null) return this.simpleName;
final int packageLength = this.getPackage().isEmpty() ? -1 : this.getPackage().length();
final int extensionLength = this.getExtension().isEmpty() ? -1 : this.getExtension().length();
- return this.simpleName = this.name.substring(
+ final String name = this.getUnversionedName();
+ return this.simpleName = name.substring(
packageLength + 1,
- this.name.length() - (extensionLength + 1)
+ name.length() - (extensionLength + 1)
);
}
+ /**
+ * If this is a multi-release variant of a class file in a multi-release
+ * jar, the version associated with this variant.
+ *
+ * @return the version, or {@link #UNVERSIONED} if this is the base version,
+ * or a file that would not be interpreted as a multi-release variant
+ * within the version folder.
+ * @see #getUnversionedName() for a description of the conditions on multi-release jars
+ */
+ public int getVersion() {
+ if (this.unversionedName != null) return this.version;
+ this.getUnversionedName(); // initialize versions
+ return this.version;
+ }
+
/**
* Gets the extension of the jar entry.
*
@@ -132,9 +229,9 @@ public final void write(final JarOutputStream jos) throws IOException {
/**
* Processes the jar entry with the given transformer.
*
- * @param vistor The transformer
+ * @param visitor The transformer
* @return The jar entry
*/
- public abstract AbstractJarEntry accept(final JarEntryTransformer vistor);
+ public abstract AbstractJarEntry accept(final JarEntryTransformer visitor);
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarClassEntry.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarClassEntry.java
index 51fdbac..3ed0049 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarClassEntry.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarClassEntry.java
@@ -42,6 +42,11 @@ public class JarClassEntry extends AbstractJarEntry {
private final byte[] contents;
+ public JarClassEntry(final int version, final String unversionedName, final long time, final byte[] contents) {
+ super(version, unversionedName, time);
+ this.contents = contents;
+ }
+
public JarClassEntry(final String name, final long time, final byte[] contents) {
super(name, time);
this.contents = contents;
@@ -58,8 +63,8 @@ public final byte[] getContents() {
}
@Override
- public final JarClassEntry accept(final JarEntryTransformer vistor) {
- return vistor.transform(this);
+ public final JarClassEntry accept(final JarEntryTransformer visitor) {
+ return visitor.transform(this);
}
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarEntryTransformer.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarEntryTransformer.java
index 95f0865..a16bfac 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarEntryTransformer.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarEntryTransformer.java
@@ -30,6 +30,9 @@
package org.cadixdev.bombe.jar;
+import java.util.Collections;
+import java.util.List;
+
/**
* A visitor for {@link AbstractJarEntry}, allowing them be be
* transformed.
@@ -41,9 +44,12 @@ public interface JarEntryTransformer {
/**
* Transforms the given class entry.
+ *
+ * It is possible to remove entries by returning {@code null}, when this
+ * occurs no further transformers will process the entry.
*
* @param entry The class entry
- * @return The transformed entry
+ * @return The transformed entry, or {@code null} if the entry should be removed
*/
default JarClassEntry transform(final JarClassEntry entry) {
return entry;
@@ -51,9 +57,12 @@ default JarClassEntry transform(final JarClassEntry entry) {
/**
* Transforms the given resource entry.
+ *
+ * It is possible to remove entries by returning {@code null}, when this
+ * occurs no further transformers will process the entry.
*
* @param entry The resource entry
- * @return The transformed entry
+ * @return The transformed entry, or {@code null} if the entry should be removed
*/
default JarResourceEntry transform(final JarResourceEntry entry) {
return entry;
@@ -61,9 +70,12 @@ default JarResourceEntry transform(final JarResourceEntry entry) {
/**
* Transforms the given manifest entry.
+ *
+ * It is possible to remove entries by returning {@code null}, when this
+ * occurs no further transformers will process the entry.
*
* @param entry The manifest entry
- * @return The transformed entry
+ * @return The transformed entry, or {@code null} if the entry should be removed
*/
default JarManifestEntry transform(final JarManifestEntry entry) {
return entry;
@@ -71,12 +83,25 @@ default JarManifestEntry transform(final JarManifestEntry entry) {
/**
* Transforms the given service provider configuration entry.
+ *
+ * It is possible to remove entries by returning {@code null}, when this
+ * occurs no further transformers will process the entry.
*
* @param entry The service provider configuration entry
- * @return The transformed entry
+ * @return The transformed entry, or {@code null} if the entry should be removed
*/
default JarServiceProviderConfigurationEntry transform(final JarServiceProviderConfigurationEntry entry) {
return entry;
}
+ /**
+ * Provides a list of {@link AbstractJarEntry jar entries} to add into the
+ * processed jar file.
+ *
+ * @return Entries to add into the final jar
+ */
+ default List additions() {
+ return Collections.emptyList();
+ }
+
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarManifestEntry.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarManifestEntry.java
index f55f012..4f66817 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarManifestEntry.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarManifestEntry.java
@@ -78,8 +78,8 @@ public final byte[] getContents() {
}
@Override
- public JarManifestEntry accept(final JarEntryTransformer vistor) {
- return vistor.transform(this);
+ public JarManifestEntry accept(final JarEntryTransformer visitor) {
+ return visitor.transform(this);
}
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarResourceEntry.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarResourceEntry.java
index 65f94ae..490bf38 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarResourceEntry.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarResourceEntry.java
@@ -42,6 +42,11 @@ public class JarResourceEntry extends AbstractJarEntry {
private final byte[] contents;
private String extension;
+ public JarResourceEntry(final int version, final String unversionedName, final long time, final byte[] contents) {
+ super(version, unversionedName, time);
+ this.contents = contents;
+ }
+
public JarResourceEntry(final String name, final long time, final byte[] contents) {
super(name, time);
this.contents = contents;
@@ -61,8 +66,8 @@ public final byte[] getContents() {
}
@Override
- public final JarResourceEntry accept(final JarEntryTransformer vistor) {
- return vistor.transform(this);
+ public final JarResourceEntry accept(final JarEntryTransformer visitor) {
+ return visitor.transform(this);
}
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarServiceProviderConfigurationEntry.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarServiceProviderConfigurationEntry.java
index d3ebb96..51fb92c 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarServiceProviderConfigurationEntry.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/JarServiceProviderConfigurationEntry.java
@@ -79,8 +79,8 @@ public final byte[] getContents() {
}
@Override
- public final JarServiceProviderConfigurationEntry accept(final JarEntryTransformer vistor) {
- return vistor.transform(this);
+ public final JarServiceProviderConfigurationEntry accept(final JarEntryTransformer visitor) {
+ return visitor.transform(this);
}
}
diff --git a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/asm/JarEntryRemappingTransformer.java b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/asm/JarEntryRemappingTransformer.java
index 9252265..777ea45 100644
--- a/bombe-jar/src/main/java/org/cadixdev/bombe/jar/asm/JarEntryRemappingTransformer.java
+++ b/bombe-jar/src/main/java/org/cadixdev/bombe/jar/asm/JarEntryRemappingTransformer.java
@@ -30,9 +30,12 @@
package org.cadixdev.bombe.jar.asm;
+import static java.util.jar.Attributes.Name.MAIN_CLASS;
+
import org.cadixdev.bombe.jar.JarClassEntry;
import org.cadixdev.bombe.jar.JarEntryTransformer;
import org.cadixdev.bombe.jar.JarManifestEntry;
+import org.cadixdev.bombe.jar.JarResourceEntry;
import org.cadixdev.bombe.jar.JarServiceProviderConfigurationEntry;
import org.cadixdev.bombe.jar.ServiceProviderConfiguration;
import org.objectweb.asm.ClassReader;
@@ -41,7 +44,9 @@
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.Remapper;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.function.BiFunction;
import java.util.jar.Attributes;
import java.util.stream.Collectors;
@@ -55,6 +60,8 @@
*/
public class JarEntryRemappingTransformer implements JarEntryTransformer {
+ private static final Attributes.Name SHA_256_DIGEST = new Attributes.Name("SHA-256-Digest");
+
private final Remapper remapper;
private final BiFunction clsRemapper;
@@ -78,22 +85,32 @@ public JarClassEntry transform(final JarClassEntry entry) {
), 0);
// Create the jar entry
- final String originalName = entry.getName().substring(0, entry.getName().length() - ".class".length());
+ final String originalName = entry.getUnversionedName().substring(0, entry.getUnversionedName().length() - ".class".length());
final String name = this.remapper.map(originalName) + ".class";
- return new JarClassEntry(name, entry.getTime(), writer.toByteArray());
+ return new JarClassEntry(entry.getVersion(), name, entry.getTime(), writer.toByteArray());
}
@Override
public JarManifestEntry transform(final JarManifestEntry entry) {
// Remap the Main-Class attribute, if present
- if (entry.getManifest().getMainAttributes().containsKey(new Attributes.Name("Main-Class"))) {
- final String mainClassObf = entry.getManifest().getMainAttributes().getValue("Main-Class")
+ if (entry.getManifest().getMainAttributes().containsKey(MAIN_CLASS)) {
+ final String mainClassObf = entry.getManifest().getMainAttributes().getValue(MAIN_CLASS)
.replace('.', '/');
final String mainClassDeobf = this.remapper.map(mainClassObf)
.replace('/', '.');
- // Since Manifest is mutable, we need'nt create a new entry \o/
- entry.getManifest().getMainAttributes().putValue("Main-Class", mainClassDeobf);
+ // Since Manifest is mutable, we needn't create a new entry \o/
+ entry.getManifest().getMainAttributes().put(MAIN_CLASS, mainClassDeobf);
+ }
+
+ // Remove all signature entries
+ for (final Iterator> it = entry.getManifest().getEntries().entrySet().iterator(); it.hasNext();) {
+ final Map.Entry section = it.next();
+ if (section.getValue().remove(SHA_256_DIGEST) != null) {
+ if (section.getValue().isEmpty()) {
+ it.remove();
+ }
+ }
}
return entry;
@@ -119,4 +136,16 @@ public JarServiceProviderConfigurationEntry transform(final JarServiceProviderCo
return new JarServiceProviderConfigurationEntry(entry.getTime(), config);
}
+ @Override
+ public JarResourceEntry transform(final JarResourceEntry entry) {
+ // Strip signature files from metadata
+ if (entry.getName().startsWith("META-INF")) {
+ if (entry.getExtension().equals("RSA")
+ || entry.getExtension().equals("SF")) {
+ return null;
+ }
+ }
+ return entry;
+ }
+
}
diff --git a/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/JarEntrySpec.groovy b/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/JarEntrySpec.groovy
index e682c63..e1c7904 100644
--- a/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/JarEntrySpec.groovy
+++ b/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/JarEntrySpec.groovy
@@ -31,6 +31,7 @@
package org.cadixdev.bombe.jar.test
import org.cadixdev.bombe.jar.AbstractJarEntry
+import org.cadixdev.bombe.jar.JarClassEntry
import org.cadixdev.bombe.jar.JarResourceEntry
import spock.lang.Specification
@@ -41,6 +42,10 @@ class JarEntrySpec extends Specification {
private static final AbstractJarEntry PACKAGED_ENTRY = new JarResourceEntry("pack/beep.boop", 0, null)
private static final AbstractJarEntry ROOT_ENTRY = new JarResourceEntry("beep.boop", 0, null)
+ private static final AbstractJarEntry VERSION_BY_PATH = new JarClassEntry("META-INF/versions/9/module-info.class", 0, null)
+ private static final AbstractJarEntry VERSION_EXPLICIT = new JarClassEntry(11, "pack/a/b.class", 0, null)
+ private static final AbstractJarEntry VERSION_MALFORMED = new JarClassEntry("META-INF/versions/ab/module-info.class", 0, null)
+ private static final AbstractJarEntry VERSION_UNVERSIONABLE = new JarClassEntry("META-INF/versions/14/META-INF/services/a.b\$Provider", 0, null)
def "reads name correctly"(final AbstractJarEntry entry,
final String packageName,
@@ -57,4 +62,22 @@ class JarEntrySpec extends Specification {
ROOT_ENTRY | '' | 'beep' | 'boop'
}
+ def "handles multirelease paths correctly"(final AbstractJarEntry entry,
+ final String fullName,
+ final int version,
+ final String name) {
+ expect:
+ entry.name == fullName
+ entry.version == version
+ entry.unversionedName == name
+
+ where:
+ entry | fullName | version | name
+ PACKAGED_ENTRY | "pack/beep.boop" | AbstractJarEntry.UNVERSIONED | "pack/beep.boop"
+ VERSION_BY_PATH | "META-INF/versions/9/module-info.class" | 9 | "module-info.class"
+ VERSION_EXPLICIT | "META-INF/versions/11/pack/a/b.class" | 11 | "pack/a/b.class"
+ VERSION_MALFORMED | "META-INF/versions/ab/module-info.class" | AbstractJarEntry.UNVERSIONED | "META-INF/versions/ab/module-info.class"
+ VERSION_UNVERSIONABLE | "META-INF/versions/14/META-INF/services/a.b\$Provider" | AbstractJarEntry.UNVERSIONED | "META-INF/versions/14/META-INF/services/a.b\$Provider"
+ }
+
}
diff --git a/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/asm/JarEntryRemappingTransformerSpec.groovy b/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/asm/JarEntryRemappingTransformerSpec.groovy
index c7e4298..fa1ebdc 100644
--- a/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/asm/JarEntryRemappingTransformerSpec.groovy
+++ b/bombe-jar/src/test/groovy/org/cadixdev/bombe/jar/test/asm/JarEntryRemappingTransformerSpec.groovy
@@ -80,6 +80,28 @@ class JarEntryRemappingTransformerSpec extends Specification {
node.name == 'pkg/Demo'
}
+ def "remaps multi-release class"() {
+ given:
+ // Create a test class
+ def obf = new ClassWriter(0)
+ obf.visit(Opcodes.V9, Opcodes.ACC_PUBLIC, 'a', null, 'java/lang/Object', null)
+
+ // Run it through the transformer
+ def entry = TRANSFORMER.transform(new JarClassEntry('META-INF/versions/9/a.class', 0, obf.toByteArray()))
+
+ // Use a ClassNode for convenience
+ def node = new ClassNode()
+ def reader = new ClassReader(entry.contents)
+ reader.accept(node, 0)
+
+ expect:
+ entry.name == 'META-INF/versions/9/pkg/Demo.class'
+ entry.version == 9
+ entry.unversionedName == 'pkg/Demo.class'
+ node.name == 'pkg/Demo'
+
+ }
+
def "remaps manifest"() {
given:
// Create a test Manifest
diff --git a/bombe/build.gradle b/bombe/build.gradle
index e195ca9..10b7abb 100644
--- a/bombe/build.gradle
+++ b/bombe/build.gradle
@@ -1,5 +1,12 @@
dependencies {
- compile "me.jamiemansfield:string:$stringVersion"
- compileOnly "org.ow2.asm:asm-commons:$asmVersion"
- testCompile "org.ow2.asm:asm-commons:$asmVersion"
+ api "me.jamiemansfield:string:$stringVersion"
+ implementation "org.ow2.asm:asm-commons:$asmVersion"
+ testImplementation "org.ow2.asm:asm-commons:$asmVersion"
}
+
+jar {
+ manifest.attributes(
+ 'Automatic-Module-Name': "${project.group}.bombe"
+ )
+}
+
diff --git a/bombe/src/main/java/org/cadixdev/bombe/analysis/InheritanceProvider.java b/bombe/src/main/java/org/cadixdev/bombe/analysis/InheritanceProvider.java
index 03bf094..7d85bb4 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/analysis/InheritanceProvider.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/analysis/InheritanceProvider.java
@@ -432,7 +432,7 @@ public Map getMethods() {
@Override
public Set provideParents(final InheritanceProvider provider) {
if (this.parents == null) {
- ClassInfo.super.provideParents(provider, this.parents = new HashSet<>());
+ super.provideParents(provider, this.parents = new HashSet<>());
}
return this.parents;
}
diff --git a/bombe/src/main/java/org/cadixdev/bombe/analysis/asm/ClassProviderInheritanceProvider.java b/bombe/src/main/java/org/cadixdev/bombe/analysis/asm/ClassProviderInheritanceProvider.java
index b178a31..da70c5b 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/analysis/asm/ClassProviderInheritanceProvider.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/analysis/asm/ClassProviderInheritanceProvider.java
@@ -31,7 +31,7 @@
package org.cadixdev.bombe.analysis.asm;
import org.cadixdev.bombe.analysis.InheritanceProvider;
-import org.cadixdev.bombe.jar.ClassProvider;
+import org.cadixdev.bombe.provider.ClassProvider;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
diff --git a/bombe/src/main/java/org/cadixdev/bombe/jar/ClassLoaderClassProvider.java b/bombe/src/main/java/org/cadixdev/bombe/provider/ClassLoaderClassProvider.java
similarity index 98%
rename from bombe/src/main/java/org/cadixdev/bombe/jar/ClassLoaderClassProvider.java
rename to bombe/src/main/java/org/cadixdev/bombe/provider/ClassLoaderClassProvider.java
index 9cb645c..99ef6c7 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/jar/ClassLoaderClassProvider.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/provider/ClassLoaderClassProvider.java
@@ -28,7 +28,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.cadixdev.bombe.jar;
+package org.cadixdev.bombe.provider;
import org.cadixdev.bombe.util.ByteStreams;
diff --git a/bombe/src/main/java/org/cadixdev/bombe/jar/ClassProvider.java b/bombe/src/main/java/org/cadixdev/bombe/provider/ClassProvider.java
similarity index 98%
rename from bombe/src/main/java/org/cadixdev/bombe/jar/ClassProvider.java
rename to bombe/src/main/java/org/cadixdev/bombe/provider/ClassProvider.java
index 011c060..a0a5e41 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/jar/ClassProvider.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/provider/ClassProvider.java
@@ -28,7 +28,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.cadixdev.bombe.jar;
+package org.cadixdev.bombe.provider;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
diff --git a/bombe/src/main/java/org/cadixdev/bombe/provider/CompositeClassProvider.java b/bombe/src/main/java/org/cadixdev/bombe/provider/CompositeClassProvider.java
new file mode 100644
index 0000000..20f0738
--- /dev/null
+++ b/bombe/src/main/java/org/cadixdev/bombe/provider/CompositeClassProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018, Jamie Mansfield
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.cadixdev.bombe.provider;
+
+import java.util.List;
+
+/**
+ * A {@link ClassProvider class provider} backed by many other class providers.
+ *
+ * @author Jamie Mansfield
+ * @since 0.5.0
+ */
+public class CompositeClassProvider implements ClassProvider {
+
+ private final List providers;
+
+ public CompositeClassProvider(final List providers) {
+ this.providers = providers;
+ }
+
+ @Override
+ public byte[] get(final String klass) {
+ for (final ClassProvider provider : this.providers) {
+ final byte[] raw = provider.get(klass);
+ if (raw != null) return raw;
+ }
+ return null;
+ }
+
+}
diff --git a/bombe/src/main/java/org/cadixdev/bombe/jar/JarFileClassProvider.java b/bombe/src/main/java/org/cadixdev/bombe/provider/JarFileClassProvider.java
similarity index 98%
rename from bombe/src/main/java/org/cadixdev/bombe/jar/JarFileClassProvider.java
rename to bombe/src/main/java/org/cadixdev/bombe/provider/JarFileClassProvider.java
index c898326..d771332 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/jar/JarFileClassProvider.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/provider/JarFileClassProvider.java
@@ -28,7 +28,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.cadixdev.bombe.jar;
+package org.cadixdev.bombe.provider;
import org.cadixdev.bombe.util.ByteStreams;
diff --git a/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptor.java b/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptor.java
index e094bb0..b84fc51 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptor.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptor.java
@@ -62,7 +62,7 @@ public final class MethodDescriptor {
* @return The descriptor
*/
public static MethodDescriptor of(final String descriptor) {
- return new MethodDescriptorReader(descriptor).read();
+ return new MethodDescriptorReader(descriptor).readDescriptor();
}
/**
diff --git a/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptorReader.java b/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptorReader.java
index 4442559..eb2795e 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptorReader.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/type/MethodDescriptorReader.java
@@ -54,7 +54,7 @@ public MethodDescriptorReader(final String descriptor) {
* @return The type
* @throws IllegalStateException If the descriptor is invalid
*/
- public MethodDescriptor read() {
+ public MethodDescriptor readDescriptor() {
final List params = new ArrayList<>();
if (this.peek() != '(') throw new IllegalStateException("Invalid descriptor provided!");
diff --git a/bombe/src/main/java/org/cadixdev/bombe/type/TypeReader.java b/bombe/src/main/java/org/cadixdev/bombe/type/TypeReader.java
index 5d331b3..254de71 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/type/TypeReader.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/type/TypeReader.java
@@ -122,13 +122,15 @@ public BaseType readBaseType() {
*/
public ObjectType readObjectType() {
final int start = this.index();
+
+ if (this.peek() != 'L') throw new IllegalStateException("Incomplete descriptor provided!");
this.advance();
while (this.available() && this.peek() != ';') {
this.advance();
}
- if (this.peek() != ';') throw new IllegalStateException("Incomplete descriptor provided!");
+ if (!this.available() || this.peek() != ';') throw new IllegalStateException("Incomplete descriptor provided!");
this.advance();
return new ObjectType(this.substring(start + 1, this.index() - 1));
diff --git a/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignature.java b/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignature.java
index 5baaf1f..1f3320a 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignature.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignature.java
@@ -65,8 +65,7 @@ public static MethodSignature of(final String name, final String descriptor) {
* @return The new method signature
*/
public static MethodSignature of(final String nameAndDescriptor) {
- int methodIndex = nameAndDescriptor.indexOf('(');
- return of(nameAndDescriptor.substring(0, methodIndex), nameAndDescriptor.substring(methodIndex));
+ return new MethodSignatureReader(nameAndDescriptor).readSignature();
}
/**
diff --git a/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignatureReader.java b/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignatureReader.java
new file mode 100644
index 0000000..6e3c965
--- /dev/null
+++ b/bombe/src/main/java/org/cadixdev/bombe/type/signature/MethodSignatureReader.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2018, Jamie Mansfield
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.cadixdev.bombe.type.signature;
+
+import me.jamiemansfield.string.StringReader;
+import org.cadixdev.bombe.type.MethodDescriptorReader;
+
+/**
+ * An {@link StringReader} for reading {@link MethodSignature}s
+ * from their raw {@link String} representation.
+ *
+ * @author Jamie Mansfield
+ * @since 0.5.0
+ */
+public class MethodSignatureReader extends MethodDescriptorReader {
+
+ public MethodSignatureReader(final String signature) {
+ super(signature);
+ }
+
+ /**
+ * Reads the next {@link MethodSignature} from source.
+ *
+ * @return The type
+ * @throws IllegalStateException If the signature is invalid
+ */
+ public MethodSignature readSignature() {
+ final int start = this.index();
+ while (this.peek() != '(') {
+ this.advance();
+ }
+ final String name = this.substring(start, this.index());
+
+ return new MethodSignature(name, this.readDescriptor());
+ }
+
+}
diff --git a/bombe/src/main/java/org/cadixdev/bombe/util/ByteStreams.java b/bombe/src/main/java/org/cadixdev/bombe/util/ByteStreams.java
index e262b14..1f8decc 100644
--- a/bombe/src/main/java/org/cadixdev/bombe/util/ByteStreams.java
+++ b/bombe/src/main/java/org/cadixdev/bombe/util/ByteStreams.java
@@ -37,62 +37,37 @@
/**
* Utility for working with byte streams.
*
- * @author Guava Authors
+ * @author Kyle Wood
* @since 0.3.0
*/
public final class ByteStreams {
- // ------------------------------------------------
- // The following code is taken from Guava (d0d5bd7)
- //
- // The code remains mostly untouched, only style-differences are present.
- // ------------------------------------------------
- // Copyright (C) 2007 The Guava Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- // in compliance with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software distributed under the License
- // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- // or implied. See the License for the specific language governing permissions and limitations under
- // the License.
- // ------------------------------------------------
-
- private static final int BUFFER_SIZE = 8192;
-
- /** Creates a new byte array for buffering reads or writes. */
- private static byte[] createBuffer() {
- return new byte[BUFFER_SIZE];
- }
-
/**
- * Copies all bytes from the input stream to the output stream. Does not close or flush either
- * stream.
+ * Copy all of the data from the {@code from} input to the {@code to} output.
*
- * @param from the input stream to read from
- * @param to the output stream to write to
- * @return the number of bytes copied
- * @throws IOException if an I/O error occurs
+ * @param from The input to copy from.
+ * @param to The output to copy to.
+ * @param buffer The byte array to use as the copy buffer.
+ * @throws IOException If an IO error occurs.
*/
- public static long copy(final InputStream from, final OutputStream to) throws IOException {
- final byte[] buf = createBuffer();
- long total = 0;
- while (true) {
- final int r = from.read(buf);
- if (r == -1) {
- break;
- }
- to.write(buf, 0, r);
- total += r;
+ public static void copy(final InputStream from, final OutputStream to, final byte[] buffer) throws IOException {
+ int read;
+ while ((read = from.read(buffer)) != -1) {
+ to.write(buffer, 0, read);
}
- return total;
}
- // ------------------------------------------------
- // End of Apache-licensed Guava code.
- // ------------------------------------------------
+ /**
+ * Copy all of the data from the {@code from} input to the {@code to} output,
+ * using a default buffer.
+ *
+ * @param from The input to copy from.
+ * @param to The output to copy to.
+ * @throws IOException If an IO error occurs.
+ */
+ public static void copy(final InputStream from, final OutputStream to) throws IOException {
+ copy(from, to, new byte[8192]);
+ }
private ByteStreams() {
}
diff --git a/bombe/src/test/groovy/org/cadixdev/bombe/test/type/TypeSpec.groovy b/bombe/src/test/groovy/org/cadixdev/bombe/test/type/TypeSpec.groovy
index 07856f5..fcb8190 100644
--- a/bombe/src/test/groovy/org/cadixdev/bombe/test/type/TypeSpec.groovy
+++ b/bombe/src/test/groovy/org/cadixdev/bombe/test/type/TypeSpec.groovy
@@ -35,6 +35,7 @@ import org.cadixdev.bombe.type.BaseType
import org.cadixdev.bombe.type.FieldType
import org.cadixdev.bombe.type.ObjectType
import org.cadixdev.bombe.type.Type
+import org.cadixdev.bombe.type.TypeReader
import org.cadixdev.bombe.type.VoidType
import spock.lang.Specification
@@ -74,6 +75,19 @@ class TypeSpec extends Specification {
'Lorg/cadixdev/demo/Test$Inner;' | _
}
+ def "throw on invalid object type"(final String raw) {
+ when:
+ new TypeReader(raw).readObjectType()
+
+ then:
+ thrown(IllegalStateException)
+
+ where:
+ raw | _
+ 'Ljava/lang/String' | _
+ 'Cjava/lang/String;' | _
+ }
+
def "reads base type"(final String raw, final BaseType expected) {
given:
def type = Type.of(raw)
diff --git a/build.gradle b/build.gradle
index ffa8610..f11cf63 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,27 +7,31 @@ allprojects {
}
subprojects {
- apply plugin: 'java'
+ apply plugin: 'java-library'
apply plugin: 'groovy'
- apply plugin: 'maven'
+ apply plugin: 'maven-publish'
- sourceCompatibility = '1.8'
- targetCompatibility = '1.8'
+ java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+
+ withJavadocJar()
+ withSourcesJar()
+ }
group = 'org.cadixdev'
archivesBaseName = project.name.toLowerCase()
- version = '0.4.3'
+ version = '0.5.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
- testCompile 'org.codehaus.groovy:groovy-all:2.5.8'
- testCompile 'org.spockframework:spock-core:1.3-groovy-2.5'
- testCompile 'org.junit.jupiter:junit-jupiter-api:5.5.1'
- testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.5.1'
- testRuntime 'org.junit.vintage:junit-vintage-engine:5.5.1'
+ testImplementation 'org.codehaus.groovy:groovy-all:3.0.7'
+ testImplementation 'org.spockframework:spock-core:2.0-M4-groovy-3.0'
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.5.1'
}
test {
@@ -46,96 +50,81 @@ subprojects {
)
}
- task javadocJar(type: Jar, dependsOn: 'javadoc') {
- from javadoc.destinationDir
- classifier = 'javadoc'
- }
+ publishing {
+ publications {
+ mavenJava(MavenPublication) {
+ artifactId = project.archivesBaseName
+ from components.java
- task sourcesJar(type: Jar, dependsOn: 'classes') {
- from sourceSets.main.allSource
- classifier = 'sources'
- }
+ pom {
+ name = project.name
+ description = project.description
+ packaging = 'jar'
+ url = project.url
+ inceptionYear = project.inceptionYear
+
+ scm {
+ url = 'https://github.com/CadixDev/Bombe'
+ connection = 'scm:git:https://github.com/CadixDev/Bombe.git'
+ developerConnection = 'scm:git:git@github.com:CadixDev/Bombe.git'
+ }
- artifacts {
- archives javadocJar
- archives sourcesJar
- }
+ issueManagement {
+ system = 'GitHub'
+ url = 'https://github.com/CadixDev/Bombe/issues'
+ }
- jacocoTestReport {
- reports {
- xml.enabled = true
- html.enabled = true
+ licenses {
+ license {
+ name = 'BSD 3-Clause'
+ url = 'https://opensource.org/licenses/BSD-3-Clause'
+ distribution = 'repo'
+ }
+ }
+
+ developers {
+ developer {
+ id = 'jamierocks'
+ name = 'Jamie Mansfield'
+ email = 'jmansfield@cadixdev.org'
+ url = 'https://www.jamiemansfield.me/'
+ timezone = 'Europe/London'
+ }
+ }
+ }
+ }
+ }
+ repositories {
+ if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
+ maven {
+ url = !version.endsWith('-SNAPSHOT') ?
+ 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' :
+ 'https://oss.sonatype.org/content/repositories/snapshots/'
+
+ credentials {
+ username = ossrhUsername
+ password = ossrhPassword
+ }
+ }
+ }
}
}
- check.dependsOn jacocoTestReport
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
apply plugin: 'signing'
signing {
- required { !version.endsWith('-SNAPSHOT') && gradle.taskGraph.hasTask(tasks.uploadArchives) }
- sign configurations.archives
+ required { !version.endsWith('-SNAPSHOT') && gradle.taskGraph.hasTask(tasks.publish) }
+ sign publishing.publications.mavenJava
}
}
- uploadArchives {
- repositories {
- mavenDeployer {
- // Maven Central
- if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
- beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
-
- repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
- authentication(userName: ossrhUsername, password: ossrhPassword)
- }
-
- snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') {
- authentication(userName: ossrhUsername, password: ossrhPassword)
- }
- }
-
- pom {
- artifactId = project.archivesBaseName
-
- project {
- name = project.name
- description = project.description
- packaging = 'jar'
- url = project.url
- inceptionYear = project.inceptionYear
-
- scm {
- url = 'https://github.com/CadixDev/Bombe'
- connection = 'scm:git:https://github.com/CadixDev/Bombe.git'
- developerConnection = 'scm:git:git@github.com:CadixDev/Bombe.git'
- }
-
- issueManagement {
- system = 'GitHub'
- url = 'https://github.com/CadixDev/Bombe/issues'
- }
-
- licenses {
- license {
- name = 'BSD 3-Clause'
- url = 'https://opensource.org/licenses/BSD-3-Clause'
- distribution = 'repo'
- }
- }
-
- developers {
- developer {
- id = 'jamierocks'
- name = 'Jamie Mansfield'
- email = 'dev@jamierocks.uk'
- url = 'https://www.jamiemansfield.me/'
- timezone = 'Europe/London'
- }
- }
- }
- }
- }
+ jacocoTestReport {
+ reports {
+ xml.enabled = true
+ html.enabled = true
}
}
+ check.dependsOn jacocoTestReport
}
task codeCoverageReport(type: JacocoReport) {
diff --git a/changelogs/0.3.5.md b/changelogs/0.3.5.md
new file mode 100644
index 0000000..5f44066
--- /dev/null
+++ b/changelogs/0.3.5.md
@@ -0,0 +1,15 @@
+Bombe 0.3.5
+===
+
+Bombe 0.3.5 is a small release introducing some new APIs to bolster the
+capabilities of the jar transformation framework, namely allowing entries to be
+introduced. To accomplish this, a `JarEntryTransformer#additions()` method has
+been introduced. The `Jars` utility has been updated to support this, and a
+release of Atlas will be made shortly to implement this feature.
+
+The `Jars` utility has been deprecated in this version, advising consumers to
+switch to Atlas. Jars was removed in 0.4.0, so this just serves as a final
+notice to any lingering applications using the utility.
+
+The remapping transformer will additionally strip signature files and entries
+in the manifest. This transformer may in future be available standalone.
diff --git a/changelogs/0.4.4.md b/changelogs/0.4.4.md
new file mode 100644
index 0000000..b8b2284
--- /dev/null
+++ b/changelogs/0.4.4.md
@@ -0,0 +1,11 @@
+Bombe 0.4.4
+===
+
+Bombe 0.4.4 is a small release introducing some new APIs to bolster the
+capabilities of the jar transformation framework, namely allowing entries to be
+introduced. To accomplish this, a `JarEntryTransformer#additions()` method has
+been introduced. A release of Atlas will be made shortly to implement this
+feature.
+
+The remapping transformer will additionally strip signature files and entries
+in the manifest. This transformer may in future be available standalone.
diff --git a/changelogs/0.5.0.md b/changelogs/0.5.0.md
new file mode 100644
index 0000000..8c1880b
--- /dev/null
+++ b/changelogs/0.5.0.md
@@ -0,0 +1,14 @@
+Bombe 0.5.0
+===
+
+Bombe 0.5.0 is largely a maintenance release, cleaning up the Bombe codebase and
+preparing it for newer Java modules.
+
+## Improvements
+
+- Handling for multi-release JAR files
+- Improved the robustness of reading descriptors
+ - Introduced a reader for signatures
+ - [GH-17] Prevent TypeReader reading invalid object descriptors
+
+[GH-17]: https://github.com/CadixDev/Bombe/issues/17
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index a5fe1cb..0d4a951 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 949819d..28ff446 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip