All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yworks.yguard.obf.ClassTree Maven / Gradle / Ivy

Go to download

The open-source Java obfuscation tool working with Ant and Gradle by yWorks - the diagramming experts

There is a newer version: 2.10.0
Show newest version
/*
 * YGuard -- an obfuscation library for Java(TM) classfiles.
 *
 * Original Copyright (c) 1999 Mark Welsh ([email protected])
 * Modifications Copyright (c) 2002 yWorks GmbH ([email protected])
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * The author may be contacted at [email protected]
 *
 * Java and all Java-based marks are trademarks or registered
 * trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
 */
package com.yworks.yguard.obf;

import com.yworks.yguard.Conversion;
import com.yworks.yguard.ParseException;
import com.yworks.yguard.obf.classfile.ClassConstants;
import com.yworks.yguard.obf.classfile.ClassFile;
import com.yworks.yguard.obf.classfile.ClassItemInfo;
import com.yworks.yguard.obf.classfile.FieldInfo;
import com.yworks.yguard.obf.classfile.LineNumberTableAttrInfo;
import com.yworks.yguard.obf.classfile.Logger;
import com.yworks.yguard.obf.classfile.MethodInfo;
import com.yworks.yguard.obf.classfile.NameMapper;

import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * Tree structure of package levels, classes, methods and fields used for obfuscation.
 *
 * @author      Mark Welsh
 */
public class ClassTree implements NameMapper
{
    // Constants -------------------------------------------------------------
    public static final char PACKAGE_LEVEL = '/';
    public static final char CLASS_LEVEL = '$';
    public static final char METHOD_FIELD_LEVEL = '/';

    // Fields ----------------------------------------------------------------
    private Vector retainAttrs = new Vector();  // List of attributes to retain
    private Pk root = null;   // Root package in database (Java default package)

    // Class methods ---------------------------------------------------------
    /** Return a fully qualified name broken into package/class segments. */
    public static Enumeration getNameEnum(String name)
    {
        Vector vec = new Vector();
        String nameOrig = name;
        while (!name.equals(""))
        {
            int posP = name.indexOf(PACKAGE_LEVEL);
            int posC = name.indexOf(CLASS_LEVEL);
            Cons cons = null;
            if (posP == -1 && posC == 0)
            {
              // this is the rare case when a toplevel class name starts with a dollar sign ('$')
              // currently GSon has this in its library and causes problems with yGuard.
              int innerClassIndex = name.indexOf(CLASS_LEVEL, 1);
              int endIndex = innerClassIndex > 0 ? innerClassIndex : name.length();
              cons = new Cons(new Character(CLASS_LEVEL), name.substring(0, endIndex));
              name = name.substring(endIndex);
            }
            if (posP == -1 && posC == -1)
            {
                cons = new Cons(new Character(CLASS_LEVEL), name);
                name = "";
            }
            if (posP == -1 && posC > 0)
            {
                cons = new Cons(new Character(CLASS_LEVEL), name.substring(0, posC));
                //fixes retroguard bug, where
                // 'ClassName$$InnerClassName' leads to a runtimeerror
                while ((posC+1 < name.length()) && (name.charAt(posC+1) == CLASS_LEVEL)) posC++;
                name = name.substring(posC + 1, name.length());
            }
            if (posP != -1 && posC == -1)
            {
                cons = new Cons(new Character(PACKAGE_LEVEL), name.substring(0, posP));
                name = name.substring(posP + 1, name.length());
            }
            if (posP != -1 && posC != -1)
            {
                if (posP < posC)
                {
                    cons = new Cons(new Character(PACKAGE_LEVEL), name.substring(0, posP));
                    name = name.substring(posP + 1, name.length());
                }
                else
                {
                    throw new IllegalArgumentException("Invalid fully qualified name (a): " +
                                          nameOrig);
                }
            }
            if (((String)cons.cdr).equals(""))
            {
                throw new IllegalArgumentException("Invalid fully qualified name (b): " +
                                      nameOrig);
            }
            vec.addElement(cons);
        }
        return vec.elements();
    }


    // Instance Methods ------------------------------------------------------
    /** Ctor. */
    public ClassTree()
    {
        root = Pk.createRoot(this);
    }

    /** Return the root node. */
    public Pk getRoot() {return root;}

    /**
     * finds tree items by looking for name components only...
     */
    public TreeItem findTreeItem(String[] nameParts){
      TreeItem tmp = root;
      for (int i = 0; tmp != null && i < nameParts.length; i++){
        String name = nameParts[i];
        tmp = findSubItem(tmp, name);
      }
      return tmp;
    }

    /**
     * walks the tree of TreeItems in order to find a class forName
     */
    public Cl findClassForName(String name){
      int dindex = name.indexOf('$');
      String innerClass = null;
      if (dindex>0){
        innerClass = name.substring(dindex+1);
        name = name.substring(0, dindex);
      }
      int pindex = name.lastIndexOf('.');
      String packageName = null;
      if (pindex>0){
        packageName = name.substring(0, pindex);
        name = name.substring(pindex+1);
      }
      Pk pk = root;
      if (packageName != null){
        for (StringTokenizer st = new StringTokenizer(packageName, ".", false); st.hasMoreTokens();){
          String token = st.nextToken();
          pk = findPackage(pk, token);
          if (pk == null) return null;
        }
      }
      Cl cl = findClass(pk, name);
      if (cl != null && innerClass != null){
        for (StringTokenizer st = new StringTokenizer(innerClass, "$", false); st.hasMoreTokens();){
          String token = st.nextToken();
          cl = findClass(cl, token);
          if (cl == null) return null;
        }
      }
      return cl;
    }

    private Pk findPackage(TreeItem parent, String pName){
      if (parent instanceof Pk){
        for (Enumeration enumeration = ((Pk)parent).getPackageEnum(); enumeration.hasMoreElements();){
          Pk subPk = (Pk) enumeration.nextElement();
          if (subPk.getInName().equals(pName)){
            return subPk;
          }
        }
      }
      return null;
    }

