/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package ca.weblite.netbeans.mirah.lexer; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.netbeans.api.java.classpath.ClassPath; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; import org.openide.filesystems.FileObject; /** * * @author shannah */ public class ClassQuery { private Class cls; Map<String,Method> methods; public ClassQuery(Class cls){ this.cls = cls; } public static ClassPath[] getClassPaths(FileObject fo){ return new ClassPath[]{ ClassPath.getClassPath(fo, ClassPath.SOURCE), ClassPath.getClassPath(fo, ClassPath.EXECUTE), ClassPath.getClassPath(fo, ClassPath.COMPILE), ClassPath.getClassPath(fo, ClassPath.BOOT) }; } public static Class findClass(String fqn, FileObject fo){ return findClass(fqn, fo, true ); } public static Class findClass(String fqn, FileObject fo, boolean cache){ ClassPath[] classPaths = getClassPaths(fo); for ( ClassPath cp : classPaths ){ try { Class cls = cp.getClassLoader(cache).loadClass(fqn); if ( cls != null ){ return cls; } } catch (Throwable ex) { //Exceptions.printStackTrace(ex); } } return null; } public ClassQuery(String className, FileObject fo, boolean cache){ this.cls = findClass(className, fo, cache); } public Set<Method> getMethods(){ if ( methods == null ){ methods = new HashMap<String,Method>(); Class c = cls; while ( c != null ){ try { for (Method m : c.getDeclaredMethods()){ String mid = getMethodId(m); if ( !methods.containsKey(mid)){ methods.put(mid, m); } } } catch ( Throwable ex){} if ( c.getSuperclass() == c ){ break; } c = c.getSuperclass(); } } return new HashSet<Method>(methods.values()); } public Set<Method> getAbstractMethods(){ Set<Method> out = new HashSet<Method>(); for ( Method m : getMethods()){ if ( Modifier.isAbstract(m.getModifiers())){ out.add(m); } } return out; } public Set<Class> getInterfaces(){ //// BROKEN!!!! Not finding any interfaces for any classes at all Set<Class> out = new HashSet<Class>(); //out.addAll(Arrays.asList(cls.getInterfaces())); Class p = cls; while ( p != null ){ out.addAll(Arrays.asList(p.getInterfaces())); p = p.getSuperclass(); } return out; } public Set<Method> getUnimplementedMethodsRequiredByInterfaces(){ Map<String,Method> required = new HashMap<String,Method>(); for ( Class i : getInterfaces()){ ClassQuery iq = new ClassQuery(i); for ( Method m : iq.getMethods()){ String mid = getMethodId(m); required.put(mid, m); } } for ( Method m : getMethods() ){ String mid = getMethodId(m); required.remove(mid); } HashSet<Method> out = new HashSet<Method>(); out.addAll(required.values()); return out; } public Set<Method> getProtectedMethods(){ Set<Method> out = new HashSet<Method>(); for ( Method m : getMethods()){ if ( Modifier.isProtected(m.getModifiers())){ out.add(m); } } return out; } boolean isAncestorOf(Class parent){ Class c = parent; while ( c != null ){ if ( c == cls){ return true; } c = c.getSuperclass(); } return false; } public Set<Method> getAccessibleMethods(Class context){ Set<Method> out = new HashSet<Method>(); boolean packagePrivateAllowed = context.getPackage().equals(cls.getPackage()); boolean protectedAllowed = this.isAncestorOf(context); boolean privateAllowed = context == cls; for ( Method m : getMethods()){ int mods = m.getModifiers(); if ( Modifier.isPrivate(mods) && !privateAllowed){ continue; } if ( Modifier.isProtected(mods) && !protectedAllowed ){ continue; } if ( !Modifier.isPublic(mods) && !Modifier.isProtected(mods) && !Modifier.isPrivate(mods) && !packagePrivateAllowed ){ continue; } out.add(m); } return out; } static String getMethodId(Method m){ StringBuilder sb = new StringBuilder(); sb.append(m.getName()); for ( Class paramType : m.getParameterTypes()){ sb.append("_").append(paramType.getName()); } return sb.toString(); } /** * Returns a list containing one parameter name for each argument accepted * by the given constructor. If the class was compiled with debugging * symbols, the parameter names will match those provided in the Java source * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for * the first argument, "arg1" for the second...). * * This method relies on the constructor's class loader to locate the * bytecode resource that defined its class. * * @param constructor * @return * @throws IOException */ public List<String> getParameterNames(Constructor<?> constructor) throws IOException { //Class<?> declaringClass = constructor.getDeclaringClass(); Class declaringClass = cls; ClassLoader declaringClassLoader = declaringClass.getClassLoader(); Type declaringType = Type.getType(declaringClass); String constructorDescriptor = Type.getConstructorDescriptor(constructor); String url = declaringType.getInternalName() + ".class"; InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url); if (classFileInputStream == null) { throw new IllegalArgumentException("The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: " + url + ")"); } ClassNode classNode; try { classNode = new ClassNode(); ClassReader classReader = new ClassReader(classFileInputStream); classReader.accept(classNode, 0); } finally { classFileInputStream.close(); } @SuppressWarnings("unchecked") List<MethodNode> methods = classNode.methods; for (MethodNode method : methods) { if (method.name.equals("<init>") && method.desc.equals(constructorDescriptor)) { Type[] argumentTypes = Type.getArgumentTypes(method.desc); List<String> parameterNames = new ArrayList<String>(argumentTypes.length); @SuppressWarnings("unchecked") List<LocalVariableNode> localVariables = method.localVariables; for (int i = 0; i < argumentTypes.length; i++) { // The first local variable actually represents the "this" object parameterNames.add(localVariables.get(i + 1).name); } return parameterNames; } } return null; } public static List<String> getParameterNames(Class cls, Method constructor, FileObject fo) throws IOException { //Class<?> declaringClass = constructor.getDeclaringClass(); Class declaringClass = cls; ClassLoader declaringClassLoader = declaringClass.getClassLoader(); if ( declaringClassLoader == null ){ cls = findClass(cls.getName(), fo); declaringClassLoader = cls.getClassLoader(); } if ( declaringClassLoader == null ){ return null; } Type declaringType = Type.getType(declaringClass); String constructorDescriptor = Type.getMethodDescriptor(constructor); String url = declaringType.getInternalName() + ".class"; InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url); if (classFileInputStream == null) { throw new IllegalArgumentException("The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: " + url + ")"); } ClassNode classNode; try { classNode = new ClassNode(); ClassReader classReader = new ClassReader(classFileInputStream); classReader.accept(classNode, 0); } finally { classFileInputStream.close(); } @SuppressWarnings("unchecked") List<MethodNode> methods = classNode.methods; for (MethodNode method : methods) { if (method.name.equals(constructor.getName()) && method.desc.equals(constructorDescriptor)) { Type[] argumentTypes = Type.getArgumentTypes(method.desc); List<String> parameterNames = new ArrayList<String>(argumentTypes.length); @SuppressWarnings("unchecked") List<LocalVariableNode> localVariables = method.localVariables; boolean isStatic = true; if ( localVariables != null && !localVariables.isEmpty() && localVariables.get(0).name.equals("this")){ isStatic=false; } int offset = isStatic ? 0:1; for (int i = 0; i < argumentTypes.length; i++) { // The first local variable actually represents the "this" object try { parameterNames.add(localVariables.get(i + offset).name); } catch ( NullPointerException npe){ npe.printStackTrace(); } } return parameterNames; } } List<String> out = null; cls = cls.getSuperclass(); while ( cls != null && out == null ){ out = getParameterNames(cls, constructor, fo); cls = cls.getSuperclass(); } if ( out != null ){ return out; } return null; } /** * Returns a list containing one parameter name for each argument accepted * by the given constructor. If the class was compiled with debugging * symbols, the parameter names will match those provided in the Java source * code. Otherwise, a generic "arg" parameter name is generated ("arg0" for * the first argument, "arg1" for the second...). * * This method relies on the constructor's class loader to locate the * bytecode resource that defined its class. * * @param constructor * @return * @throws IOException */ public List<String> getParameterNames(Method constructor, FileObject fo) throws IOException { return getParameterNames(cls, constructor, fo); } }