/* PackageIdentifier Copyright (C) 1999-2002 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 jode.GlobalOptions; import jode.bytecode.ClassInfo; import jode.bytecode.FieldInfo; import jode.bytecode.MethodInfo; import java.lang.reflect.Modifier; import java.io.*; import java.util.Vector; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import @COLLECTIONS@.Map; import @COLLECTIONS@.HashMap; import @COLLECTIONS@.Iterator; import @COLLECTIONS@.List; import @COLLECTIONS@.ArrayList; import @COLLECTIONS@.Arrays; import @COLLECTIONS@.Collections; public class PackageIdentifier extends Identifier { ClassBundle bundle; PackageIdentifier parent; String name; String fullName; boolean loadOnDemand; Map loadedClasses; Map loadedPackages; List swappedClasses; public PackageIdentifier(ClassBundle bundle, PackageIdentifier parent, String fullName, String name) { super(name); this.bundle = bundle; this.parent = parent; this.fullName = fullName; this.name = name; this.loadedClasses = new HashMap(); this.loadedPackages = new HashMap(); } /** * Marks the parent package as preserved, too. */ protected void setSinglePreserved() { if (parent != null) parent.setPreserved(); } public void setLoadOnDemand() { if (loadOnDemand) return; loadOnDemand = true; if ((Main.stripping & Main.STRIP_UNREACH) == 0) { String fullNamePrefix = (fullName.length() > 0) ? fullName + "." : ""; // Load all classes and packages now, so they don't get stripped Enumeration enumeration = ClassInfo.getClassesAndPackages(getFullName()); while (enumeration.hasMoreElements()) { String subclazz = ((String)enumeration.nextElement()).intern(); String subFull = (fullNamePrefix + subclazz).intern(); if (ClassInfo.isPackage(subFull) && !loadedPackages.containsKey(subclazz)) { PackageIdentifier ident = new PackageIdentifier (bundle, this, subFull, subclazz); loadedPackages.put(subclazz, ident); swappedClasses = null; ident.setLoadOnDemand(); } if (ClassInfo.exists(subFull) && !loadedClasses.containsKey(subclazz)) { ClassIdentifier ident = new ClassIdentifier (this, subFull, subclazz, ClassInfo.forName(subFull)); if (GlobalOptions.verboseLevel > 1) GlobalOptions.err.println("preloading Class " + subFull); loadedClasses.put(subclazz, ident); swappedClasses = null; bundle.addClassIdentifier(ident); ((ClassIdentifier) ident).initClass(); } } // Everything is loaded, we don't need to load on demand anymore. loadOnDemand = false; } } public ClassIdentifier getClassIdentifier(String name) { if (loadOnDemand) { ClassIdentifier ident = loadClass(name); return ident; } int index = name.indexOf('.'); if (index == -1) return (ClassIdentifier) loadedClasses.get(name); else { PackageIdentifier pack = (PackageIdentifier) loadedPackages.get(name.substring(0, index)); if (pack != null) return pack.getClassIdentifier(name.substring(index+1)); else return null; } } public ClassIdentifier loadClass(String name) { int index = name.indexOf('.'); if (index == -1) { ClassIdentifier ident = (Identifier) loadedClasses.get(name); if (ident == null) { String subFull = (fullName.length() > 0) ? fullName + "."+ name : name; subFull = subFull.intern(); /* may be a package to load with the same name as class */ if (ClassInfo.isPackage(subFull) && !loadedPackages.containsKey(name)) { PackageIdentifier pack = new PackageIdentifier(bundle, this, subFull, name); loadedPackages.put(name, pack); swappedClasses = null; pack.setLoadOnDemand(); } if (!ClassInfo.exists(subFull)) { GlobalOptions.err.println("Warning: Can't find class " + subFull); Thread.dumpStack(); } else { ident = new ClassIdentifier(this, subFull, name, ClassInfo.forName(subFull)); loadedClasses.put(name, ident); swappedClasses = null; bundle.addClassIdentifier(ident); ((ClassIdentifier) ident).initClass(); } } return ident; } else { String subpack = name.substring(0, index); PackageIdentifier pack = (PackageIdentifier) loadedPackages.get(subpack); if (pack == null) { String subFull = (fullName.length() > 0) ? fullName + "."+ subpack : subpack; subFull = subFull.intern(); if (ClassInfo.isPackage(subFull)) { pack = new PackageIdentifier(bundle, this, subFull, subpack); loadedPackages.put(subpack, pack); swappedClasses = null; if (loadOnDemand) pack.setLoadOnDemand(); } } if (pack != null) return pack.loadClass(name.substring(index+1)); else return null; } } public void loadMatchingClasses(IdentifierMatcher matcher) { String component = matcher.getNextComponent(this); if (component != null) { component = component.intern(); String subFull = (fullName.length() > 0) ? fullName + "."+ component : component; subFull = subFull.intern(); if (loadedClasses.get(component) == null && ClassInfo.exists(subFull)) { if (GlobalOptions.verboseLevel > 1) GlobalOptions.err.println("loading Class " +subFull); ClassIdentifier ident = new ClassIdentifier(this, subFull, component, ClassInfo.forName(subFull)); if (loadOnDemand || matcher.matches(ident)) { loadedClasses.put(component, ident); swappedClasses = null; bundle.addClassIdentifier(ident); ident.initClass(); } } /* can be a package AND a class with the same name */ PackageIdentifier pkgIdent = (PackageIdentifier) loadedPackages.get(component); if (pkgIdent == null && ClassInfo.isPackage(subFull)) { pkgIdent = new PackageIdentifier(bundle, this, subFull, component); loadedPackages.put(component, pkgIdent); swappedClasses = null; if (loadOnDemand) pkgIdent.setLoadOnDemand(); } if (pkgIdent != null) { if (matcher.matches(pkgIdent)) { if (GlobalOptions.verboseLevel > 0) GlobalOptions.err.println("loading Package " +pkgIdent.getFullName()); pkgIdent.setLoadOnDemand(); } if (matcher.matchesSub(pkgIdent, null)) pkgIdent.loadMatchingClasses(matcher); } } else { String fullNamePrefix = (fullName.length() > 0) ? fullName + "." : ""; /* Load all matching classes and packages */ Enumeration enumeration = ClassInfo.getClassesAndPackages(getFullName()); while (enumeration.hasMoreElements()) { String subclazz = ((String)enumeration.nextElement()).intern(); String subFull = (fullNamePrefix + subclazz).intern(); if (matcher.matchesSub(this, subclazz)) { if (ClassInfo.isPackage(subFull) && !loadedPackages.containsKey(subclazz)) { if (GlobalOptions.verboseLevel > 0) GlobalOptions.err.println("loading Package " + subFull); PackageIdentifier ident = new PackageIdentifier (bundle, this, subFull, subclazz); loadedPackages.put(subclazz, ident); swappedClasses = null; if (loadOnDemand || matcher.matches(ident)) ident.setLoadOnDemand(); } if (ClassInfo.exists(subFull) && !loadedClasses.containsKey(subclazz)) { ClassIdentifier ident = new ClassIdentifier (this, subFull, subclazz, ClassInfo.forName(subFull)); if (loadOnDemand || matcher.matches(ident)) { if (GlobalOptions.verboseLevel > 1) GlobalOptions.err.println("loading Class " + subFull); loadedClasses.put(subclazz, ident); swappedClasses = null; bundle.addClassIdentifier(ident); ((ClassIdentifier) ident).initClass(); } } } } List list = new ArrayList(); list.addAll(loadedPackages.values()); for (Iterator i = list.iterator(); i.hasNext(); ) { PackageIdentifier ident = (PackageIdentifier) i.next(); if (matcher.matches(ident)) ((PackageIdentifier) ident).setLoadOnDemand(); if (matcher.matchesSub(ident, null)) ((PackageIdentifier) ident) .loadMatchingClasses(matcher); } } } public void applyPreserveRule(IdentifierMatcher preserveRule) { if (loadOnDemand) loadMatchingClasses(preserveRule); super.applyPreserveRule(preserveRule); } /** * @return the full qualified name. */ public String getFullName() { return fullName; } /** * @return the full qualified alias. */ public String getFullAlias() { if (parent != null) { String parentAlias = parent.getFullAlias(); String alias = getAlias(); if (alias.length() == 0) return parentAlias; else if (parentAlias.length() == 0) return alias; else return parentAlias + "." + alias; } return ""; } public void buildTable(Renamer renameRule) { loadOnDemand = false; super.buildTable(renameRule); } public void doTransformations() { for (Iterator i = getChilds(); i.hasNext(); ) { Identifier ident = (Identifier) i.next(); if (ident instanceof ClassIdentifier) { ((ClassIdentifier) ident).doTransformations(); } else ((PackageIdentifier) ident).doTransformations(); } } public void readTable(Map table) { if (parent != null) setAlias((String) table.get(getFullName())); for (Iterator i = loadedClasses.values().iterator(); i.hasNext(); ) { Identifier ident = (Identifier) i.next(); if ((Main.stripping & Main.STRIP_UNREACH) == 0 || ident.isReachable()) ident.readTable(table); } for (Iterator i = loadedPackages.values().iterator(); i.hasNext(); ) { Identifier ident = (Identifier) i.next(); if ((Main.stripping & Main.STRIP_UNREACH) == 0 || ident.isReachable()) ident.readTable(table); } } public Identifier getParent() { return parent; } public String getName() { return name; } public String getType() { return "package"; } public Iterator getChilds() { /* Since loadedClasses is somewhat sorted by the hashcode * of the _original_ names, swap it here to prevent to guess * even parts of the names. */ if (swappedClasses == null) { swappedClasses = new ArrayList(loadedClasses.values()); swappedClasses.addAll(loadedPackages.values()); Collections.shuffle(swappedClasses, Main.rand); } return swappedClasses.iterator(); } public void storeClasses(ZipOutputStream zip) { for (Iterator i = getChilds(); i.hasNext(); ) { Identifier ident = (Identifier) i.next(); if ((Main.stripping & Main.STRIP_UNREACH) != 0 && !ident.isReachable()) { if (GlobalOptions.verboseLevel > 4) GlobalOptions.err.println("Class/Package " + ident.getFullName() + " is not reachable"); continue; } if (ident instanceof PackageIdentifier) ((PackageIdentifier) ident).storeClasses(zip); else { try { String filename = ident.getFullAlias().replace('.','/') + ".class"; zip.putNextEntry(new ZipEntry(filename)); DataOutputStream out = new DataOutputStream (new BufferedOutputStream(zip)); ((ClassIdentifier) ident).storeClass(out); out.flush(); zip.closeEntry(); } catch (java.io.IOException ex) { GlobalOptions.err.println("Can't write Class " + ident.getName()); ex.printStackTrace(GlobalOptions.err); } } } } public void storeClasses(File destination) { File newDest = (parent == null) ? destination : new File(destination, getAlias()); if (!newDest.exists() && !newDest.mkdir()) { GlobalOptions.err.println("Could not create directory " +newDest.getPath()+", check permissions."); } for (Iterator i = getChilds(); i.hasNext(); ) { Identifier ident = (Identifier) i.next(); if ((Main.stripping & Main.STRIP_UNREACH) != 0 && !ident.isReachable()) { if (GlobalOptions.verboseLevel > 4) GlobalOptions.err.println("Class/Package " + ident.getFullName() + " is not reachable"); continue; } if (ident instanceof PackageIdentifier) ((PackageIdentifier) ident) .storeClasses(newDest); else { try { File file = new File(newDest, ident.getAlias()+".class"); // if (file.exists()) { // GlobalOptions.err.println // ("Refuse to overwrite existing class file " // +file.getPath()+". Remove it first."); // return; // } DataOutputStream out = new DataOutputStream (new BufferedOutputStream (new FileOutputStream(file))); ((ClassIdentifier) ident).storeClass(out); out.close(); } catch (java.io.IOException ex) { GlobalOptions.err.println("Can't write Class " + ident.getName()); ex.printStackTrace(GlobalOptions.err); } } } } public String toString() { return (parent == null) ? "base package" : getFullName(); } public boolean contains(String newAlias, Identifier except) { for (Iterator i = loadedClasses.values().iterator(); i.hasNext(); ) { ClassIdentifier ident = (ClassIdentifier)i.next(); if (ident != except) { if (((Main.stripping & Main.STRIP_UNREACH) == 0 || ident.isReachable()) && ident.getAlias().equalsIgnoreCase(newAlias)) return true; } } for (Iterator i = loadedPackages.values().iterator(); i.hasNext(); ) { PackageIdentifier ident = (PackageIdentifier)i.next(); if (ident != except) { if (ident.getAlias().length() == 0 && ident.contains(newAlias, this)) return true; } } if (getAlias().length() == 0 && parent != null && parent != except && parent.contains(newAlias, this)) return true; return false; } public boolean conflicting(String newAlias) { return parent.contains(newAlias, this); } }