    private Cl findClass(PkCl parent, String pName){
      for (Enumeration enumeration = ((PkCl)parent).getClassEnum(); enumeration.hasMoreElements();){
        Cl cl = (Cl) enumeration.nextElement();
        if (cl.getInName().equals(pName)){
          return cl;
        }
      }
      return null;
    }

    private TreeItem findSubItem(TreeItem parent, String childName){
      if (parent instanceof Pk){
        for (Enumeration enumeration = ((Pk)parent).getPackageEnum(); enumeration.hasMoreElements();){
          Pk subPk = (Pk) enumeration.nextElement();
          if (subPk.getInName().equals(childName)){
            return subPk;
          }
        }
        for (Enumeration enumeration = ((Pk)parent).getClassEnum(); enumeration.hasMoreElements();){
          Cl cl = (Cl) enumeration.nextElement();
          if (cl.getInName().equals(childName)){
            return cl;
          }
        }
      }
      if (parent instanceof Cl){
        for (Enumeration enumeration = ((Cl)parent).getClassEnum(); enumeration.hasMoreElements();){
          Cl cl = (Cl) enumeration.nextElement();
          if (cl.getInName().equals(childName)){
            return cl;
          }
        }
        return null;
      }
      return null;
    }

    /** Update the path of the passed filename, if that path corresponds to a package. */
    public String getOutName(String inName)
    {
        try
        {
            TreeItem ti = root;
            StringBuffer sb = new StringBuffer();
            for (Enumeration nameEnum = getNameEnum(inName); nameEnum.hasMoreElements(); )
            {
                Cons nameSegment = (Cons)nameEnum.nextElement();
                char tag = ((Character)nameSegment.car).charValue();
                String name = (String)nameSegment.cdr;
                switch (tag)
                {
                case PACKAGE_LEVEL:
                    if (ti != null)
                    {
                        ti = ((Pk)ti).getPackage(name);
                        if (ti != null)
                        {
                            sb.append(ti.getOutName());
                        }
                        else
                        {
                            sb.append(name);
                        }
                    }
                    else
                    {
                        sb.append(name);
                    }
                    sb.append(PACKAGE_LEVEL);
                    break;

                case CLASS_LEVEL:
                    sb.append(name);
                    return sb.toString();

                default:
                    throw new RuntimeException("Internal error: illegal package/class name tag");
                }
            }
        }
        catch (Exception e)
        {
            // Just drop through and return the original name
        }
        return inName;
    }

    /** Add a classfile's package, class, method and field entries to database. */
    public void addClassFile(ClassFile cf)
    {
        // Add the fully qualified class name
        TreeItem ti = root;
        char parentTag = PACKAGE_LEVEL;
        for (Enumeration nameEnum = getNameEnum(cf.getName()); nameEnum.hasMoreElements(); )
        {
            Cons nameSegment = (Cons)nameEnum.nextElement();
            char tag = ((Character)nameSegment.car).charValue();
            String name = (String)nameSegment.cdr;
            switch (tag)
            {
            case PACKAGE_LEVEL:
                ti = ((Pk)ti).addPackage(name);
                break;

            case CLASS_LEVEL:
                // If this is an inner class, just add placeholder classes up the tree
                if (nameEnum.hasMoreElements())
                {
                    ti = ((PkCl)ti).addPlaceholderClass(name);
                }
                else
                {
                  Object[] classInfo = {
                      name, cf.getSuper(), cf.getInterfaces(), cf.getModifiers(), ClassItemInfo.getObfuscationConfig(cf.getAttributes())
                  };
                  Cl cl =((PkCl)ti).addClass(classInfo);
                  cl.setInnerClassModifiers(cf.getInnerClassModifiers());
                  cl.setClassFileAccess(cf.getClassFileAccess());
                  ti = cl;
                }
                break;

            default:
                throw new ParseException("Internal error: illegal package/class name tag");
            }
            parentTag = tag;
        }

        // We must have a class before adding methods and fields
        if (ti instanceof Cl)
        {
            Cl cl = (Cl)ti;
            cl.access = cf.getModifiers();

            // Add the class's methods to the database
            for (Enumeration enumeration = cf.getMethodEnum(); enumeration.hasMoreElements(); )
            {
              cl.addMethod((MethodInfo) enumeration.nextElement());
            }

            // Add the class's fields to the database
            for (Enumeration enumeration = cf.getFieldEnum(); enumeration.hasMoreElements(); )
            {
              cl.addField((FieldInfo) enumeration.nextElement());
            }
        }
        else
        {
            throw new ParseException("Inconsistent class file.");
        }
    }

    /** Mark an attribute type for retention. */
    public void retainAttribute(String name)
    {
        retainAttrs.addElement(name);
    }

    private boolean modifierMatch(int level, int mods){
        if (level == YGuardRule.LEVEL_NONE) return false;
        if (Modifier.isPublic(mods)){
            return (level & YGuardRule.PUBLIC) == YGuardRule.PUBLIC;
        }
        if (Modifier.isProtected(mods)){
            return (level & YGuardRule.PROTECTED) == YGuardRule.PROTECTED;
        }
        if (Modifier.isPrivate(mods)){
            return (level & YGuardRule.PRIVATE) == YGuardRule.PRIVATE;
        }
        // package friendly left only
        return (level & YGuardRule.FRIENDLY) == YGuardRule.FRIENDLY;
    }

