Sto lavorando a un progetto di reverse engineering Java, in cui sto analizzando bytecode e sto cercando di identificare classi, metodi e campi utilizzando il ASM quadro. Dopo aver identificato questi, trasformo alcune classi per implementare le mie interfacce personalizzate con, ad esempio, i metodi getter.
Per identificare le classi, ho creato una classe astratta Analyzer
con un metodo astratto per eseguire l'analisi su una raccolta di classi. Le classi secondarie di Analyzer
cercano di identificare una determinata classe / campo / metodo di interesse. Ad esempio, AnimalAnalyzer
tenta di identificare la classe che rappresenta un animale.
Dopo aver eseguito gli analizzatori, inserisco le mie interfacce e le loro implementazioni nelle classi. Analogamente a Analyzer
, ho creato un'interfaccia Injector
con un metodo inject(List<Analyzer> analyzers)
. Per ogni classe che voglio iniettare c'è una classe separata che implementa l'interfaccia, ad esempio AnimalInjector
. L'iniettore itera l'elenco degli analizzatori, che contengono i risultati della loro analisi e utilizza quelli rilevanti controllandone il tipo con l'operatore instanceof
. Per "quelli rilevanti" intendo gli analizzatori che contengono le informazioni necessarie per generare il bytecode con.
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)
}
}
Uno dei motivi per cui ho scelto di farlo in questo modo è che gli analizzatori devono essere eseguiti in un ordine specifico. Ad esempio, per identificare un Dog
che estende Animal
, quest'ultimo deve essere trovato per primo. A tale scopo ho creato MasterAnalyzer
, che memorizza un elenco di analizzatori in un ordine specifico. Quando run
viene richiamato su questo analizzatore, richiama l'esecuzione sugli analizzatori nell'elenco nel giusto ordine.
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);
}
}
}
Gli iniettori, a loro volta, possono essere tutti eseguiti con lo stesso comando: inject(masterAnalyzers.getAnalyzers())
. Mentre ciò rende più semplice dal punto di vista del cliente, fa sì che gli iniettori sappiano più del necessario. Ad esempio, supponiamo di avere anche CatAnalyzer
. Ora, quando eseguiamo DogInjector
, l'elenco degli analizzatori includerà CatAnalyzer
, anche se DogInjector
non gli interessa. Questo, insieme ai cattivi usi di instanceof
, mi provoca segnali rossi. Ora sto contattando sviluppatori più esperti per alcuni suggerimenti su come migliorare il mio design. Durante la ricerca di modi alternativi, ho trovato il pattern Visitatori , che potrebbe essere applicabile qui. Tuttavia, non sono ancora sicuro di come applicarlo e se possa migliorare il mio design o meno.