/*
* 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);
}
}