    /** Mark a class/interface type (and possibly methods and fields defined in class) for retention. */
    public void retainClass(String name, int classLevel, int methodLevel, int fieldLevel, boolean retainHierarchy)
    {

        // Mark the class (or classes, if this is a wildcarded specifier)
        for (Enumeration clEnum = getClEnum(name, classLevel); clEnum.hasMoreElements(); )
        {
            Cl classItem = (Cl)clEnum.nextElement();
            if(retainHierarchy && classLevel != YGuardRule.LEVEL_NONE) retainHierarchy(classItem);
            // Retain methods if requested
            if (methodLevel != YGuardRule.LEVEL_NONE)
            {
                for (Enumeration enumeration = classItem.getMethodEnum(); enumeration.hasMoreElements(); )
                {
                    Md md = (Md)enumeration.nextElement();
                    if (modifierMatch(methodLevel, md.getModifiers()))
                    {
                        md.setOutName(md.getInName());
                        md.setFromScript();
                    }
                }
                // do super classes and interfaces...
                if ((methodLevel & (YGuardRule.PUBLIC | YGuardRule.PROTECTED | YGuardRule.FRIENDLY)) != 0
                  ||(fieldLevel & (YGuardRule.PUBLIC | YGuardRule.PROTECTED | YGuardRule.FRIENDLY)) != 0){
                  int mask = YGuardRule.PRIVATE;
                  int ml = methodLevel & ~mask;
                  int fl = fieldLevel & ~mask;
                  int cl = classLevel & ~mask;
                  String[] interfaces = classItem.getInterfaces();
                  if (interfaces != null){
                    for (int i = 0; i < interfaces.length; i++){
                      String interfaceClass = interfaces[i];
                      retainClass(interfaceClass, cl, ml, fl, false);
                    }
                  }
                  String superClass = classItem.getSuperClass();
                  if (superClass != null){
                    // staying in package?!
                    if (!superClass.startsWith(classItem.getParent().getFullInName())){
                      mask |= YGuardRule.FRIENDLY;
                      ml = methodLevel & ~mask;
                      fl = fieldLevel & ~mask;
                      cl = classLevel & ~mask;
                    }
                    retainClass(superClass, cl, ml, fl, false);
                  }
                }
            }

            // Retain fields if requested
            if (fieldLevel != YGuardRule.LEVEL_NONE)
            {
                for (Enumeration enumeration = classItem.getFieldEnum(); enumeration.hasMoreElements(); )
                {
                    Fd fd = (Fd)enumeration.nextElement();
                    if (modifierMatch(fieldLevel, fd.getModifiers()))
                    {
                        fd.setOutName(fd.getInName());
                        fd.setFromScript();
                    }
                }
            }
        }
    }

    /** Mark a method type for retention. */
    public void retainMethod(String name, String descriptor)
    {
        for (Enumeration enumeration = getMdEnum(name, descriptor);
             enumeration.hasMoreElements(); ) {
            Md md = (Md)enumeration.nextElement();
            md.setOutName(md.getInName());
            md.setFromScript();
        }
    }

    /** Mark a field type for retention. */
    public void retainField(String name)
    {
        for (Enumeration enumeration = getFdEnum(name); enumeration.hasMoreElements(); ) {
            Fd fd = (Fd)enumeration.nextElement();
            fd.setOutName(fd.getInName());
            fd.setFromScript();
        }
    }

    /** Mark a package for retention, and specify its new name. */
    public void retainPackageMap(String name, String obfName)
    {
        retainItemMap(getPk(name), obfName);
    }

    /** Mark a class/interface type for retention, and specify its new name. */
    public void retainClassMap(String name, String obfName)
    {
        retainItemMap(getCl(name), obfName);
    }

    /** Mark a method type for retention, and specify its new name. */
    public void retainMethodMap(String name, String descriptor,
                                String obfName)
    {
        retainItemMap(getMd(name, descriptor), obfName);
    }

    /** Mark a field type for retention, and specify its new name. */
    public void retainFieldMap(String name, String obfName)
    {
        retainItemMap(getFd(name), obfName);
    }

    // Mark an item for retention, and specify its new name.
    private void retainItemMap(TreeItem item, String obfName)
    {
        if (!item.isFixed())
        {
            item.setOutName(obfName);
            item.setFromScriptMap();
        } else {
          if (!item.getOutName().equals(obfName)){
            item.setOutName(obfName);
            item.setFromScriptMap();
            // do warning
            Logger.getInstance().warning("'" + item.getFullInName() + "' will be remapped to '" + obfName + "' according to mapping rule!");
          }
        }
//        if (!item.isFixed())
//        {
//          item.setFromScriptMap();
//        }
//        item.setOutName(obfName);
    }

    /** Traverse the class tree, generating obfuscated names within each namespace. */
    public void generateNames()
    {
        walkTree(new TreeAction() {
            public void packageAction(Pk pk)  {pk.generateNames();}
            public void classAction(Cl cl)  {cl.generateNames();}
        });
    }

    /** Resolve the polymorphic dependencies of each class. */
    public void resolveClasses() throws ClassNotFoundException
    {
        walkTree(new TreeAction() {
            public void classAction(Cl cl)  {cl.resetResolve();}
        });
        walkTree(new TreeAction() {
            public void classAction(Cl cl)  {cl.setupNameListDowns();}
        });
        Cl.nameSpace = 0;
        final ClassNotFoundException[] ex = new ClassNotFoundException[1];
        try{
          walkTree(new TreeAction() {
              public void classAction(Cl cl)  {
                try{
                  cl.resolveOptimally();
                } catch (ClassNotFoundException cnfe){
                  ex[0] = cnfe;
                  throw new RuntimeException();
                }
              }
          });
        } catch (RuntimeException rte){
          if (ex[0] != null){
            throw ex[0];
          } else {
            throw rte;
          }
        }
    }

    /** Return a list of attributes marked to keep. */
    public String[] getAttrsToKeep()
    {
        String[] attrs = new String[retainAttrs.size()];
        for (int i = 0; i < attrs.length; i++)
        {
            attrs[i] = (String)retainAttrs.elementAt(i);
        }
        return attrs;
    }

