org.rhq.bindings.util.InterfaceSimplifier Maven / Gradle / Ivy
Show all versions of rhq-script-bindings Show documentation
/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.bindings.util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ByteMemberValue;
import javassist.bytecode.annotation.CharMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.DoubleMemberValue;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.FloatMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.LongMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.MemberValueVisitor;
import javassist.bytecode.annotation.ShortMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.auth.Subject;
/**
* The scripts can use simplified interfaces that omit the first "Subject" argument
* to most methods from RHQ's remote API. This helper class prepares such simplified interfaces.
*
* @author Greg Hinkle
* @author Lukas Krejci
*/
public class InterfaceSimplifier {
private static final Log LOG = LogFactory.getLog(InterfaceSimplifier.class);
private InterfaceSimplifier() {
}
/**
* Returns the method on the original interface that the simplified interface with given method was generated from
* using the {@link #simplify(Class)} method (i.e. this method is kind of reverse to the {@link #simplify(Class)}
* method).
*
* The returned method may or may not have different signature from the supplied method - that depends on whether
* the {@link #simplify(Class)} simplified the method or not.
*
* @param method the potentially simplified method
* @return null if the method doesn't come from a simplified class, otherwise a method on the original interface
* that the supplied method was generated from.
*/
public static Method getOriginalMethod(Method method) {
SimplifiedClass simplifiedClass = method.getDeclaringClass().getAnnotation(SimplifiedClass.class);
if (simplifiedClass == null) {
return null;
} else {
SimplifiedMethod simplifiedMethod = method.getAnnotation(SimplifiedMethod.class);
Class> origClass = simplifiedClass.originalClass();
if (simplifiedMethod == null) {
try {
return origClass.getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Inconsisten interface simplification. The non-simplified method "
+ method + " should have had a counterpart with the exact signature on the interface "
+ origClass + " but it could not be found.", e);
}
} else {
Class>[] paramTypes = new Class>[method.getParameterTypes().length + 1];
paramTypes[0] = Subject.class;
System.arraycopy(method.getParameterTypes(), 0, paramTypes, 1, paramTypes.length - 1);
try {
return origClass.getMethod(method.getName(), paramTypes);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Inconsistent interface simplification. The simplified method "
+ method + " should have had a counterpart on the interface " + origClass
+ " but it couldn't be found.", e);
}
}
}
}
/**
* Determines whether given class is simplified or not. This method will return true for any class returned from
* {@link #simplify(Class)}.
*
* @param cls the class
* @return true if the class object was created by the {@link #simplify(Class)} method, false otherwise.
*/
public static boolean isSimplified(Class> cls) {
return cls.getAnnotation(SimplifiedClass.class) != null;
}
/**
* Determines whether the method (declared on the simplified interface, i.e.
* isSimplified(method.getDeclaringClass()
returns true) has been "tampered with" by the simplifier or
* has been left intact.
*
* If you want to get the original method that the supplied method corresponds to, use
* the {@link #getOriginalMethod(Method)} method.
*
* @param method the potentially simplified method present on a simplified class
* @return true if the method's signature has been modified by the simplifier, false otherwise.
*/
public static boolean isSimplified(Method method) {
return method.getAnnotation(SimplifiedMethod.class) != null;
}
public static Class> simplify(Class> intf) {
try {
ClassPool classPool = ClassPoolFactory.getClassPoolForCurrentContextClassLoader();
String simplifiedName = getSimplifiedName(intf);
LOG.debug("Simplifying " + intf + " (simplified interface name: " + simplifiedName + ")...");
CtClass cached = null;
try {
cached = classPool.get(simplifiedName);
return Class.forName(simplifiedName, false, classPool.getClassLoader());
} catch (NotFoundException e) {
// ok... load it
} catch (ClassNotFoundException e) {
LOG.debug("Class [" + simplifiedName + "] not found - cause: " + e, e);
if (cached != null) {
// strange - we found the class definition in the class pool, which means we must have touched it
// before but Class.forName failed to find the class in the class pool's class loader.
return cached.toClass();
}
}
CtClass originalClass = classPool.get(intf.getName());
ClassFile originalClassFile = originalClass.getClassFile();
CtClass newClass = classPool.makeInterface(simplifiedName);
newClass.defrost();
ClassFile newClassFile = newClass.getClassFile();
//we'll be adding new constants to the class file (for generics and annotations)
ConstPool constPool = newClassFile.getConstPool();
//copy the annotations on the class
AnnotationsAttribute annotations = (AnnotationsAttribute) originalClassFile
.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute newAnnotations = copyAnnotations(annotations, constPool);
//add our @Simplified annotation to the new class
newAnnotations = addSimplifiedClassAnnotation(originalClass.getName(), newAnnotations, constPool);
newClassFile.addAttribute(newAnnotations);
//copy the generic signature of the class
SignatureAttribute signature = (SignatureAttribute) originalClassFile.getAttribute(SignatureAttribute.tag);
if (signature != null) {
newClassFile.addAttribute(new SignatureAttribute(constPool, signature.getSignature()));
}
//now copy over the methods
CtMethod[] methods = originalClass.getMethods();
for (CtMethod originalMethod : methods) {
//we are only simplifying interfaces here, but the CtClass.getMethods() also returns concrete methods
//inherited from Object. Let's just skip those - we don't need to worry about them...
if (!Modifier.isAbstract(originalMethod.getModifiers())) {
continue;
}
CtClass[] params = originalMethod.getParameterTypes();
//capture all the runtime visible method annotations on the original method
annotations = (AnnotationsAttribute) originalMethod.getMethodInfo().getAttribute(
AnnotationsAttribute.visibleTag);
//capture all the runtime visible parameter annotations on the original method
ParameterAnnotationsAttribute parameterAnnotations = (ParameterAnnotationsAttribute) originalMethod
.getMethodInfo().getAttribute(ParameterAnnotationsAttribute.visibleTag);
//capture the generic signature of the original method.
signature = (SignatureAttribute) originalMethod.getMethodInfo().getAttribute(
SignatureAttribute.tag);
boolean simplify = params.length > 0 && params[0].getName().equals(Subject.class.getName());
if (simplify) {
//generate new params, leaving out the first parameter (the subject)
CtClass[] simpleParams = new CtClass[params.length - 1];
System.arraycopy(params, 1, simpleParams, 0, params.length - 1);
params = simpleParams;
}
//generate the new method with possibly modified parameters
CtMethod newMethod = CtNewMethod.abstractMethod(originalMethod.getReturnType(),
originalMethod.getName(), params, originalMethod.getExceptionTypes(), newClass);
//copy over the method annotations
annotations = copyAnnotations(annotations, constPool);
if (simplify) {
//add the @SimplifiedMethod to the method annotations
annotations = addSimplifiedMethodAnnotation(annotations, constPool);
if (signature != null) {
//fun, we need to modify the signature, too, because we have left out the parameter
MethodSignature sig = MethodSignature.parse(signature.getSignature());
sig.paramTypes.remove(0);
signature = new SignatureAttribute(constPool, sig.toString());
}
//next, we need to copy the parameter annotations
parameterAnnotations = copyParameterAnnotations(parameterAnnotations, constPool, 1);
} else {
//just copy the sig and parameter annotations verbatim
if (signature != null) {
signature = new SignatureAttribute(constPool, signature.getSignature());
}
parameterAnnotations = copyParameterAnnotations(parameterAnnotations, constPool, 0);
}
if (parameterAnnotations != null) {
newMethod.getMethodInfo().addAttribute(parameterAnnotations);
}
if (signature != null) {
newMethod.getMethodInfo().addAttribute(signature);
}
if (annotations != null) {
newMethod.getMethodInfo().addAttribute(annotations);
}
//it is important to add the method directly to the classfile, not the class
//because otherwise the generics info wouldn't survive
newClassFile.addMethod(newMethod.getMethodInfo());
}
return newClass.toClass();
} catch (Exception e) {
String msg = "Failed to simplify " + intf + ".";
LOG.error(msg, e);
throw new IllegalStateException(msg, e);
}
}
private static String getSimplifiedName(Class> interfaceClass) {
String fullName = interfaceClass.getName();
String simpleName = interfaceClass.getSimpleName();
Package pkg = interfaceClass.getPackage();
String packageName = (pkg != null) ? pkg.getName() : fullName.substring(0,
fullName.length() - (simpleName.length() + 1));
return packageName + ".wrapped." + simpleName + "Simple";
}
/**
* Copies the provided annotation into the provided const pool.
*
* @param annotation
* @param constPool
* @return
* @throws NotFoundException
*/
private static Annotation cloneAnnotation(Annotation annotation, final ConstPool constPool)
throws NotFoundException {
Annotation ret = new Annotation(annotation.getTypeName(), constPool);
if (annotation.getMemberNames() != null) {
for (Object m : annotation.getMemberNames()) {
final String memberName = (String) m;
MemberValue origValue = annotation.getMemberValue(memberName);
final MemberValue[] newValue = new MemberValue[1];
origValue.accept(new ArrayIndexAssigningVisitor(newValue, 0, constPool));
ret.addMemberValue(memberName, newValue[0]);
}
}
return ret;
}
private static AnnotationsAttribute copyAnnotations(AnnotationsAttribute annotations, ConstPool constPool)
throws NotFoundException {
if (annotations != null) {
Annotation[] origAnnotations = annotations.getAnnotations();
Annotation[] newClassAnnotations = new Annotation[origAnnotations.length];
for (int i = 0; i < newClassAnnotations.length; ++i) {
newClassAnnotations[i] = cloneAnnotation(origAnnotations[i], constPool);
}
AnnotationsAttribute newAnnotations = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
newAnnotations.setAnnotations(newClassAnnotations);
return newAnnotations;
}
return null;
}
private static ParameterAnnotationsAttribute copyParameterAnnotations(
ParameterAnnotationsAttribute parameterAnnotations, ConstPool constPool, int fromIndex)
throws NotFoundException {
if (parameterAnnotations != null) {
Annotation[][] originalAnnotations = parameterAnnotations.getAnnotations();
//return early if there are no annotations to copy
if (originalAnnotations.length - fromIndex <= 0) {
return null;
}
Annotation[][] newParameterAnnotations = new Annotation[originalAnnotations.length - fromIndex][];
for (int i = fromIndex; i < originalAnnotations.length; i++) {
newParameterAnnotations[i - fromIndex] = new Annotation[originalAnnotations[i].length];
for (int j = 0; j < originalAnnotations[i].length; ++j) {
Annotation origAnnotation = originalAnnotations[i][j];
newParameterAnnotations[i - fromIndex][j] = cloneAnnotation(origAnnotation, constPool);
}
}
ParameterAnnotationsAttribute newAnnotationsAttribute = new ParameterAnnotationsAttribute(constPool,
ParameterAnnotationsAttribute.visibleTag);
newAnnotationsAttribute.setAnnotations(newParameterAnnotations);
return newAnnotationsAttribute;
}
return null;
}
private static class ArrayIndexAssigningVisitor implements MemberValueVisitor {
private MemberValue[] array;
private int index;
private ConstPool constPool;
public ArrayIndexAssigningVisitor(MemberValue[] array, int index, ConstPool constPool) {
this.array = array;
this.index = index;
this.constPool = constPool;
}
@Override
public void visitStringMemberValue(StringMemberValue node) {
array[index] = new StringMemberValue(node.getValue(), constPool);
}
@Override
public void visitShortMemberValue(ShortMemberValue node) {
array[index] = new ShortMemberValue(node.getValue(), constPool);
}
@Override
public void visitLongMemberValue(LongMemberValue node) {
array[index] = new LongMemberValue(node.getValue(), constPool);
}
@Override
public void visitIntegerMemberValue(IntegerMemberValue node) {
array[index] = new IntegerMemberValue(constPool, node.getValue());
}
@Override
public void visitFloatMemberValue(FloatMemberValue node) {
array[index] = new FloatMemberValue(node.getValue(), constPool);
}
@Override
public void visitEnumMemberValue(EnumMemberValue node) {
EnumMemberValue val = new EnumMemberValue(constPool);
val.setType(node.getType());
val.setValue(node.getValue());
array[index] = val;
}
@Override
public void visitDoubleMemberValue(DoubleMemberValue node) {
array[index] = new DoubleMemberValue(node.getValue(), constPool);
}
@Override
public void visitClassMemberValue(ClassMemberValue node) {
array[index] = new ClassMemberValue(node.getValue(), constPool);
}
@Override
public void visitCharMemberValue(CharMemberValue node) {
array[index] = new CharMemberValue(node.getValue(), constPool);
}
@Override
public void visitByteMemberValue(ByteMemberValue node) {
array[index] = new ByteMemberValue(node.getValue(), constPool);
}
@Override
public void visitBooleanMemberValue(BooleanMemberValue node) {
array[index] = new BooleanMemberValue(node.getValue(), constPool);
}
@Override
public void visitArrayMemberValue(ArrayMemberValue node) {
ArrayMemberValue val = new ArrayMemberValue(node.getType(), constPool);
MemberValue[] newVals = new MemberValue[node.getValue().length];
for (int i = 0; i < node.getValue().length; ++i) {
node.getValue()[i].accept(new ArrayIndexAssigningVisitor(newVals, i, constPool));
}
val.setValue(newVals);
array[index] = val;
}
@Override
public void visitAnnotationMemberValue(AnnotationMemberValue node) {
array[index] = new AnnotationMemberValue(node.getValue(), constPool);
}
}
//a quick and dirty method signature parser
//see http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
private static class MethodSignature {
public String returnType;
public List paramTypes = new ArrayList();
public String typeParameters;
public String exceptionTypes;
public static MethodSignature parse(String signature) {
int startParams = signature.indexOf('(') + 1;
int endParams = signature.indexOf(')');
int startExceptions = signature.indexOf('^');
MethodSignature sig = new MethodSignature();
sig.typeParameters = signature.substring(0, startParams - 1);
if (startExceptions == -1) {
sig.returnType = signature.substring(endParams + 1);
sig.exceptionTypes = "";
} else {
sig.returnType = signature.substring(endParams + 1, startExceptions);
sig.exceptionTypes = signature.substring(startExceptions);
}
int idx = startParams;
while (idx < endParams) {
int end = findEndOfTypeSignature(idx, signature);
sig.paramTypes.add(signature.substring(idx, end));
idx = end;
}
return sig;
}
private static int findEndOfTypeSignature(int idx, String signature) {
int c = signature.charAt(idx);
switch (c) {
case 'L':
return findEndOfClassSignature(idx, signature);
case '[':
return findEndOfTypeSignature(idx + 1, signature);
case 'T':
return signature.indexOf(';', idx + 1) + 1;
default:
return idx + 1;
}
}
private static int findEndOfClassSignature(int indexOfL, String signature) {
int idx = indexOfL + 1;
int genericDeclDepth = 0;
while (idx < signature.length()) {
boolean sigComplete = false;
char c = signature.charAt(idx++);
switch (c) {
case '<':
genericDeclDepth++;
break;
case '>':
genericDeclDepth--;
break;
case ';':
sigComplete = genericDeclDepth == 0;
break;
}
if (sigComplete) {
break;
}
}
return idx;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder(typeParameters);
bld.append("(");
for (String p : paramTypes) {
bld.append(p);
}
bld.append(")");
bld.append(returnType);
bld.append(exceptionTypes);
return bld.toString();
}
}
private static AnnotationsAttribute addSimplifiedClassAnnotation(String originalClassName,
AnnotationsAttribute annotations, ConstPool constPool) {
if (annotations == null) {
annotations = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
}
Annotation simplified = new Annotation(SimplifiedClass.class.getName(), constPool);
simplified.addMemberValue("originalClass", new ClassMemberValue(originalClassName, constPool));
annotations.addAnnotation(simplified);
return annotations;
}
private static AnnotationsAttribute addSimplifiedMethodAnnotation(AnnotationsAttribute annotations,
ConstPool constPool) {
if (annotations == null) {
annotations = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
}
annotations.addAnnotation(new Annotation(SimplifiedMethod.class.getName(), constPool));
return annotations;
}
}