blob: 5a33c7adcdbfc07f3871d498a8b581e44fe82046 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.scrplugin.helper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.felix.scrplugin.Log;
import org.apache.felix.scrplugin.SCRDescriptorException;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
/**
* Helper class for injecting/generating accessor methods for
* unary references.
*/
public abstract class ClassModifier {
/**
* Add bind/unbind methods
* @param className The class name in which the methods are injected
* @param referenceName Name of the reference
* @param referenceType Type of the reference
* @param fieldName Name of the field
* @param fieldType Type of the field
* @param createBind Name of the bind method or null
* @param createUnbind Name of the unbind method or null
* @param outputDirectory Output directory where the class file is stored
* @throws SCRDescriptorException
*/
public static void addMethods(final String className,
final String referenceName,
final String referenceType,
final String fieldName,
final String fieldType,
final boolean createBind,
final boolean createUnbind,
final ClassLoader classLoader,
final String outputDirectory,
final Log logger)
throws SCRDescriptorException {
// now do byte code manipulation
final String fileName = outputDirectory + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
final ClassNode cn = new ClassNode();
try {
final FileInputStream fis = new FileInputStream(fileName);
try {
final ClassReader reader = new ClassReader(fis);
reader.accept(cn, 0);
}
finally {
fis.close();
}
// For target Java7 and above use: ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES
final int mask = (cn.version > 50 ? ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES : 0);
final ClassWriter writer = new ClassWriter(mask) {
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
Class<?> c, d;
try {
c = classLoader.loadClass(type1.replace('/', '.'));
d = classLoader.loadClass(type2.replace('/', '.'));
} catch (final Exception e) {
throw new RuntimeException(e.toString(), e);
}
if (c.isAssignableFrom(d)) {
return type1;
}
if (d.isAssignableFrom(c)) {
return type2;
}
if (c.isInterface() || d.isInterface()) {
return "java/lang/Object";
}
do {
c = c.getSuperclass();
} while (!c.isAssignableFrom(d));
return c.getName().replace('.', '/');
}
};
cn.accept(writer);
if ( createBind ) {
logger.debug("Adding bind " + className + " " + fieldName);
createMethod(writer, className, referenceName, referenceType, fieldName, fieldType, true);
}
if ( createUnbind ) {
logger.debug("Adding unbind " + className + " " + fieldName);
createMethod(writer, className, referenceName, referenceType, fieldName, fieldType, false);
}
final FileOutputStream fos = new FileOutputStream(fileName);
try {
fos.write(writer.toByteArray());
}
finally {
fos.close();
}
} catch (final Exception e) {
throw new SCRDescriptorException("Unable to add methods to " + className, referenceType, e);
}
}
private static void createMethod(final ClassWriter cw, final String className, final String referenceName, final String referenceTypeName, final String fieldName, final String fieldTypeName, final boolean bind) {
final org.objectweb.asm.Type referenceType = org.objectweb.asm.Type.getType("L" + referenceTypeName.replace('.', '/') + ";");
final org.objectweb.asm.Type fieldType = org.objectweb.asm.Type.getType("L" + fieldTypeName.replace('.', '/') + ";");
final String methodName = (bind ? "" : "un") + "bind" + referenceName.substring(0, 1).toUpperCase() + referenceName.substring(1);
final MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, methodName, "(" + referenceType.toString() + ")V", null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
if ( bind ) {
mv.visitVarInsn(referenceType.getOpcode(Opcodes.ILOAD), 1);
mv.visitFieldInsn(Opcodes.PUTFIELD, className.replace('.', '/'), fieldName, fieldType.toString());
} else {
mv.visitFieldInsn(Opcodes.GETFIELD, className.replace('.', '/'), fieldName, fieldType.toString());
mv.visitVarInsn(Opcodes.ALOAD, 1);
final Label jmpLabel = new Label();
mv.visitJumpInsn(Opcodes.IF_ACMPNE, jmpLabel);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitFieldInsn(Opcodes.PUTFIELD, className.replace('.', '/'), fieldName, fieldType.toString());
mv.visitLabel(jmpLabel);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2);
}
}