    /** Get classes in tree from the fully qualified name
        (can be wildcarded). */
    public Enumeration getClEnum(String fullName)
    {
       return getClEnum(fullName, YGuardRule.LEVEL_PRIVATE);
    }

    /** Get classes in tree from the fully qualified name
        (can be wildcarded). */
    public Enumeration getClEnum(String fullName, final int classMode)
    {
        final Vector vec = new Vector();

        // Wildcarded?
        // Then return list of all classes (including inner classes) in package
        if (fullName.indexOf('*') != -1) {
            // Recursive?
            if (fullName.indexOf('!') == 0) {
                final String fName = fullName.substring(1);
                walkTree(new TreeAction() {
                    public void classAction(Cl cl)  {
                        if (cl.isWildcardMatch(fName) && modifierMatch(classMode, cl.getModifiers())) {
                            vec.addElement(cl);
                        }
                    }
                });
            }
            else
            {
                // non-recursive
                final String fName = fullName;
                walkTree(new TreeAction() {
                    public void classAction(Cl cl)  {
                        if (cl.isNRWildcardMatch(fName) && modifierMatch(classMode, cl.getModifiers())) {
                            vec.addElement(cl);
                        }
                    }
                });
            }
        }
        else
        {
            // Single class
            Cl cl = getCl(fullName);
            if (cl != null )
            {
              int mods = cl.getModifiers();
              if (cl.isInnerClass()){
                Cl outer = (Cl) cl.getParent();
              }
              boolean match = modifierMatch(classMode, cl.getModifiers());
              if (match || classMode == YGuardRule.LEVEL_NONE ){ //(RW)
                vec.addElement(cl);
              }
            }
        }
        return vec.elements();
    }

    /** Get methods in tree from the fully qualified, and possibly
        wildcarded, name. */
    public Enumeration getMdEnum(String fullName,
                                 String descriptor)
    {
        final Vector vec = new Vector();
        final String fDesc = descriptor;
        if (fullName.indexOf('*') != -1 ||
            descriptor.indexOf('*') != -1) {
            // Recursive?
            if (fullName.indexOf('!') == 0) {
                final String fName = fullName.substring(1);
                // recursive wildcarding
                walkTree(new TreeAction() {
                    public void methodAction(Md md)  {
                        if (md.isWildcardMatch(fName, fDesc)) {
                            vec.addElement(md);
                        }
                    }
                });
            }
            else
            {
                final String fName = fullName;
                // non-recursive wildcarding
                walkTree(new TreeAction() {
                    public void methodAction(Md md)  {
                        if (md.isNRWildcardMatch(fName, fDesc)) {
                            vec.addElement(md);
                        }
                    }
                });
            }
        } else {
            Md md = getMd(fullName, descriptor);
            if (md != null) {
                vec.addElement(md);
            }
        }
        return vec.elements();
    }

    /** Get fields in tree from the fully qualified, and possibly
        wildcarded, name. */
    public Enumeration getFdEnum(String fullName)
    {
        final Vector vec = new Vector();
        if (fullName.indexOf('*') != -1) {
            // Recursive?
            if (fullName.indexOf('!') == 0) {
                // recursive wildcarding
                final String fName = fullName.substring(1);
                walkTree(new TreeAction() {
                    public void fieldAction(Fd fd)  {
                        if (fd.isWildcardMatch(fName)) {
                            vec.addElement(fd);
                        }
                    }
                });
            }
            else
            {
                // non-recursive wildcarding
                final String fName = fullName;
                walkTree(new TreeAction() {
                    public void fieldAction(Fd fd)  {
                        if (fd.isNRWildcardMatch(fName)) {
                            vec.addElement(fd);
                        }
                    }
                });
            }
        } else {
            Fd fd = getFd(fullName);
            if (fd != null) {
                vec.addElement(fd);
            }
        }
        return vec.elements();
    }

    /** Get class in tree from the fully qualified name, returning null if name not found. */
    public Cl getCl(String fullName)
    {
        TreeItem ti = root;
        for (Enumeration nameEnum = getNameEnum(fullName); nameEnum.hasMoreElements(); )
        {
            Cons nameSegment = (Cons)nameEnum.nextElement();
            char tag = ((Character)nameSegment.car).charValue();
            String name = (String)nameSegment.cdr;
            switch (tag)
            {
            case PACKAGE_LEVEL:
                ti = ((Pk)ti).getPackage(name);
                break;

            case CLASS_LEVEL:
                ti = ((PkCl)ti).getClass(name);
                break;

            default:
                throw new ParseException("Internal error: illegal package/class name tag");
            }

            // If the name is not in the database, return null
            if (ti == null)
            {
                return null;
            }
        }

        // It is an error if we do not end up with a class or interface
        if (!(ti instanceof Cl))
        {
            throw new ParseException("Inconsistent class or interface name.");
        }
        return (Cl)ti;
    }

    /** Get package in tree from the fully qualified name, returning null if name not found. */
    public Pk getPk(String fullName)
    {
        TreeItem ti = root;
        for (Enumeration nameEnum = getNameEnum(fullName); nameEnum.hasMoreElements(); )
        {
            Cons nameSegment = (Cons)nameEnum.nextElement();
            String name = (String)nameSegment.cdr;
            ti = ((Pk)ti).getPackage(name);

            // If the name is not in the database, return null
            if (ti == null)
            {
                return null;
            }
            // It is an error if we do not end up with a package
            if (!(ti instanceof Pk))
            {
                throw new ParseException("Inconsistent package.");
            }
        }
        return (Pk)ti;
    }

    /** Get method in tree from the fully qualified name. */
    public Md getMd(String fullName, String descriptor)
    {
        // Split into class and method names
        int pos = fullName.lastIndexOf(METHOD_FIELD_LEVEL);
        Cl cl = getCl(fullName.substring(0, pos));
        return cl.getMethod(fullName.substring(pos + 1), descriptor);
    }

