Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to tell if a class is an interface?

I'm trying to examine (at a bytecode level, ASM) classes implementing some specific interfaces (in this case, java.sql.Connection) and find that in some cases, the library has another interface extending something from my set of interfaces... and then their classes implement THAT interface. (In this case, a new extended interface com.mysql.jdbc.Connection extend java.sql.Connection and then their implementations, e.g, ConnectionImpl implement com.mysql.jdbc.Connection.) I therefore miss identifying ConnectionImpl as a target class.

So.. the result is that I need to identify com.mysql.jdbc.Connection as an 'interesting' interface when loading the class. But I can't see how to identify the class AS an interface versus just a normal class. Is there something in the ClassReader than can give me that sort of information?

like image 401
ticktock Avatar asked Oct 18 '16 23:10

ticktock


2 Answers

As per the title, if you want to check if a class is an interface:

ClassNode node = // However you wish to load the class
if ((node.access & Opcodes.ACC_INTERFACE) != 0){
    // is interface
} else {
    // is not an interface
}

In your post you state you wish to find children/implementations of java.sql.Connection.

The issue you're having is this:
java.sql.Connection -> com.mysql.jdbc.Connection -> ConnectionImpl

ConnectionImpl does not directly implement java.sql.Connection so it's not detected as a child/implementation. For an issue like this what you would have to do is travel the class hierarchy. If I were in your situation I would load a map of ClassNodes <String, ClassNode> where the string is the ClassNode's name. Then I would use a recursive method to check if a given class is the intended type. So if you send a ClassNode in it would call itself with the node's parent and then the interfaces. Eventually if the initially given node is the correct type eventually the java.sql.Connection interface will be passed through and found. Also if you wanted to skip redundencies you could store the results of each ClassNode in a map so that you wouldn't have to check the entire hierarchy over and over again.

Edit: Sorta like this

public static boolean isMatch(ClassNode cn, Map<String,ClassNode> nodes, String target){
    if (cn.name.equals(target)) return true;
    else{
        if (nodes.containsKey(cn.superName) && isMatch(nodes.get(cn.superName),nodes,target)) return true;
        for (String interf : cn.interfaces){
            if (nodes.containsKey(interf) && isMatch(nodes.get(interf),nodes,target)) return true;
        }
    }
    return false;
}
like image 102
Display Name Avatar answered Oct 21 '22 05:10

Display Name


As you already mentioned, you need to check every interface type for its interfaces in order to determine such a subtype-relation. This is however not difficult to do if you have access to all resources.

When you are using ASM, you simply need to take the interface names of your original class and find the class file to each such interface. You can then parse each class file for its interfaces and so on. This way, you can determine the entire graph and decide on the subtype relationship.

If you do not want to do this manually, you can use Byte Buddy which offers you methods similar to the reflection API for unloaded types:

ClassFileLocator cfl = ... // can be file system, class loader, etc.
TypePool.Default.of(cfl).describe("your.initial.type")
                        .resolve()
                        .isAssignableTo(someInterface);

You can also use the TypePool to read the target interface, if it is not available.

like image 39
Rafael Winterhalter Avatar answered Oct 21 '22 05:10

Rafael Winterhalter