obfuscator scripting implemented

git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@1090 379699f6-c40d-0410-875b-85095c16579e
branch_1_1
jochen 26 years ago
parent f7be2d00d5
commit 2f04c0e0cb
  1. 6
      jode/configure.in
  2. 2
      jode/jode/Decompiler.java
  3. 10
      jode/jode/Makefile.am
  4. 26
      jode/jode/jode.jodescript
  5. 231
      jode/jode/obfuscator/ClassBundle.java.in
  6. 17
      jode/jode/obfuscator/ClassIdentifier.java.in
  7. 14
      jode/jode/obfuscator/ConstantAnalyzer.java.in
  8. 5
      jode/jode/obfuscator/Identifier.java.in
  9. 19
      jode/jode/obfuscator/IdentifierMatcher.java
  10. 331
      jode/jode/obfuscator/Main.java.in
  11. 1
      jode/jode/obfuscator/Makefile.am
  12. 28
      jode/jode/obfuscator/MethodIdentifier.java.in
  13. 8
      jode/jode/obfuscator/ModifierMatcher.java
  14. 109
      jode/jode/obfuscator/MultiIdentifierMatcher.java.in
  15. 87
      jode/jode/obfuscator/NameSwapper.java.in
  16. 8
      jode/jode/obfuscator/OptionHandler.java.in
  17. 276
      jode/jode/obfuscator/PackageIdentifier.java.in
  18. 7
      jode/jode/obfuscator/ParseException.java
  19. 3
      jode/jode/obfuscator/Renamer.java.in
  20. 258
      jode/jode/obfuscator/ScriptParser.java.in
  21. 63
      jode/jode/obfuscator/StrongRenamer.java
  22. 141
      jode/jode/obfuscator/StrongRenamer.java.in
  23. 21
      jode/jode/obfuscator/UniqueRenamer.java.in
  24. 51
      jode/jode/obfuscator/WildCard.java.in

@ -161,6 +161,10 @@ jode/flow/IfThenElseBlock.java
jode/flow/InstructionBlock.java jode/flow/InstructionBlock.java
jode/flow/FlowBlock.java jode/flow/FlowBlock.java
jode/flow/TransformExceptionHandlers.java jode/flow/TransformExceptionHandlers.java
jode/obfuscator/Renamer.java
jode/obfuscator/StrongRenamer.java
jode/obfuscator/OptionHandler.java
jode/obfuscator/ScriptParser.java
jode/obfuscator/TranslationTable.java jode/obfuscator/TranslationTable.java
jode/obfuscator/Main.java jode/obfuscator/Main.java
jode/obfuscator/FieldIdentifier.java jode/obfuscator/FieldIdentifier.java
@ -172,6 +176,8 @@ jode/obfuscator/ConstantAnalyzer.java
jode/obfuscator/LocalIdentifier.java jode/obfuscator/LocalIdentifier.java
jode/obfuscator/MethodIdentifier.java jode/obfuscator/MethodIdentifier.java
jode/obfuscator/ClassIdentifier.java jode/obfuscator/ClassIdentifier.java
jode/obfuscator/MultiIdentifierMatcher.java
jode/obfuscator/WildCard.java
jode/swingui/Main.java jode/swingui/Main.java
jode/swingui/PackagesTreeModel.java jode/swingui/PackagesTreeModel.java
jode/util/SimpleSet.java jode/util/SimpleSet.java