    /** Get field in tree from the fully qualified name. */
    public Fd getFd(String fullName)
    {
        // Split into class and field names
        int pos = fullName.lastIndexOf(METHOD_FIELD_LEVEL);
        Cl cl = getCl(fullName.substring(0, pos));
        return cl.getField(fullName.substring(pos + 1));
    }

    public String[] getAttrsToKeep(String className) {
      Cl cl = getCl(className);
      if (cl != null){
        Set attrs = cl.getAttributesToKeep();
        if (attrs != null && attrs.size() > 0){
          String[] other = getAttrsToKeep();
          Set tmp = new HashSet(attrs);
          for (int i = 0; i < other.length; i++) {
            tmp.add(other[i]);
          }
          return (String[]) tmp.toArray(new String[tmp.size()]);
        } else {
          return getAttrsToKeep();
        }
      } else {
        return getAttrsToKeep();
      }
    }

    public String mapLocalVariable(String thisClassName, String methodName, String descriptor, String string) {
      return string;
    }

    /** Mapping for fully qualified class name.
     *  @see NameMapper#mapClass */
    public String mapClass(String className)
    {
//      System.out.println("map class " + className);
        // Check for array -- requires special handling
        if (className.length() > 0 && className.charAt(0) == '[') {
            StringBuffer newName = new StringBuffer();
            int i = 0;
            while (i < className.length()) {
                char ch = className.charAt(i++);
                switch (ch) {
                case '[':
                case ';':
                    newName.append(ch);
                    break;

                case 'L':
                    newName.append(ch);
                    int pos = className.indexOf(';', i);
                    if (pos < 0) {
                        throw new ParseException("Invalid class name encountered: " + className);
                    }
                    newName.append(mapClass(className.substring(i, pos)));
                    i = pos;
                    break;

                default:
                    return className;
                }
            }
            return newName.toString();
        } else {
            Cl cl = getCl(className);
            if (cl == null){
              try {
                Class aClass = Cl.getClassResolver().resolve(Conversion.toJavaClass(className));
                // ok class exists...
                return className;
              } catch (ClassNotFoundException e) {
                if (pedantic){
                  throw new NoSuchMappingException("Class "+Conversion.toJavaClass(className));
                } else {
                  Logger.getInstance().warningToLogfile("Unresolved external dependency: "+Conversion.toJavaClass(className)+
                                     " not found!");
                  Logger.getInstance().setUnresolved();
                  return className;
                }
              }
            }
            return cl.getFullOutName();
        }
    }

    /** Mapping for method name, of fully qualified class.
     *  @see NameMapper#mapMethod */
    public String mapMethod(String className, String methodName, String descriptor)
    {
      // check if the className is an array...
      if (className.startsWith("[") && className.endsWith(";")){
        int count = 0;
        while (className.charAt(count) == '['){
          count++;
        }
        if (className.charAt(count) == 'L'){
          className = className.substring(count + 1, className.length() - 1);
        }
      }
      Cl cl = getCl(className);
      if (cl != null && cl.getMethod(methodName, descriptor) != null)
      {
        return cl.getMethod(methodName, descriptor).getOutName();
      }
      else
      {
        if (cl == null)
        {
          try {
            Class aClass = Cl.getClassResolver().resolve(Conversion.toJavaClass(className));
          } catch (ClassNotFoundException e) {
            if (pedantic){
              throw new NoSuchMappingException("Class "+Conversion.toJavaClass(className));
            } else {
              Logger.getInstance().warningToLogfile( "No mapping found: " + Conversion.toJavaClass( className ) );
            }
          }
          // method is not in database use unobfuscated name...
          return methodName;
        }
        else
        {
          try
          {
//              System.out.println("Try: "+cl.getFieldObfNameUp(fieldName));
            String result =  cl.getMethodOutNameUp(methodName,descriptor);
            if (result != null)
              return result;
          }
          catch (Exception ex)
          {
            System.out.println(ex);
            //System.out.println("ME: Error: Try not succeeded");
          }
          if ((!methodName.equals("") &&
               (!methodName.equals("")))){
            if (pedantic){
              throw new NoSuchMappingException("Method "+Conversion.toJavaClass(className)+"."+methodName);
            } else {
              Logger.getInstance().error("Method "+Conversion.toJavaClass(className)+"."+methodName+
                                 " could not be mapped !\n Probably broken code! Try rebuilding from source!");
              return methodName;
            }
          }
          return methodName;
        }
      }
    }

    /** Mapping for annotation field/method name, of fully qualified class.
     *  @see NameMapper#mapAnnotationField */
    public String mapAnnotationField(String className, String methodName)
    {
        Cl cl = getCl(className);
        if (cl != null)
        {
          for (Enumeration enumeration = cl.getMethodEnum(); enumeration.hasMoreElements();){
            Md md = (Md) enumeration.nextElement();
            if (md.getInName().equals(methodName)){
              return md.getOutName();
            }
          }
          // actually this should not happen - is this an exception?!
          return methodName;
        }
        else
        {
          // method is not in database use unobfuscated name...
          return methodName;
        }
    }

