Skip to content

Commit

Permalink
Added field constant. Clean up for use of defined shape fields.
Browse files Browse the repository at this point in the history
  • Loading branch information
Rafael Winterhalter committed Dec 18, 2015
1 parent 4cd8bf4 commit 12bdec6
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 11 deletions.
Expand Up @@ -414,7 +414,7 @@ interface Context {
* that is put onto the operand stack by this method's returned stack manipulation.
* @return A description of a field that was defined on the instrumented type which contains the given value.
*/
FieldDescription cache(StackManipulation fieldValue, TypeDescription fieldType);
FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType);

/**
* Represents an extractable view of an {@link Implementation.Context} which
Expand Down Expand Up @@ -568,7 +568,7 @@ public TypeDescription register(AuxiliaryType auxiliaryType) {
}

@Override
public FieldDescription cache(StackManipulation fieldValue, TypeDescription fieldType) {
public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
throw new IllegalStateException("Field values caching was disabled: " + fieldType);
}

Expand Down Expand Up @@ -687,7 +687,7 @@ class Default implements ExtractableView, AuxiliaryType.MethodAccessorFactory {
/**
* A map of already registered field caches to their field representation.
*/
private final Map<FieldCacheEntry, FieldDescription> registeredFieldCacheEntries;
private final Map<FieldCacheEntry, FieldDescription.InDefinedShape> registeredFieldCacheEntries;

/**
* A random suffix to append to the names of accessor methods.
Expand Down Expand Up @@ -726,7 +726,7 @@ protected Default(TypeDescription instrumentedType,
registeredSetters = new HashMap<FieldDescription, MethodDescription.InDefinedShape>();
accessorMethods = new LinkedList<TypeWriter.MethodPool.Record>();
auxiliaryTypes = new HashMap<AuxiliaryType, DynamicType>();
registeredFieldCacheEntries = new HashMap<FieldCacheEntry, FieldDescription>();
registeredFieldCacheEntries = new HashMap<FieldCacheEntry, FieldDescription.InDefinedShape>();
suffix = RandomString.make();
fieldCacheCanAppendEntries = true;
prohibitTypeInitiailzer = false;
Expand Down Expand Up @@ -786,9 +786,9 @@ public List<DynamicType> getRegisteredAuxiliaryTypes() {
}

@Override
public FieldDescription cache(StackManipulation fieldValue, TypeDescription fieldType) {
public FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType) {
FieldCacheEntry fieldCacheEntry = new FieldCacheEntry(fieldValue, fieldType);
FieldDescription fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry);
FieldDescription.InDefinedShape fieldCache = registeredFieldCacheEntries.get(fieldCacheEntry);
if (fieldCache != null) {
return fieldCache;
}
Expand All @@ -804,7 +804,7 @@ public FieldDescription cache(StackManipulation fieldValue, TypeDescription fiel
public void drain(ClassVisitor classVisitor, TypeWriter.MethodPool methodPool, InjectedCode injectedCode) {
fieldCacheCanAppendEntries = false;
InstrumentedType.TypeInitializer typeInitializer = this.typeInitializer;
for (Map.Entry<FieldCacheEntry, FieldDescription> entry : registeredFieldCacheEntries.entrySet()) {
for (Map.Entry<FieldCacheEntry, FieldDescription.InDefinedShape> entry : registeredFieldCacheEntries.entrySet()) {
classVisitor.visitField(entry.getValue().getModifiers(),
entry.getValue().getInternalName(),
entry.getValue().getDescriptor(),
Expand Down
@@ -0,0 +1,139 @@
package net.bytebuddy.implementation.bytecode.constant;

import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import org.objectweb.asm.MethodVisitor;

import java.lang.reflect.Field;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

/**
* Represents a {@link Field} constant for a given type.
*/
public class FieldConstant implements StackManipulation {

/**
* The {@link Class#getDeclaredField(String)} method.
*/
private static final MethodDescription.InDefinedShape GET_DECLARED_FIELD;

/*
* Looks up the method for finding a class's declared field.
*/
static {
GET_DECLARED_FIELD = new TypeDescription.ForLoadedType(Class.class).getDeclaredMethods()
.filter(named("getDeclaredField").and(takesArguments(String.class)))
.getOnly();
}

/**
* The field to be represent as a {@link Field}.
*/
private final FieldDescription.InDefinedShape fieldDescription;

/**
* Creates a new field constant.
*
* @param fieldDescription The field to be represent as a {@link Field}.
*/
public FieldConstant(FieldDescription.InDefinedShape fieldDescription) {
this.fieldDescription = fieldDescription;
}

/**
* Retruns a cached version of this field constant.
*
* @return A cached version of this field constant.
*/
public StackManipulation cached() {
return new Cached(this);
}

@Override
public boolean isValid() {
return true;
}

@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return new Compound(
ClassConstant.of(fieldDescription.getDeclaringType()),
new TextConstant(fieldDescription.getInternalName()),
MethodInvocation.invoke(GET_DECLARED_FIELD)
).apply(methodVisitor, implementationContext);
}

@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fieldDescription.equals(((FieldConstant) other).fieldDescription);
}

@Override
public int hashCode() {
return fieldDescription.hashCode();
}

@Override
public String toString() {
return "FieldConstant{" +
"fieldDescription=" + fieldDescription +
'}';
}

/**
* A cached version of a {@link FieldConstant}.
*/
protected static class Cached implements StackManipulation {

/**
* The field constant stack manipulation.
*/
private final StackManipulation fieldConstant;

/**
* Creates a new cached version of a field constant.
*
* @param fieldConstant The field constant stack manipulation.
*/
public Cached(StackManipulation fieldConstant) {
this.fieldConstant = fieldConstant;
}

@Override
public boolean isValid() {
return fieldConstant.isValid();
}

@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return FieldAccess.forField(implementationContext.cache(fieldConstant, new TypeDescription.ForLoadedType(Field.class)))
.getter()
.apply(methodVisitor, implementationContext);
}

