/* Soot - a J*va Optimization Framework
 * Copyright (C) 2003 Jerome Miecznikowski
 *
 * 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.1 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.
 */

package soot.dava.toolkits.base.misc;

import soot.*;
import java.io.*;
import java.util.*;
import soot.util.*;
import soot.dava.*;
import java.util.jar.*;

public class PackageNamer
{
    public PackageNamer( Singletons.Global g ) {}
    public static PackageNamer v() { return G.v().soot_dava_toolkits_base_misc_PackageNamer(); }

    public boolean has_FixedNames(){	
    	return fixed;
    }

    public boolean use_ShortName( String fixedPackageName, String fixedShortClassName){
    	if (fixed == false)
    		return false;

    	if (fixedPackageName.equals( Dava.v().get_CurrentPackage()))
    		return true;

    	IterableSet packageContext = Dava.v().get_CurrentPackageContext();
    	if (packageContext == null)
    		return true;

    	packageContext = patch_PackageContext( packageContext);

    	int count = 0;
    	StringTokenizer st = new StringTokenizer( classPath, pathSep);
    	while (st.hasMoreTokens()) {
    		String classpathDir = st.nextToken();
    		
    		Iterator packIt = packageContext.iterator();
    		while (packIt.hasNext()) 
    			if (package_ContainsClass( classpathDir, (String) packIt.next(), fixedShortClassName))
    				if (++count > 1)
    					return false;
    	}

    	return true;
    }

    public String get_FixedClassName( String originalFullClassName)
    {
	if (fixed == false)
	    return originalFullClassName;

	Iterator<NameHolder> it = appRoots.iterator();
	while (it.hasNext()) {
	    NameHolder h = it.next();
	    if (h.contains_OriginalName( new StringTokenizer( originalFullClassName, "."), true))
		return h.get_FixedName( new StringTokenizer( originalFullClassName, "."), true);
	}
	
	return originalFullClassName.substring( originalFullClassName.lastIndexOf( ".") + 1);
    }

    public String get_FixedPackageName( String originalPackageName)
    {
	if (fixed == false)
	    return originalPackageName;

	if (originalPackageName.equals( ""))
	    return "";

	Iterator<NameHolder> it = appRoots.iterator();
	while (it.hasNext()) {
	    NameHolder h = it.next();
	    if (h.contains_OriginalName( new StringTokenizer( originalPackageName, "."), false))
		return h.get_FixedName( new StringTokenizer( originalPackageName, "."), false);
	}

	return originalPackageName;
    }


    private class NameHolder
    {
    	private final String originalName;
		private String packageName, className;
    	private final ArrayList<NameHolder> children;
    	private NameHolder parent;
    	private boolean isClass;
	

    	public NameHolder( String name, NameHolder parent, boolean isClass)
    	{
    		originalName = name;
    		className = name;
    		packageName = name;
    		
    		this.parent = parent;
    		this.isClass = isClass;

    		children = new ArrayList<NameHolder>();
    	}

    	public NameHolder get_Parent() {
			return parent;
		}

		public void set_ClassAttr() {
			isClass = true;
		}

		public boolean is_Class() {
			if (children.isEmpty())
				return true;
			else
				return isClass;
		}

		public boolean is_Package() {
			return (children.isEmpty() == false);
		}

		public String get_PackageName() {
			return packageName;
		}

		public String get_ClassName() {
			return className;
		}

		public void set_PackageName(String packageName) {
			this.packageName = packageName;
		}

		public void set_ClassName(String className) {
			this.className = className;
		}

		public String get_OriginalName() {
			return originalName;
		}

		public ArrayList<NameHolder> get_Children() {
			return children;
		}

		public String get_FixedPackageName() {
			if (parent == null)
				return "";

			return parent.retrieve_FixedPackageName();
		}

	public String retrieve_FixedPackageName()
	{
	    if (parent == null)
		return packageName;
	    
	    return parent.get_FixedPackageName() + "." + packageName;
	}

	public String get_FixedName( StringTokenizer st, boolean forClass)
	{
	    if (st.nextToken().equals( originalName) == false)
		throw new RuntimeException( "Unable to resolve naming.");

	    return retrieve_FixedName( st, forClass);
	}

	private String retrieve_FixedName( StringTokenizer st, boolean forClass)
	{
	    if (st.hasMoreTokens() == false) {
		if (forClass)
		    return className;
		else
		    return packageName;
	    }

	    String subName = st.nextToken();
	    Iterator<NameHolder> cit = children.iterator();
	    while (cit.hasNext()) {
	    	NameHolder h = cit.next();

	    	if (h.get_OriginalName().equals( subName)) {
	    		if (forClass)
	    			return h.retrieve_FixedName( st, forClass);
	    		else 
	    			return packageName + "." + h.retrieve_FixedName( st, forClass);
	    	}
	    }
	    throw new RuntimeException( "Unable to resolve naming.");
	}

	public String get_OriginalPackageName( StringTokenizer st)
	{
	    if (st.hasMoreTokens() == false)
		return get_OriginalName();

	    String subName = st.nextToken();

	    Iterator<NameHolder> cit = children.iterator();
	    while (cit.hasNext()) {
		NameHolder h = cit.next();

		if (h.get_PackageName().equals( subName)) {
		    String originalSubPackageName = h.get_OriginalPackageName( st);

		    if (originalSubPackageName == null)
			return null;
		    else
			return get_OriginalName() + "." + originalSubPackageName;
		}
	    }

	    return null;
	}

	public boolean contains_OriginalName( StringTokenizer st, boolean forClass)
	{
	    if (get_OriginalName().equals( st.nextToken()) == false)
		return false;

	    return finds_OriginalName( st, forClass);
	}

	private boolean finds_OriginalName( StringTokenizer st, boolean forClass)
	{
	    if (st.hasMoreTokens() == false)
		return (((forClass) && (is_Class())) || ((!forClass) && (is_Package())));

	    String subName = st.nextToken();
	    Iterator<NameHolder> cit = children.iterator();
	    while (cit.hasNext()) {
		NameHolder h = cit.next();

		if (h.get_OriginalName().equals( subName))
		    return h.finds_OriginalName( st, forClass);
	    }
	    
	    return false;
	}

	public void fix_ClassNames( String curPackName)
	{
	    if ((is_Class()) && (keywords.contains( className))) {
		String tClassName = className;
		
		if (Character.isLowerCase( className.charAt( 0))) {
		    tClassName = tClassName.substring( 0, 1).toUpperCase() + tClassName.substring( 1);
		    className = tClassName;
		}
		
		for (int i=0; keywords.contains( className); i++)
		    className = tClassName + "_c" + i;
	    }

	    Iterator<NameHolder> it = children.iterator();
	    while (it.hasNext())
		it.next().fix_ClassNames( curPackName + "." + packageName);
	}

	public void fix_PackageNames()
	{
	    if ((is_Package()) && (verify_PackageName() == false)) {
		String tPackageName = packageName;

		if (Character.isUpperCase( packageName.charAt( 0))) {
		    tPackageName = tPackageName.substring( 0, 1).toLowerCase() + tPackageName.substring( 1);
		    packageName = tPackageName;
		}
		
		for (int i=0; verify_PackageName() == false; i++)
		    packageName = tPackageName + "_p" + i;
	    }

	    Iterator<NameHolder> it = children.iterator();
	    while (it.hasNext())
		it.next().fix_PackageNames();
	}


	public boolean verify_PackageName()
	{
	    return ((keywords.contains( packageName) == false) && 
		    (siblingClashes( packageName) == false) &&
		    ((is_Class() == false) || (className.equals( packageName) == false)));
	}

	public boolean siblingClashes( String name)
	{
	    Iterator<NameHolder> it = null;

	    if (parent == null) {

		if (appRoots.contains( this))
		    it = appRoots.iterator();
		else
		    throw new RuntimeException( "Unable to find package siblings.");
	    }
	    else 
		it = parent.get_Children().iterator();

	    while (it.hasNext()) {
		NameHolder sibling = it.next();

		if (sibling == this)
		    continue;

		if (((sibling.is_Package()) && (sibling.get_PackageName().equals( name))) ||
		    ((sibling.is_Class()) && (sibling.get_ClassName().equals( name))))
		    
		    return true;
	    }

	    return false;
	}

	public void dump( String indentation)
	{
	    G.v().out.print( indentation + "\"" + originalName + "\", \"" + packageName + "\", \"" + className + "\" (");
	    if (is_Class())
		G.v().out.print("c");
	    if (is_Package())
		G.v().out.print("p");
	    G.v().out.println( ")");

	    Iterator<NameHolder> it = children.iterator();
	    while (it.hasNext())
	    	it.next().dump( indentation + "  ");
		}
    }

    private boolean fixed = false;
    private final ArrayList<NameHolder> appRoots = new ArrayList<NameHolder>();
    private final ArrayList<NameHolder> otherRoots = new ArrayList<NameHolder>();
    private final HashSet<String> keywords = new HashSet<String>();
    private char fileSep;
    private String classPath, pathSep;
    
    public void fixNames()
    {
	if (fixed)
	    return;

	String[] keywordArray =
	{
	    "abstract",	    "default",	    "if",            "private",	    "this",	    "boolean",
	    "do",	    "implements",	    "protected",	    "throw",	    "break",
	    "double",	    "import",	    "public",	    "throws",	    "byte",	    "else",
	    "instanceof",	    "return",	    "transient",	    "case",	    "extends",
	    "int",	    "short",	    "try",	    "catch",	    "final",	    "interface",
	    "static",	    "void",             "char",	    "finally",	    "long",	    "strictfp",
	    "volatile",	    "class",	    "float",	    "native",	    "super",	    "while",
	    "const",	    "for",	    "new",	    "switch",	    "continue",	    "goto",
	    "package",	    "synchronized",	    "true",	    "false",	    "null"
	};

	for (String element : keywordArray)
		keywords.add( element);

	Iterator classIt = Scene.v().getLibraryClasses().iterator();
	while (classIt.hasNext())
	    add_ClassName( ((SootClass) classIt.next()).getName(), otherRoots);

	classIt = Scene.v().getApplicationClasses().iterator();
	while (classIt.hasNext())
	    add_ClassName( ((SootClass) classIt.next()).getName(), appRoots);

	Iterator<NameHolder> arit = appRoots.iterator();
	while (arit.hasNext())
	    arit.next().fix_ClassNames( "");

	arit = appRoots.iterator();
	while (arit.hasNext())
	    arit.next().fix_PackageNames();
	
	fileSep = System.getProperty( "file.separator").charAt(0);
	pathSep = System.getProperty( "path.separator");
	classPath = System.getProperty( "java.class.path");
	
	fixed = true;
    }

    private void add_ClassName( String className, ArrayList<NameHolder> roots)
    {
	ArrayList<NameHolder> children = roots;
	NameHolder curNode = null;
	
	StringTokenizer st = new StringTokenizer( className, ".");
	while (st.hasMoreTokens()) {
	    String curName = st.nextToken();

	    NameHolder child = null;
	    boolean found = false;
	    Iterator<NameHolder> lit = children.iterator();
	    
	    while (lit.hasNext()) {
		child = lit.next();
		
		if (child.get_OriginalName().equals( curName)) {

		    if (st.hasMoreTokens() == false)
			child.set_ClassAttr();

		    found = true;
		    break;
		}
	    }
		
	    if (!found) {
		child = new NameHolder( curName, curNode, st.hasMoreTokens() == false);
		children.add( child);
	    }
	    
	    curNode = child;
	    children = child.get_Children();
	}
    }

    public boolean package_ContainsClass( String classpathDir, String packageName, String className)
    {
	File p = new File( classpathDir);

	if (p.exists() == false)
	    return false;

	packageName = packageName.replace( '.', fileSep);
	if ((packageName.length() > 0) && (packageName.charAt( packageName.length() - 1) != fileSep))
	    packageName += fileSep;

	String name = packageName + className + ".class";

	if (p.isDirectory()) {
	    if ((classpathDir.length() > 0) && (classpathDir.charAt( classpathDir.length() - 1) != fileSep))
		classpathDir += fileSep;
	    
	    return (new File( classpathDir + name)).exists();
	}

	else {
	    JarFile jf = null;

	    try {
		jf = new JarFile( p);
	    }
	    catch (IOException ioe) {
		return false;
	    }

	    return (jf.getJarEntry( name) != null);
	}
    }

    IterableSet patch_PackageContext( IterableSet currentContext) 
    {
	IterableSet newContext = new IterableSet();

	Iterator it = currentContext.iterator();
	while (it.hasNext()) {
	    String 
		currentPackage = (String) it.next(),
		newPackage = null;

	    StringTokenizer st = new StringTokenizer( currentPackage, ".");
	    
	    if (st.hasMoreTokens() == false) {
		newContext.add( currentPackage);
		continue;
	    }

	    String firstToken = st.nextToken();
	    Iterator<NameHolder> arit = appRoots.iterator();
	    while (arit.hasNext()) {
		NameHolder h = arit.next();

		if (h.get_PackageName().equals( firstToken)) {
		    newPackage = h.get_OriginalPackageName( st);
		    break;
		}
	    }
	    if (newPackage != null)
		newContext.add( newPackage);
	    else
		newContext.add( currentPackage);
	}

	return newContext;
    }
}