    /** Mapping for field name, of fully qualified class.
     *  @see NameMapper#mapField */
    public String mapField(String className, String fieldName)
    {
//        System.out.println("Map "+className+"."+fieldName);
        Cl cl = getCl(className);
        if ((cl != null) && (cl.getField(fieldName) != null))
        {
          //special .class construct name mapping....
          if (fieldName.startsWith("class$")  && isReplaceClassNameStrings()){
            String realClassName = fieldName.substring(6);
            List nameParts = new ArrayList(20);
            for (StringTokenizer st = new StringTokenizer(realClassName, "$", false); st.hasMoreTokens();){
              nameParts.add(st.nextToken());
            }
            String[] names = new String[nameParts.size()];
            nameParts.toArray(names);
            TreeItem ti = findTreeItem(names);
            if (ti instanceof Cl){
              Fd fd = cl.getField(fieldName);
              String newClassName = mapClass(ti.getFullInName());
              String outName = "class$"+newClassName.replace('/','$');
              fd.setOutName(outName);
              return outName;
            }
          }
//        System.out.println("Standard Field Map");
          return cl.getField(fieldName).getOutName();
        }
        else
        {
          if (cl == null)
          {
//            System.out.println("Error: "+className+
//                               " class not found !");
            return fieldName;
          }
          else
          {
//            System.out.println("ERROR: "+className+"."+fieldName+
//                               " cannot be mapped !");
            try
            {
//              System.out.println("Try: "+cl.getFieldObfNameUp(fieldName));
              String result = cl.getFieldOutNameUp(fieldName);
              if (result != null)
                return result;
            }
            catch (Exception ex)
            {
//              System.out.println("Try not succeeded");
            }
            if (!fieldName.equals("this")) {
              if (pedantic){
                throw new NoSuchMappingException("Field "+className+"."+fieldName);
              } else {
                Logger.getInstance().error("Field "+className+"."+fieldName+
                   " could not be mapped !\n Probably broken code! Try rebuilding from source!");
              }
            }
            return fieldName;
          }
        }


//        return cl != null && cl.getField(fieldName) != null ?
//                    cl.getField(fieldName).getOutName() :
//                    fieldName;
    }

    /** Mapping for signatures (used for generics in 1.5).
     *  @see NameMapper#mapSignature
     */
    public String mapSignature(String signature){
        // Pass everything through unchanged, except for the String between
        // 'L' and ';' -- this is passed through mapClass(String)

//      System.out.println( "signature: "+signature );

        StringBuffer classString = new StringBuffer();

        StringBuffer newSignature = new StringBuffer();
        int i = 0;
        while (i < signature.length())
        {
            char ch = signature.charAt(i++);
            switch (ch)
            {
            case '[':
            case 'B':
            case 'C':
            case 'D':
            case 'F':
            case 'I':
            case 'J':
            case 'S':
            case 'Z':
            case 'V':
            case '(':
            case ')':
              case '+':
              case ':':
              case '-':
              case '*':
                newSignature.append(ch);
                break;
            case ';':
                newSignature.append(ch);
                classString.setLength( 0 );
              break;
            case 'T':
            {   // Template name
                newSignature.append(ch);
                int pos = signature.indexOf(';', i);
                if (pos < 0)
                {
                    throw new ParseException("Invalid signature string encountered.");
                }
                newSignature.append(signature.substring(i, pos));
                i = pos;
                break;
            }
            case '<':
            {
              // formal parameters
              newSignature.append(ch);
              while (true){
                int first = i;
                while (signature.charAt(i) !=  ':'){
                  i++;
                }
                String templateName = signature.substring(first, i);
                newSignature.append(templateName);

                while (signature.charAt(i) == ':'){
                  newSignature.append(':');
                  i++;
                  int firstPos = i;
                  int bracketCount = 0;
                  while (!(bracketCount == 0 && signature.charAt(i) == ';')){
                    if (signature.charAt(i) == '<') {
                      bracketCount++;
                    } else if (signature.charAt(i) == '>'){
                      bracketCount--;
                    }
                    i++;
                  }
                  i++;
                  newSignature.append(mapSignature(signature.substring(firstPos, i)));
                }
                if (signature.charAt(i) == '>'){
                  newSignature.append('>');
                  i++;
                  break;
                }
              }
                break;
            }

              case '^':
              {
                newSignature.append(ch);
                if (signature.charAt(i) == 'T'){
                  // identifier
                  while (signature.charAt(i) !=  ';'){
                    newSignature.append(signature.charAt(i));
                    i++;
                  }
                  continue;
                } else if (signature.charAt(i) == 'L'){
                  // class
                  int first = i;
                  int bracketCount = 0;
                  while (signature.charAt(i) != ';' || bracketCount != 0){
                    char c = signature.charAt(i);
                    if (c == '<'){
                      bracketCount++;
                    } else if (c == '>'){
                      bracketCount--;
                    }
                    i++;
                  }
                  i++;
                  String classSig = signature.substring(first, i);
                  newSignature.append(mapSignature(classSig));
                } else {
                  throw new IllegalStateException("Could not map signature " + signature);
                }
              }
              break;
            case 'L':
            case '.':  // inner class
            {
                newSignature.append(ch);
                int pos = signature.indexOf(';', i);
                int bracketPos = signature.indexOf('<', i);
                if (bracketPos >= i && bracketPos < pos){
                  // found a bracket - find the matching one..
                  int bracketCount = 0;
                  int closingBracket = signature.length();
                  for (int walker = bracketPos + 1; walker < signature.length(); walker++){
                    char c = signature.charAt(walker);
                    if (c == '<'){
                      bracketCount++;
                    } else if (c == '>'){
                      if (bracketCount == 0){
                        closingBracket = walker;
                        break;
                      } else {
                        bracketCount--;
                      }
                    }
                  }
                  // TODO check!!!!
                  pos = closingBracket + 1;
//                  pos = signature.indexOf(';', closingBracket);
                  String templateArg = signature.substring(bracketPos + 1, closingBracket);
                  String classNamePart = signature.substring( i, bracketPos );
                  if (ch == '.'){ // inner class part - translate to class file name
                    appendInnerClass(classString, newSignature, classNamePart);
                  } else { // toplevel class 'L'
                    classString.append( classNamePart );
                    newSignature.append(mapClass( classString.toString() ));
                  }
                  newSignature.append('<');
                  newSignature.append(mapSignature(templateArg));
                  newSignature.append('>');
                  i = pos;
                } else {

                  // no generics to parse
                  
                  if (pos < 0)
                  {
                      throw new ParseException("Invalid signature string encountered: " + signature);
                  }

                  String classNamePart = signature.substring( i, pos );
                  if (ch == '.'){ // inner class part - translate to class file name
                    appendInnerClass(classString,newSignature,classNamePart);
                  } else {
                    classString.append( classNamePart );
                    newSignature.append(mapClass( classString.toString() ));
                  }

                  i = pos;
                }
                break;
            }
            default:
                throw new ParseException("Invalid signature string encountered: " +signature + " parsing char " + ch);
            }
        }
        return newSignature.toString();
    }