@ -100,6 +100,8 @@ public class Decompiler {
err.println("Usage: java jode.Decompiler [OPTIONS]... [CLASSES]..."); err.println("Usage: java jode.Decompiler [OPTIONS]... [CLASSES]...");
err.println(" -h, --help "+ err.println(" -h, --help "+
"show this information."); "show this information.");
err.println(" -V, --version "+
"output version information and exit.");
err.println(" -v, --verbose "+ err.println(" -v, --verbose "+
"be verbose (multiple times means more verbose)."); "be verbose (multiple times means more verbose).");
err.println(" -c, --classpath <path> "+ err.println(" -c, --classpath <path> "+

@ -37,11 +37,5 @@ clean-local:
$(JARFILE): $(noinst_DATA) $(JARFILE): $(noinst_DATA)
CLASSPATH=$(top_builddir):$(CLASSPATH) $(JAVA) -mx80m \ CLASSPATH=$(top_builddir):$(CLASSPATH) $(JAVA) -mx80m \
jode.obfuscator.Main --dest $(JARFILE) \ jode.obfuscator.Main --classpath=$(top_builddir) \
--revtable rename.table \ --dest=$(JARFILE) -v -v $(srcdir)/jode.jodescript
--rename=none --breakserial --strip=unreach -v -v \
--preserve 'jode.Decompiler.main.*' \
--preserve 'jode.JodeApplet.<init>.*' \
--preserve 'jode.JodeWindow.main.*' \
--preserve 'jode.obfuscator.Main.main.*' \
--preserve 'jode.swingui.Main.main.*' jode

@ -0,0 +1,26 @@
# This is a sample script file to obfuscate the JODE project.
# First we select what we want to strip. There are several possibilities:
# unreach - strip unreachable methods and classes.
# source - strip source file attribute.
# lnt - strip line number table.
# lvt - strip local variable table.
# inner - strip inner class info
strip = "unreach"
load = new WildCard { value = "jode" }
preserve = new WildCard { value = "jode.Decompiler.main.*" },
new WildCard { value = "jode.JodeApplet.<init>.()V" },
new WildCard { value = "jode.JodeWindow.main.*" },
new WildCard { value = "jode.obfuscator.Main.main.*" },
new WildCard { value = "jode.swingui.Main.main.*" }
# value = "jode.Decompiler.main.*",
# "jode.JodeApplet.<init>.()V",
# "jode.JodeWindow.main.*",
# "jode.obfuscator.Main.main.*",
# "jode.swingui.Main.main.*"
analyzer = new SimpleAnalyzer
post = new LocalOptimizer, new RemovePopAnalyzer

@ -19,6 +19,7 @@
package jode.obfuscator; package jode.obfuscator;
import jode.GlobalOptions; import jode.GlobalOptions;
import jode.bytecode.SearchPath;
import jode.bytecode.ClassInfo; import jode.bytecode.ClassInfo;
import jode.bytecode.Reference; import jode.bytecode.Reference;
import java.io.*; import java.io.*;
@ -30,6 +31,7 @@ import @COLLECTIONS@.Set;
import @COLLECTIONS@.HashSet; import @COLLECTIONS@.HashSet;
import @COLLECTIONS@.Map; import @COLLECTIONS@.Map;
import @COLLECTIONS@.TreeMap; import @COLLECTIONS@.TreeMap;
import @COLLECTIONEXTRA@.UnsupportedOperationException;
///#ifdef JDK12 ///#ifdef JDK12
///import @COLLECTIONS@.WeakHashMap; ///import @COLLECTIONS@.WeakHashMap;
@ -37,17 +39,34 @@ import @COLLECTIONS@.TreeMap;
import @COLLECTIONS@.HashMap; import @COLLECTIONS@.HashMap;
///#endif ///#endif
public class ClassBundle { public class ClassBundle implements OptionHandler {
ModifierMatcher preserveRule = null;
PackageIdentifier basePackage; PackageIdentifier basePackage;
/** /**
* the identifiers that must be analyzed. * the identifiers that must be analyzed.
*/ */
Set toAnalyze = new HashSet(); Set toAnalyze = new HashSet();
String classPath;
String destDir;
String tableFile;
String toTableFile;
IdentifierMatcher loading;
IdentifierMatcher preserving;
IdentifierMatcher reaching;
CodeTransformer[] preTrafos;
CodeAnalyzer analyzer;
CodeTransformer[] postTrafos;
Renamer renamer;
public ClassBundle() { public ClassBundle() {
basePackage = new PackageIdentifier(this, null, "", false); classPath = System.getProperty("java.class.path")
.replace(File.pathSeparatorChar, SearchPath.pathSeparatorChar);
destDir = ".";
basePackage = new PackageIdentifier(this, null, "");
basePackage.setReachable(); basePackage.setReachable();
basePackage.setPreserved(); basePackage.setPreserved();
} }
@ -58,6 +77,102 @@ public class ClassBundle {
private static final Map aliasesHash = new HashMap(); private static final Map aliasesHash = new HashMap();
///#endif ///#endif
public static void setStripOptions(Collection stripString) {
}
public void setOption(String option, Collection values) {
if (option.equals("classpath")) {
Iterator i = values.iterator();
StringBuffer sb = new StringBuffer((String) i.next());
while (i.hasNext()) {
sb.append(SearchPath.pathSeparatorChar)
.append((String)i.next());
}
ClassInfo.setClassPath(sb.toString());
return;
}
if (option.equals("dest")) {
if (values.size() != 1)
throw new IllegalArgumentException
("Only one destination path allowed");
destDir = (String) values.iterator().next();
return;
}
if (option.equals("strip")) {
next_token:
for (Iterator iter = values.iterator(); iter.hasNext(); ) {
String token = (String) iter.next();
for (int i=0; i < Main.stripNames.length; i++) {
if (token.equals(Main.stripNames[i])) {
Main.stripping |= 1 << i;
continue next_token;
}
}
throw new IllegalArgumentException("Unknown strip option: `"
+token+"'");
}
return;
}
if (option.equals("load")) {
if (values.size() == 1)
loading = (IdentifierMatcher) values.iterator().next();
else
loading = new MultiIdentifierMatcher
(MultiIdentifierMatcher.OR, (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]));
return;
}
if (option.equals("preserve")) {
if (values.size() == 1)
preserving = (IdentifierMatcher) values.iterator().next();
else
preserving = new MultiIdentifierMatcher
(MultiIdentifierMatcher.OR, (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]));
return;
}
if (option.equals("reach")) {
// NOT IMPLEMENTED YET
if (values.size() == 1)
reaching = (IdentifierMatcher) values.iterator().next();
else
reaching = new MultiIdentifierMatcher
(MultiIdentifierMatcher.OR, (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]));
}
if (option.equals("pre")) {
preTrafos = (CodeTransformer[])
values.toArray(new CodeTransformer[values.size()]);
return;
}
if (option.equals("analyzer")) {
if (values.size() != 1)
throw new IllegalArgumentException
("Only one analyzer is allowed");
analyzer = (CodeAnalyzer) values.iterator().next();
return;
}
if (option.equals("post")) {
postTrafos = (CodeTransformer[])
values.toArray(new CodeTransformer[values.size()]);
return;
}
if (option.equals("renamer")) {
if (values.size() != 1)
throw new IllegalArgumentException
("Only one renamer allowed");
renamer = (Renamer) values.iterator().next();
return;
}
throw new IllegalArgumentException("Invalid option `"+option+"'.");
}
public Reference getReferenceAlias(Reference ref) { public Reference getReferenceAlias(Reference ref) {
Reference alias = (Reference) aliasesHash.get(ref); Reference alias = (Reference) aliasesHash.get(ref);
if (alias == null) { if (alias == null) {
@ -113,27 +228,10 @@ public class ClassBundle {
return ident.getIdentifier(ref.getName(), ref.getType()); return ident.getIdentifier(ref.getName(), ref.getType());
} }
public void loadClasses(String wildcard) {
System.err.println("Loading: "+wildcard);
basePackage.loadMatchingClasses(new WildCard(wildcard));
}
public void reachableIdentifier(String fqn, boolean isVirtual) { public void reachableIdentifier(String fqn, boolean isVirtual) {
basePackage.reachableIdentifier(fqn, isVirtual); basePackage.reachableIdentifier(fqn, isVirtual);
} }
public void setPreserved(ModifierMatcher preserveRule,
Collection fullqualifiednames) {
this.preserveRule = preserveRule;
basePackage.applyPreserveRule(preserveRule);
for (Iterator i = fullqualifiednames.iterator(); i.hasNext(); ) {
basePackage.preserveMatchingIdentifier
(new WildCard((String) i.next()));
}
analyze();
}
public void analyzeIdentifier(Identifier ident) { public void analyzeIdentifier(Identifier ident) {
if (ident == null) if (ident == null)
throw new NullPointerException(); throw new NullPointerException();
@ -148,32 +246,48 @@ public class ClassBundle {
} }
} }
public IdentifierMatcher getPreserveRule() {
return preserving;
}
public CodeAnalyzer getCodeAnalyzer() {
return analyzer;
}
public CodeTransformer[] getPreTransformers() {
return preTrafos;
}
public CodeTransformer[] getPostTransformers() {
return postTrafos;
}
public void buildTable(Renamer renameRule) { public void buildTable(Renamer renameRule) {
basePackage.buildTable(renameRule); basePackage.buildTable(renameRule);
} }
public void readTable(String filename) { public void readTable() {
try { try {
TranslationTable table = new TranslationTable(); TranslationTable table = new TranslationTable();
InputStream input = new FileInputStream(filename); InputStream input = new FileInputStream(tableFile);
table.load(input); table.load(input);
input.close(); input.close();
basePackage.readTable(table); basePackage.readTable(table);
} catch (java.io.IOException ex) { } catch (java.io.IOException ex) {
GlobalOptions.err.println("Can't read rename table "+filename); GlobalOptions.err.println("Can't read rename table " + tableFile);
ex.printStackTrace(GlobalOptions.err); ex.printStackTrace(GlobalOptions.err);
} }
} }
public void writeTable(String filename) { public void writeTable() {
TranslationTable table = new TranslationTable(); TranslationTable table = new TranslationTable();
basePackage.writeTable(table); basePackage.writeTable(table);
try { try {
OutputStream out = new FileOutputStream(filename); OutputStream out = new FileOutputStream(toTableFile);
table.store(out); table.store(out);
out.close(); out.close();
} catch (java.io.IOException ex) { } catch (java.io.IOException ex) {
GlobalOptions.err.println("Can't write rename table "+filename); GlobalOptions.err.println("Can't write rename table "+toTableFile);
ex.printStackTrace(GlobalOptions.err); ex.printStackTrace(GlobalOptions.err);
} }
} }
@ -182,28 +296,77 @@ public class ClassBundle {
basePackage.doTransformations(); basePackage.doTransformations();
} }
public void storeClasses(String destination) { public void storeClasses() {
if (destination.endsWith(".jar") || if (destDir.endsWith(".jar") ||
destination.endsWith(".zip")) { destDir.endsWith(".zip")) {
try { try {
ZipOutputStream zip = new ZipOutputStream ZipOutputStream zip = new ZipOutputStream
(new FileOutputStream(destination)); (new FileOutputStream(destDir));
basePackage.storeClasses(zip); basePackage.storeClasses(zip);
zip.close(); zip.close();
} catch (IOException ex) { } catch (IOException ex) {
GlobalOptions.err.println GlobalOptions.err.println
("Can't write zip file: "+destination); ("Can't write zip file: "+destDir);
ex.printStackTrace(GlobalOptions.err); ex.printStackTrace(GlobalOptions.err);
} }
} else { } else {
File directory = new File(destination); File directory = new File(destDir);
if (!directory.exists()) { if (!directory.exists()) {
GlobalOptions.err.println("Destination directory " GlobalOptions.err.println("Destination directory "
+directory.getPath() +directory.getPath()
+" doesn't exists."); +" doesn't exists.");
return; return;
} }
basePackage.storeClasses(new File(destination)); basePackage.storeClasses(new File(destDir));
}
}
public void run() {
if (analyzer == null)
analyzer = new SimpleAnalyzer();
if (preTrafos == null)
preTrafos = new CodeTransformer[0];
if (postTrafos == null)
postTrafos = new CodeTransformer[0];
if (renamer == null)
renamer = new Renamer() {
public Iterator generateNames(Identifier ident) {
final String base = ident.getName();
return new Iterator() {
int last = 0;
public boolean hasNext() {
return true;
}
public Object next() {
return (last++ == 1 ? base : base + last);
} }
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
GlobalOptions.err.println("Loading and preserving classes");
basePackage.loadMatchingClasses(loading);
basePackage.applyPreserveRule(preserving);
GlobalOptions.err.println("Computing reachable settings");
analyze();
GlobalOptions.err.println("Renaming methods");
if (tableFile != null)
readTable();
buildTable(renamer);
if (toTableFile != null)
writeTable();
GlobalOptions.err.println("Transforming the classes");
doTransformations();
GlobalOptions.err.println("Writing new classes");
storeClasses();
} }
} }

@ -66,23 +66,6 @@ public class ClassIdentifier extends Identifier {
} }
} }
public void preserveMatchingIdentifier(WildCard wildcard) {
String fullName = getFullName() + ".";
for (Iterator i = getChilds(); i.hasNext(); ) {
Identifier ident = (Identifier) i.next();
System.err.println("checking "+ident);
if (wildcard.matches(fullName + ident.getName())
|| wildcard.matches(fullName + ident.getName()
+ "." +ident.getType())) {
if (GlobalOptions.verboseLevel > 0)
GlobalOptions.err.println("Preserving "+ident);
setPreserved();
ident.setPreserved();
ident.setReachable();
}
}
}
private FieldIdentifier findField(String name, String typeSig) { private FieldIdentifier findField(String name, String typeSig) {
for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) { for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) {
FieldIdentifier ident = (FieldIdentifier) i.next(); FieldIdentifier ident = (FieldIdentifier) i.next();

@ -46,11 +46,9 @@ import @COLLECTIONS@.Iterator;
* @author Jochen Hoenicke * @author Jochen Hoenicke
*/ */
public class ConstantAnalyzer implements Opcodes, CodeAnalyzer { public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
boolean working;
Map constInfos; Map constInfos;
BytecodeInfo bytecode; BytecodeInfo bytecode;
Identifier listener;
private static ConstantRuntimeEnvironment runtime private static ConstantRuntimeEnvironment runtime
= new ConstantRuntimeEnvironment(); = new ConstantRuntimeEnvironment();
@ -485,7 +483,7 @@ public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
} }
} }
public void handleOpcode(StackLocalInfo info) { public void handleOpcode(StackLocalInfo info, Identifier fieldListener) {
Instruction instr = info.instr; Instruction instr = info.instr;
constInfos.put(instr, unknownConstInfo); constInfos.put(instr, unknownConstInfo);
@ -1206,7 +1204,7 @@ public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
shortInfo.constant = obj; shortInfo.constant = obj;
result = new ConstValue(obj); result = new ConstValue(obj);
result.addConstantListener(shortInfo); result.addConstantListener(shortInfo);
fi.addFieldListener(listener); fi.addFieldListener(fieldListener);
} }
} else } else
result = unknownValue[type.stackSize()-1]; result = unknownValue[type.stackSize()-1];
@ -1367,10 +1365,9 @@ public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
} }
} }
public void analyzeCode(MethodIdentifier listener, BytecodeInfo bytecode) { public void analyzeCode(MethodIdentifier methodIdent,
this.listener = listener; BytecodeInfo bytecode) {
this.bytecode = bytecode; this.bytecode = bytecode;
working = true;
if (constInfos == null) if (constInfos == null)
constInfos = new HashMap(); constInfos = new HashMap();
Set modifiedQueue = new HashSet(); Set modifiedQueue = new HashSet();
@ -1385,7 +1382,7 @@ public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
Iterator iter = modifiedQueue.iterator(); Iterator iter = modifiedQueue.iterator();
StackLocalInfo info = (StackLocalInfo) iter.next(); StackLocalInfo info = (StackLocalInfo) iter.next();
iter.remove(); iter.remove();
handleOpcode(info); handleOpcode(info, methodIdent);
} }
Handler[] handlers = bytecode.getExceptionHandlers(); Handler[] handlers = bytecode.getExceptionHandlers();
@ -1394,7 +1391,6 @@ public class ConstantAnalyzer implements Opcodes, CodeAnalyzer {
&& handlers[i].type != null) && handlers[i].type != null)
Main.getClassBundle().reachableIdentifier(handlers[i].type, false); Main.getClassBundle().reachableIdentifier(handlers[i].type, false);
} }
working = false;
for (Instruction instr = bytecode.getFirstInstr(); for (Instruction instr = bytecode.getFirstInstr();
instr != null; instr = instr.getNextByAddr()) instr != null; instr = instr.getNextByAddr())
instr.setTmpInfo(null); instr.setTmpInfo(null);