@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass()) && fieldConstant.equals(((Cached) other).fieldConstant);
}

@Override
public int hashCode() {
return fieldConstant.hashCode();
}

@Override
public String toString() {
return "FieldConstant.Cached{" +
"fieldConstant=" + fieldConstant +
'}';
}
}
}
Expand Up @@ -65,7 +65,9 @@ public enum FieldAccess {
* @return A stack manipulation for reading the enumeration.
*/
public static StackManipulation forEnumeration(EnumerationDescription enumerationDescription) {
FieldList<?> fieldList = enumerationDescription.getEnumerationType().getDeclaredFields().filter(named(enumerationDescription.getValue()));
FieldList<FieldDescription.InDefinedShape> fieldList = enumerationDescription.getEnumerationType()
.getDeclaredFields()
.filter(named(enumerationDescription.getValue()));
return fieldList.size() != 1 || !fieldList.getOnly().isStatic() || !fieldList.getOnly().isPublic() || !fieldList.getOnly().isEnum()
? StackManipulation.Illegal.INSTANCE
: STATIC.new AccessDispatcher(fieldList.getOnly()).getter();
Expand Down Expand Up @@ -201,14 +203,14 @@ protected class AccessDispatcher implements Defined {
/**
* A description of the accessed field.
*/
private final FieldDescription fieldDescription;
private final FieldDescription.InDefinedShape fieldDescription;

/**
* Creates a new access dispatcher.
*
* @param fieldDescription A description of the accessed field.
*/
protected AccessDispatcher(FieldDescription fieldDescription) {
protected AccessDispatcher(FieldDescription.InDefinedShape fieldDescription) {
this.fieldDescription = fieldDescription;
}

Expand Down Expand Up @@ -264,7 +266,7 @@ public boolean isValid() {
@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
methodVisitor.visitFieldInsn(getOpcode(),
fieldDescription.getDeclaringType().asErasure().getInternalName(),
fieldDescription.getDeclaringType().getInternalName(),
fieldDescription.getInternalName(),
fieldDescription.getDescriptor());
return resolveSize(fieldDescription.getType().getStackSize());
Expand Down
@@ -0,0 +1,94 @@
package net.bytebuddy.implementation.bytecode.constant;

import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.test.utility.MockitoRule;
import net.bytebuddy.test.utility.ObjectPropertyAssertion;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.mockito.Mock;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.lang.reflect.Field;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;

public class FieldConstantTest {

private static final String FOO = "foo", BAR = "bar", QUX = "qux", BAZ = "baz";

@Rule
public TestRule mockitoRule = new MockitoRule(this);

@Mock
private FieldDescription.InDefinedShape fieldDescription, cacheField;

@Mock
private TypeDescription declaringType, cacheDeclaringType, cacheFieldType;

@Mock
private MethodVisitor methodVisitor;

@Mock
private Implementation.Context implementationContext;

@Before
public void setUp() throws Exception {
when(declaringType.getInternalName()).thenReturn(FOO);
when(fieldDescription.getInternalName()).thenReturn(BAR);
when(fieldDescription.getDeclaringType()).thenReturn(declaringType);
when(declaringType.getDescriptor()).thenReturn("L" + QUX + ";");
when(implementationContext.cache(new FieldConstant(fieldDescription), new TypeDescription.ForLoadedType(Field.class)))
.thenReturn(cacheField);
when(cacheField.getDeclaringType()).thenReturn(cacheDeclaringType);
when(cacheField.isStatic()).thenReturn(true);
when(cacheDeclaringType.getInternalName()).thenReturn(BAZ);
when(cacheField.getName()).thenReturn(FOO + BAR);
when(cacheField.getType()).thenReturn(cacheFieldType);
when(cacheFieldType.getStackSize()).thenReturn(StackSize.SINGLE);
when(cacheField.getInternalName()).thenReturn(FOO + BAR);
when(cacheField.getDescriptor()).thenReturn(QUX + BAZ);
}

@Test
public void testConstantCreation() throws Exception {
StackManipulation.Size size = new FieldConstant(fieldDescription).apply(methodVisitor, implementationContext);
assertThat(size.getSizeImpact(), is(1));
assertThat(size.getMaximalSize(), is(2));
verify(methodVisitor).visitLdcInsn(Type.getObjectType(QUX));
verify(methodVisitor).visitLdcInsn(BAR);
verify(methodVisitor).visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Class",
"getDeclaredField",
"(Ljava/lang/String;)Ljava/lang/reflect/Field;",
false);
verifyNoMoreInteractions(methodVisitor);
verifyZeroInteractions(implementationContext);
}

@Test
public void testCached() throws Exception {
StackManipulation.Size size = new FieldConstant(fieldDescription).cached().apply(methodVisitor, implementationContext);
assertThat(size.getSizeImpact(), is(1));
assertThat(size.getMaximalSize(), is(1));
verify(implementationContext).cache(new FieldConstant(fieldDescription), new TypeDescription.ForLoadedType(Field.class));
verifyNoMoreInteractions(implementationContext);
verify(methodVisitor).visitFieldInsn(Opcodes.GETSTATIC, BAZ, FOO + BAR, QUX + BAZ);
verifyNoMoreInteractions(methodVisitor);
}

@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(FieldConstant.class).apply();
ObjectPropertyAssertion.of(FieldConstant.Cached.class).apply();
}
}

0 comments on commit 12bdec6

Please sign in to comment.