  private void appendInnerClass(StringBuffer classString, StringBuffer newSignature, String classNamePart) {
    classString.append( '$' );
    classString.append( classNamePart );
    String className = classString.toString();
    // do basically the same that mapClass() does, but return the last part only.
    String result = getClassNamePart(classNamePart, className);
    newSignature.append(result);
  }


  private String getClassNamePart(String classNamePart, String className) {

    int j = className.indexOf(classNamePart);

    if( classNamePart.indexOf('.') != -1 && j > 0 ) { // nested inner classes?

        // e.g. classNamePart: a.b, className: g/h/j$a.b

        String outerClassName = className.substring(0,j-1);
        String retval = "";
        String currentClassName = outerClassName;

        StringBuilder innerClassName = new StringBuilder();
        for( int i=0; i 0 ) {
        retval = retval + '.';
      }
      retval = retval + cl.getOutName();
    } else {
      try {
        Class aClass = Cl.getClassResolver().resolve(Conversion.toJavaClass(currentClassName));
        // ok class exists...
        retval = retval + "." + currentClassName;
      } catch (ClassNotFoundException e) {
        if (pedantic){
          throw new NoSuchMappingException("Class "+Conversion.toJavaClass(currentClassName));
        } else {
          Logger.getInstance().warningToLogfile("Unresolved external dependency: "+Conversion.toJavaClass(currentClassName)+
                             " not found!");
          Logger.getInstance().setUnresolved();
          retval = retval + "." + currentClassName;
        }
      }
    }
    return retval;
  }

  public String mapSourceFile(String className, String sourceFileName){
    final Cl cl = getCl(className);
    if (cl.isSourceFileMappingSet()){
      return cl.getSourceFileMapping();
    } else {
      return sourceFileName;
    }
  }

  public boolean mapLineNumberTable(String className, String methodName, String methodSignature, LineNumberTableAttrInfo info) {
    final Cl cl = getCl(className);
    if (cl.getLineNumberTableMapper() != null){
      return cl.getLineNumberTableMapper().mapLineNumberTable(className, methodName, methodSignature, info);
    } else {
      return true;
    }
  }

    /**
     * Mapping for descriptor of field or method.
     * @see NameMapper#mapDescriptor
     */
    public String mapDescriptor(String descriptor)
    {
        // Pass everything through unchanged, except for the String between
        // 'L' and ';' -- this is passed through mapClass(String)
        StringBuffer newDesc = new StringBuffer();
        int i = 0;
        while (i < descriptor.length())
        {
            char ch = descriptor.charAt(i++);
            switch (ch)
            {
            case '[':
            case 'B':
            case 'C':
            case 'D':
            case 'F':
            case 'I':
            case 'J':
            case 'S':
            case 'Z':
            case 'V':
            case '(':
            case ')':
            case ';':
                newDesc.append(ch);
                break;

            case 'L':
                newDesc.append(ch);
                int pos = descriptor.indexOf(';', i);
                if (pos < 0)
                {
                    throw new ParseException("Invalid descriptor string encountered.");
                }
                newDesc.append(mapClass(descriptor.substring(i, pos)));
                i = pos;
                break;

            default:
                throw new ParseException("Invalid descriptor string encountered.");
            }
        }
        return newDesc.toString();
    }

    /**
     * Mapping for package names.
     * @see NameMapper#mapPackage(String)
     */
    public String mapPackage( final String packageName ) {
        final Pk pk = getPk(packageName);
        return pk == null ? packageName : pk.getFullOutName();
    }

