/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * 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, either version 3 of the License, or * (at your option) any later version. * * 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, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.jdt.command.src; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.PrintWriter; import java.io.Writer; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.SystemUtils; import org.eclim.Services; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.logging.Logger; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclim.util.IOUtils; import org.eclim.util.StringUtils; import org.eclim.util.file.FileUtils; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; /** * Command that creates a source prototype of the specified class. * * @author Eric Van Dewoestine */ @Command( name = "java_class_prototype", options = "REQUIRED c classname ARG," + "OPTIONAL p project ARG," + "OPTIONAL f file ARG" ) public class ClassPrototypeCommand extends AbstractCommand { private static final Logger logger = Logger.getLogger(ClassPrototypeCommand.class); private static final String INDENT = "\t"; private static final String IMPORT_PATTERN = "(<.*>|\\[\\]|\\.[0-9])$"; private static final String OBJECT = "java/lang/Object"; private static final String ANNOTATION = "java/lang/annotation/Annotation"; /** * {@inheritDoc} */ public Object execute(CommandLine commandLine) throws Exception { String className = commandLine.getValue(Options.CLASSNAME_OPTION); if (!commandLine.hasOption(Options.PROJECT_OPTION) && !commandLine.hasOption(Options.FILE_OPTION)) { throw new RuntimeException( Services.getMessage("prototype.missing.argument")); } File file = new File( SystemUtils.JAVA_IO_TMPDIR + '/' + className.replace('.', '/') + ".java"); new File(FileUtils.getFullPath(file.getAbsolutePath())).mkdirs(); file.deleteOnExit(); FileWriter out = null; try{ out = new FileWriter(file); if(commandLine.hasOption(Options.FILE_OPTION)){ prototype(commandLine.getValue(Options.FILE_OPTION), out); }else{ String projectName = commandLine.getValue(Options.PROJECT_OPTION); IJavaProject javaProject = JavaUtils.getJavaProject(projectName); IType type = javaProject.findType(className); if(type == null){ throw new IllegalArgumentException( Services.getMessage("type.not.found", projectName, className)); } String prototype = prototype(type); out.write(prototype); } }finally{ IOUtils.closeQuietly(out); } return file.getAbsolutePath(); } /** * Generate a prototype for the supplied file path. * * @param file The path to the class file. * @param writer The writer to output the prototype to. */ protected void prototype(String file, Writer writer) throws Exception { FileInputStream in = null; try{ in = new FileInputStream(file); ClassReader reader = new ClassReader(in); reader.accept(new AsmClassVisitor(new PrintWriter(writer)), false); }finally{ IOUtils.closeQuietly(in); } } /** * Top level method for generating a prototype of the supplied type. * * @param type The type. * @return The resulting prototype. */ protected String prototype(IType type) throws Exception { Set<String> imports = new TreeSet<String>(); StringBuffer buffer = new StringBuffer(); buffer.append(Services.getMessage("prototype.header")); buffer.append("package ") .append(type.getPackageFragment().getElementName()) .append(";\n"); prototype(buffer, type, "", imports); // insert the imports StringBuffer importClauses = new StringBuffer().append("\n\n"); for(String className : imports){ importClauses.append("import ").append(className).append(";\n"); } buffer.insert(buffer.indexOf(";") + 1, importClauses.toString()); return buffer.toString(); } /** * Prototype the supplied type. * * @param buffer The buffer to append to. * @param type The type. * @param indent The indent. * @param imports Keep track of imports. */ protected void prototype( StringBuffer buffer, IType type, String indent, Set<String> imports) throws Exception { buffer.append(indent); prototypeFlags(buffer, type); int flags = type.getFlags(); if(Flags.isEnum(flags)){ buffer.append("enum "); }else if(Flags.isInterface(flags)){ buffer.append("interface "); }else if(Flags.isAnnotation(flags)){ buffer.append("@interface "); }else{ buffer.append("class "); } buffer.append(type.getElementName()); // extends String superclass = type.getSuperclassName(); if(superclass != null){ buffer.append('\n').append(indent).append(INDENT) .append("extends ").append(superclass); } // implements String[] interfaces = type.getSuperInterfaceNames(); if(interfaces != null && interfaces.length > 0){ buffer.append('\n').append(indent).append(INDENT).append("implements "); for(int ii = 0; ii < interfaces.length; ii++){ if(ii != 0){ buffer.append(", "); } buffer.append(interfaces[ii]); } } buffer.append('\n').append(indent).append("{\n"); int length = buffer.length(); // fields IField[] fields = type.getFields(); for(int ii = 0; ii < fields.length; ii++){ prototypeField(buffer, fields[ii], indent + INDENT, imports); } // methods IMethod[] methods = type.getMethods(); if(methods != null && methods.length > 0){ for(int ii = 0; ii < methods.length; ii++){ if(length != buffer.length()){ buffer.append('\n'); } length = buffer.length(); prototypeMethod(buffer, methods[ii], indent + INDENT, imports); } } // inner classes, enums, etc. IType[] types = type.getTypes(); if(types != null && types.length > 0){ if(length != buffer.length()){ buffer.append('\n'); } for(int ii = 0; ii < types.length; ii++){ if(ii > 0){ buffer.append('\n'); } prototype(buffer, types[ii], indent + INDENT, imports); buffer.append('\n'); } } buffer.append(indent).append("}"); } /** * Prototypes the supplied field. * * @param buffer The buffer to append to. * @param field The field. * @param indent The current indentation. * @param imports Keep track of imports. */ protected void prototypeField( StringBuffer buffer, IField field, String indent, Set<String> imports) throws Exception { String fieldName = field.getElementName(); if(fieldName.indexOf("$") == -1){ buffer.append(indent); prototypeFlags(buffer, field); String type = field.getTypeSignature(); String typeName = Signature.getSignatureSimpleName(type); buffer.append(typeName).append(' ').append(field.getElementName()); addImport(imports, type); Object defaultValue = field.getConstant(); if(defaultValue != null){ buffer.append(" = "); if(typeName.equals("char")){ buffer.append('\'').append(defaultValue).append('\''); }else if(typeName.equals("int") || typeName.equals("long") || typeName.equals("short") || typeName.equals("double") || typeName.equals("float") || typeName.equals("boolean") || typeName.equals("byte")) { buffer.append(defaultValue); }else if(defaultValue instanceof String){ buffer.append('"').append(defaultValue).append('"'); }else{ logger.warn("Unhandled constant value: '{}' '{}'", defaultValue.getClass().getName(), defaultValue); } } buffer.append(";\n"); } } /** * Prototypes the supplied method. * * @param buffer The buffer to append to. * @param method The method. * @param indent The current indentation. * @param imports Keep track of imports. */ protected void prototypeMethod( StringBuffer buffer, IMethod method, String indent, Set<String> imports) throws Exception { String methodName = method.getElementName(); if(methodName.indexOf("$") == -1 && !methodName.equals("<clinit>")){ buffer.append(indent); prototypeFlags(buffer, method); String returnType = method.getReturnType(); String returnTypeName = Signature.getSignatureSimpleName(returnType); addImport(imports, returnType); buffer.append(returnTypeName) .append(' ').append(methodName).append(" ("); // parameters String[] paramNames = method.getParameterNames(); String[] paramTypes = method.getParameterTypes(); if(paramNames.length > 0){ for(int ii = 0; ii < paramNames.length; ii++){ if(ii != 0){ buffer.append(", "); } addImport(imports, paramTypes[ii]); String typeName = Signature.getSignatureSimpleName(paramTypes[ii]); buffer.append(typeName).append(' ').append(paramNames[ii]); } } buffer.append(")"); // throws String[] exceptions = method.getExceptionTypes(); if(exceptions.length > 0){ buffer.append('\n').append(indent).append(INDENT).append("throws "); for(int ii = 0; ii < exceptions.length; ii++){ if(ii != 0){ buffer.append(", "); } buffer.append(Signature.getSignatureSimpleName(exceptions[ii])); } } buffer.append(";\n"); } } /** * Prototypes the given member's flags. * * @param buffer The buffer to append to. * @param member The member instance. */ protected void prototypeFlags(StringBuffer buffer, IMember member) throws Exception { int flags = member.getFlags(); if(Flags.isPublic(flags)){ buffer.append("public "); }else if(Flags.isProtected(flags)){ buffer.append("protected "); }else if(Flags.isPrivate(flags)){ buffer.append("private "); } if(Flags.isStatic(flags)){ buffer.append("static "); } if(Flags.isFinal(flags)){ buffer.append("final "); } if(Flags.isAbstract(flags)){ buffer.append("abstract "); } if(Flags.isNative(flags)){ buffer.append("native "); } if(Flags.isTransient(flags)){ buffer.append("transient "); } if(Flags.isVolatile(flags)){ buffer.append("volatile "); } if(Flags.isSynchronized(flags)){ buffer.append("synchronized "); } } /** * Adds the supplied signature to the specified set of imports. * * @param imports The imports. * @param signature The signature of the type to add. */ protected void addImport(Set<String> imports, String signature) { String name = Signature.getSignatureSimpleName(signature); if(name.length() > 1 && !name.equals("void")){ String pckg = Signature.getSignatureQualifier(signature); if(pckg != null && pckg.length() > 0){ name = pckg + '.' + name; imports.add(name.replaceFirst(IMPORT_PATTERN, "")); } } } private static class AsmClassVisitor implements ClassVisitor { private PrintWriter writer; private StringBuffer classBuffer; private String name; /** * Constructs a new instance. * * @param writer The writer for this instance. */ public AsmClassVisitor(PrintWriter writer) { super(); this.writer = writer; } /** * {@inheritDoc} * @see ClassVisitor#visit(int,int,String,String,String,String[]) */ public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { writer.print(Services.getMessage("prototype.header.asm")); int index = name.lastIndexOf('/'); if(index != -1){ String pack = name.substring(0, index).replace('/', '.'); name = name.substring(index + 1); writer.print("package "); writer.print(pack); writer.println(";\n"); } this.name = name; classBuffer = new StringBuffer() .append(getAccess(access)); if((access & Opcodes.ACC_INTERFACE) == 1){ classBuffer.append("interface "); }else if((access & Opcodes.ACC_ENUM) == 1){ classBuffer.append("enum "); }else{ if (interfaces.length > 0 && interfaces[0].equals(ANNOTATION)){ classBuffer.append("@interface "); }else{ classBuffer.append("class "); } } classBuffer.append(name).append('\n'); if (!OBJECT.equals(superName)){ classBuffer.append(INDENT) .append("extends ") .append(superName.replace('/', '.')) .append('\n'); } StringBuffer ifaces = new StringBuffer(); for(String iface : interfaces){ if(!ANNOTATION.equals(iface)){ if(ifaces.length() > 0){ ifaces.append(", "); } ifaces.append(iface.replace('/', '.')); } } if(ifaces.length() > 0){ classBuffer.append(INDENT) .append("implements ") .append(ifaces) .append('\n'); } classBuffer.append("{\n"); } /** * {@inheritDoc} * @see ClassVisitor#visitSource(String,String) */ public void visitSource(String source, String debug) { } /** * {@inheritDoc} * @see ClassVisitor#visitOuterClass(String,String,String) */ public void visitOuterClass(String owner, String name, String desc) { } /** * {@inheritDoc} * @see ClassVisitor#visitAnnotation(String,boolean) */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { writer.print('@'); writer.print(getDescName(desc)); return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see ClassVisitor#visitAttribute(Attribute) */ public void visitAttribute(Attribute attr) { } /** * {@inheritDoc} * @see ClassVisitor#visitInnerClass(String,String,String,int) */ public void visitInnerClass( String name, String outerName, String innerName, int access) { flushClassDeclaration(); } /** * {@inheritDoc} * @see ClassVisitor#visitField(int,String,String,String,Object) */ public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { flushClassDeclaration(); return new AsmFieldVisitor(access, name, desc, value); } /** * {@inheritDoc} * @see ClassVisitor#visitMethod(int,String,String,String,String[]) */ public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { flushClassDeclaration(); return new AsmMethodVisitor( access, name, signature != null ? signature : desc, exceptions); } private void flushClassDeclaration() { if (classBuffer != null){ writer.write(classBuffer.toString()); classBuffer = null; } } /** * {@inheritDoc} * @see ClassVisitor#visitEnd() */ public void visitEnd() { flushClassDeclaration(); writer.print('}'); } private String getDescName(String desc) { if (desc != null){ // genrics int indexStart = desc.indexOf('<'); int indexEnd = desc.lastIndexOf('>'); if (indexStart != -1 && indexEnd != -1){ String outer = desc.substring(0, indexStart); String inner = desc.substring(indexStart + 1, indexEnd); return getDescName(outer) + '<' + getDescName(inner) + '>'; } int index = desc.lastIndexOf('/'); if (index != -1){ String name = desc.substring(index + 1); if(name.endsWith(";")){ name = name.substring(0, name.length() - 1); } while (desc.startsWith("[")){ desc = desc.substring(1); name += "[]"; } return name; } String primitive = null; if ("V".equals(desc)){ return "void"; }else if (desc.endsWith("I")){ primitive = "int"; }else if (desc.endsWith("J")){ primitive = "long"; }else if (desc.endsWith("S")){ primitive = "short"; }else if (desc.endsWith("C")){ primitive = "char"; }else if (desc.endsWith("B")){ primitive = "byte"; }else if (desc.endsWith("Z")){ primitive = "boolean"; } if (primitive != null){ for (int ii = 0; ii < desc.length() - 1; ii++){ primitive += "[]"; } return primitive; } } return StringUtils.EMPTY; } private String getAccess(int access) { StringBuffer buffer = new StringBuffer(); if((access & Opcodes.ACC_PUBLIC) != 0){ buffer.append("public "); } if((access & Opcodes.ACC_PRIVATE) != 0){ buffer.append("private "); } if((access & Opcodes.ACC_PROTECTED) != 0){ buffer.append("protected "); } if((access & Opcodes.ACC_STATIC) != 0){ buffer.append("static "); } if((access & Opcodes.ACC_FINAL) != 0){ buffer.append("final "); } if((access & Opcodes.ACC_TRANSIENT) != 0){ buffer.append("transient "); } if((access & Opcodes.ACC_NATIVE) != 0){ buffer.append("native "); } if((access & Opcodes.ACC_SYNCHRONIZED) != 0){ buffer.append("synchronized "); } if((access & Opcodes.ACC_ABSTRACT) != 0){ buffer.append("abstract "); } return buffer.toString(); } private String getValueString(Object value) { if(value instanceof String){ return new StringBuffer() .append('"').append(value).append('"').toString(); }else if(value instanceof Character){ return new StringBuffer() .append('\'').append(value).append('\'').toString(); } return String.valueOf(value); } private class AsmAnnotationVisitor implements AnnotationVisitor { private boolean wrote = false; private boolean wroteOpenParen = false; /** * {@inheritDoc} * @see AnnotationVisitor#visit(String,Object) */ public void visit(String name, Object value) { if(name != null){ writeOpenParen(); writer.print(name); writer.print(" = "); writer.print(getValueString(value)); wrote = true; } } /** * {@inheritDoc} * @see AnnotationVisitor#visitEnum(String,String,String) */ public void visitEnum(String name, String desc, String value) { writeOpenParen(); writer.print(getDescName(desc)); writer.print('.'); writer.print(value); wrote = true; } /** * {@inheritDoc} * @see AnnotationVisitor#visitAnnotation(String,String) */ public AnnotationVisitor visitAnnotation(String name, String desc) { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see AnnotationVisitor#visitArray(String) */ public AnnotationVisitor visitArray(String name) { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see AnnotationVisitor#visitEnd() */ public void visitEnd() { if (wrote){ if(wroteOpenParen){ writer.print(')'); } writer.println(); wrote = false; } } private void writeOpenParen() { if (!wroteOpenParen){ writer.print('('); wroteOpenParen = true; }else{ writer.print(", "); } } } private class AsmMethodVisitor implements MethodVisitor { private int access; private String name; private String desc; private String[] exceptions; /** * Constructs a new instance. * * @param access The access for this instance. * @param name The name for this instance. * @param desc The desc for this instance. * @param exceptions The exceptions for this instance. */ public AsmMethodVisitor( int access, String name, String desc, String[] exceptions) { this.access = access; this.name = name; this.desc = desc; this.exceptions = exceptions; } /** * {@inheritDoc} * @see MethodVisitor#visitAnnotationDefault() */ public AnnotationVisitor visitAnnotationDefault() { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see MethodVisitor#visitAnnotation(String,boolean) */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see MethodVisitor#visitParameterAnnotation(int,String,boolean) */ public AnnotationVisitor visitParameterAnnotation( int parameter, String desc, boolean visible) { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see MethodVisitor#visitAttribute(Attribute) */ public void visitAttribute(Attribute attr) { } /** * {@inheritDoc} * @see MethodVisitor#visitCode() */ public void visitCode() { } /** * {@inheritDoc} * @see MethodVisitor#visitInsn(int) */ public void visitInsn(int opcode) { } /** * {@inheritDoc} * @see MethodVisitor#visitIntInsn(int,int) */ public void visitIntInsn(int opcode, int operand) { } /** * {@inheritDoc} * @see MethodVisitor#visitVarInsn(int,int) */ public void visitVarInsn(int opcode, int var) { } /** * {@inheritDoc} * @see MethodVisitor#visitTypeInsn(int,String) */ public void visitTypeInsn(int opcode, String desc) { } /** * {@inheritDoc} * @see MethodVisitor#visitFieldInsn(int,String,String,String) */ public void visitFieldInsn( int opcode, String owner, String name, String desc) { } /** * {@inheritDoc} * @see MethodVisitor#visitMethodInsn(int,String,String,String) */ public void visitMethodInsn( int opcode, String owner, String name, String desc) { } /** * {@inheritDoc} * @see MethodVisitor#visitJumpInsn(int,Label) */ public void visitJumpInsn(int opcode, Label operand) { } /** * {@inheritDoc} * @see MethodVisitor#visitLabel(Label) */ public void visitLabel(Label label) { } /** * {@inheritDoc} * @see MethodVisitor#visitLdcInsn(Object) */ public void visitLdcInsn(Object cst) { } /** * {@inheritDoc} * @see MethodVisitor#visitIincInsn(int,int) */ public void visitIincInsn(int var, int increment) { } /** * {@inheritDoc} * @see MethodVisitor#visitTableSwitchInsn(int,int,Label,Label[]) */ public void visitTableSwitchInsn( int min, int max, Label dflt, Label[] labels) { } /** * {@inheritDoc} * @see MethodVisitor#visitLookupSwitchInsn(Label,int[],Label[]) */ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { } /** * {@inheritDoc} * @see MethodVisitor#visitMultiANewArrayInsn(String,int) */ public void visitMultiANewArrayInsn(String desc, int dims) { } /** * {@inheritDoc} * @see MethodVisitor#visitTryCatchBlock(Label,Label,Label,String) */ public void visitTryCatchBlock( Label start, Label end, Label handler, String type) { } /** * {@inheritDoc} * @see MethodVisitor#visitLocalVariable(String,String,String,Label,Label,int) */ public void visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index) { } /** * {@inheritDoc} * @see MethodVisitor#visitLineNumber(int,Label) */ public void visitLineNumber(int line, Label start) { } /** * {@inheritDoc} * @see MethodVisitor#visitMaxs(int,int) */ public void visitMaxs(int maxStack, int maxLocal) { } /** * {@inheritDoc} * @see MethodVisitor#visitEnd() */ public void visitEnd() { // skip static initializers if ("<clinit>".equals(name)){ return; } if ("<init>".equals(name)){ name = AsmClassVisitor.this.name; } String ret = "void"; int index = desc.lastIndexOf(')'); if(index != -1){ ret = getDescName(desc.substring(index + 1, desc.length())); } StringBuffer params = new StringBuffer(); int openParen = desc.indexOf('('); int closeParen = desc.indexOf(')'); if (closeParen > (openParen + 1)){ String args = desc.substring(openParen + 1, closeParen); StringBuffer buffer = new StringBuffer(); int paramIndex = 0; int typeLevel = 0; boolean inType = false; for(char c : args.toCharArray()){ // arrays, generic type if (c == '[' || c == '<'){ inType = false; buffer.append(c); continue; } // end of non-primitive or primitive type if (c == ';' || ( !inType && Character.isLetter(c) && Character.isUpperCase(c) && c != 'L')) { buffer.append(c); // edge case for generics if (c == ';'){ typeLevel -= 1; inType = false; } if (typeLevel > 0){ continue; } if(params.length() != 0){ params.append(", "); } params.append(getDescName(buffer.toString())); params.append(" arg").append(paramIndex++); buffer = new StringBuffer(); continue; } if ((!inType && c == 'L') || c == '<'){ typeLevel += 1; inType = true; } buffer.append(c); } } writer.print(INDENT); writer.print(getAccess(access)); writer.print(ret); writer.print(' '); writer.print(name); writer.print('('); writer.print(params); writer.print(')'); if(exceptions != null && exceptions.length > 0){ writer.println(); writer.print(INDENT); writer.print(INDENT); writer.print("throws "); for(int ii = 0; ii < exceptions.length; ii++){ if(ii > 0){ writer.print(", "); } writer.print(getDescName(exceptions[ii])); } } writer.print(";\n"); } } private class AsmFieldVisitor implements FieldVisitor { private int access; private String name; private String desc; private Object value; /** * Constructs a new instance. * * @param access The access for this instance. * @param name The name for this instance. * @param desc The desc for this instance. * @param value The value for this instance. */ public AsmFieldVisitor(int access, String name, String desc, Object value) { this.access = access; this.name = name; this.desc = desc; this.value = value; } /** * {@inheritDoc} * @see MethodVisitor#visitAnnotation(String,boolean) */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return new AsmAnnotationVisitor(); } /** * {@inheritDoc} * @see FieldVisitor#visitAttribute(Attribute) */ public void visitAttribute(Attribute attr) { } /** * {@inheritDoc} * @see FieldVisitor#visitEnd() */ public void visitEnd() { writer.print(INDENT); writer.print(getAccess(access)); writer.print(getDescName(desc)); writer.print(' '); writer.print(name); if(value != null){ writer.print(" = "); writer.print(getValueString(value)); } writer.print(";\n"); } } } }