Skip to content
Commits on Source (11)
......@@ -3,19 +3,27 @@ language: java
sudo: false
dist: trusty
before_install:
- unset _JAVA_OPTIONS
- wget https://raw.githubusercontent.com/sormuras/bach/master/install-jdk.sh
matrix:
include:
# Java 9 (openjdk-9.0.1 fails with java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty)
# Java 9
- jdk: oraclejdk9
env: JDK_RELEASE='JDK 9' TARGET='-Pjava9'
env: JDK='JDK 9' TARGET='-Pjava9'
install: echo "The default Travis install script is being skipped!"
# Java 10
- env: JDK_RELEASE='JDK 10' TARGET='-Pjava10'
install: . ./.travis/install-jdk.sh -F 10 -L BCL
- env: JDK='JDK 10' TARGET='-Pjava10'
install: . ./install-jdk.sh -F 10 -L GPL
# Java 11
- env: JDK_RELEASE='JDK 11-ea' TARGET='-Pjava11'
install: . ./.travis/install-jdk.sh -F 11 -L BCL
- env: JDK='JDK 11' TARGET='-Pjava11'
install: . ./install-jdk.sh -F 11 -L BCL
# The following environments are known to fail
allow_failures:
- env: JDK='JDK 10' TARGET='-Pjava10'
- env: JDK='JDK 11' TARGET='-Pjava11'
script:
- ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -Dnet.bytebuddy.test.ci=true
- ./mvnw jacoco:prepare-agent verify jacoco:report ${TARGET} -Pintegration -Dfindbugs.skip=true -Dnet.bytebuddy.test.ci=true
- ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -Dnet.bytebuddy.test.ci=true -pl '!byte-buddy-gradle-plugin'
- ./mvnw jacoco:prepare-agent verify jacoco:report ${TARGET} -Pintegration -Dfindbugs.skip=true -Dnet.bytebuddy.test.ci=true -pl '!byte-buddy-gradle-plugin'
#!/usr/bin/env bash
#
# Install JDK for Linux
#
# This script determines the most recent early-access build number,
# downloads the JDK archive to the user home directory and extracts
# it there.
#
# Example usage
#
# install-jdk.sh | install most recent (early-access) JDK
# install-jdk.sh -W /usr/opt | install most recent (early-access) JDK to /usr/opt
# install-jdk.sh -F 9 | install most recent OpenJDK 9
# install-jdk.sh -F 10 | install most recent OpenJDK 10
# install-jdk.sh -F 10 -L BCL | install most recent OracleJDK 10
# install-jdk.sh -F 11 | install most recent OpenJDK 10
# install-jdk.sh -F 11 -L BCL | install most recent OracleJDK 10
#
# Options
#
# -F f | Feature number of the JDK release [9|10|...]
# -B b | Build number of the JDK release [?|1|2...]
# -L l | License of the JDK [GPL|BCL]
# -W w | Working directory and install path [${HOME}]
#
# Exported environment variables
#
# JAVA_HOME is set to the extracted JDK directory
# PATH is prepended with ${JAVA_HOME}/bin
#
# (C) 2018 Christian Stein
#
# https://github.com/sormuras/bach/blob/master/install-jdk.sh
#
set -e
JDK_FEATURE='11'
JDK_BUILD='?'
JDK_LICENSE='GPL'
JDK_WORKSPACE=${HOME}
while getopts F:B:L:W: option
do
case "${option}" in
F) JDK_FEATURE=${OPTARG};;
B) JDK_BUILD=${OPTARG};;
L) JDK_LICENSE=${OPTARG};;
W) JDK_WORKSPACE=${OPTARG};;
esac
done
#
# Other constants
#
JDK_DOWNLOAD='https://download.java.net/java'
JDK_BASENAME='openjdk'
if [ "${JDK_LICENSE}" != 'GPL' ]; then
JDK_BASENAME='jdk'
fi
#
# 9
#
if [ "${JDK_FEATURE}" == '9' ]; then
if [ "${JDK_BUILD}" == '?' ]; then
TMP=$(curl -L jdk.java.net/${JDK_FEATURE})
TMP="${TMP#*<h1>JDK}" # remove everything before the number
TMP="${TMP%%General-Availability Release*}" # remove everything after the number
JDK_BUILD="$(echo -e "${TMP}" | tr -d '[:space:]')" # remove all whitespace
fi
JDK_ARCHIVE=${JDK_BASENAME}-${JDK_BUILD}_linux-x64_bin.tar.gz
JDK_URL=${JDK_DOWNLOAD}/GA/jdk${JDK_FEATURE}/${JDK_BUILD}/binaries/${JDK_ARCHIVE}
JDK_HOME=jdk-${JDK_BUILD}
fi
#
# 10
#
if [ "${JDK_FEATURE}" == '10' ]; then
if [ "${JDK_BUILD}" == '?' ]; then
TMP=$(curl -L jdk.java.net/${JDK_FEATURE})
TMP="${TMP#*Most recent build: jdk-${JDK_FEATURE}+}" # remove everything before the number
TMP="${TMP%%<*}" # remove everything after the number
JDK_BUILD="$(echo -e "${TMP}" | tr -d '[:space:]')" # remove all whitespace
fi
JDK_ARCHIVE=${JDK_BASENAME}-${JDK_FEATURE}+${JDK_BUILD}_linux-x64_bin.tar.gz
JDK_URL=${JDK_DOWNLOAD}/jdk${JDK_FEATURE}/archive/${JDK_BUILD}/${JDK_LICENSE}/${JDK_ARCHIVE}
JDK_HOME=jdk-${JDK_FEATURE}
fi
#
# 11
#
if [ "${JDK_FEATURE}" == '11' ]; then
if [ "${JDK_BUILD}" == '?' ]; then
TMP=$(curl -L jdk.java.net/${JDK_FEATURE})
TMP="${TMP#*Most recent build: jdk-${JDK_FEATURE}-ea+}" # remove everything before the number
TMP="${TMP%%<*}" # remove everything after the number
JDK_BUILD="$(echo -e "${TMP}" | tr -d '[:space:]')" # remove all whitespace
fi
JDK_ARCHIVE=${JDK_BASENAME}-${JDK_FEATURE}-ea+${JDK_BUILD}_linux-x64_bin.tar.gz
JDK_URL=${JDK_DOWNLOAD}/early_access/jdk${JDK_FEATURE}/${JDK_BUILD}/${JDK_LICENSE}/${JDK_ARCHIVE}
JDK_HOME=jdk-${JDK_FEATURE}
fi
#
# Create any missing intermediate paths, switch to workspace, download, unpack, switch back.
#
mkdir -p ${JDK_WORKSPACE}
cd ${JDK_WORKSPACE}
wget ${JDK_URL}
tar -xzf ${JDK_ARCHIVE}
cd -
#
# Update environment and test-drive.
#
export JAVA_HOME=${JDK_WORKSPACE}/${JDK_HOME}
export PATH=${JAVA_HOME}/bin:$PATH
java --version
......@@ -5,7 +5,7 @@
<parent>
<artifactId>byte-buddy-parent</artifactId>
<groupId>net.bytebuddy</groupId>
<version>1.7.11</version>
<version>1.8.2</version>
</parent>
<artifactId>byte-buddy-agent</artifactId>
......
......@@ -364,7 +364,7 @@ public class ByteBuddyAgent {
private static void install(AttachmentProvider attachmentProvider, String processId, String argument, AgentProvider agentProvider) {
AttachmentProvider.Accessor attachmentAccessor = attachmentProvider.attempt();
if (!attachmentAccessor.isAvailable()) {
throw new IllegalStateException("No compatible attachment provider is not available");
throw new IllegalStateException("No compatible attachment provider is available");
}
try {
if (ATTACHMENT_TYPE_EVALUATOR.requiresExternalAttachment(processId)) {
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>byte-buddy-parent</artifactId>
<groupId>net.bytebuddy</groupId>
<version>1.7.11</version>
<version>1.8.2</version>
</parent>
<artifactId>byte-buddy-android</artifactId>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>byte-buddy-parent</artifactId>
<groupId>net.bytebuddy</groupId>
<version>1.7.11</version>
<version>1.8.2</version>
</parent>
<artifactId>byte-buddy-benchmark</artifactId>
......
......@@ -5,7 +5,7 @@
<parent>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-parent</artifactId>
<version>1.7.11</version>
<version>1.8.2</version>
</parent>
<artifactId>byte-buddy-dep</artifactId>
......
......@@ -72,9 +72,14 @@ public class ClassFileVersion implements Comparable<ClassFileVersion> {
public static final ClassFileVersion JAVA_V9 = new ClassFileVersion(Opcodes.V9);
/**
* The class file version of Java 10 (preliminary).
* The class file version of Java 10.
*/
public static final ClassFileVersion JAVA_V10 = new ClassFileVersion(Opcodes.V9 + 1);
public static final ClassFileVersion JAVA_V10 = new ClassFileVersion(Opcodes.V10);
/**
* The class file version of Java 11 (preliminary).
*/
public static final ClassFileVersion JAVA_V11 = new ClassFileVersion(Opcodes.V10 + 1);
/**
* A version locator for the executing JVM.
......@@ -136,6 +141,8 @@ public class ClassFileVersion implements Comparable<ClassFileVersion> {
return JAVA_V9;
} else if (javaVersionString.equals("1.10") || javaVersionString.equals("10")) {
return JAVA_V10;
} else if (javaVersionString.equals("1.11") || javaVersionString.equals("11")) {
return JAVA_V11;
} else {
throw new IllegalArgumentException("Unknown Java version string: " + javaVersionString);
}
......@@ -171,6 +178,8 @@ public class ClassFileVersion implements Comparable<ClassFileVersion> {
return JAVA_V9;
case 10:
return JAVA_V10;
case 11:
return JAVA_V11;
default:
throw new IllegalArgumentException("Unknown Java version: " + javaVersion);
}
......
......@@ -1372,7 +1372,7 @@ public interface AgentBuilder {
*
* @return A listener writing events to the standard output stream.
*/
public static Listener toSystemOut() {
public static StreamWriting toSystemOut() {
return new StreamWriting(System.out);
}
......@@ -1381,10 +1381,28 @@ public interface AgentBuilder {
*
* @return A listener writing events to the standard error stream.
*/
public static Listener toSystemError() {
public static StreamWriting toSystemError() {
return new StreamWriting(System.err);
}
/**
* Returns a version of this listener that only reports successfully transformed classes and failed transformations.
*
* @return A version of this listener that only reports successfully transformed classes and failed transformations.
*/
public Listener withTransformationsOnly() {
return new WithTransformationsOnly(this);
}
/**
* Returns a version of this listener that only reports failed transformations.
*
* @return A version of this listener that only reports failed transformations.
*/
public Listener withErrorsOnly() {
return new WithErrorsOnly(this);
}
@Override
public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
printStream.printf(PREFIX + " DISCOVERY %s [%s, %s, loaded=%b]%n", typeName, classLoader, module, loaded);
......@@ -1477,6 +1495,63 @@ public interface AgentBuilder {
}
}
/**
* A listener that only delegates events if they are successful or failed transformations.
*/
@EqualsAndHashCode(callSuper = false)
class WithTransformationsOnly extends Listener.Adapter {
/**
* The delegate listener.
*/
private final Listener delegate;
/**
* Creates a new listener that only delegates events if they are succesful or failed transformations.
*
* @param delegate The delegate listener.
*/
public WithTransformationsOnly(Listener delegate) {
this.delegate = delegate;
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
delegate.onTransformation(typeDescription, classLoader, module, loaded, dynamicType);
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
delegate.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* A listener that only delegates events if they are failed transformations.
*/
@EqualsAndHashCode(callSuper = false)
class WithErrorsOnly extends Listener.Adapter {
/**
* The delegate listener.
*/
private final Listener delegate;
/**
* Creates a new listener that only delegates events if they are failed transformations.
*
* @param delegate The delegate listener.
*/
public WithErrorsOnly(Listener delegate) {
this.delegate = delegate;
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
delegate.onError(typeName, classLoader, module, loaded, throwable);
}
}
/**
* A listener that adds read-edges to any module of an instrumented class upon its transformation.
*/
......
package net.bytebuddy.build;
import lombok.EqualsAndHashCode;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.EqualsMethod;
import net.bytebuddy.implementation.HashCodeMethod;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.annotation.*;
import static net.bytebuddy.matcher.ElementMatchers.*;
/**
* A build tool plugin that adds {@link Object#hashCode()} and {@link Object#equals(Object)} methods to a class if the
* {@link Enhance} annotation is present and no explicit method declaration was added.
*/
@EqualsAndHashCode
public class HashCodeAndEqualsPlugin implements Plugin {
@Override
public boolean matches(TypeDescription target) {
return target.getDeclaredAnnotations().isAnnotationPresent(Enhance.class);
}
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
Enhance enhance = typeDescription.getDeclaredAnnotations().ofType(Enhance.class).loadSilent();
if (typeDescription.getDeclaredMethods().filter(isHashCode()).isEmpty()) {
builder = builder.method(isHashCode()).intercept(enhance.invokeSuper()
.hashCodeMethod(typeDescription)
.withIgnoredFields(enhance.includeSyntheticFields()
? ElementMatchers.<FieldDescription>none()
: ElementMatchers.<FieldDescription>isSynthetic())
.withIgnoredFields(new ValueMatcher(ValueHandling.Sort.IGNORE))
.withNonNullableFields(nonNullable(new ValueMatcher(ValueHandling.Sort.REVERSE_NULLABILITY))));
}
if (typeDescription.getDeclaredMethods().filter(isEquals()).isEmpty()) {
EqualsMethod equalsMethod = enhance.invokeSuper()
.equalsMethod(typeDescription)
.withIgnoredFields(enhance.includeSyntheticFields()
? ElementMatchers.<FieldDescription>none()
: ElementMatchers.<FieldDescription>isSynthetic())
.withIgnoredFields(new ValueMatcher(ValueHandling.Sort.IGNORE))
.withNonNullableFields(nonNullable(new ValueMatcher(ValueHandling.Sort.REVERSE_NULLABILITY)));
builder = builder.method(isEquals()).intercept(enhance.permitSubclassEquality() ? equalsMethod.withSubclassEquality() : equalsMethod);
}
return builder;
}
/**
* Resolves the matcher to identify non-nullable fields.
*
* @param matcher The matcher that identifies fields that are either nullable or non-nullable.
* @return The actual matcher to identify non-nullable fields.
*/
protected ElementMatcher<FieldDescription> nonNullable(ElementMatcher<FieldDescription> matcher) {
return matcher;
}
/**
* A version of the {@link HashCodeAndEqualsPlugin} that assumes that all fields are non-nullable unless they are explicitly marked.
*/
@EqualsAndHashCode(callSuper = true)
public static class WithNonNullableFields extends HashCodeAndEqualsPlugin {
@Override
protected ElementMatcher<FieldDescription> nonNullable(ElementMatcher<FieldDescription> matcher) {
return not(matcher);
}
}
/**
* Instructs the {@link HashCodeAndEqualsPlugin} to generate {@link Object#hashCode()} and {@link Object#equals(Object)} for the annotated
* class unless these methods are already declared explicitly.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Enhance {
/**
* Determines the base value of any added method, i.e. if hash code or equality is based on the super type or not.
*
* @return A strategy for determining the base value.
*/
InvokeSuper invokeSuper() default InvokeSuper.IF_DECLARED;
/**
* Determines if synthetic fields should be included in the hash code and equality contract.
*
* @return {@code true} if synthetic fields should be included.
*/
boolean includeSyntheticFields() default false;
/**
* Determines if instances subclasses of the instrumented type are accepted upon an equality check.
*
* @return {@code true} if instances subclasses of the instrumented type are accepted upon an equality check.
*/
boolean permitSubclassEquality() default false;
/**
* A strategy for determining the base value of a hash code or equality contract.
*/
enum InvokeSuper {
/**
* Only invokes the super method's hash code and equality methods if any super class that is not {@link Object} explicitly defines such a method.
*/
IF_DECLARED {
@Override
protected HashCodeMethod hashCodeMethod(TypeDescription instrumentedType) {
TypeDefinition typeDefinition = instrumentedType.getSuperClass();
while (typeDefinition != null && !typeDefinition.represents(Object.class)) {
if (typeDefinition.asErasure().getDeclaredAnnotations().isAnnotationPresent(Enhance.class)) {
return HashCodeMethod.usingSuperClassOffset();
}
MethodList<?> hashCode = typeDefinition.getDeclaredMethods().filter(isHashCode());
if (!hashCode.isEmpty()) {
return hashCode.getOnly().isAbstract()
? HashCodeMethod.usingDefaultOffset()
: HashCodeMethod.usingSuperClassOffset();
}
typeDefinition = typeDefinition.getSuperClass();
}
return HashCodeMethod.usingDefaultOffset();
}
@Override
protected EqualsMethod equalsMethod(TypeDescription instrumentedType) {
TypeDefinition typeDefinition = instrumentedType.getSuperClass();
while (typeDefinition != null && !typeDefinition.represents(Object.class)) {
if (typeDefinition.asErasure().getDeclaredAnnotations().isAnnotationPresent(Enhance.class)) {
return EqualsMethod.requiringSuperClassEquality();
}
MethodList<?> hashCode = typeDefinition.getDeclaredMethods().filter(isHashCode());
if (!hashCode.isEmpty()) {
return hashCode.getOnly().isAbstract()
? EqualsMethod.isolated()
: EqualsMethod.requiringSuperClassEquality();
}
typeDefinition = typeDefinition.getSuperClass().asErasure();
}
return EqualsMethod.isolated();
}
},
/**
* Only invokes the super method's hash code and equality methods if the super class is also annotated with {@link Enhance}.
*/
IF_ANNOTATED {
@Override
protected HashCodeMethod hashCodeMethod(TypeDescription instrumentedType) {
TypeDefinition superClass = instrumentedType.getSuperClass();
return superClass != null && superClass.asErasure().getDeclaredAnnotations().isAnnotationPresent(Enhance.class)
? HashCodeMethod.usingSuperClassOffset()
: HashCodeMethod.usingDefaultOffset();
}
@Override
protected EqualsMethod equalsMethod(TypeDescription instrumentedType) {
TypeDefinition superClass = instrumentedType.getSuperClass();
return superClass != null && superClass.asErasure().getDeclaredAnnotations().isAnnotationPresent(Enhance.class)
? EqualsMethod.requiringSuperClassEquality()
: EqualsMethod.isolated();
}
},
/**
* Always invokes the super class's hash code and equality methods.
*/
ALWAYS {
@Override
protected HashCodeMethod hashCodeMethod(TypeDescription instrumentedType) {
return HashCodeMethod.usingSuperClassOffset();
}
@Override
protected EqualsMethod equalsMethod(TypeDescription instrumentedType) {
return EqualsMethod.requiringSuperClassEquality();
}
},
/**
* Never invokes the super class's hash code and equality methods.
*/
NEVER {
@Override
protected HashCodeMethod hashCodeMethod(TypeDescription instrumentedType) {
return HashCodeMethod.usingDefaultOffset();
}
@Override
protected EqualsMethod equalsMethod(TypeDescription instrumentedType) {
return EqualsMethod.isolated();
}
};
/**
* Resolves the hash code method to use.
*
* @param instrumentedType The instrumented type.
* @return The hash code method to use.
*/
protected abstract HashCodeMethod hashCodeMethod(TypeDescription instrumentedType);
/**
* Resolves the equals method to use.
*
* @param instrumentedType The instrumented type.
* @return The equals method to use.
*/
protected abstract EqualsMethod equalsMethod(TypeDescription instrumentedType);
}
}
/**
* Determines how a field should be used within generated hash code and equality methods.
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValueHandling {
/**
* Determines the handling of the annotated field.
*
* @return The handling of the annotated field.
*/
Sort value();
/**
* Determines how a field should be handled.
*/
enum Sort {
/**
* Excludes the field from hash code and equality methods.
*/
IGNORE,
/**
* Reverses the nullability of the field, i.e. assumes this field to be non-null or {@code null} if {@link WithNonNullableFields} is used.
*/
REVERSE_NULLABILITY
}
}
/**
* An element matcher for a {@link ValueHandling} annotation.
*/
@EqualsAndHashCode
protected static class ValueMatcher implements ElementMatcher<FieldDescription> {
/**
* The matched value.
*/
private final ValueHandling.Sort sort;
/**
* Creates a new value matcher.
*
* @param sort The matched value.
*/
protected ValueMatcher(ValueHandling.Sort sort) {
this.sort = sort;
}
@Override
public boolean matches(FieldDescription target) {
AnnotationDescription.Loadable<ValueHandling> annotation = target.getDeclaredAnnotations().ofType(ValueHandling.class);
return annotation != null && annotation.loadSilent().value() == sort;
}
}
}
package net.bytebuddy.build;
import lombok.EqualsAndHashCode;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.ToStringMethod;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.annotation.*;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isToString;
/**
* A build tool plugin that adds a {@link Object#toString()} and method to a class if the {@link Enhance} annotation is present and no
* explicit method declaration was added.
*/
@EqualsAndHashCode
public class ToStringPlugin implements Plugin {
@Override
public boolean matches(TypeDescription target) {
return target.getDeclaredAnnotations().isAnnotationPresent(Enhance.class);
}
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription) {
Enhance enhance = typeDescription.getDeclaredAnnotations().ofType(Enhance.class).loadSilent();
if (typeDescription.getDeclaredMethods().filter(isToString()).isEmpty()) {
builder = builder.method(isToString()).intercept(ToStringMethod.prefixedBy(enhance.prefix().getPrefixResolver())
.withIgnoredFields(enhance.includeSyntheticFields()
? ElementMatchers.<FieldDescription>none()
: ElementMatchers.<FieldDescription>isSynthetic())
.withIgnoredFields(isAnnotatedWith(Exclude.class)));
}
return builder;
}
/**
* Instructs the {@link ToStringPlugin} to generate a {@link Object#toString()} method for the annotated class unless this method
* is already declared explicitly.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Enhance {
/**
* Determines the prefix to be used for the string representation prior to adding field values.
*
* @return The prefix to use.
*/
Prefix prefix() default Prefix.SIMPLE;
/**
* Determines if synthetic fields should be included in the string representation.
*
* @return {@code true} if synthetic fields should be included.
*/
boolean includeSyntheticFields() default false;
/**
* A strategy for defining a prefix.
*/
enum Prefix {
/**
* Determines the use of a fully qualified class name as a prefix.
*/
FULLY_QUALIFIED(ToStringMethod.PrefixResolver.Default.FULLY_QUALIFIED_CLASS_NAME),
/**
* Determines the use of the canonical class name as a prefix.
*/
CANONICAL(ToStringMethod.PrefixResolver.Default.CANONICAL_CLASS_NAME),
/**
* Determines the use of the simple class name as a prefix.
*/
SIMPLE(ToStringMethod.PrefixResolver.Default.SIMPLE_CLASS_NAME);
/**
* The prefix resolver to use.
*/
private final ToStringMethod.PrefixResolver.Default prefixResolver;
/**
* Creates a new prefix.
*
* @param prefixResolver The prefix resolver to use.
*/
Prefix(ToStringMethod.PrefixResolver.Default prefixResolver) {
this.prefixResolver = prefixResolver;
}
/**
* Returns the prefix resolver to use.
*
* @return The prefix resolver to use.
*/
protected ToStringMethod.PrefixResolver.Default getPrefixResolver() {
return prefixResolver;
}
}
}
/**
* Determines that the annotated field is excluded from a string representation of the {@link ToStringPlugin}.
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Exclude {
/* does not declare any properties */
}
}
......@@ -7016,26 +7016,35 @@ public interface TypeDescription extends TypeDefinition, ByteCodeElement, TypeVa
@Override
public String getCanonicalName() {
return isAnonymousClass() || isLocalClass()
? NO_NAME
: getName().replace('$', '.');
if (isAnonymousClass() || isLocalClass()) {
return NO_NAME;
}
String internalName = getInternalName();
TypeDescription enclosingType = getEnclosingType();
if (enclosingType != null && internalName.startsWith(enclosingType.getInternalName() + "$")) {
return enclosingType.getCanonicalName() + "." + internalName.substring(enclosingType.getInternalName().length() + 1);
} else {
return getName();
}
}
@Override
public String getSimpleName() {
String internalName = getInternalName();
int simpleNameIndex = internalName.lastIndexOf('$');
simpleNameIndex = simpleNameIndex == -1
? internalName.lastIndexOf('/')
: simpleNameIndex;
if (simpleNameIndex == -1) {
return internalName;
TypeDescription enclosingType = getEnclosingType();
int simpleNameIndex;
if (enclosingType != null && internalName.startsWith(enclosingType.getInternalName() + "$")) {
simpleNameIndex = enclosingType.getInternalName().length() + 1;
} else {
while (simpleNameIndex < internalName.length() && !Character.isLetter(internalName.charAt(simpleNameIndex))) {
simpleNameIndex += 1;
simpleNameIndex = internalName.lastIndexOf('/');
if (simpleNameIndex == -1) {
return internalName;
}
return internalName.substring(simpleNameIndex);
}
while (simpleNameIndex < internalName.length() && !Character.isLetter(internalName.charAt(simpleNameIndex))) {
simpleNameIndex += 1;
}
return internalName.substring(simpleNameIndex);
}
@Override
......
......@@ -54,6 +54,11 @@ public interface TypeList extends FilterableList<TypeDescription, TypeList> {
protected TypeList wrap(List<TypeDescription> values) {
return new Explicit(values);
}
@Override
public int getStackSize() {
return StackSize.of(this);
}
}
/**
......@@ -105,11 +110,6 @@ public interface TypeList extends FilterableList<TypeDescription, TypeList> {
? NO_INTERFACES
: internalNames;
}
@Override
public int getStackSize() {
return StackSize.sizeOf(types);
}
}
/**
......@@ -161,15 +161,6 @@ public interface TypeList extends FilterableList<TypeDescription, TypeList> {
? NO_INTERFACES
: internalNames;
}
@Override
public int getStackSize() {
int stackSize = 0;
for (TypeDescription typeDescription : typeDescriptions) {
stackSize += typeDescription.getStackSize().getSize();
}
return stackSize;
}
}
/**
......
......@@ -19,9 +19,7 @@ import net.bytebuddy.description.type.TypeVariableToken;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.InjectionClassLoader;
import net.bytebuddy.dynamic.scaffold.*;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.*;
import net.bytebuddy.implementation.attribute.*;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
......@@ -887,6 +885,24 @@ public interface DynamicType {
*/
MethodDefinition.ImplementationDefinition<T> invokable(LatentMatcher<? super MethodDescription> matcher);
/**
* Implements {@link Object#hashCode()} and {@link Object#equals(Object)} methods for the instrumented type if those
* methods are not declared as {@code final} by a super class. The implementations do not consider any implementations
* of a super class and compare a class field by field without considering synthetic fields.
*
* @return A new type builder that defines {@link Object#hashCode()} and {@link Object#equals(Object)} methods accordingly.
*/
Builder<T> withHashCodeEquals();
/**
* Implements a {@link Object#toString()} method for the instrumented type if such a method is not declared as {@code final}
* by a super class. The implementation prefixes the string with the simple class name and prints each non-synthetic field's
* value after the field's name.
*
* @return A new type builder that defines {@link Object#toString()} method accordingly.
*/
Builder<T> withToString();
/**
* <p>
* Creates the dynamic type this builder represents. If the specified dynamic type is not legal, an {@link IllegalStateException} is thrown.
......@@ -2629,6 +2645,19 @@ public interface DynamicType {
return invokable(new LatentMatcher.Resolved<MethodDescription>(matcher));
}
@Override
public Builder<S> withHashCodeEquals() {
return method(isHashCode())
.intercept(HashCodeMethod.usingDefaultOffset().withIgnoredFields(isSynthetic()))
.method(isEquals())
.intercept(EqualsMethod.isolated().withIgnoredFields(isSynthetic()));
}
@Override
public Builder<S> withToString() {
return method(isToString()).intercept(ToStringMethod.prefixedBySimpleClassName());
}
@Override
public Unloaded<S> make(TypePool typePool) {
return make(TypeResolutionStrategy.Passive.INSTANCE, typePool);
......
......@@ -3968,6 +3968,13 @@ public interface TypeWriter<T> {
: IGNORE_ANNOTATION;
}
@Override
public void visitAnnotableParameterCount(int count, boolean visible) {
if (annotationRetention.isEnabled()) {
super.visitAnnotableParameterCount(count, visible);
}
}
@Override
public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
......@@ -4043,6 +4050,13 @@ public interface TypeWriter<T> {
: IGNORE_ANNOTATION;
}
@Override
public void visitAnnotableParameterCount(int count, boolean visible) {
if (annotationRetention.isEnabled()) {
super.visitAnnotableParameterCount(count, visible);
}
}
@Override
public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) {
return annotationRetention.isEnabled()
......
......@@ -695,6 +695,11 @@ public interface Implementation extends InstrumentedType.Prepareable {
*/
private final Map<FieldCacheEntry, FieldDescription.InDefinedShape> registeredFieldCacheEntries;
/**
* A set of registered field cache entries.
*/
private final Set<FieldDescription.InDefinedShape> registeredFieldCacheFields;
/**
* A random suffix to append to the names of accessor methods.
*/
......@@ -728,6 +733,7 @@ public interface Implementation extends InstrumentedType.Prepareable {
registeredSetters = new HashMap<FieldDescription, DelegationRecord>();
auxiliaryTypes = new HashMap<AuxiliaryType, DynamicType>();
registeredFieldCacheEntries = new HashMap<FieldCacheEntry, FieldDescription.InDefinedShape>();
registeredFieldCacheFields = new HashSet<FieldDescription.InDefinedShape>();
suffix = RandomString.make();
fieldCacheCanAppendEntries = true;
}
......@@ -795,7 +801,7 @@ public interface Implementation extends InstrumentedType.Prepareable {
int hashCode = fieldValue.hashCode();
do {
fieldCache = new CacheValueField(instrumentedType, fieldType.asGenericType(), suffix, hashCode++);
} while (registeredFieldCacheEntries.values().contains(fieldCache));
} while (!registeredFieldCacheFields.add(fieldCache));
registeredFieldCacheEntries.put(fieldCacheEntry, fieldCache);
return fieldCache;
}
......
......@@ -1445,6 +1445,9 @@ public class MethodCall implements Implementation.Composable {
TypeDescription instrumentedType,
Assigner assigner,
Assigner.Typing typing) {
if (instrumentedMethod.isStatic() && !invokedMethod.isStatic() && !invokedMethod.isConstructor()) {
throw new IllegalStateException("Cannot invoke " + invokedMethod + " from " + instrumentedMethod);
}
return new StackManipulation.Compound(
invokedMethod.isStatic()
? StackManipulation.Trivial.INSTANCE
......@@ -2008,10 +2011,10 @@ public class MethodCall implements Implementation.Composable {
argumentLoaders.addAll(argumentLoader.make(implementationTarget.getInstrumentedType(), instrumentedMethod, invokedMethod));
}
ParameterList<?> parameters = invokedMethod.getParameters();
Iterator<? extends ParameterDescription> parameterIterator = parameters.iterator();
if (parameters.size() != argumentLoaders.size()) {
throw new IllegalStateException(invokedMethod + " does not take " + argumentLoaders.size() + " arguments");
}
Iterator<? extends ParameterDescription> parameterIterator = parameters.iterator();
List<StackManipulation> argumentInstructions = new ArrayList<StackManipulation>(argumentLoaders.size());
for (ArgumentLoader argumentLoader : argumentLoaders) {
argumentInstructions.add(argumentLoader.resolve(parameterIterator.next(), assigner, typing));
......