    /** Dump the content of the class tree to the specified file (used for logging). */
    public void dump(final PrintWriter log)
    {
        log.println("");
        walkTree(new TreeAction() {
            public void classAction(Cl cl) {
                final String name = cl.getFullInName();
                if (cl.isFromScript() && !"module-info".equals(name)) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(name));
                    log.println("  ");
                }
            }
            public void methodAction(Md md) {
                if (md.isFromScript()) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(md.getParent().getFullInName()));
                    String method = toUtf8XmlString(Conversion.toJavaMethod(md.getInName(), md.getDescriptor()));
                    log.println("  ");
                }
            }
            public void fieldAction(Fd fd) {
                if (fd.isFromScript()) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(fd.getParent().getFullInName()));
                    log.println("  ");
                }
            }
            public void packageAction(Pk pk) {
                // No action
            }
        });
        log.println("");
        log.println("");
        walkTree(new TreeAction() {
            public void classAction(Cl cl) {
                final String name = cl.getFullInName();
                if (!cl.isFromScript() && !"module-info".equals(name)) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(name));
                    log.println("  ");
                }
            }
            public void methodAction(Md md) {
                if (!md.isFromScript()) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(md.getParent().getFullInName()));
                    String method = toUtf8XmlString(Conversion.toJavaMethod(md.getInName(), md.getDescriptor()));
                    log.println("  ");
                }
            }
            public void fieldAction(Fd fd) {
                if (!fd.isFromScript()) {
                    String cla = toUtf8XmlString(Conversion.toJavaClass(fd.getParent().getFullInName()));
                    log.println("  ");
                }
            }
            public void packageAction(Pk pk) {
                if (!pk.isFromScript() && pk.getFullInName().length() > 0) {
                    String pa = toUtf8XmlString(Conversion.toJavaClass(pk.getFullInName()));
                    log.println("  ");
                }
            }
        });
        log.println("");
    }

    public static final String toUtf8XmlString(String s){
      boolean bad = false;
      for (int i = 0; i< s.length(); i++){
        char c = s.charAt(i);
        if ((c >= 0x80) || (c =='"') || (c == '<')){
          bad = true;
          break;
        }
      }
      if (bad){
        StringBuffer buf = new StringBuffer(s.length());
        for (int i = 0; i < s.length(); i++){
          buf.append(toUtf8XmlChar(s.charAt(i)));
        }
        return buf.toString();
      } else {
        return s;
      }
    }

    private static final String toUtf8XmlChar(char c){
      if (c < 0x80){
        if (c == '"'){
          return """;
        } else if (c == '<'){
          return "<";
        }
        return new String(new char[]{c});
      }
      else if (c < 0x800)
      {
        StringBuffer buf = new StringBuffer(8);
        buf.append("&#x");
        buf.append(hex[(c >> 8) & 0xff]);
        buf.append(hex[c & 0xff]);
        buf.append(';');
        return buf.toString();
      }
      else
      {
        StringBuffer buf = new StringBuffer(10);
        buf.append("&#x");
        buf.append(hex[(c >> 16) & 0xff]);
        buf.append(hex[(c >> 8) & 0xff]);
        buf.append(hex[c & 0xff]);
        buf.append(';');
        return buf.toString();
      }
    }

    private static final String[] hex;
    static {
      hex = new String[256];
      for (int i = 0; i < 256; i++){
        hex[i] = toHex(i);
      }
    }

    private static final String hexChars = "0123456789abcdef";

    /** Holds value of property replaceClassNameStrings. */
    private boolean replaceClassNameStrings;

    /** Holds value of property pedantic. */
    private boolean pedantic;

    private static String toHex(int i){
      StringBuffer buf = new StringBuffer(2);
      buf.append(hexChars.charAt((i/16)&15));
      buf.append(hexChars.charAt(i&15));
      return buf.toString();
    }

    // Private Methods -------------------------------------------------------
    // Mark TreeItem and all parents for retention.
    private void retainHierarchy(TreeItem ti)
    {
        if (!ti.isFixed())
        {
            ti.setOutName(ti.getInName());
            ti.setFromScript();
        }
        if (ti.parent != null)
        {
            retainHierarchy(ti.parent);
        }
    }

    /** Walk the whole tree taking action once only on each package level, class, method and field. */
    public void walkTree(TreeAction ta)
    {
        walkTree(ta, root);
    }

    // Walk the tree which has TreeItem as its root taking action once only on each
    // package level, class, method and field.
    private void walkTree(TreeAction ta, TreeItem ti)
    {
        if (ti instanceof Pk)
        {
            Enumeration packageEnum = ((Pk)ti).getPackageEnum();
            ta.packageAction((Pk)ti);
            while (packageEnum.hasMoreElements())
            {
                walkTree(ta, (TreeItem)packageEnum.nextElement());
            }
        }
        if (ti instanceof PkCl)
        {
            Enumeration classEnum = ((PkCl)ti).getClassEnum();
            while (classEnum.hasMoreElements())
            {
                walkTree(ta, (TreeItem)classEnum.nextElement());
            }
        }
        if (ti instanceof Cl)
        {
            Enumeration fieldEnum = ((Cl)ti).getFieldEnum();
            Enumeration methodEnum = ((Cl)ti).getMethodEnum();
            ta.classAction((Cl)ti);
            while (fieldEnum.hasMoreElements())
            {
                ta.fieldAction((Fd)fieldEnum.nextElement());
            }
            while (methodEnum.hasMoreElements())
            {
                ta.methodAction((Md)methodEnum.nextElement());
            }
        }
    }

    /** Getter for property replaceClassNameStrings.
     * @return Value of property replaceClassNameStrings.
     *
     */
    public boolean isReplaceClassNameStrings()
    {
      return this.replaceClassNameStrings;
    }

    /** Setter for property replaceClassNameStrings.
     * @param replaceClassNameStrings New value of property replaceClassNameStrings.
     *
     */
    public void setReplaceClassNameStrings(boolean replaceClassNameStrings)
    {
      this.replaceClassNameStrings = replaceClassNameStrings;
    }

    /** Getter for property pedantic.
     * @return Value of property pedantic.
     *
     */
    public boolean isPedantic()
    {
      return this.pedantic;
    }

    /** Setter for property pedantic.
     * @param pedantic New value of property pedantic.
     *
     */
    public void setPedantic(boolean pedantic)
    {
      this.pedantic = pedantic;
    }

  public void retainSourceFileAttributeMap(String name, String obfName) {
    for (Enumeration clEnum = getClEnum(name); clEnum.hasMoreElements(); )
    {
      Cl classItem = (Cl)clEnum.nextElement();
      classItem.setSourceFileMapping(obfName);
      classItem.getAttributesToKeep().add(ClassConstants.ATTR_SourceFile);
    }
  }

  public void retainLineNumberTable(String name, final LineNumberTableMapper lineNumberTableMapper) {
    for (Enumeration clEnum = getClEnum(name); clEnum.hasMoreElements(); )
    {
      Cl classItem = (Cl)clEnum.nextElement();
      classItem.setLineNumberTableMapper(lineNumberTableMapper);
      classItem.getAttributesToKeep().add(ClassConstants.ATTR_LineNumberTable);
    }
  }

  public void retainAttributeForClass(String className, String attributeDescriptor) {
    for (Enumeration clEnum = getClEnum(className); clEnum.hasMoreElements(); )
    {
      Cl classItem = (Cl)clEnum.nextElement();
      final Set set = classItem.getAttributesToKeep();
      set.add(attributeDescriptor);
    }
  }

  public void retainPackage(String packageName) {
    retainHierarchy(getPk(packageName));
  }
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy