I'm working on a Java reverse engineering project, where I am analyzing bytecode and trying to identify classes, methods and fields using the ASM framework. After identifying these I transform some classes to implement my custom interfaces with, e.g., getter methods.
For identifying classes, I have created an abstract class Analyzer
with an abstract method for running the analysis on a collection of classes. Child classes of Analyzer
try to identify a certain class/field/method of interest. For instance, AnimalAnalyzer
attempts to identify the class which represents an animal.
After running the analyzers, I inject my own interfaces and their implementations into the classes. Similarly to Analyzer
, I have created an Injector
interface with an inject(List<Analyzer> analyzers)
method. For each class that I want to inject there is a separate class which implements the interface, e.g., AnimalInjector
. The injector iterates the list of Analyzers, which contain the results of their analysis, and uses the relevant ones by checking their type with the instanceof
operator. By "relevant ones" I mean the analyzers which contain the information needed to generate the bytecode with.
public abstract class Analyzer {
public abstract void run(ClassCollection classCollection);
}
public class AnimalAnalyzer extends Analyzer {
private ClassNode identifiedClass = null;
@Override
public void run(ClassCollection classCollection) {
for (ClassNode classNode : classCollection.getAllClassNodes()) {
boolean isMatch = doesClassMatch(classNode); // method omitted from example
if (isMatch) {
identifiedClass = classNode;
break;
}
}
}
}
public interface Injector {
void inject(List<Analyzer> analyzers);
}
public class AnimalInjector implements Injector {
@Override
public void inject(List<Analyzer> analyzers) {
AnimalAnalyzer animalAnalyzer = null;
for (Analyzer analyzer : analyzers) {
if (analyzer instanceof AnimalAnalyzer) {
animalAnalyzer = (AnimalAnalyzer) analyzer;
}
}
if (animalAnalyzer == null) {
throw new IllegalStateException("AnimalAnalyzer not found in list");
}
... (bytecode generation and injection)
}
}
One of the reasons that I chose to do it this way is that the analyzers have to be run in a specific order. For example, to identify a Dog
which extends Animal
, the latter has to be found first. For this purpose I created MasterAnalyzer
, which stores a list of the analyzers in a specific order. When run
is invoked on this analyzer, it calls run on the analyzers in the list in the right order.
public class MasterAnalyzer extends Analyzer {
private List<Analyzer> analyzers = new ArrayList<>();
public MasterAnalyzer() {
analyzers.add(new AnimalAnalyzer());
analyzers.add(new DogAnalyzer());
... (more analyzers)
}
public List<Analyzer> getAnalyzers() {
return analyzers;
}
@Override
public void run(ClassCollection classCollection) {
for (Analyzer analyzer : analyzers) {
analyzer.run(classCollection);
}
}
}
The injectors, in turn, can all be run with the same command: inject(masterAnalyzers.getAnalyzers())
. While this makes it easy at the client side, it causes the injectors to know more than they need to know. For instance, let's assume that we also have a CatAnalyzer
. Now, when we run the DogInjector
, the list of analyzers will include the CatAnalyzer
, even though the DogInjector
does not care about it. This, along with the nasty instanceof
usages, causes red signals to me. Now I am reaching out to you more experienced developers for some tips on how to improve my design. While searching for alternative ways, I found the Visitor pattern, which could be applicable here. However, I am still unsure how to apply it and whether it would improve my design or not.