net.bytebuddy.dynamic.loading.PackageDefinitionStrategy Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* 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.
*/
package net.bytebuddy.dynamic.loading;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* A package definer is responsible for defining a package's properties when a class of a new package is loaded. Also,
* a package definer can choose not to define a package at all.
*/
public interface PackageDefinitionStrategy {
/**
* Returns a package definition for a given package.
*
* @param classLoader The class loader for which this package is being defined.
* @param packageName The name of the package.
* @param typeName The name of the type being loaded that triggered the package definition.
* @return A definition of the package.
*/
Definition define(ClassLoader classLoader, String packageName, String typeName);
/**
* A package definer that does not define any package.
*/
enum NoOp implements PackageDefinitionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Definition define(ClassLoader classLoader, String packageName, String typeName) {
return Definition.Undefined.INSTANCE;
}
}
/**
* A package definer that only defines packages without any meta data.
*/
enum Trivial implements PackageDefinitionStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Definition define(ClassLoader classLoader, String packageName, String typeName) {
return Definition.Trivial.INSTANCE;
}
}
/**
* A definition of a package.
*/
interface Definition {
/**
* Indicates if a package should be defined at all.
*
* @return {@code true} if the package is to be defined.
*/
boolean isDefined();
/**
* Returns the package specification's title or {@code null} if no such title exists. This method must only be called
* for defined package definitions.
*
* @return The package specification's title.
*/
@MaybeNull
String getSpecificationTitle();
/**
* Returns the package specification's version or {@code null} if no such version exists. This method must only be called
* for defined package definitions.
*
* @return The package specification's version.
*/
@MaybeNull
String getSpecificationVersion();
/**
* Returns the package specification's vendor or {@code null} if no such vendor exists. This method must only be called
* for defined package definitions.
*
* @return The package specification's vendor.
*/
@MaybeNull
String getSpecificationVendor();
/**
* Returns the package implementation's title or {@code null} if no such title exists. This method must only be called
* for defined package definitions.
*
* @return The package implementation's title.
*/
@MaybeNull
String getImplementationTitle();
/**
* Returns the package implementation's version or {@code null} if no such version exists. This method must only be called
* for defined package definitions.
*
* @return The package implementation's version.
*/
@MaybeNull
String getImplementationVersion();
/**
* Returns the package implementation's vendor or {@code null} if no such vendor exists. This method must only be called
* for defined package definitions.
*
* @return The package implementation's vendor.
*/
@MaybeNull
String getImplementationVendor();
/**
* The URL representing the seal base. This method must only be called for defined package definitions.
*
* @return The seal base of the package.
*/
@MaybeNull
URL getSealBase();
/**
* Validates that this package definition is compatible to a previously defined package. This method must only be
* called for defined package definitions.
*
* @param definedPackage The previously defined package.
* @return {@code false} if this package and the defined package's sealing information are not compatible.
*/
boolean isCompatibleTo(Package definedPackage);
/**
* A canonical implementation of an undefined package.
*/
enum Undefined implements Definition {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean isDefined() {
return false;
}
/**
* {@inheritDoc}
*/
public String getSpecificationTitle() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public String getSpecificationVersion() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public String getSpecificationVendor() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public String getImplementationTitle() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public String getImplementationVersion() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public String getImplementationVendor() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public URL getSealBase() {
throw new IllegalStateException("Cannot read property of undefined package");
}
/**
* {@inheritDoc}
*/
public boolean isCompatibleTo(Package definedPackage) {
throw new IllegalStateException("Cannot check compatibility to undefined package");
}
}
/**
* A package definer that defines packages without any meta data.
*/
enum Trivial implements Definition {
/**
* The singleton instance.
*/
INSTANCE;
/**
* An empty value of a package's property.
*/
@AlwaysNull
private static final String NO_VALUE = null;
/**
* Represents an unsealed package.
*/
@AlwaysNull
private static final URL NOT_SEALED = null;
/**
* {@inheritDoc}
*/
public boolean isDefined() {
return true;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationTitle() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationVersion() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationVendor() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getImplementationTitle() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getImplementationVersion() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
public String getImplementationVendor() {
return NO_VALUE;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public URL getSealBase() {
return NOT_SEALED;
}
/**
* {@inheritDoc}
*/
public boolean isCompatibleTo(Package definedPackage) {
return true;
}
}
/**
* A simple package definition where any property is represented by a value.
*/
class Simple implements Definition {
/**
* The seal base or {@code null} if the package is not sealed.
*/
@MaybeNull
protected final URL sealBase;
/**
* The package specification's title or {@code null} if no such title exists.
*/
@MaybeNull
private final String specificationTitle;
/**
* The package specification's version or {@code null} if no such version exists.
*/
@MaybeNull
private final String specificationVersion;
/**
* The package specification's vendor or {@code null} if no such vendor exists.
*/
@MaybeNull
private final String specificationVendor;
/**
* The package implementation's title or {@code null} if no such title exists.
*/
@MaybeNull
private final String implementationTitle;
/**
* The package implementation's version or {@code null} if no such version exists.
*/
@MaybeNull
private final String implementationVersion;
/**
* The package implementation's vendor or {@code null} if no such vendor exists.
*/
@MaybeNull
private final String implementationVendor;
/**
* Creates a new simple package definition.
*
* @param specificationTitle The package specification's title or {@code null} if no such title exists.
* @param specificationVersion The package specification's version or {@code null} if no such version exists.
* @param specificationVendor The package specification's vendor or {@code null} if no such vendor exists.
* @param implementationTitle The package implementation's title or {@code null} if no such title exists.
* @param implementationVersion The package implementation's version or {@code null} if no such version exists.
* @param implementationVendor The package implementation's vendor or {@code null} if no such vendor exists.
* @param sealBase The seal base or {@code null} if the package is not sealed.
*/
public Simple(@MaybeNull String specificationTitle,
@MaybeNull String specificationVersion,
@MaybeNull String specificationVendor,
@MaybeNull String implementationTitle,
@MaybeNull String implementationVersion,
@MaybeNull String implementationVendor,
@MaybeNull URL sealBase) {
this.specificationTitle = specificationTitle;
this.specificationVersion = specificationVersion;
this.specificationVendor = specificationVendor;
this.implementationTitle = implementationTitle;
this.implementationVersion = implementationVersion;
this.implementationVendor = implementationVendor;
this.sealBase = sealBase;
}
/**
* {@inheritDoc}
*/
public boolean isDefined() {
return true;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationTitle() {
return specificationTitle;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationVersion() {
return specificationVersion;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getSpecificationVendor() {
return specificationVendor;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getImplementationTitle() {
return implementationTitle;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getImplementationVersion() {
return implementationVersion;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public String getImplementationVendor() {
return implementationVendor;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public URL getSealBase() {
return sealBase;
}
/**
* {@inheritDoc}
*/
public boolean isCompatibleTo(Package definedPackage) {
if (sealBase == null) {
return !definedPackage.isSealed();
} else {
return definedPackage.isSealed(sealBase);
}
}
@Override
@SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality.")
public int hashCode() {
int result = specificationTitle != null ? specificationTitle.hashCode() : 0;
result = 31 * result + (specificationVersion != null ? specificationVersion.hashCode() : 0);
result = 31 * result + (specificationVendor != null ? specificationVendor.hashCode() : 0);
result = 31 * result + (implementationTitle != null ? implementationTitle.hashCode() : 0);
result = 31 * result + (implementationVersion != null ? implementationVersion.hashCode() : 0);
result = 31 * result + (implementationVendor != null ? implementationVendor.hashCode() : 0);
result = 31 * result + (sealBase != null ? sealBase.hashCode() : 0);
return result;
}
@Override
@SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality.")
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (other == null || getClass() != other.getClass()) {
return false;
}
Simple simple = (Simple) other;
return !(specificationTitle != null ? !specificationTitle.equals(simple.specificationTitle) : simple.specificationTitle != null)
&& !(specificationVersion != null ? !specificationVersion.equals(simple.specificationVersion) : simple.specificationVersion != null)
&& !(specificationVendor != null ? !specificationVendor.equals(simple.specificationVendor) : simple.specificationVendor != null)
&& !(implementationTitle != null ? !implementationTitle.equals(simple.implementationTitle) : simple.implementationTitle != null)
&& !(implementationVersion != null ? !implementationVersion.equals(simple.implementationVersion) : simple.implementationVersion != null)
&& !(implementationVendor != null ? !implementationVendor.equals(simple.implementationVendor) : simple.implementationVendor != null)
&& !(sealBase != null ? !sealBase.equals(simple.sealBase) : simple.sealBase != null);
}
}
}
/**
* A package definer that reads a class loader's manifest file.
*/
@HashCodeAndEqualsPlugin.Enhance
class ManifestReading implements PackageDefinitionStrategy {
/**
* A URL defined a non-sealed package.
*/
@AlwaysNull
private static final URL NOT_SEALED = null;
/**
* Contains all attributes that are relevant for defining a package.
*/
private static final Attributes.Name[] ATTRIBUTE_NAMES = new Attributes.Name[]{
Attributes.Name.SPECIFICATION_TITLE,
Attributes.Name.SPECIFICATION_VERSION,
Attributes.Name.SPECIFICATION_VENDOR,
Attributes.Name.IMPLEMENTATION_TITLE,
Attributes.Name.IMPLEMENTATION_VERSION,
Attributes.Name.IMPLEMENTATION_VENDOR,
Attributes.Name.SEALED
};
/**
* A locator for a sealed package's URL.
*/
private final SealBaseLocator sealBaseLocator;
/**
* Creates a manifest reading package definition strategy that attempts to extract sealing information from a defined class's URL.
*/
public ManifestReading() {
this(new SealBaseLocator.ForTypeResourceUrl());
}
/**
* Creates a new package definer that reads a class loader's manifest file.
*
* @param sealBaseLocator A locator for a sealed package's URL.
*/
public ManifestReading(SealBaseLocator sealBaseLocator) {
this.sealBaseLocator = sealBaseLocator;
}
/**
* {@inheritDoc}
*/
public Definition define(ClassLoader classLoader, String packageName, String typeName) {
InputStream inputStream = classLoader.getResourceAsStream(JarFile.MANIFEST_NAME);
if (inputStream != null) {
try {
try {
Manifest manifest = new Manifest(inputStream);
Map values = new HashMap();
Attributes mainAttributes = manifest.getMainAttributes();
if (mainAttributes != null) {
for (Attributes.Name attributeName : ATTRIBUTE_NAMES) {
values.put(attributeName, mainAttributes.getValue(attributeName));
}
}
Attributes attributes = manifest.getAttributes(packageName.replace('.', '/').concat("/"));
if (attributes != null) {
for (Attributes.Name attributeName : ATTRIBUTE_NAMES) {
String value = attributes.getValue(attributeName);
if (value != null) {
values.put(attributeName, value);
}
}
}
return new Definition.Simple(values.get(Attributes.Name.SPECIFICATION_TITLE),
values.get(Attributes.Name.SPECIFICATION_VERSION),
values.get(Attributes.Name.SPECIFICATION_VENDOR),
values.get(Attributes.Name.IMPLEMENTATION_TITLE),
values.get(Attributes.Name.IMPLEMENTATION_VERSION),
values.get(Attributes.Name.IMPLEMENTATION_VENDOR),
Boolean.parseBoolean(values.get(Attributes.Name.SEALED))
? sealBaseLocator.findSealBase(classLoader, typeName)
: NOT_SEALED);
} finally {
inputStream.close();
}
} catch (IOException exception) {
throw new IllegalStateException("Error while reading manifest file", exception);
}
} else {
return Definition.Trivial.INSTANCE;
}
}
/**
* A locator for a seal base URL.
*/
public interface SealBaseLocator {
/**
* Locates the URL that should be used for sealing a package.
*
* @param classLoader The class loader loading the package.
* @param typeName The name of the type being loaded that triggered the package definition.
* @return The URL that is used for sealing a package or {@code null} if the package should not be sealed.
*/
@MaybeNull
URL findSealBase(ClassLoader classLoader, String typeName);
/**
* A seal base locator that never seals a package.
*/
enum NonSealing implements SealBaseLocator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
@MaybeNull
public URL findSealBase(ClassLoader classLoader, String typeName) {
return NOT_SEALED;
}
}
/**
* A seal base locator that seals all packages with a fixed URL.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFixedValue implements SealBaseLocator {
/**
* The seal base URL.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final URL sealBase;
/**
* Creates a new seal base locator for a fixed URL.
*
* @param sealBase The seal base URL.
*/
public ForFixedValue(@MaybeNull URL sealBase) {
this.sealBase = sealBase;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public URL findSealBase(ClassLoader classLoader, String typeName) {
return sealBase;
}
@Override
@SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality.")
public int hashCode() {
return sealBase == null
? 17
: sealBase.hashCode();
}
@Override
@SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL", justification = "Package sealing relies on URL equality.")
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (other == null || getClass() != other.getClass()) {
return false;
}
ForFixedValue forFixedValue = (ForFixedValue) other;
return sealBase == null
? forFixedValue.sealBase == null
: sealBase.equals(forFixedValue.sealBase);
}
}
/**
* A seal base locator that imitates the behavior of a {@link java.net.URLClassLoader}, i.e. tries
* to deduct the base from a class's resource URL.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForTypeResourceUrl implements SealBaseLocator {
/**
* An index to indicate to a {@link String} manipulation that the initial slash should be excluded.
*/
private static final int EXCLUDE_INITIAL_SLASH = 1;
/**
* The file extension for a class file.
*/
private static final String CLASS_FILE_EXTENSION = ".class";
/**
* The protocol name of a jar file.
*/
private static final String JAR_FILE = "jar";
/**
* The protocol name of a file system link.
*/
private static final String FILE_SYSTEM = "file";
/**
* The protocol name of a Java 9 runtime image.
*/
private static final String RUNTIME_IMAGE = "jrt";
/**
* The seal base locator to fallback to when a resource is not found or an unexpected URL protocol is discovered.
*/
private final SealBaseLocator fallback;
/**
* Creates a new seal base locator that attempts deduction from a type's URL while using a
* {@link net.bytebuddy.dynamic.loading.PackageDefinitionStrategy.ManifestReading.SealBaseLocator.NonSealing} seal base locator
* as a fallback.
*/
public ForTypeResourceUrl() {
this(NonSealing.INSTANCE);
}
/**
* Creates a new seal base locator that attempts deduction from a type's URL.
*
* @param fallback The seal base locator to fallback to when a resource is not found or an unexpected URL protocol is discovered.
*/
public ForTypeResourceUrl(SealBaseLocator fallback) {
this.fallback = fallback;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public URL findSealBase(ClassLoader classLoader, String typeName) {
URL url = classLoader.getResource(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
if (url != null) {
try {
if (url.getProtocol().equals(JAR_FILE)) {
return URI.create(url.getPath().substring(0, url.getPath().indexOf('!'))).toURL();
} else if (url.getProtocol().equals(FILE_SYSTEM)) {
return url;
} else if (url.getProtocol().equals(RUNTIME_IMAGE)) {
String path = url.getPath();
int modulePathIndex = path.indexOf('/', EXCLUDE_INITIAL_SLASH);
return modulePathIndex == -1
? url
: URI.create(RUNTIME_IMAGE + ":" + path.substring(0, modulePathIndex)).toURL();
}
} catch (MalformedURLException exception) {
throw new IllegalStateException("Unexpected URL: " + url, exception);
}
}
return fallback.findSealBase(classLoader, typeName);
}
}
}
}
}