blob: db5872874130e01bc52f473c7e80c6c9bcc25a26 [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.tags.qdox;
import java.io.*;
import java.util.*;
import org.apache.felix.scrplugin.tags.*;
import org.apache.felix.scrplugin.tags.JavaField;
import org.apache.felix.scrplugin.tags.JavaMethod;
import org.apache.maven.plugin.MojoExecutionException;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import com.thoughtworks.qdox.model.*;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.Type;
/**
* <code>QDoxJavaClassDescription.java</code>...
*
*/
public class QDoxJavaClassDescription
implements JavaClassDescription, ModifiableJavaClassDescription {
protected final JavaClass javaClass;
protected final JavaClassDescriptorManager manager;
protected final JavaSource source;
/** The compiled class. */
protected final Class clazz;
public QDoxJavaClassDescription(Class clazz, JavaSource source, JavaClassDescriptorManager m) {
this.javaClass = source.getClasses()[0];
this.manager = m;
this.source = source;
this.clazz = clazz;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getSuperClass()
*/
public JavaClassDescription getSuperClass() throws MojoExecutionException {
final JavaClass parent = this.javaClass.getSuperJavaClass();
if ( parent != null ) {
return this.manager.getJavaClassDescription(parent.getFullyQualifiedName());
}
return null;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getTagByName(java.lang.String)
*/
public JavaTag getTagByName(String name) {
final DocletTag tag = this.javaClass.getTagByName(name);
if ( tag == null ) {
return null;
}
return new QDoxJavaTag(tag, this);
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getName()
*/
public String getName() {
return this.javaClass.getFullyQualifiedName();
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getTagsByName(java.lang.String, boolean)
*/
public JavaTag[] getTagsByName(String name, boolean inherited)
throws MojoExecutionException {
final DocletTag[] tags = this.javaClass.getTagsByName(name, false);
JavaTag[] javaTags;
if ( tags == null || tags.length == 0 ) {
javaTags = new JavaTag[0];
} else {
javaTags = new JavaTag[tags.length];
for(int i=0; i<tags.length;i++) {
javaTags[i] = new QDoxJavaTag(tags[i], this);
}
}
if ( inherited && this.getSuperClass() != null ) {
final JavaTag[] superTags = this.getSuperClass().getTagsByName(name, inherited);
if ( superTags.length > 0 ) {
final List list = new ArrayList(Arrays.asList(javaTags));
list.addAll(Arrays.asList(superTags));
javaTags = (JavaTag[]) list.toArray(new JavaTag[list.size()]);
}
}
return javaTags;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getFields()
*/
public JavaField[] getFields() {
final com.thoughtworks.qdox.model.JavaField fields[] = this.javaClass.getFields();
if ( fields == null || fields.length == 0 ) {
return new JavaField[0];
}
final JavaField[] f = new JavaField[fields.length];
for(int i=0; i<fields.length; i++) {
f[i] = new QDoxJavaField(fields[i], this);
}
return f;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getFieldByName(java.lang.String)
*/
public JavaField getFieldByName(String name)
throws MojoExecutionException {
final com.thoughtworks.qdox.model.JavaField field = this.javaClass.getFieldByName(name);
if ( field != null ) {
return new QDoxJavaField(field, this);
}
if ( this.getSuperClass() != null ) {
return this.getSuperClass().getFieldByName(name);
}
return null;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getExternalFieldByName(java.lang.String)
*/
public JavaField getExternalFieldByName(String name)
throws MojoExecutionException {
int lastDot = name.lastIndexOf('.');
// if there is no dot, this should be a static import
if ( lastDot == -1 ) {
final String importDef = this.searchImport('.' + name);
if ( importDef != null ) {
int sep = importDef.lastIndexOf('.');
final String className = importDef.substring(0, sep);
final String constantName = importDef.substring(sep+1);
final JavaClassDescription jcd = this.manager.getJavaClassDescription(className);
if ( jcd != null ) {
return jcd.getFieldByName(constantName);
}
}
} else {
// check for fully qualified
int firstDot = name.indexOf('.');
if ( firstDot == lastDot ) {
// we only have one dot, so either the class is imported or in the same package
final String className = name.substring(0, lastDot);
final String constantName = name.substring(lastDot+1);
final String importDef = this.searchImport('.' + className);
if ( importDef != null ) {
final JavaClassDescription jcd = this.manager.getJavaClassDescription(importDef);
if ( jcd != null ) {
return jcd.getFieldByName(constantName);
}
}
final JavaClassDescription jcd = this.manager.getJavaClassDescription(this.javaClass.getSource().getPackage() + '.' + className);
if ( jcd != null ) {
return jcd.getFieldByName(constantName);
}
} else {
// we have more than one dot, so this is a fully qualified class
final String className = name.substring(0, lastDot);
final String constantName = name.substring(lastDot+1);
final JavaClassDescription jcd = this.manager.getJavaClassDescription(className);
if ( jcd != null ) {
return jcd.getFieldByName(constantName);
}
}
}
return null;
}
protected String searchImport(String name) {
final String[] imports = this.javaClass.getSource().getImports();
if ( imports != null ) {
for(int i=0; i<imports.length; i++ ) {
if ( imports[i].endsWith(name) ) {
return imports[i];
}
}
}
return null;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getImplementedInterfaces()
*/
public JavaClassDescription[] getImplementedInterfaces()
throws MojoExecutionException {
final JavaClass[] interfaces = this.javaClass.getImplementedInterfaces();
if ( interfaces == null || interfaces.length == 0 ) {
return JavaClassDescription.EMPTY_RESULT;
}
final JavaClassDescription[] descs = new JavaClassDescription[interfaces.length];
for(int i=0;i<interfaces.length; i++) {
descs[i] = this.manager.getJavaClassDescription(interfaces[i].getFullyQualifiedName());
}
return descs;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getMethodBySignature(java.lang.String, java.lang.String[])
*/
public JavaMethod getMethodBySignature(String name, String[] parameters)
throws MojoExecutionException {
Type[] types = null;
if ( parameters == null || parameters.length == 0 ) {
types = new Type[0];
} else {
types = new Type[parameters.length];
for(int i=0;i<parameters.length;i++) {
types[i] = new Type(parameters[i]);
}
}
final com.thoughtworks.qdox.model.JavaMethod m = this.javaClass.getMethodBySignature(name, types);
if ( m == null ) {
if ( this.getSuperClass() != null ) {
return this.getSuperClass().getMethodBySignature(name, parameters);
}
return null;
}
return new QDoxJavaMethod(m);
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#getMethods()
*/
public JavaMethod[] getMethods() {
final com.thoughtworks.qdox.model.JavaMethod[] methods = this.javaClass.getMethods();
if ( methods == null || methods.length == 0) {
return JavaMethod.EMPTY_RESULT;
}
final JavaMethod[] m = new JavaMethod[methods.length];
for(int i=0;i<methods.length;i++) {
m[i] = new QDoxJavaMethod(methods[i]);
}
return m;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#isA(java.lang.String)
*/
public boolean isA(String type) throws MojoExecutionException {
final Type qType = new Type(type);
if ( this.javaClass.isA(type) ) {
return true;
}
final Type[] interfaces = this.javaClass.getImplements();
if ( interfaces != null ) {
for(int i=0; i<interfaces.length; i++) {
if ( interfaces[i].isA(qType) ) {
return true;
}
}
}
if ( this.getSuperClass() != null ) {
return this.getSuperClass().isA(type);
}
return false;
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#isAbstract()
*/
public boolean isAbstract() {
return this.javaClass.isAbstract();
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#isInterface()
*/
public boolean isInterface() {
return this.javaClass.isInterface();
}
/**
* @see org.apache.felix.scrplugin.tags.JavaClassDescription#isPublic()
*/
public boolean isPublic() {
return this.javaClass.isPublic();
}
/**
* @see org.apache.felix.scrplugin.tags.ModifiableJavaClassDescription#addMethods(java.lang.String, java.lang.String, boolean, boolean)
*/
public void addMethods(final String propertyName,
final String className,
final boolean createBind,
final boolean createUnbind)
throws MojoExecutionException {
// now do byte code manipulation
final String targetDirectory = this.manager.getProject().getBuild().getOutputDirectory();
final String fileName = targetDirectory + File.separatorChar + this.getName().replace('.', File.separatorChar) + ".class";
final ClassNode cn = new ClassNode();
try {
final ClassReader reader = new ClassReader(new FileInputStream(fileName));
reader.accept(cn, 0);
final ClassWriter writer = new ClassWriter(0);
// remove existing implementation von previous builds
final ClassAdapter adapter = new ClassAdapter(writer) {
protected final String bindMethodName = "bind" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
protected final String unbindMethodName = "unbind" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
protected final String description = "(L" + className.replace('.', '/') + ";)V";
/**
* @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
public MethodVisitor visitMethod(int access,
String name,
String desc,
String signature,
String[] exceptions) {
if ( createBind && name.equals(bindMethodName) && description.equals(desc) ) {
return null;
}
if ( createUnbind && name.equals(unbindMethodName) && description.equals(desc) ) {
return null;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
};
cn.accept(adapter);
if ( createBind ) {
this.createMethod(writer, propertyName, className, true);
}
if ( createUnbind ) {
this.createMethod(writer, propertyName, className, false);
}
final FileOutputStream fos = new FileOutputStream(fileName);
fos.write(writer.toByteArray());
fos.close();
} catch (Exception e) {
throw new MojoExecutionException("Unable to add methods to " + this.getName(), e);
}
}
protected void createMethod(ClassWriter cw, String propertyName, String typeName, boolean bind) {
final org.objectweb.asm.Type type = org.objectweb.asm.Type.getType("L" + typeName.replace('.', '/') + ";");
final String methodName = (bind ? "" : "un") + "bind" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, methodName, "(" + type.toString() + ")V", null, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
if ( bind ) {
mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), 1);
mv.visitFieldInsn(Opcodes.PUTFIELD, this.getName().replace('.', '/'), propertyName, type.toString());
} else {
mv.visitFieldInsn(Opcodes.GETFIELD, this.getName().replace('.', '/'), propertyName, type.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, this.getName().replace('.', '/'), propertyName, type.toString());
mv.visitLabel(jmpLabel);
}
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2);
// add to qdox
final JavaParameter param = new JavaParameter(new Type(typeName), "param");
final JavaParameter[] params = new JavaParameter[] {param};
final com.thoughtworks.qdox.model.JavaMethod meth = new com.thoughtworks.qdox.model.JavaMethod();
meth.setName(methodName);
meth.setParameters(params);
meth.setModifiers(new String[] {"protected"});
this.javaClass.addMethod(meth);
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return getName();
}
public Class getCompiledClass() {
return this.clazz;
}
}