@ -181,10 +181,10 @@ public abstract class Identifier {
// set alias to empty string, so it won't conflict! // set alias to empty string, so it won't conflict!
rep.alias = ""; rep.alias = "";
String newAlias = null; Iterator aliases = renameRule.generateNames(this);
next_alias: next_alias:
for (;;) { for (;;) {
newAlias = renameRule.generateName(this, newAlias); String newAlias = (String) aliases.next();
Identifier ptr = rep; Identifier ptr = rep;
while (ptr != null) { while (ptr != null) {
if (ptr.conflicting(newAlias)) if (ptr.conflicting(newAlias))
@ -224,6 +224,7 @@ public abstract class Identifier {
public void applyPreserveRule(IdentifierMatcher preserveRule) { public void applyPreserveRule(IdentifierMatcher preserveRule) {
if (preserveRule.matches(this)) { if (preserveRule.matches(this)) {
System.err.println("preserving: "+this);
setReachable(); setReachable();
setPreserved(); setPreserved();
} }

@ -20,6 +20,25 @@
package jode.obfuscator; package jode.obfuscator;
public interface IdentifierMatcher { public interface IdentifierMatcher {
/**
* Returns true, if the ident is matched by this matcher.
*/
public boolean matches(Identifier ident); public boolean matches(Identifier ident);
/**
* Returns true, if there may be a sub ident, that is matched by
* this matcher.
* @param subIdent the name of the sub ident, or null if every
* name is okay.
*/
public boolean matchesSub(Identifier ident, String subIdent);
/**
* Returns the unique name of the single sub item, for which matches
* or matchesSub returns true.
* @return the unique name, or null, if there is not a unique sub
* item.
*/
public String getNextComponent(Identifier ident);
} }

@ -21,14 +21,15 @@ package jode.obfuscator;
import jode.bytecode.ClassInfo; import jode.bytecode.ClassInfo;
import jode.bytecode.SearchPath; import jode.bytecode.SearchPath;
import jode.GlobalOptions; import jode.GlobalOptions;
import java.util.Vector;
import java.util.StringTokenizer; import gnu.getopt.LongOpt;
import gnu.getopt.Getopt;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.File; import java.io.FileReader;
import @COLLECTIONS@.Collection; import java.io.IOException;
import @COLLECTIONS@.Arrays; import @COLLECTIONS@.Collections;
import @COLLECTIONS@.HashSet;
public class Main { public class Main {
public static boolean swapOrder = false; public static boolean swapOrder = false;
@ -37,6 +38,16 @@ public class Main {
public static final int OPTION_PRESERVESERIAL = 0x0002; public static final int OPTION_PRESERVESERIAL = 0x0002;
public static int options = OPTION_PRESERVESERIAL; public static int options = OPTION_PRESERVESERIAL;
private static final LongOpt[] longOptions = new LongOpt[] {
new LongOpt("cp", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
new LongOpt("classpath", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
new LongOpt("destpath", LongOpt.REQUIRED_ARGUMENT, null, 'd'),
new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'),
new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'V'),
new LongOpt("verbose", LongOpt.OPTIONAL_ARGUMENT, null, 'v'),
new LongOpt("debug", LongOpt.OPTIONAL_ARGUMENT, null, 'D'),
};
public static final String[] stripNames = { public static final String[] stripNames = {
"unreach", "inner", "lvt", "lnt", "source" "unreach", "inner", "lvt", "lnt", "source"
}; };
@ -51,252 +62,118 @@ public class Main {
public static void usage() { public static void usage() {
PrintWriter err = GlobalOptions.err; PrintWriter err = GlobalOptions.err;
err.println("usage: jode.Obfuscator flags* [class | package]*"); err.println("usage: jode.Obfuscator flags* script");
err.println("\t-v "+ err.println(" -h, --help "+
"Verbose output (allowed multiple times)."); "show this information.");
err.println("\t--nostrip "+ err.println(" -V, --version "+
"Don't strip not needed methods"); "output version information and exit.");
err.println(" -v, --verbose "+
err.println("\t--cp <classpath> "+ "be verbose (multiple times means more verbose).");
"The class path; should contain classes.zip"); err.println(" -c, --classpath <path> "+
err.println("\t-d,--dest <directory> "+ "search for classes in specified classpath.");
"Destination directory for output classes"); err.println(" "+
err.println("Preserve options: "); "The directories should be separated by ','.");
err.println("\t--package "+ err.println(" -d, --dest <dir> "+
"Preserve all package members"); "write decompiled files to disk into directory destdir.");
err.println("\t--protected "+ err.println(" -D, --debug=... "+
"Preserve all protected members");
err.println("\t--public "+
"Preserve all public members");
err.println("\t--preserve <name> "+
"Preserve only the given name (allowed multiple times)");
err.println("\t--breakserial "+
"Allow the serialized form to change");
err.println("Obfuscating options: ");
err.println("\t--rename={strong|weak|unique|none} "+
"Rename identifiers with given scheme");
err.println("\t--strip=... "+
"use --strip=help for more information.");
err.println("\t--table <file> "+
"Read (some) translation table from file");
err.println("\t--revtable <file> "+
"Write reversed translation table to file");
err.println("\t--swaporder "+
"Swap the order of fields and methods.");
err.println("\t--debug=... "+
"use --debug=help for more information."); "use --debug=help for more information.");
} }
public static void usageStrip() {
PrintWriter err = GlobalOptions.err;
err.println("Strip options: --strip=flag1,flag2,...");
err.println("possible flags:");
err.println("\tunreach " +
"strip all unreachable methods and classes.");
err.println("\tinner " +
"strip inner classes info.");
err.println("\tlvt " +
"strip local variable tables.");
err.println("\tlnt " +
"strip line number tables.");
err.println("\tsource " +
"strip the name of the source file.");
err.println("\tinout " +
"show T1/T2 in/out set analysis.");
err.println("\tlvt " +
"dump LocalVariableTable.");
err.println("\tcheck " +
"do time consuming sanity checks.");
err.println("\tlocals " +
"dump local merging information.");
err.println("\tconstructors " +
"dump constructor simplification.");
err.println("\tinterpreter " +
"debug execution of interpreter.");
System.exit(0);
}
public static void setStripOptions(String stripString) {
if (stripString.length() == 0 || stripString.equals("help"))
usageStrip();
StringTokenizer st = new StringTokenizer(stripString, ",");
next_token:
while (st.hasMoreTokens()) {
String token = st.nextToken().intern();
for (int i=0; i < stripNames.length; i++) {
if (token == stripNames[i]) {
stripping |= 1 << i;
continue next_token;
}
}
GlobalOptions.err.println("Illegal strip flag: "+token);
usageStrip();
}
}
public static Renamer getRenamer(String option) {
if (option.equals("strong"))
return new StrongRenamer
("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
else if (option.equals("weak"))
return new StrongRenamer
("abcdefghijklmnopqrstuvwxyz",
"0123456789abcdefghijklmnopqrstuvwxyz");
else if (option.equals("unique")) {
return new Renamer() {
static int serialnr = 0;
public String generateName(Identifier ident, String lastName) {
return ("xxx" + serialnr++);
}
};
} else if (option.equals("none")) {
return new Renamer() {
public String generateName(Identifier ident, String lastName) {
String name = ident.getName();
if (lastName == null)
return name;
int nr = 2;
if (lastName.length() > name.length())
nr = 1 + Integer.parseInt
(lastName.substring(name.length()));
return name + nr;
}
};
}
GlobalOptions.err.println("Incorrect value for --rename option: "
+ option);
usage();
System.exit(0);
return null;
}
public static ClassBundle getClassBundle() { public static ClassBundle getClassBundle() {
return bundle; return bundle;
} }
public static CodeAnalyzer createCodeAnalyzer() {
return new SimpleAnalyzer() /*XXX*/;
}
static CodeTransformer[] codeTransformers = {
new LocalOptimizer(),
new RemovePopAnalyzer()
};
public static Collection getCodeTransformers() {
return Arrays.asList(codeTransformers);
}
public static void main(String[] params) { public static void main(String[] params) {
int i; if (params.length == 0) {
String sourcePath = System.getProperty("java.class.path")
.replace(File.pathSeparatorChar, SearchPath.pathSeparatorChar);
String destPath = ".";
Collection preservedIdents = new HashSet();
ModifierMatcher preserveRule = ModifierMatcher.denyAll;
Renamer renamer = null;
String table = null;
String toTable = null;
for (i=0; i<params.length && params[i].startsWith("-"); i++) {
if (params[i].equals("-v"))
GlobalOptions.verboseLevel++;
else if (params[i].startsWith("--debug")) {
String flags;
if (params[i].startsWith("--debug=")) {
flags = params[i].substring(8);
} else if (params[i].length() != 7) {
usage(); usage();
return; return;
} else {
flags = params[++i];
} }
GlobalOptions.setDebugging(flags); String cp = null, dest = null;
} else if (params[i].equals("--strip"))
setStripOptions(params[++i]);
else if (params[i].startsWith("--strip="))
setStripOptions(params[i].substring(8));
else if (params[i].equals("--sourcepath")
|| params[i].equals("--classpath")
|| params[i].equals("--cp"))
sourcePath = params[++i];
else if (params[i].equals("--dest")
|| params[i].equals("-d"))
destPath = params[++i];
/* Preserve options */ GlobalOptions.err.println(GlobalOptions.copyright);
else if (params[i].equals("--package")) bundle = new ClassBundle();
preserveRule = ModifierMatcher.allowAll.forceAccess boolean errorInParams = false;
(0, true); Getopt g = new Getopt("jode.obfuscator.Main", params, "hVvc:d:D:",
else if (params[i].equals("--protected")) longOptions, true);
preserveRule = ModifierMatcher.allowAll.forceAccess for (int opt = g.getopt(); opt != -1; opt = g.getopt()) {
(Modifier.PROTECTED, true); switch(opt) {
else if (params[i].equals("--public")) case 0:
preserveRule = ModifierMatcher.allowAll.forceAccess break;
(Modifier.PUBLIC, true); case 'h':
else if (params[i].equals("--preserve")) { usage();
String ident = params[++i]; errorInParams = true;
preservedIdents.add(ident); break;
case 'V':
GlobalOptions.err.println(GlobalOptions.version);
break;
case 'c':
cp = g.getOptarg();
break;
case 'd':
dest = g.getOptarg();
break;
case 'v': {
String arg = g.getOptarg();
if (arg == null)
GlobalOptions.verboseLevel++;
else {
try {
GlobalOptions.verboseLevel = Integer.parseInt(arg);
} catch (NumberFormatException ex) {
GlobalOptions.err.println
("jode.Decompiler: Argument `"
+arg+"' to --verbose must be numeric:");
errorInParams = true;
} }
else if (params[i].equals("--breakserial"))
options &= ~OPTION_PRESERVESERIAL;
/* Obfuscate options */
else if (params[i].equals("--rename"))
renamer = getRenamer(params[++i]);
else if (params[i].startsWith("--rename="))
renamer = getRenamer(params[i].substring(9));
else if (params[i].equals("--table")) {
table = params[++i];
} }
else if (params[i].equals("--revtable")) { break;
toTable = params[++i];
} }
else if (params[i].equals("--swaporder")) case 'D': {
swapOrder = true; String arg = g.getOptarg();
else if (params[i].equals("--")) { if (arg == null)
i++; arg = "help";
errorInParams |= !GlobalOptions.setDebugging(arg);
break;
}
default:
errorInParams = true;
break; break;
} else {
if (!params[i].equals("-h") && !params[i].equals("--help"))
GlobalOptions.err.println("Unknown option: "+params[i]);
usage();
return;
} }
} }
if (renamer == null) if (errorInParams)
renamer = getRenamer("none"); return;
if (i == params.length) { if (g.getOptind() != params.length - 1) {
GlobalOptions.err.println("No package or classes specified."); GlobalOptions.err.println("You must specify exactly one script.");
usage();
return; return;
} }
GlobalOptions.err.println("Loading classes");
ClassInfo.setClassPath(sourcePath);
bundle = new ClassBundle();
for (; i< params.length; i++)
bundle.loadClasses(params[i]);
GlobalOptions.err.println("Computing reachable / preserved settings"); try {
bundle.setPreserved(preserveRule, preservedIdents); ScriptParser parser = new ScriptParser
(new FileReader(params[g.getOptind()]));
parser.parseOptions(bundle);
} catch (IOException ex) {
GlobalOptions.err.println
("IOException while reading script file.");
ex.printStackTrace(GlobalOptions.err);
return;
} catch (ParseException ex) {
GlobalOptions.err.println("Syntax error in script file: ");
GlobalOptions.err.println(ex.getMessage());
if (GlobalOptions.verboseLevel > 5)
ex.printStackTrace(GlobalOptions.err);
return;
}
GlobalOptions.err.println("Renaming methods"); // Command Line overwrites script options:
if (table != null) if (cp != null)
bundle.readTable(table); bundle.setOption("classpath", Collections.singleton(cp));
bundle.buildTable(renamer); if (dest != null)
if (toTable != null) bundle.setOption("dest", Collections.singleton(dest));
bundle.writeTable(toTable);
GlobalOptions.err.println("Transforming the classes"); bundle.run();
bundle.doTransformations();
GlobalOptions.err.println("Writing new classes");
bundle.storeClasses(destPath);
} }
} }

@ -23,6 +23,7 @@ MY_JAVA_FILES = \
Main.java \ Main.java \
MethodIdentifier.java \ MethodIdentifier.java \
ModifierMatcher.java \ ModifierMatcher.java \
MultiIdentifierMatcher.java \
NameSwapper.java \ NameSwapper.java \
OptionHandler.java \ OptionHandler.java \
PackageIdentifier.java \ PackageIdentifier.java \

@ -60,9 +60,15 @@ public class MethodIdentifier extends Identifier implements Opcodes {
info.getBytecode().setLocalVariableTable(null); info.getBytecode().setLocalVariableTable(null);
if ((Main.stripping & Main.STRIP_LNT) != 0) if ((Main.stripping & Main.STRIP_LNT) != 0)
info.getBytecode().setLineNumberTable(null); info.getBytecode().setLineNumberTable(null);
codeAnalyzer = Main.createCodeAnalyzer(); codeAnalyzer = Main.getClassBundle().getCodeAnalyzer();
}
CodeTransformer[] trafos
= Main.getClassBundle().getPreTransformers();
for (int i = 0; i < trafos.length; i++) {
trafos[i].transformCode(bytecode);
}
info.setBytecode(bytecode);
}
} }
public Iterator getChilds() { public Iterator getChilds() {
@ -105,8 +111,7 @@ public class MethodIdentifier extends Identifier implements Opcodes {
} }
public void writeTable(Map table) { public void writeTable(Map table) {
table.put(getFullAlias() + "." table.put(getFullAlias(), getName());
+ Main.getClassBundle().getTypeAlias(getType()), getName());
} }
public Identifier getParent() { public Identifier getParent() {
@ -114,11 +119,12 @@ public class MethodIdentifier extends Identifier implements Opcodes {
} }
public String getFullName() { public String getFullName() {
return clazz.getFullName() + "." + getName(); return clazz.getFullName() + "." + getName() + "." + getType();
} }
public String getFullAlias() { public String getFullAlias() {
return clazz.getFullAlias() + "." + getAlias(); return clazz.getFullAlias() + "." + getAlias() + "."
+ Main.getClassBundle().getTypeAlias(getType());
} }
public String getName() { public String getName() {
@ -167,15 +173,15 @@ public class MethodIdentifier extends Identifier implements Opcodes {
("doTransformation called on transformed method"); ("doTransformation called on transformed method");
wasTransformed = true; wasTransformed = true;
info.setName(getAlias()); info.setName(getAlias());
info.setType(Main.getClassBundle().getTypeAlias(type)); ClassBundle bundle = Main.getClassBundle();
info.setType(bundle.getTypeAlias(type));
if (codeAnalyzer != null) { if (codeAnalyzer != null) {
BytecodeInfo bytecode = info.getBytecode(); BytecodeInfo bytecode = info.getBytecode();
try { try {
codeAnalyzer.transformCode(bytecode); codeAnalyzer.transformCode(bytecode);
for (Iterator i = Main.getCodeTransformers().iterator(); CodeTransformer[] trafos = bundle.getPostTransformers();
i.hasNext(); ) { for (int i = 0; i < trafos.length; i++) {
CodeTransformer transformer = (CodeTransformer) i.next(); trafos[i].transformCode(bytecode);
transformer.transformCode(bytecode);
} }
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
ex.printStackTrace(GlobalOptions.err); ex.printStackTrace(GlobalOptions.err);

@ -251,6 +251,14 @@ public class ModifierMatcher implements IdentifierMatcher, Cloneable {
return matches(modifiers); return matches(modifiers);
} }
public final boolean matchesSub(Identifier ident, String name) {
return true;
}
public final String getNextComponent(Identifier ident) {
return null;
}
public Object clone() { public Object clone() {
try { try {
return super.clone(); return super.clone();

@ -0,0 +1,109 @@
/* AndIdentifierMatcher Copyright (C) 1999 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package jode.obfuscator;
import @COLLECTIONS@.Collection;
public class MultiIdentifierMatcher implements IdentifierMatcher, OptionHandler {
/**
* Useful constant for giving to the constructor.
*/
public static boolean OR = true;
/**
* Useful constant for giving to the constructor.
*/
public static boolean AND = false;
IdentifierMatcher[] matchers;
boolean isOr;
/**
* Create an empty MultiIdentifierMatcher.
*/
public MultiIdentifierMatcher() {
this.matchers = new IdentifierMatcher[0];
}
/**
* Create an IdentifierMatcher out of other matchers.
* @param isOr if true, match should return the logical (shortcut)
* or of the underlying matchers, if false it returns the logical and.
* @param matchers the underlying matchers
*/
public MultiIdentifierMatcher(boolean isOr,
IdentifierMatcher[] matchers) {
this.isOr = isOr;
this.matchers = matchers;
}
public void setOption(String option, Collection values) {
if (option.equals("or")) {
isOr = true;
matchers = (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]);
} else if (option.equals("and")) {
isOr = false;
matchers = (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]);
} else
throw new IllegalArgumentException("Invalid option `"+option+"'.");
}
public boolean matches(Identifier ident) {
for (int i=0; i< matchers.length; i++) {
if (matchers[i].matches(ident) == isOr)
return isOr;
}
return !isOr;
}
public boolean matchesSub(Identifier ident, String name) {
for (int i=0; i< matchers.length; i++) {
if (matchers[i].matchesSub(ident, name) == isOr)
return isOr;
}
return !isOr;
}
public String getNextComponent(Identifier ident) {
if (isOr == AND) {
for (int i=0; i< matchers.length; i++) {
String next = matchers[i].getNextComponent(ident);
if (next != null && matchesSub(ident, next))
return next;
}
return null;
}
// OR case
String next = null;
for (int i = 0; i < matchers.length; i++) {
if (!matchesSub(ident, null))
continue;
if (next != null
&& matchers[i].getNextComponent(ident) != next)
return null;
next = matchers[i].getNextComponent(ident);
if (next == null)
return null;
}
return next;
}
}

@ -18,12 +18,13 @@
*/ */
package jode.obfuscator; package jode.obfuscator;
import java.util.Random;
import @COLLECTIONS@.Collection; import @COLLECTIONS@.Collection;
import @COLLECTIONS@.Set; import @COLLECTIONS@.Set;
import @COLLECTIONS@.HashSet; import @COLLECTIONS@.HashSet;
import @COLLECTIONS@.Iterator; import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.Random;
import @COLLECTIONEXTRA@.UnsupportedOperationException;
public class NameSwapper implements Renamer { public class NameSwapper implements Renamer {
@ -46,74 +47,52 @@ public class NameSwapper implements Renamer {
this(swapAll, System.currentTimeMillis()); this(swapAll, System.currentTimeMillis());
} }
private void addName(Collection coll, String name) { private class NameGenerator implements Iterator {
coll.add(name); Collection pool;
}
private String getName(Collection coll) {
///#ifdef JDK12
/// int pos = rand.nextInt(coll.size());
///#else
int pos = rand.nextInt() % coll.size();
///#endif
Iterator i = coll.iterator();
while (pos > 0)
i.next();
return (String) i.next();
}
public final void addPackageName(String name) { NameGenerator(Collection c) {
addName(packs, name); pool = c;
} }
public final void addClassName(String name) { public boolean hasNext() {
addName(clazzes, name); return true;
} }
public final void addMethodName(String name) { public Object next() {
addName(methods, name); int pos = rand.nextInt(pool.size());
Iterator i = pool.iterator();
while (pos > 0)
i.next();
return (String) i.next();
} }
public final void addFieldName(String name) { public void remove() {
addName(fields, name); throw new UnsupportedOperationException();
} }
public final void addLocalName(String name) {
addName(locals, name);
} }
public final String getPackageName() { public final Collection getCollection(Identifier ident) {
return getName(packs); if (ident instanceof PackageIdentifier)
return packs;
else if (ident instanceof ClassIdentifier)
return clazzes;
else if (ident instanceof MethodIdentifier)
return methods;
else if (ident instanceof FieldIdentifier)
return fields;
else if (ident instanceof LocalIdentifier)
return locals;
else
throw new IllegalArgumentException(ident.getClass().getName());
} }
public final String getClassName() { public final void addIdentifierName(Identifier ident) {
return getName(clazzes); getCollection(ident).add(ident.getName());
} }
public final String getMethodName() { public Iterator generateNames(Identifier ident) {
return getName(methods); return new NameGenerator(getCollection(ident));
} }
public final String getFieldName() {
return getName(fields);
} }
public final String getLocalName() {
return getName(locals);
}
public String generateName(Identifier ident, String lastName) {
Collection coll = null;
if (ident instanceof PackageIdentifier)
coll = packs;
else if (ident instanceof ClassIdentifier)
coll = clazzes;
else if (ident instanceof MethodIdentifier)
coll = methods;
else if (ident instanceof FieldIdentifier)
coll = fields;
// else if (ident instanceof LocalIdentifier)
// coll = locals;
return getName(coll);
}
}

@ -0,0 +1,8 @@
package jode.obfuscator;
import @COLLECTIONS@.Collection;
public interface OptionHandler {
public void setOption(String option, Collection values)
throws IllegalArgumentException;
}

@ -43,14 +43,12 @@ public class PackageIdentifier extends Identifier {
public PackageIdentifier(ClassBundle bundle, public PackageIdentifier(ClassBundle bundle,
PackageIdentifier parent, PackageIdentifier parent,
String name, boolean loadOnDemand) { String name) {
super(name); super(name);
this.bundle = bundle; this.bundle = bundle;
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.loadedClasses = new HashMap(); this.loadedClasses = new HashMap();
if (loadOnDemand)
setLoadOnDemand();
} }
/** /**
@ -94,18 +92,21 @@ public class PackageIdentifier extends Identifier {
? fullname + "."+ subclazz ? fullname + "."+ subclazz
: subclazz; : subclazz;
if (ClassInfo.isPackage(fullname)) { if (ClassInfo.isPackage(fullname)) {
Identifier ident = new PackageIdentifier PackageIdentifier ident = new PackageIdentifier
(bundle, this, subclazz, true); (bundle, this, subclazz);
loadedClasses.put(subclazz, ident); loadedClasses.put(subclazz, ident);
ident.setLoadOnDemand();
} else { } else {
Identifier ident = new ClassIdentifier Identifier ident = new ClassIdentifier
(this, subclazz, ClassInfo.forName(fullname)); (this, subclazz, ClassInfo.forName(fullname));
loadedClasses.put(subclazz, ident); loadedClasses.put(subclazz, ident);
if (GlobalOptions.verboseLevel > 0)
GlobalOptions.err.println("preloading "+ident);
((ClassIdentifier) ident).initClass(); ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null)
ident.applyPreserveRule(bundle.preserveRule);
} }
} }
// Everything is loaded, we don't need to load on demand anymore.
loadOnDemand = false;
} }
} }
@ -114,7 +115,6 @@ public class PackageIdentifier extends Identifier {
Identifier ident = loadClass(name); Identifier ident = loadClass(name);
return ident; return ident;
} }
int index = name.indexOf('.'); int index = name.indexOf('.');
if (index == -1) if (index == -1)
return (Identifier) loadedClasses.get(name); return (Identifier) loadedClasses.get(name);
@ -138,8 +138,11 @@ public class PackageIdentifier extends Identifier {
? fullname + "."+ name ? fullname + "."+ name
: name; : name;
if (ClassInfo.isPackage(fullname)) { if (ClassInfo.isPackage(fullname)) {
ident = new PackageIdentifier(bundle, this, name, true); PackageIdentifier pack
loadedClasses.put(name, ident); = new PackageIdentifier(bundle, this, name);
loadedClasses.put(name, pack);
pack.setLoadOnDemand();
ident = pack;
} else if (!ClassInfo.exists(fullname)) { } else if (!ClassInfo.exists(fullname)) {
GlobalOptions.err.println("Warning: Can't find class " GlobalOptions.err.println("Warning: Can't find class "
+ fullname); + fullname);
@ -149,8 +152,6 @@ public class PackageIdentifier extends Identifier {
ClassInfo.forName(fullname)); ClassInfo.forName(fullname));
loadedClasses.put(name, ident); loadedClasses.put(name, ident);
((ClassIdentifier) ident).initClass(); ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null)
ident.applyPreserveRule(bundle.preserveRule);
} }
} }
return ident; return ident;
@ -164,8 +165,10 @@ public class PackageIdentifier extends Identifier {
? fullname + "."+ subpack : subpack; ? fullname + "."+ subpack : subpack;
if (ClassInfo.isPackage(fullname)) { if (ClassInfo.isPackage(fullname)) {
pack = new PackageIdentifier(bundle, this, pack = new PackageIdentifier(bundle, this,
subpack, loadOnDemand); subpack);
loadedClasses.put(subpack, pack); loadedClasses.put(subpack, pack);
if (loadOnDemand)
pack.setLoadOnDemand();
} }
} }
@ -176,8 +179,8 @@ public class PackageIdentifier extends Identifier {
} }
} }
public void loadMatchingClasses(WildCard wildcard) { public void loadMatchingClasses(IdentifierMatcher matcher) {
String component = wildcard.getNextComponent(getFullName()); String component = matcher.getNextComponent(this);
if (component != null) { if (component != null) {
Identifier ident = (Identifier) loadedClasses.get(component); Identifier ident = (Identifier) loadedClasses.get(component);
if (ident == null) { if (ident == null) {
@ -187,33 +190,34 @@ public class PackageIdentifier extends Identifier {
: component; : component;
if (ClassInfo.isPackage(fullname)) { if (ClassInfo.isPackage(fullname)) {
ident = new PackageIdentifier(bundle, this, ident = new PackageIdentifier(bundle, this,
component, loadOnDemand); component);
loadedClasses.put(component, ident); loadedClasses.put(component, ident);
} else if (ClassInfo.exists(fullname) if (loadOnDemand)
&& wildcard.matches(fullname)) { ((PackageIdentifier) ident).setLoadOnDemand();
} else if (ClassInfo.exists(fullname)) {
if (GlobalOptions.verboseLevel > 1) if (GlobalOptions.verboseLevel > 1)
GlobalOptions.err.println("loading Class " +fullname); GlobalOptions.err.println("loading Class " +fullname);
ident = new ClassIdentifier(this, component, ident = new ClassIdentifier(this, component,
ClassInfo.forName(fullname)); ClassInfo.forName(fullname));
if (loadOnDemand || matcher.matches(ident)) {
loadedClasses.put(component, ident); loadedClasses.put(component, ident);
((ClassIdentifier) ident).initClass(); ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null) }
ident.applyPreserveRule(bundle.preserveRule);
} else { } else {
GlobalOptions.err.println GlobalOptions.err.println
("Warning: Can't find class/package " + fullname); ("Warning: Can't find class/package " + fullname);
} }
} }
if (ident instanceof PackageIdentifier) { if (ident instanceof PackageIdentifier) {
if (wildcard.matches(ident.getFullName())) { if (matcher.matches(ident)) {
if (GlobalOptions.verboseLevel > 0) if (GlobalOptions.verboseLevel > 0)
GlobalOptions.err.println("loading Package " GlobalOptions.err.println("loading Package "
+ident.getFullName()); +ident.getFullName());
((PackageIdentifier) ident).setLoadOnDemand(); ((PackageIdentifier) ident).setLoadOnDemand();
} }
if (wildcard.startsWith(ident.getFullName()+".")) if (matcher.matchesSub(ident, null))
((PackageIdentifier) ident).loadMatchingClasses(wildcard); ((PackageIdentifier) ident).loadMatchingClasses(matcher);
} }
} else { } else {
String fullname = getFullName(); String fullname = getFullName();
@ -228,47 +232,49 @@ public class PackageIdentifier extends Identifier {
continue; continue;
String subFull = fullname + subclazz; String subFull = fullname + subclazz;
if (wildcard.matches(subFull)) { if (matcher.matchesSub(this, subclazz)) {
if (ClassInfo.isPackage(subFull)) { if (ClassInfo.isPackage(subFull)) {
if (GlobalOptions.verboseLevel > 0) if (GlobalOptions.verboseLevel > 0)
GlobalOptions.err.println("loading Package " +subFull); GlobalOptions.err.println("loading Package "
Identifier ident = new PackageIdentifier + subFull);
(bundle, this, subclazz, true); PackageIdentifier ident = new PackageIdentifier
(bundle, this, subclazz);
loadedClasses.put(subclazz, ident); loadedClasses.put(subclazz, ident);
if (loadOnDemand || matcher.matches(ident))
ident.setLoadOnDemand();
} else { } else {
if (GlobalOptions.verboseLevel > 1)
GlobalOptions.err.println("loading Class " +subFull);
ClassIdentifier ident = new ClassIdentifier ClassIdentifier ident = new ClassIdentifier
(this, subclazz, ClassInfo.forName(subFull)); (this, subclazz, ClassInfo.forName(subFull));
if (loadOnDemand || matcher.matches(ident)) {
if (GlobalOptions.verboseLevel > 1)
GlobalOptions.err.println("loading Class "
+ subFull);
loadedClasses.put(subclazz, ident); loadedClasses.put(subclazz, ident);
((ClassIdentifier) ident).initClass(); ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null)
ident.applyPreserveRule(bundle.preserveRule);
} }
} else if (ClassInfo.isPackage(subFull) }
&& wildcard.startsWith(subFull + ".")) {
Identifier ident = new PackageIdentifier
(bundle, this, subclazz, loadOnDemand);
loadedClasses.put(subclazz, ident);
} }
} }
for (Iterator i = loadedClasses.values().iterator(); for (Iterator i = loadedClasses.values().iterator();
i.hasNext(); ) { i.hasNext(); ) {
Identifier ident = (Identifier) i.next(); Identifier ident = (Identifier) i.next();
if (ident instanceof PackageIdentifier) { if (ident instanceof PackageIdentifier) {
if (wildcard.matches(ident.getFullName())) if (matcher.matches(ident))
((PackageIdentifier) ident).setLoadOnDemand(); ((PackageIdentifier) ident).setLoadOnDemand();
if (wildcard.startsWith(ident.getFullName()+".")) if (matcher.matchesSub(ident, null))
((PackageIdentifier) ident).loadMatchingClasses(wildcard); ((PackageIdentifier) ident)
.loadMatchingClasses(matcher);
} }
} }
} }
} }
public void applyPreserveRule(ModifierMatcher preserveRule) { public void applyPreserveRule(IdentifierMatcher preserveRule) {
for (Iterator i = loadedClasses.values().iterator(); i.hasNext(); ) if (loadOnDemand)
((Identifier) i.next()).applyPreserveRule(preserveRule); loadMatchingClasses(preserveRule);
super.applyPreserveRule(preserveRule);
} }
public void reachableIdentifier(String fqn, boolean isVirtual) { public void reachableIdentifier(String fqn, boolean isVirtual) {
@ -300,99 +306,99 @@ public class PackageIdentifier extends Identifier {
} }
} }
public void preserveMatchingIdentifier(WildCard wildcard) { // public void preserveMatchingIdentifier(IdentifierMatcher matcher) {
String component = wildcard.getNextComponent(getFullName()); // String component = matcher.getNextComponent(getFullName());
if (component != null) { // if (component != null) {
String fullname = getFullName(); // String fullname = getFullName();
fullname = (fullname.length() > 0) // fullname = (fullname.length() > 0)
? fullname + "."+ component : component; // ? fullname + "."+ component : component;
Identifier ident = (Identifier) loadedClasses.get(component); // Identifier ident = (Identifier) loadedClasses.get(component);
if (ident == null) { // if (ident == null) {
if (!loadOnDemand) { // if (!loadOnDemand) {
GlobalOptions.err.println // GlobalOptions.err.println
("Warning: Didn't load package/class "+ fullname); // ("Warning: Didn't load package/class "+ fullname);
return; // return;
} // }
if (ClassInfo.isPackage(fullname)) { // if (ClassInfo.isPackage(fullname)) {
ident = new PackageIdentifier(bundle, this, // ident = new PackageIdentifier(bundle, this,
component, loadOnDemand); // component, loadOnDemand);
loadedClasses.put(component, ident); // loadedClasses.put(component, ident);
} else if (ClassInfo.exists(fullname)) { // } else if (ClassInfo.exists(fullname)) {
ident = new ClassIdentifier(this, component, // ident = new ClassIdentifier(this, component,
ClassInfo.forName(fullname)); // ClassInfo.forName(fullname));
loadedClasses.put(component, ident); // loadedClasses.put(component, ident);
((ClassIdentifier) ident).initClass(); // ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null) // if (bundle.preserveRule != null)
ident.applyPreserveRule(bundle.preserveRule); // ident.applyPreserveRule(bundle.preserveRule);
} else { // } else {
GlobalOptions.err.println("Warning: Can't find class " // GlobalOptions.err.println("Warning: Can't find class "
+ fullname); // + fullname);
return; // return;
} // }
} // }
if (wildcard.matches(fullname)) { // if (matcher.matches(fullname)) {
if (GlobalOptions.verboseLevel > 1) // if (GlobalOptions.verboseLevel > 1)
GlobalOptions.err.println("preserving "+ident); // GlobalOptions.err.println("preserving "+ident);
ident.setPreserved(); // ident.setPreserved();
} // }
if (wildcard.startsWith(fullname+".")) { // if (matcher.startsWith(fullname+".")) {
if (ident instanceof PackageIdentifier) // if (ident instanceof PackageIdentifier)
((PackageIdentifier) ident) // ((PackageIdentifier) ident)
.preserveMatchingIdentifier(wildcard); // .preserveMatchingIdentifier(matcher);
else // else
((ClassIdentifier) ident) // ((ClassIdentifier) ident)
.preserveMatchingIdentifier(wildcard); // .preserveMatchingIdentifier(matcher);
} // }
} else { // } else {
String fullname = getFullName(); // String fullname = getFullName();
if (fullname.length() > 0) // if (fullname.length() > 0)
fullname += "."; // fullname += ".";
if (loadOnDemand) { // if (loadOnDemand) {
/* Load all matching classes and packages */ // /* Load all matching classes and packages */
Enumeration enum = // Enumeration enum =
ClassInfo.getClassesAndPackages(getFullName()); // ClassInfo.getClassesAndPackages(getFullName());
while (enum.hasMoreElements()) { // while (enum.hasMoreElements()) {
String subclazz = (String)enum.nextElement(); // String subclazz = (String)enum.nextElement();
if (loadedClasses.containsKey(subclazz)) // if (loadedClasses.containsKey(subclazz))
continue; // continue;
String subFull = fullname + subclazz; // String subFull = fullname + subclazz;
if (wildcard.startsWith(subFull)) { // if (matcher.startsWith(subFull)) {
if (ClassInfo.isPackage(subFull)) { // if (ClassInfo.isPackage(subFull)) {
System.err.println("is package: "+subFull); // System.err.println("is package: "+subFull);
Identifier ident = new PackageIdentifier // Identifier ident = new PackageIdentifier
(bundle, this, subclazz, true); // (bundle, this, subclazz, true);
loadedClasses.put(subclazz, ident); // loadedClasses.put(subclazz, ident);
} else { // } else {
ClassIdentifier ident = new ClassIdentifier // ClassIdentifier ident = new ClassIdentifier
(this, subclazz, ClassInfo.forName(subFull)); // (this, subclazz, ClassInfo.forName(subFull));
loadedClasses.put(subclazz, ident); // loadedClasses.put(subclazz, ident);
((ClassIdentifier) ident).initClass(); // ((ClassIdentifier) ident).initClass();
if (bundle.preserveRule != null) // if (bundle.preserveRule != null)
ident.applyPreserveRule(bundle.preserveRule); // ident.applyPreserveRule(bundle.preserveRule);
} // }
} // }
} // }
} // }
for (Iterator i = loadedClasses.values().iterator(); // for (Iterator i = loadedClasses.values().iterator();
i.hasNext(); ) { // i.hasNext(); ) {
Identifier ident = (Identifier) i.next(); // Identifier ident = (Identifier) i.next();
if (wildcard.matches(ident.getFullName())) { // if (matcher.matches(ident.getFullName())) {
if (GlobalOptions.verboseLevel > 1) // if (GlobalOptions.verboseLevel > 1)
GlobalOptions.err.println("Preserving "+ident); // GlobalOptions.err.println("Preserving "+ident);
ident.setPreserved(); // ident.setPreserved();
} // }
if (wildcard.startsWith(ident.getFullName()+".")) { // if (matcher.startsWith(ident.getFullName()+".")) {
if (ident instanceof PackageIdentifier) // if (ident instanceof PackageIdentifier)
((PackageIdentifier) ident) // ((PackageIdentifier) ident)
.preserveMatchingIdentifier(wildcard); // .preserveMatchingIdentifier(matcher);
else // else
((ClassIdentifier) ident) // ((ClassIdentifier) ident)
.preserveMatchingIdentifier(wildcard); // .preserveMatchingIdentifier(matcher);
} // }
} // }
} // }
} // }
/** /**
* @return the full qualified name. * @return the full qualified name.

@ -0,0 +1,7 @@
package jode.obfuscator;
public class ParseException extends Exception {
public ParseException(int linenr, String message) {
super ("line "+linenr+": "+message);
}
}

@ -18,6 +18,7 @@
*/ */
package jode.obfuscator; package jode.obfuscator;
import @COLLECTIONS@.Iterator;
public interface Renamer { public interface Renamer {
@ -27,5 +28,5 @@ public interface Renamer {
* @param lastName the last name generated for this identifier, or * @param lastName the last name generated for this identifier, or
* null, if no name was generated yet. * null, if no name was generated yet.
*/ */
public String generateName(Identifier ident, String lastName); public Iterator generateNames(Identifier ident);
} }

@ -0,0 +1,258 @@
package jode.obfuscator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.LinkedList;
public class ScriptParser {
static int NO_TOKEN = -2;
static int EOF_TOKEN = -1;
static int STRING_TOKEN = 0;
static int NEW_TOKEN = 1;
static int EQUALS_TOKEN = 2;
static int COMMA_TOKEN = 3;
static int OPENBRACE_TOKEN = 4;
static int CLOSEBRACE_TOKEN = 5;
static int IDENTIFIER_TOKEN = 6;
static int NUMBER_TOKEN = 7;
Scanner scanner;
class Scanner {
BufferedReader input;
String value;
String line;
int column;
int linenr;
int pushback = NO_TOKEN;
public Scanner(Reader i) {
input = new BufferedReader(i);
}
public void readString() throws ParseException {
StringBuffer val = new StringBuffer();
while (column < line.length()) {
char c = line.charAt(column++);
if (c == '"') {
value = val.toString();
return;
}
if (c == '\\') {
c = line.charAt(column++);
switch (c) {
case 'n':
val.append('\n');
break;
case 't':
val.append('\t');
break;
case 'r':
val.append('\r');
break;
case 'u':
if (column+4 <= line.length()) {
try {
char uni = (char) Integer.parseInt
(line.substring(column, column+4), 16);
column += 4;
val.append(uni);
} catch (NumberFormatException ex) {
throw new ParseException
(linenr,
"Invalid unicode escape character");
}
} else
throw new ParseException
(linenr,
"Invalid unicode escape character");
break;
default:
val.append(c);
}
} else
val.append(c);
}
throw new ParseException(linenr,
"String spans over multiple lines");
}
public void readIdentifier() {
int start = column-1;
while (column < line.length()
&& Character.isUnicodeIdentifierPart(line.charAt(column)))
column++;
value = line.substring(start, column);
}
public void readNumber() {
boolean hex = false;
int start = column-1;
/* special case for hex numbers */
if (line.charAt(start) == '0' && line.charAt(column) == 'x') {
column++;
hex = true;
}
while (column < line.length()) {
char c = line.charAt(column);
if (!Character.isDigit(c)) {
if (!hex)
break;
if ((c < 'A' || c > 'F') && (c < 'a' || c > 'f'))
break;
}
column++;
}
value = line.substring(start, column);
}
public void pushbackToken(int token) {
if (pushback != NO_TOKEN)
throw new IllegalStateException
("Can only handle one pushback");
pushback = token;
}
public int getToken() throws ParseException, IOException {
if (pushback != NO_TOKEN) {
int result = pushback;
pushback = NO_TOKEN;
return result;
}
value = null;
while (true) {
if (line == null) {
line = input.readLine();
if (line == null)
return EOF_TOKEN;
linenr++;
column = 0;
}
while (column < line.length()) {
char c = line.charAt(column++);
if (Character.isWhitespace(c))
continue;
if (c == '#')
// this is a comment, skip this line
break;
if (c == '=')
return EQUALS_TOKEN;
if (c == ',')
return COMMA_TOKEN;
if (c == '{')
return OPENBRACE_TOKEN;
if (c == '}')
return CLOSEBRACE_TOKEN;
if (c == '"') {
readString();
return STRING_TOKEN;
}
if (Character.isDigit(c) || c == '+' || c == '-') {
readNumber();
return NUMBER_TOKEN;
}
if (Character.isUnicodeIdentifierStart(c)) {
readIdentifier();
if (value.equals("new"))
return NEW_TOKEN;
return IDENTIFIER_TOKEN;
}
throw new ParseException
(linenr, "Illegal character `"+c+"'");
}
line = null;
}
}
public String getValue() {
return value;
}
public int getLineNr() {
return linenr;
}
}
public ScriptParser(Reader reader) {
this.scanner = new Scanner(reader);
}
public Object parseClass() throws ParseException, IOException {
int linenr = scanner.getLineNr();
int token = scanner.getToken();
if (token != IDENTIFIER_TOKEN)
throw new ParseException(linenr, "Class name expected");
Object instance;
try {
Class clazz = Class.forName("jode.obfuscator."+scanner.getValue());
instance = clazz.newInstance();
} catch (ClassNotFoundException ex) {
throw new ParseException(scanner.getLineNr(),
"Class `"+scanner.getValue()
+"' not found");
} catch (Exception ex) {
throw new ParseException(scanner.getLineNr(),
"Class `"+scanner.getValue()
+"' not valid: "+ex.getMessage());
}
token = scanner.getToken();
if (token == OPENBRACE_TOKEN) {
if (!(instance instanceof OptionHandler))
throw new ParseException
(scanner.getLineNr(),
"Class `"+instance.getClass().getName()
+"' doesn't handle options.");
parseOptions((OptionHandler) instance);
if (scanner.getToken() != CLOSEBRACE_TOKEN)
throw new ParseException(scanner.getLineNr(), "`}' expected");
} else
scanner.pushbackToken(token);
return instance;
}
public void parseOptions(OptionHandler optionHandler)
throws ParseException, IOException
{
int token = scanner.getToken();
while (true) {
if (token == EOF_TOKEN || token == CLOSEBRACE_TOKEN) {
scanner.pushbackToken(token);
return;
}
if (token != IDENTIFIER_TOKEN)
throw new ParseException(scanner.getLineNr(),
"identifier expected");
String ident = scanner.getValue();
if (scanner.getToken() != EQUALS_TOKEN)
throw new ParseException(scanner.getLineNr(),
"equal sign expected");
int linenr = scanner.getLineNr();
Collection values = new LinkedList();
do {
token = scanner.getToken();
if (token == NEW_TOKEN) {
values.add(parseClass());
} else if (token == STRING_TOKEN) {
values.add(scanner.getValue());
} else if (token == NUMBER_TOKEN) {
values.add(new Integer(scanner.getValue()));
}
token = scanner.getToken();
} while (token == COMMA_TOKEN);
try {
optionHandler.setOption(ident, values);
} catch (IllegalArgumentException ex) {
throw new ParseException(linenr,
optionHandler.getClass().getName()
+": "+ex.getMessage());
} catch (RuntimeException ex) {
throw new ParseException(linenr,
optionHandler.getClass().getName()
+": Illegal value: "+ex.getMessage());
}
}
}
}

@ -1,63 +0,0 @@
/* StrongRenamer Copyright (C) 1999 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package jode.obfuscator;
public class StrongRenamer implements Renamer {
String charSetStart;
String charSetCont;
public StrongRenamer(String charSetStart, String charSetCont) {
this.charSetStart = charSetStart;
this.charSetCont = charSetCont;
}
public String generateName(Identifier ident, String lastName) {
if (lastName == null)
return charSetStart.substring(0,1);
char firstCont = charSetCont.charAt(0);
int pos = lastName.length() - 1;
StringBuffer sb = new StringBuffer(lastName.length() + 1);
while (pos > 0) {
int index = charSetCont.indexOf(lastName.charAt(pos)) + 1;
if (index < charSetCont.length()) {
sb.append(lastName.substring(0, pos));
sb.append(charSetCont.charAt(index));
for (int i = lastName.length() - pos - 1; i-- > 0; )
sb.append(firstCont);
return sb.toString();
}
pos --;
}
int index = charSetStart.indexOf(lastName.charAt(pos)) + 1;
if (index < charSetStart.length()) {
sb.append(charSetStart.charAt(index));
for (int i = lastName.length() - 1; i-- > 0; )
sb.append(firstCont);
return sb.toString();
} else {
sb.append(charSetStart.charAt(0));
for (int i = lastName.length(); i-- > 0; )
sb.append(firstCont);
}
return sb.toString();
}
}

@ -0,0 +1,141 @@
/* StrongRenamer Copyright (C) 1999 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package jode.obfuscator;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.Iterator;
import @COLLECTIONEXTRA@.UnsupportedOperationException;
public class StrongRenamer implements Renamer, OptionHandler {
static String[] idents = {
"Package", "Class", "Field", "Method", "Local"
};
static String[] parts = {
"Start", "Part"
};
String charsets[][];
public StrongRenamer() {
charsets = new String[idents.length][parts.length];
for (int i=0; i< idents.length; i++)
for (int j=0; j< parts.length; j++)
charsets[i][j] = "abcdefghijklmnopqrstuvwxyz";
}
public void setOption(String option, Collection values) {
if (option.startsWith("charset")) {
Object value = values.iterator().next();
if (values.size() != 1 || !(value instanceof String))
throw new IllegalArgumentException
("Only string parameter are supported.");
String set = (String) value;
String remOpt = option.substring("charset".length());
int part = -1, ident = -1;
if (remOpt.length() > 0) {
for (int i=0; i < idents.length; i++) {
if (remOpt.startsWith(idents[i])) {
remOpt = option.substring(idents[i].length());
ident = i;
break;
}
}
}
if (remOpt.length() > 0) {
for (int j=0; j < parts.length; j++) {
if (remOpt.startsWith(parts[j])) {
remOpt = option.substring(parts[j].length());
part = j;
break;
}
}
}
if (remOpt.length() > 0)
throw new IllegalArgumentException("Invalid charset `"
+option+"'");
for (int i = 0; i < idents.length; i++) {
if (ident >= 0 && ident != i)
continue;
for (int j = 0; j < parts.length; j++) {
if (part >= 0 && part != j)
continue;
charsets[i][j] = set;
}
}
} else
throw new IllegalArgumentException("Invalid option `"
+option+"'");
}
public Iterator generateNames(Identifier ident) {
final String[] currCharset;
if (ident instanceof PackageIdentifier)
currCharset = charsets[0];
else if (ident instanceof PackageIdentifier)
currCharset = charsets[1];
else if (ident instanceof ClassIdentifier)
currCharset = charsets[2];
else if (ident instanceof FieldIdentifier)
currCharset = charsets[3];
else if (ident instanceof MethodIdentifier)
currCharset = charsets[4];
else if (ident instanceof LocalIdentifier)
currCharset = charsets[5];
else
throw new IllegalArgumentException(ident.getClass().getName());
return new Iterator() {
char[] name = null;
public boolean hasNext() {
return true;
}
public Object next() {
if (name == null) {
name = new char[] { currCharset[0].charAt(0) };
return new String(name);
}
int pos = name.length - 1;
String charset = currCharset[1];
while (pos >= 0) {
if (pos == 0)
charset = currCharset[0];
int index = charset.indexOf(name[pos]) + 1;
if (index < charset.length()) {
name[pos] = charset.charAt(index);
return new String(name);
}
name[pos--] = charset.charAt(0);
}
name = new char[name.length+1];
name[0] = currCharset[0].charAt(0);
char firstCont = currCharset[1].charAt(0);
for (int i=1; i <name.length; i++)
name[i] = firstCont;
return new String(name);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

@ -0,0 +1,21 @@
package jode.obfuscator;
import @COLLECTIONS@.Iterator;
public class UniqueRenamer implements Renamer {
static int serialnr = 0;
public Iterator generateNames(Identifier ident) {
return new Iterator() {
public boolean hasNext() {
return true;
}
public Object next() {
return "xxx" + serialnr++;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

@ -18,25 +18,40 @@
*/ */
package jode.obfuscator; package jode.obfuscator;
import @COLLECTIONS@.Collection;
public class WildCard { public class WildCard implements IdentifierMatcher, OptionHandler {
String wildcard; String wildcard;
int firstStar; int firstStar;
public WildCard() {
}
public WildCard(String wild) { public WildCard(String wild) {
wildcard = wild; wildcard = wild;
firstStar = wildcard.indexOf('*'); firstStar = wildcard.indexOf('*');
} }
public String getNextComponent(String prefix) { public void setOption(String option, Collection values) {
if (option.equals("value")) {
if (values.size() != 1)
throw new IllegalArgumentException
("Wildcard supports only one value.");
wildcard = (String) values.iterator().next();
firstStar = wildcard.indexOf('*');
} else
throw new IllegalArgumentException("Invalid option `"+option+"'.");
}
public String getNextComponent(Identifier ident) {
String prefix = ident.getFullName();
if (prefix.length() > 0)
prefix += ".";
int lastDot = prefix.length(); int lastDot = prefix.length();
if (!wildcard.startsWith(prefix)) if (!wildcard.startsWith(prefix))
return null; return null;
if (lastDot > 0) {
if (wildcard.charAt(lastDot++) != '.')
return null;
}
int nextDot = wildcard.indexOf('.', lastDot); int nextDot = wildcard.indexOf('.', lastDot);
if (nextDot > 0 if (nextDot > 0
@ -48,15 +63,25 @@ public class WildCard {
return null; return null;
} }
public boolean startsWith(String test) { public boolean matchesSub(Identifier ident, String subident) {
if (firstStar == -1 || firstStar >= test.length()) String prefix = ident.getFullName();
return wildcard.startsWith(test); if (prefix.length() > 0)
return test.startsWith(wildcard.substring(0, firstStar)); prefix += ".";
if (subident != null)
prefix += subident;
if (firstStar == -1 || firstStar >= prefix.length())
return wildcard.startsWith(prefix);
return prefix.startsWith(wildcard.substring(0, firstStar));
} }
public boolean matches(String test) { public boolean matches(Identifier ident) {
if (firstStar == -1) String test = ident.getFullName();
return wildcard.equals(test); if (firstStar == -1) {
if (wildcard.equals(test)) {
return true;
}
return false;
}
if (!test.startsWith(wildcard.substring(0, firstStar))) if (!test.startsWith(wildcard.substring(0, firstStar)))
return false; return false;
Loading…
Cancel
Save