/*
* Copyright (c) 2007, 2010, James Leigh 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 openrdf.org 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 OWNER 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 net.enilink.composition.properties.behaviours;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FieldNode;
import net.enilink.composition.ClassDefiner;
import net.enilink.composition.annotations.Iri;
import net.enilink.composition.asm.BehaviourClassNode;
import net.enilink.composition.asm.BehaviourClassProcessor;
import net.enilink.composition.asm.ExtendedMethod;
import net.enilink.composition.asm.Types;
import net.enilink.composition.asm.util.BehaviourMethodGenerator;
import net.enilink.composition.properties.PropertyMapper;
import net.enilink.composition.properties.PropertySet;
import net.enilink.composition.properties.PropertySetFactory;
import net.enilink.composition.properties.traits.Mergeable;
import net.enilink.composition.properties.traits.PropertySetOwner;
import net.enilink.composition.properties.traits.Refreshable;
import net.enilink.composition.properties.util.UnmodifiablePropertySet;
import com.google.inject.Inject;
/**
* Properties that have the {@link Iri} annotation are replaced with getters and
* setters that access an underlying repository directly.
*/
public class PropertyMapperProcessor implements BehaviourClassProcessor,
Opcodes, Types {
private static final String FACTORY_FIELD = "_$propertySetFactory";
private static final String PROPERTY_SUFFIX = "Property";
@Inject
protected ClassDefiner definer;
protected PropertyMapper propertyMapper;
private void addPropertySetFactoryField(BehaviourClassNode node) {
FieldNode factoryField = new FieldNode(Opcodes.ACC_PRIVATE,
getFactoryField(),
Type.getDescriptor(PropertySetFactory.class), null, null);
factoryField.visitAnnotation(Type.getDescriptor(Inject.class), true);
node.addField(factoryField);
}
private String getFactoryField() {
return FACTORY_FIELD;
}
private String getPropertyFieldName(String property) {
return "_$" + property + PROPERTY_SUFFIX;
}
private Collection<FieldNode> getPropertySetFields(BehaviourClassNode node) {
Collection<FieldNode> fields = new ArrayList<FieldNode>();
for (Object field : node.fields) {
if (((FieldNode) field).name.endsWith(PROPERTY_SUFFIX)) {
fields.add((FieldNode) field);
}
}
return fields;
}
private void loadPropertySet(PropertyDescriptor pd,
BehaviourMethodGenerator gen) {
loadPropertySet(pd, gen, false);
}
private void loadPropertySet(PropertyDescriptor pd,
BehaviourMethodGenerator gen, boolean forceModifiable) {
gen.loadThis();
gen.invokeVirtual(
gen.getMethod().getOwner().getType(),
new org.objectweb.asm.commons.Method("_$get" + pd.getName(),
Type.getMethodDescriptor(Type
.getType(PropertySet.class))));
// when forceModifiable is requested, check if the resulting property is
// unmodifiable and call its getDelegate()-method if it is
if (forceModifiable) {
gen.dup();
gen.instanceOf(Type.getType(UnmodifiablePropertySet.class));
Label notInstanceOf = gen.newLabel();
gen.ifZCmp(IFEQ, notInstanceOf);
// it is unmodifiable, call getDelegate()
gen.checkCast(Type.getType(UnmodifiablePropertySet.class));
gen.invoke(Methods.UNMODIFIABLEPROPERTYSET_GET_DELEGATE);
gen.mark(notInstanceOf);
}
}
@SuppressWarnings("unchecked")
private void createPropertySetAccessor(PropertyDescriptor pd,
BehaviourClassNode node) throws Exception {
String fieldName = getPropertyFieldName(pd.getName());
FieldNode propertyField = new FieldNode(Opcodes.ACC_PRIVATE, fieldName,
Type.getDescriptor(PropertySet.class), null, null);
node.addField(propertyField);
ExtendedMethod propertyAccessor = new ExtendedMethod(node,
Opcodes.ACC_PRIVATE, "_$get" + pd.getName(),
Type.getMethodDescriptor(Type.getType(PropertySet.class)),
null, null);
node.methods.add(propertyAccessor);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(
propertyAccessor);
lazyInitializePropertySet(pd, node, propertyField, gen);
gen.returnValue();
gen.endMethod();
}
private void lazyInitializePropertySet(PropertyDescriptor pd,
BehaviourClassNode node, FieldNode field,
BehaviourMethodGenerator gen) throws Exception {
gen.loadThis();
gen.getField(field.name, Type.getType(field.desc));
gen.dup();
Label exists = gen.newLabel();
gen.ifNonNull(exists);
gen.pop();
Method getter = pd.getReadMethod();
Method setter = pd.getWriteMethod();
loadFactory(node, gen);
gen.loadBean();
gen.push(propertyMapper.findPredicate(pd));
// load element type
Class<?> propertyType = pd.getPropertyType();
if (Set.class.equals(propertyType)) {
java.lang.reflect.Type t = pd.getReadMethod()
.getGenericReturnType();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
java.lang.reflect.Type[] args = pt.getActualTypeArguments();
if (args.length == 1 && args[0] instanceof Class<?>) {
propertyType = (Class<?>) args[0];
}
}
}
gen.push(Type.getType(propertyType));
// instantiate PropertyDescriptor
Type propertyDescType = Type.getType(PropertyDescriptor.class);
gen.newInstance(propertyDescType);
gen.dup();
// call constructor (propertyName, method, method)
gen.push(pd.getName());
loadMethodObject(Type.getType(getter.getDeclaringClass()),
getter.getName(), Type.getReturnType(getter),
Type.getArgumentTypes(getter), gen);
if (setter == null) {
gen.push((String) null);
} else {
loadMethodObject(Type.getType(setter.getDeclaringClass()),
setter.getName(), Type.getReturnType(setter),
Type.getArgumentTypes(setter), gen);
}
gen.invokeConstructor(propertyDescType,
new org.objectweb.asm.commons.Method("<init>", Type.VOID_TYPE,
new Type[] { STRING_TYPE, METHOD_TYPE, METHOD_TYPE }));
// retrieve read method annotations
gen.invokeVirtual(propertyDescType,
new org.objectweb.asm.commons.Method("getReadMethod",
METHOD_TYPE, new Type[0]));
gen.invokeVirtual(
METHOD_TYPE,
new org.objectweb.asm.commons.Method("getAnnotations", Type
.getType(Annotation[].class), new Type[0]));
gen.invoke(Methods.PROPERTYSETFACTORY_CREATEPROPERTYSET);
if (setter == null) {
// this property set is readonly
gen.invoke(Methods.PROPERTYSETS_UNMODIFIABLE);
}
// create return value
gen.dup();
// store property set in field
gen.loadThis();
gen.swap();
gen.putField(field.name, Type.getType(field.desc));
gen.mark(exists);
}
private void loadMethodObject(Type declaringClass, String name,
Type returnType, Type[] paramTypes, BehaviourMethodGenerator gen) {
gen.push(declaringClass);
gen.push(name);
gen.loadArray(paramTypes);
gen.invokeVirtual(
Type.getType(Class.class),
new org.objectweb.asm.commons.Method("getDeclaredMethod", Type
.getType(Method.class),
new Type[] { Type.getType(String.class),
Type.getType(Class[].class) }));
}
private void implementGetter(PropertyDescriptor pd, BehaviourClassNode node)
throws Exception {
Class<?> classType = pd.getReadMethod().getReturnType();
Type type = Type.getType(classType);
ExtendedMethod mn = node.addExtendedMethod(pd.getReadMethod(), definer);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(mn);
loadPropertySet(pd, gen);
if (isCollection(classType)) {
gen.invoke(Methods.PROPERTYSET_GET_ALL);
gen.returnValue();
} else if (classType.isPrimitive()) {
gen.invoke(Methods.PROPERTYSET_GET_SINGLE);
gen.dup();
Label isNull = gen.newLabel();
gen.ifNull(isNull);
gen.unbox(type);
gen.returnValue();
gen.mark(isNull);
gen.pop();
gen.push(0);
gen.cast(Type.INT_TYPE, type);
gen.returnValue();
} else {
Label tryLabel = gen.mark();
// try
gen.invoke(Methods.PROPERTYSET_GET_SINGLE);
gen.checkCast(type);
gen.returnValue();
// catch (ClassCastException)
Label catchLabel = gen.mark();
Type exceptionType = Type.getType(ClassCastException.class);
gen.catchException(tryLabel, catchLabel, exceptionType);
gen.newInstance(exceptionType);
gen.dup();
gen.newStringBuilder();
// reload property value
loadPropertySet(pd, gen);
gen.invoke(Methods.PROPERTYSET_GET_SINGLE);
gen.invokeToString();
gen.appendToStringBuilder();
gen.push("cannot be cast to " + classType.getName());
gen.appendToStringBuilder();
gen.invokeToString();
gen.invokeConstructor(exceptionType,
org.objectweb.asm.commons.Method
.getMethod("void <init>(String)"));
gen.throwException();
}
gen.endMethod();
}
private void implementProperty(PropertyDescriptor pd,
BehaviourClassNode node) throws Exception {
createPropertySetAccessor(pd, node);
implementGetter(pd, node);
Method setter = pd.getWriteMethod();
if (setter != null) {
implementSetter(pd, node);
}
}
@Override
public boolean implementsClass(Class<?> concept) {
return !propertyMapper.findProperties(concept).isEmpty();
}
private void implementSetter(PropertyDescriptor pd, BehaviourClassNode node)
throws Exception {
Class<?> classType = pd.getWriteMethod().getParameterTypes()[0];
Type type = Type.getType(classType);
ExtendedMethod mn = node
.addExtendedMethod(pd.getWriteMethod(), definer);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(mn);
loadPropertySet(pd, gen);
gen.loadArgs();
if (isCollection(classType)) {
gen.invoke(Methods.PROPERTYSET_SET_ALL);
} else {
gen.box(type);
gen.invoke(Methods.PROPERTYSET_SET_SINGLE);
}
// enable method chaining for setters
Class<?> clazz = mn.getOverriddenMethod().getDeclaringClass();
if (mn.getOverriddenMethod().getReturnType().isAssignableFrom(clazz)) {
if (clazz.isInterface()) {
gen.loadBean();
} else {
gen.loadThis();
}
}
gen.returnValue();
gen.endMethod();
}
private boolean isCollection(Class<?> type) throws Exception {
return Set.class.equals(type);
}
private void loadFactory(BehaviourClassNode node,
BehaviourMethodGenerator gen) {
gen.loadThis();
gen.getField(getFactoryField(), Type.getType(PropertySetFactory.class));
}
private void mergeProperty(PropertyDescriptor pd,
BehaviourMethodGenerator gen) throws Exception {
Class<?> type = pd.getPropertyType();
if (type.isPrimitive()) {
loadPropertySet(pd, gen, true);
if (Type.getType(type).getSize() == 1) {
gen.swap();
} else {
// support long and double types
gen.dupX2();
gen.pop();
}
persistValue(type, gen);
} else {
gen.dup();
Label isNull = gen.newLabel();
gen.ifNull(isNull);
loadPropertySet(pd, gen, true);
gen.swap();
persistValue(type, gen);
Label end = gen.newLabel();
gen.goTo(end);
gen.mark(isNull);
gen.pop();
gen.mark(end);
}
}
private void overrideMergeMethod(BehaviourClassNode node) throws Exception {
Method merge = Mergeable.class.getMethod("merge", Object.class);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(
node.addExtendedMethod(merge, definer));
// invoke overridden method
invokeSuper(gen, merge);
gen.loadArg(0);
gen.instanceOf(node.getParentType());
Label notInstanceOf = gen.newLabel();
gen.ifZCmp(IFEQ, notInstanceOf);
// access property value with corresponding interface method
for (PropertyDescriptor pd : propertyMapper.findProperties(node
.getParentClass())) {
gen.loadArg(0);
gen.checkCast(Type.getType(pd.getReadMethod().getDeclaringClass()));
gen.invoke(pd.getReadMethod());
mergeProperty(pd, gen);
}
Label end = gen.newLabel();
gen.goTo(end);
gen.mark(notInstanceOf);
// test if other object is a PropertySetOwner
gen.loadArg(0);
gen.instanceOf(Type.getType(PropertySetOwner.class));
gen.ifZCmp(IFEQ, end);
// access property set with "getPropertySet" method
Method getPropertySet = PropertySetOwner.class.getMethod(
"getPropertySet", String.class);
for (PropertyDescriptor pd : propertyMapper.findProperties(node
.getParentClass())) {
// load other property set
gen.loadArg(0);
gen.push(propertyMapper.findPredicate(pd));
gen.invoke(getPropertySet);
gen.dup();
Label isNull = gen.newLabel();
gen.ifNull(isNull);
// load own property set (force it to be modifiable)
loadPropertySet(pd, gen, true);
gen.swap();
// copy values
gen.invoke(Methods.PROPERTYSET_GET_ALL);
gen.invoke(Methods.PROPERTYSET_ADD_ALL);
gen.pop();
Label endLoop = gen.newLabel();
gen.goTo(endLoop);
gen.mark(isNull);
gen.pop();
gen.mark(endLoop);
}
gen.mark(end);
gen.returnValue();
gen.endMethod();
}
private void overrideRefreshMethod(BehaviourClassNode node)
throws Exception {
Method refresh = Refreshable.class.getMethod("refresh");
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(
node.addExtendedMethod(refresh, definer));
// invoke overridden method
invokeSuper(gen, refresh);
for (FieldNode field : getPropertySetFields(node)) {
gen.loadThis();
gen.getField(field.name, Type.getType(field.desc));
gen.dup();
Label isNull = gen.newLabel();
gen.ifNull(isNull);
gen.invoke(refresh);
Label end = gen.newLabel();
gen.goTo(end);
gen.mark(isNull);
gen.pop();
gen.mark(end);
}
gen.returnValue();
gen.endMethod();
}
private void persistValue(Class<?> type, BehaviourMethodGenerator gen)
throws Exception {
if (isCollection(type)) {
gen.invoke(Methods.PROPERTYSET_ADD_ALL);
} else {
gen.box(Type.getType(type));
gen.invoke(Methods.PROPERTYSET_ADD_SINGLE);
}
gen.pop();
}
private void invokeSuper(BehaviourMethodGenerator gen, Method method) {
try {
// check if there is an already implemented merge method
Method implementedMethod = gen.getMethod().getOwner()
.getParentClass()
.getMethod(method.getName(), method.getParameterTypes());
if ((implementedMethod.getModifiers() & Modifier.ABSTRACT) == 0) {
// invoke super.getPropertySet()
gen.loadThis();
gen.loadArgs();
gen.invokeSpecial(gen.getMethod().getOwner().getParentType(),
org.objectweb.asm.commons.Method
.getMethod(implementedMethod));
}
} catch (NoSuchMethodException e) {
// continue
}
}
private void overrideGetPropertySetMethod(BehaviourClassNode node,
Collection<PropertyDescriptor> properties) throws Exception {
Method getPropertySet = PropertySetOwner.class.getMethod(
"getPropertySet", String.class);
BehaviourMethodGenerator gen = new BehaviourMethodGenerator(
node.addExtendedMethod(getPropertySet, definer));
// invoke overridden method
invokeSuper(gen, getPropertySet);
org.objectweb.asm.commons.Method equals = new org.objectweb.asm.commons.Method(
"equals", Type.BOOLEAN_TYPE, new Type[] { OBJECT_TYPE });
Label endLabel = gen.newLabel();
for (PropertyDescriptor pd : properties) {
Label notEqualsLabel = gen.newLabel();
gen.push(propertyMapper.findPredicate(pd));
gen.loadArg(0);
gen.invokeVirtual(STRING_TYPE, equals);
gen.ifZCmp(IFEQ, notEqualsLabel);
// if ("...".equals(uri))
loadPropertySet(pd, gen);
gen.goTo(endLabel);
gen.mark(notEqualsLabel);
}
// else
gen.push((String) null);
gen.mark(endLabel);
gen.returnValue();
gen.endMethod();
}
@Override
public void process(BehaviourClassNode classNode) throws Exception {
classNode.addInterface(Type.getInternalName(Mergeable.class));
classNode.addInterface(Type.getInternalName(Refreshable.class));
classNode.addInterface(Type.getInternalName(PropertySetOwner.class));
classNode.addInjectorField();
addPropertySetFactoryField(classNode);
Collection<PropertyDescriptor> properties = propertyMapper
.findProperties(classNode.getParentClass());
for (PropertyDescriptor pd : properties) {
implementProperty(pd, classNode);
}
overrideMergeMethod(classNode);
overrideRefreshMethod(classNode);
overrideGetPropertySetMethod(classNode, properties);
}
@Inject
public void setPropertyMapper(PropertyMapper mapper) {
this.propertyMapper = mapper;
}
}