/*******************************************************************************
* Copyright (c) 2006 IONA Technologies PLC
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* 
* Contributors:
*     IONA Technologies PLC - initial API and implementation
*******************************************************************************/
package org.eclipse.stp.sc.common.utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.URL;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;


public class JDTUtils {
    public static final String JAVA_FILE_EXT = "java";
    
    private static final LoggingProxy LOG = LoggingProxy.getlogger(JDTUtils.class);

    protected JDTUtils() {
    }

    /**
     * get the output classes location of user assigned project
     * @param project, the project
     * @return IPath, root path to the output classes
     * @throws JavaModelException problem to load the java project
     */
    public static IPath getProjectOutputPath(IProject project)
        throws JavaModelException {
        IJavaProject javaProject = findJavaProject(project.getName());

        return javaProject.getOutputLocation();
    }

    /**
     * get classpath for java project within workspace.
     * this will return the classpath defined in jdt for this project and
     * project output classpath.
     * this method is moved from artix designer javatowsdl wizard
     * @return
     */
    @SuppressWarnings("deprecation")
    public static URL[] getProjectClasspath(IProject project) {
        try {
            //get the root of the workspace
            IWorkspace workspace = ResourcesPlugin.getWorkspace();
            String rootDir = workspace.getRoot().getLocation().toOSString();

            //LOG.debug("workspace root: " + rootDir);
            //create the initClassPath
            IJavaProject javaProject = findJavaProject(project.getName());
            IClasspathEntry[] rawClassPath = javaProject.getResolvedClasspath(true);

            IPath[] initClassPath = new Path[rawClassPath.length];

            for (int i = 0; i < rawClassPath.length; i++) {
                initClassPath[i] = rawClassPath[i].getPath();
            }

            //process the initClassPath, support the imported user class file,
            //include linked folder
            //for (int i = 0; i < initClassPath.length; i++) {
            for (int i = 0; i < initClassPath.length; i++) {
                if ((!initClassPath[i].toOSString().startsWith(rootDir))
                        && (!initClassPath[i].toFile().isFile())
                        && (!initClassPath[i].toFile().isDirectory())) {
                    
                    IResource res = workspace.getRoot().findMember(initClassPath[i]);

                    if ((res != null) && (res.isLinked())) {
                        initClassPath[i] = res.getRawLocation();
                    } else {
                        String classPathToProcess = initClassPath[i].toOSString();
                        IPath classPathAfterProcess = new Path(rootDir + File.separator + classPathToProcess);
                        initClassPath[i] = classPathAfterProcess;
                    }
                }
            }

            // construct the project outputClassPath
            String outputDir = rootDir + File.separator + javaProject.getOutputLocation().toOSString();

            //LOG.debug("outputClassPath: " + outputDir);
            File outputClassPath = new File(outputDir);

            // combine the processed initClassPath and outputClassPath to
            // targetClassPath
            URL[] targetClassPath = new URL[initClassPath.length + 1];
            targetClassPath[0] = outputClassPath.toURL();

            for (int i = 0; i < initClassPath.length; i++) {
                File tempClassPath = new File(initClassPath[i].toOSString());
                targetClassPath[i + 1] = tempClassPath.toURL();
            }
            return targetClassPath;

        } catch (Exception e) {
            LOG.error("", e);
            return null;
        }
    }

    /**
     * get the java project's classpath, seperated by File.pathSeperator
     * @param project
     * @return
     */
    public static String getProjectClasspathAsString(IProject project) {
        URL[] projectClasspaths = JDTUtils.getProjectClasspath(project);
        if (projectClasspaths == null) {
        	return "";
        }
        StringBuffer sb = new StringBuffer();

        for (URL url : projectClasspaths) {
            String strClasspath = url.getFile();

            // convert slashes to target platform
            String os = System.getProperty("os.name");

            if (os.indexOf("Window") >= 0) {
                strClasspath = strClasspath.substring(1);
                strClasspath = strClasspath.replace('/', '\\');
            } else {
                strClasspath = strClasspath.replace('\\', '/');
            }

            sb.append(strClasspath);
            sb.append(File.pathSeparator);
        }

        return sb.toString();
    }


    public static ICompilationUnit findUnitByFileName(IJavaElement javaElem,
                                                      String filePath) throws Exception {
        ICompilationUnit unit = null;

        if (!javaElem.getOpenable().isOpen()) {
            javaElem.getOpenable().open(null);
        }

        IJavaElement[] elems = null;

        if (javaElem instanceof IParent) {
            IParent parent = (IParent)javaElem;
            elems = parent.getChildren();
        }

        if (elems == null) {
            return null;
        }

        for (IJavaElement elem : elems) {
            if (elem.getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT) {
                IPackageFragmentRoot root = (IPackageFragmentRoot)elem;

                if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
                    unit = findUnitByFileName(elem, filePath);

                    if (unit != null) {
                        return unit;
                    }
                }
            } else if ((elem.getElementType() == IJavaElement.PACKAGE_FRAGMENT)
                    || (elem.getElementType() == IJavaElement.JAVA_PROJECT)) {
                unit = findUnitByFileName(elem, filePath);

                if (unit != null) {
                    return unit;
                }
            } else if (elem.getElementType() == IJavaElement.COMPILATION_UNIT) {
                ICompilationUnit compUnit = (ICompilationUnit)elem;

                if (compUnit.getPath().toString().equals(filePath)) {
                    compUnit.open(null);

                    return compUnit;
                }
            }
        }

        return null;
    }

    public static IJavaProject getJavaProjectByName(String projectName) throws JavaModelException {

        IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
        model.open(null);

        IJavaProject[] projects = model.getJavaProjects();

        for (IJavaProject proj : projects) {
            if (proj.getProject().getName().equals(projectName)) {
                return proj;
            }
        }

        return null;
    }

    /**
     * get Java compilation unit by file path
     * @param javaFile the java sour file to look
     * @return ICompilationUnit, JDK compilation unit for this java file.
     */
    public static ICompilationUnit getJavaUnitFromFile(IFile javaFile) {
        try {
            IJavaProject project = getJavaProjectByName(javaFile.getProject().getName());

            if (project == null) {
                return null;
            }

            return findUnitByFileName(project, javaFile.getFullPath().toString());
        } catch (Exception e) {
            LOG.error("", e);
            return null;
        }
    }

    /**
     * get java class full name from java source file
     * @return
     */
    public static String getJavaClassNameFromFile(IFile javaFile) {
        try {
            ICompilationUnit javaUnit = getJavaUnitFromFile(javaFile);
            String clsName = "";
            IPackageDeclaration[] pkgs = javaUnit.getPackageDeclarations();

            if ((pkgs != null) && (pkgs.length > 0)) {
                clsName = pkgs[0].getElementName();
                clsName += "." + javaUnit.getElementName().substring(0,
                        	javaUnit.getElementName().lastIndexOf("."));
            }else{
            	clsName = javaUnit.getElementName().substring(0,
                        	javaUnit.getElementName().lastIndexOf("."));
            }


            return clsName;
        } catch (Exception e) {
            LOG.error("", e);
            return null;
        }
    }

    /**
     * Construct the root complilation unit DOM object for the specified <code>IMember</code>.
     * @param jdtMember an member of the java model
     * @return compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     */
    public static CompilationUnit getDomRootCompilationUnit(IMember jdtMember) {
    	return getDomRootCompilationUnit(jdtMember.getCompilationUnit());
    }
    
    public static CompilationUnit getDomRootCompilationUnit(ICompilationUnit unit) {
        ASTParser parser = ASTParser.newParser(AST.JLS3);
        parser.setSource(unit);

        return (CompilationUnit)parser.createAST(null);
    } 

    /**
     * retrieves the annotations for a specific method parameter
     * @param compilationUnitAstNode the root AST node against which we want the annotations ... and later on
     *  make the changes to...
     * @param method
     * @param parameterName 
     * @return the annotation data
     */
    public static List<Annotation> getParameterAnnotations(CompilationUnit compilationUnitAstNode,
                                                           IMethod method,
                                                           String parameterName) {

        SingleVariableDeclaration methodParamDecl = getMethodParamDeclaration(compilationUnitAstNode,
                                                                              method,
                                                                              parameterName);

        return methodParamDecl == null ? null : getAnnotationsFromParamDecl(methodParamDecl);
    }

    /**
     * retrieves a method parameter declaration
     * @param compilationUnitAstNode the root AST node against which we want the declaration ... and later on
     *  make the changes to...
     * @param method
     * @param parameterName
     * @return
     */
    @SuppressWarnings("unchecked")
    public static SingleVariableDeclaration getMethodParamDeclaration(CompilationUnit compilationUnitAstNode,
                                                                      IMethod method,
                                                                      String parameterName) {
        BodyDeclaration bd = getBodyDeclaration(compilationUnitAstNode, method);
        if (!(bd instanceof MethodDeclaration)) {
            return null;
        }
        MethodDeclaration methodDesc = (MethodDeclaration)bd;
        Iterator paraItor = methodDesc.parameters().iterator();

        SingleVariableDeclaration methodParamDecl = null;
        while (paraItor.hasNext()) {
            SingleVariableDeclaration desc = (SingleVariableDeclaration)paraItor.next();

            if (desc.getName().getIdentifier().equals(parameterName)) {
                methodParamDecl = desc;
            }
        }
        return methodParamDecl;
    }
    
    /**
     * retrieves the annotations of a method parameter (via its delcaration)
     * @param desc
     * @return
     */public static List<Annotation> getAnnotationsFromParamDecl(SingleVariableDeclaration desc) {
        return getAnnotationListFromModifier(desc.modifiers());
    }

    /**
     * returns the <code>BodyDeclaration</code> associated to the of the specified java model element
     * @param jdtMember the <code>IMember</code> for which we want the <code>BodyDeclaration</code>
     * @return the associated <code>BodyDeclaration</code> if the specified <code>IMember</code> is either a
     *  class, method or attribute declaration and null otherwise
     */
    private static BodyDeclaration getBodyDeclaration(IMember jdtMember) {
        return getBodyDeclaration(JDTUtils.getDomRootCompilationUnit(jdtMember), jdtMember);
    }

    /**
     * returns the <code>BodyDeclaration</code> associated to the of the specified java model element
     * @param compilationUnitAstNode the root AST node against which we want the declaration ... and later on
     *  make the changes to...
     * @param jdtMember the <code>IMember</code> for which we want the <code>BodyDeclaration</code>
     * @return the associated <code>BodyDeclaration</code> if the specified <code>IMember</code> is either a
     *  class, method or attribute declaration and null otherwise
     */
    @SuppressWarnings("unchecked")
    public static BodyDeclaration getBodyDeclaration(CompilationUnit compilationUnitAstNode,
                                                     IMember jdtMember) {
        if (jdtMember == null) {
            return null;
        }

        String parentTypeName = (jdtMember.getDeclaringType() != null)
            ? jdtMember.getDeclaringType().getElementName()
            : jdtMember.getElementName();
        String seekedDeclarationName = jdtMember.getElementName();

        Iterator i = compilationUnitAstNode.types().iterator();

        while (i.hasNext()) {
        	Object obj = i.next();
        	//it maybe org.eclipse.jdt.core.dom.EnumDeclaration
            if (!(obj instanceof TypeDeclaration)) {
            	return null;
            }
            TypeDeclaration td = (TypeDeclaration)obj;
            String tname = td.getName().getIdentifier();

            if (tname.equals(parentTypeName)) {
                //class declarations
                if (seekedDeclarationName.equals(tname)) {
                    return td;
                }

                //class attributes
                FieldDeclaration[] fields = td.getFields();

                for (int inx = 0; inx < fields.length; inx++) {
                    fields[inx].fragments();

                    for (Iterator j = fields[inx].fragments().iterator();
                            j.hasNext();) {
                        String name = ((VariableDeclarationFragment)j.next()).getName().getIdentifier();

                        if (seekedDeclarationName.equals(name)) {
                            return fields[inx];
                        }
                    }
                }

                //class methods
                MethodDeclaration[] methods = td.getMethods();

                for (int inx = 0; inx < methods.length; inx++) {
                    if (seekedDeclarationName.equals(methods[inx].getName().getIdentifier())) {
                        return methods[inx];
                    }
                }

                LOG.debug("the specified IMember '"
                          + jdtMember.getElementName()
                          + "' was neither a class, nor a method, nor an attribute declaration");

                return null;
            }
        }

        LOG.debug("no containing declaration element was found the specified IMember '"
                  + jdtMember.getElementName()
                  + "'.");

        return null;
    }

    /**
     * returns the initial copy of the JDT document containing the specified
     * @param jdtMember the relevant <code>IMember</code>
     * @return the containing <code>Document</code> or null if it doesn't exists or cannot be retrieved
     */
    public static Document getJavaDocumentCopy(IMember jdtMember) {
        try {
            return new Document(jdtMember.getCompilationUnit().getBuffer().getContents());
        } catch (JavaModelException e) {
            LOG.error("couldn't retrieve the jface.text.Document for the java member: "
                          + jdtMember.getElementName(),
                      e);

            return null;
        }
    }

    /**
     * gets all the annotations attached to the specified <code>BodyDeclaration</code>
     * @param the <code>BodyDeclaration</code> for which we wants the annotations info
     * @return the list of <code>Annotation</code> attached or an empty list if the
     * <code>BodyDeclaration</code> is unspecified/null
     */
    public static List<Annotation> getAnnotations(BodyDeclaration bodyDecl) {
        if (bodyDecl == null) {
            return new ArrayList<Annotation>(0);
        }

        return getAnnotationListFromModifier(bodyDecl.modifiers());
    }

    @SuppressWarnings("unchecked")
    private static List<Annotation> getAnnotationListFromModifier(List modifier) {
        List<Annotation> annotationNodesList = new ArrayList<Annotation>(modifier.size());

        for (Iterator iter = modifier.iterator(); iter.hasNext();) {
            Object currentModifier = iter.next();

            if (currentModifier instanceof Annotation) {
                annotationNodesList.add((Annotation)currentModifier);
            }
        }

        return annotationNodesList;
    }

    /**
     * gets all the annotations attached to the specified <code>IMember</code>
     * @param compilationUnitAstNode the root AST node against which we want the declaration ... and later on
     *  make the changes to...
     * @param the <code>BodyDeclaration</code> for which we wants the annotations info
     * @return the list of <code>Annotation</code> attached or an empty list if the
     * <code>BodyDeclaration</code> is unspecified/null
     */
    public static List<Annotation> getAnnotations(CompilationUnit compilationUnitAstNode, IMember jdtMember) {
        return getAnnotations(getBodyDeclaration(compilationUnitAstNode, jdtMember));
    }

    public static List<Annotation> getAnnotations(IMember jdtMember) {
        if (jdtMember == null) {
            return null;
        }
        return getAnnotations(getBodyDeclaration(jdtMember));
    }

    /**
     * checks whether the specified <code>Annotation</code> is currently present on
     * the specified <code>IMember</code>
     * @param member the <code>IMember</code> to check annotations on
     * @param annotationNode the <code>Annotation</code> to check for
     * @return true if the <code>Annotation</code> currently exists on this <code>IMember</code>, 
     * false if it doesn't
     */
    @SuppressWarnings("unchecked")
    public static boolean hasAnnotation(IMember member, Annotation annotationNode) {
        String annotName = getBasicAnnotName(annotationNode);

        for (Iterator it1 = getBodyDeclaration(member).modifiers().iterator(); it1.hasNext();) {

            Object o = it1.next();
            if (o instanceof Annotation) {
                String name = getBasicAnnotName((Annotation)o);

                if (name.equals(annotName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * retrieves a specific annotation from a given JDT memebr
     * @param compilationUnitAstNode the root AST node against which we want the declaration ... and later on
     *  make the changes to...
     * @param member
     * @param annotationNode
     * @return
     */
    public static Annotation findAnnotation(CompilationUnit compilationUnitAstNode,
                                            IMember member,
                                            Annotation annotationNode) {
        return findAnnotation(getAnnotations(compilationUnitAstNode, member), annotationNode);
    }
    
    /**
     * searches for the specified <code>Annotation</code> on the specified <code>IMember</code>
     * @param member the <code>IMember</code> to check annotations on
     * @param annotationNode the <code>Annotation</code> to check for
     * @return annotation if found, null if not found
     */
    @SuppressWarnings("unchecked")
    public static Annotation findAnnotation(List list, Annotation annotationNode) {
        String annotName = getBasicAnnotName(annotationNode);

        for (Iterator it1 = list.iterator(); it1.hasNext();) {

            Object o = it1.next();
            if (o instanceof Annotation) {
                String name = getBasicAnnotName((Annotation)o);

                if (name.equals(annotName)) {
                    return (Annotation)o;
                }
            }
        }

        return null;
    }

    public static String getBasicAnnotName(Annotation annotationNode) {
        Name typeName = annotationNode.getTypeName();
        String basicName = typeName.getFullyQualifiedName();
        if (typeName.isQualifiedName()) {
            basicName = ((QualifiedName) typeName).getName().getFullyQualifiedName();
        }
        return basicName;
    }
    
    /**
     * create a new <code>NormalAnnotation</code> for the specified <code>CompilationUnit</code>.
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param name the type name of the annotation
     * @param valuePairs array list of <code>MemberValuePair</code> objects to add to annotation
     * @return
     */
    @SuppressWarnings("unchecked")
    public static NormalAnnotation newNormalAnnotation(CompilationUnit compilationUnitAstNode,
                                                       String name,
                                                       List valuePairs) {
        AST ast = compilationUnitAstNode.getAST();
        NormalAnnotation normAnnotation = ast.newNormalAnnotation();
        Name typeName = ast.newName(name);
        normAnnotation.setTypeName(typeName);

        if (valuePairs != null) {
            for (int i = 0; i < valuePairs.size(); i++) {
                if (valuePairs.get(i) instanceof MemberValuePair) {
                    MemberValuePair memberValuePair = (MemberValuePair)valuePairs.get(i);
                    ((AbstractList<MemberValuePair>)normAnnotation.values()).add(memberValuePair);
                }
            }
        }

        return normAnnotation;
    }

    /**
     * create a new <code>SingleMemberAnnotation</code> for the specified <code>CompilationUnit</code>.
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param name the type name of the annotation
     * @param expression string express to add to annotation
     * @return <code>SingleMemberAnnotation</code>
     */
    public static SingleMemberAnnotation newSingleMemberAnnotation(CompilationUnit compilationUnitAstNode,
                                                                   String name,
                                                                   Expression exp) {
        AST ast = compilationUnitAstNode.getAST();
        SingleMemberAnnotation singleAnnotation = ast.newSingleMemberAnnotation();
        Name typeName = ast.newName(name);
        singleAnnotation.setTypeName(typeName);
        singleAnnotation.setValue(exp);

        return singleAnnotation;
    }

    /**
     * 
     * @param ast
     * @param primitive
     * @return
     */
    private static SimpleType convertPrimitiveTypeToSimple(AST ast, PrimitiveType primitive) {
        String clsName = "";
        if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.BYTE)) {
            clsName = Byte.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.BOOLEAN)) {
            clsName = Boolean.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.CHAR)) {
            clsName = Character.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.DOUBLE)) {
            clsName = Double.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.FLOAT)) {
            clsName = Float.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.INT)) {
            clsName = Integer.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.LONG)) {
            clsName = Long.class.getName();
        } else if (primitive.getPrimitiveTypeCode().equals(PrimitiveType.SHORT)) {
            clsName = Short.class.getName();
        } else {
            return null;
        }
                
        clsName = clsName.substring(clsName.lastIndexOf(".") + 1);
        SimpleName sName = ast.newSimpleName(clsName);
        return ast.newSimpleType(sName);
    }
    
    /**
     * create a new <code>MemberValuePair</code> attached to the specified <code>CompilationUnit</code> node
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param name the name to be given the new <code>MemberValuePair</code>
     * @param value the value to be given the new <code>MemberValuePair</code>
     * @return <code>MemberValuePair</code>
     */
    public static MemberValuePair newMemberValuePair(CompilationUnit compilationUnitAstNode,
                                                     String name,
                                                     Object value) {
        return newMemberValuePair(compilationUnitAstNode.getAST(), name, value, false); 
    }
    
    /**
     * create a new <code>MemberValuePair</code> attached to the specified <code>CompilationUnit</code> node
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param name the name to be given the new <code>MemberValuePair</code>
     * @param value the value to be given the new <code>MemberValuePair</code>
     * @param convertPrimitiveType for some case, we need to convert primitive type to their class.
     * such as convert 'int' to 'Integer.class'. 
     * @return <code>MemberValuePair</code>
     */
    @SuppressWarnings("unchecked")
    public static MemberValuePair newMemberValuePair(CompilationUnit compilationUnitAstNode,
                                                     String name, 
                                                     Object value,
                                                     boolean convertPrimitiveType) {
        return newMemberValuePair(compilationUnitAstNode.getAST(), name, value,  convertPrimitiveType);
    }

    /**
     * create a new <code>MemberValuePair</code> attached to the specified <code>AST</code> model
     * @param ast the model instance against which we want to work ... and later on make the changes to...
     * @param name the name to be given the new <code>MemberValuePair</code>
     * @param value the value to be given the new <code>MemberValuePair</code>
     * @param convertPrimitiveType for some case, we need to convert primitive type to their class.
     * such as convert 'int' to 'Integer.class'. 
     * @return <code>MemberValuePair</code>
     */
    @SuppressWarnings("unchecked")
    private static MemberValuePair newMemberValuePair(AST ast,
                                                      String name, 
                                                      Object value,
                                                      boolean convertPrimitiveType) {
        MemberValuePair valPair = ast.newMemberValuePair();
        valPair.setName(ast.newSimpleName(name));

        Expression exp = null;

        if (value instanceof String) {
            StringLiteral sl = ast.newStringLiteral();
            sl.setLiteralValue((String)value);
            exp = sl;
        
        } else if (value instanceof Boolean) {
            exp = ast.newBooleanLiteral(((Boolean)value).booleanValue());
        
        } else if (value instanceof Long) {
            exp = ast.newNumberLiteral(((Long)value).toString());
        
        } else if (value instanceof Integer) {
            exp = ast.newNumberLiteral(((Integer)value).toString());
        
        } else if (value instanceof Double) {
            exp = ast.newNumberLiteral(((Double)value).toString());
        
        } else if (value instanceof Float) {
            exp = ast.newNumberLiteral(((Float)value).toString());

        } else if (value instanceof Type) {
            TypeLiteral tl = ast.newTypeLiteral();

            //start to create new type node without parent for the typeliteral
            Type type = (Type)value;
            value = createType(ast, name, type, convertPrimitiveType);

//            if (type.isArrayType()) {
//            	System.err.println("component type:" 
//            			+ ((ArrayType)type).getComponentType());
//            	System.err.println("component type class:" 
//            			+ ((ArrayType)type).getComponentType().getClass().getName());
//                Type compType = ((ArrayType)type).getComponentType();
//                
////                value = ast.newArrayType(((ArrayType)type).getComponentType());
//            } else if (type.isParameterizedType()) {
//                value = ast.newParameterizedType(type);
//            } else if (type.isPrimitiveType()) {
//                if (convertPrimitiveType) {
//                    value = convertPrimitiveTypeToSimple(ast, (PrimitiveType)type);
//                } else {
//                    PrimitiveType pType = (PrimitiveType)type;
//                    value = ast.newPrimitiveType(pType.getPrimitiveTypeCode());
//                }
//            } else if (type.isQualifiedType()) {
//            	// comment out by frank? why?
//                 value = ast.newQualifiedType(type, ast.newSimpleName(name));
//            } else if (type.isSimpleType()) {
//            	// comment out by frank? why?
//                 SimpleType st = (SimpleType)type;
//                 SimpleName sName = ast.newSimpleName(st.getName().getFullyQualifiedName());
//                 value = ast.newSimpleType(sName);
//            }

            tl.setType((Type)value);
            exp = tl;
        } else if (value.getClass().isArray()) {
            //StringArray
       		String[] strings = (String[])value;
       		ArrayInitializer arrayInit = ast.newArrayInitializer();
       		List expList = arrayInit.expressions();

       		for (String str : strings) {
       			StringLiteral sl = ast.newStringLiteral();
       			sl.setLiteralValue(str);
       			expList.add(sl);
       		}

       		exp = arrayInit;
        } else if (value.getClass().isEnum()) {
            LOG.debug("handling enum attribute");

            
            Name enumName = null;
            
            if(value.getClass().isMemberClass()){
            	enumName = ast.newSimpleName(value.getClass().getEnclosingClass().getSimpleName());
            	enumName = ast.newQualifiedName(enumName, ast.newSimpleName(value.getClass().getSimpleName()));
            }else
            {
            	enumName = ast.newSimpleName(value.getClass().getSimpleName());
            }
            enumName = ast.newQualifiedName(enumName, ast.newSimpleName(value.toString()));
            exp = enumName;
        } else if (value instanceof Class) {
            LOG.debug("handling class attribute");
            exp = createTypeLiteralFromClass(ast, ((Class)value).getCanonicalName());

        }

        valPair.setValue(exp);

        return valPair;
    }
    
    private static Type createType(AST ast, String name, Type type, boolean convertPrimitiveType) {
    	Type value = null;
        if (type.isArrayType()) {
            Type compType = ((ArrayType)type).getComponentType();
            //we need to create new comp type below. otherwise, will got error
            Type newCompType = createType(ast, name, compType, convertPrimitiveType);
            value = ast.newArrayType(newCompType);
        } else if (type.isParameterizedType()) {
        	ParameterizedType pt = (ParameterizedType)type;
//        	pt.typeArguments()
//            value = ast.newParameterizedType(type);
        	System.err.println("pt type:" + pt.getType());
        	System.err.println("pt args type:" + pt.typeArguments().get(0));
        	Type newParmType = createType(ast, name, (Type)pt.typeArguments().get(0), convertPrimitiveType);
            value = ast.newParameterizedType(newParmType);
        } else if (type.isPrimitiveType()) {
            if (convertPrimitiveType) {
                value = convertPrimitiveTypeToSimple(ast, (PrimitiveType)type);
            } else {
                PrimitiveType pType = (PrimitiveType)type;
                value = ast.newPrimitiveType(pType.getPrimitiveTypeCode());
            }
        } else if (type.isQualifiedType()) {
        	// comment out by frank? why?
             value = ast.newQualifiedType(type, ast.newSimpleName(name));
        } else if (type.isSimpleType()) {
        	// comment out by frank? why?
             SimpleType st = (SimpleType)type;
             SimpleName sName = ast.newSimpleName(st.getName().getFullyQualifiedName());
             value = ast.newSimpleType(sName);
        }
    	return value;
    }

    /**
     * add an "import" statement to a java document...provided thatit's there yet 
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param importStatement
     * @param rewrite
     */
    @SuppressWarnings("unchecked")
    public static void addImport(CompilationUnit compilationUnitAstNode,
                                 String importStatement,
                                 ASTRewrite rewrite) {
        for (Iterator it = compilationUnitAstNode.imports().iterator(); it.hasNext();) {
            ImportDeclaration imp = (ImportDeclaration)it.next();

            if (imp.getName().getFullyQualifiedName().equals(importStatement)) {
                return; // the import is already in place
            }
        }

        ImportDeclaration imp = compilationUnitAstNode.getAST().newImportDeclaration();
        Name impname = compilationUnitAstNode.getAST().newName(importStatement);
        imp.setName(impname);

        ListRewrite lrw1 = rewrite.getListRewrite(compilationUnitAstNode, CompilationUnit.IMPORTS_PROPERTY);
        lrw1.insertLast(imp, null);
    }
    
    /**
     * add any "import" statement for the specified annotation to a java document...provided thatit's there yet 
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param annotationNode
     * @param rewrite
     */
    
    public static void addAnnotationImport(CompilationUnit compilationUnitAstNode,
                                           Annotation annotationNode,
                                           ASTRewrite rewrite) {
    	Exception e  = new Exception();
    	e.printStackTrace();
    	
    	/*
        String fullAnno = ScAnnotationSupportUtils.getAnnotationImport(annotationNode);

        if (fullAnno == null) {
            fullAnno = JaxBindAnnotationUtils.getAnnotationImport(annotationNode);
        }
        
        if (fullAnno != null) {
            // usually happens when the annotation was manually added in the code with its fully qualified name
            //  (so no need for an import) 
            addImport(compilationUnitAstNode, fullAnno, rewrite);
        }
        */
        
        
    	//do nothing by default
    }
    
    
    

    /**
     * retrieves a field declaration within the spcified model
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param field
     * @return
     */
    @SuppressWarnings("unchecked")
    public static FieldDeclaration getFieldDeclaration(CompilationUnit compilationUnitAstNode, IField field) {
        IType type = field.getDeclaringType();

        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                String fieldName = field.getElementName();
                FieldDeclaration[] fields = t.getFields();

                for (int inx = 0; inx < fields.length; inx++) {
                    VariableDeclarationFragment variable = 
                        (VariableDeclarationFragment)fields[inx].fragments().get(0);

                    if (variable.getName().getIdentifier().equals(fieldName)) {
                        return fields[inx];
                    }
                }
            }
        }

        return null;
    }
    
    @SuppressWarnings("unchecked")
    public static void removeAnnotationOnField(CompilationUnit compilationUnitAstNode,
                                               Annotation annotationNode,
                                               IField field,
                                               ASTRewrite rewrite) {

    	IType type = field.getDeclaringType();

        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                FieldDeclaration[] fields = t.getFields();
                String fieldName = field.getElementName();

                for (int inx = 0; inx < fields.length; inx++) {
                	for (Object obj : fields[inx].fragments()) {
                		VariableDeclarationFragment var = (VariableDeclarationFragment)obj;
                		if (var.getName().getFullyQualifiedName().equals(fieldName)) {
                            ListRewrite lrw2 = rewrite.getListRewrite((ASTNode)fields[inx],
                                                                      FieldDeclaration.MODIFIERS2_PROPERTY);
                            Annotation annot = findAnnotation(lrw2.getOriginalList(), annotationNode);
                            
                            if (annot != null) {
                            	lrw2.remove(annot, null);
                            }
                		}

                	}
                }
            }
        }
    	
    }

    @SuppressWarnings("unchecked")
    public static void removeAnnotationOnMethod(CompilationUnit compilationUnitAstNode,
                                                Annotation annotationNode,
                                                IMethod method,
                                                ASTRewrite rewrite) {
        IType type = method.getDeclaringType();

        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                MethodDeclaration[] meths = t.getMethods();
                String methodName = method.getElementName();

                for (int inx = 0; inx < meths.length; inx++) {
                    if (meths[inx].getName().getIdentifier().equals(methodName)) {
                        ListRewrite lrw2 = rewrite.getListRewrite((ASTNode)meths[inx],
                                                                  MethodDeclaration.MODIFIERS2_PROPERTY);
                        Annotation annot = findAnnotation(lrw2.getOriginalList(), annotationNode);

                        if (annot != null) {
                            lrw2.remove(annot, null);
                        }
                    }
                }
            }
        }
    }

    public static void removeAnnotationOnMethodParam(CompilationUnit compilationUnitAstNode,
                                                     Annotation annotationNode,
                                                     SingleVariableDeclaration memberParam,
                                                     ASTRewrite rewrite) {

        ListRewrite lrw2 = rewrite.getListRewrite(memberParam,
                                                  SingleVariableDeclaration.MODIFIERS2_PROPERTY);
        Annotation annot = findAnnotation(lrw2.getOriginalList(), annotationNode);

        if (annot != null) {
            lrw2.remove(annot, null);
        }
    }

    @SuppressWarnings("unchecked")
    public static void removeAnnotationOnType(CompilationUnit compilationUnitAstNode,
                                              Annotation annotationNode,
                                              IType type,
                                              ASTRewrite rewrite) {
        
        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                ListRewrite lrw2 = rewrite.getListRewrite(t, TypeDeclaration.MODIFIERS2_PROPERTY);
                Annotation annot = findAnnotation(lrw2.getOriginalList(), annotationNode);

                if (annot != null) {
                    lrw2.remove(annot, null);
                }
            }
        }

        
        
    }

    /**
    * Returns the smallest element within this java file that includes the given source position
    * (that is, a method, field, etc.), or null if:
    * <br> - there is no element other than the compilation unit itself at the given position
    * <br> - the given position is not within the source range of this java file
    * <br> - the java model cannot be processed
    * <br> - the file is not a java file
    * <br> - the file is null.
    * @param javaFile
    * @param offset
    * @return
    */
    public static IJavaElement getJavaElementFromFile(IFile javaFile, int offset) {
    	LOG.debug("Getting element from file: " + javaFile);
        if ((javaFile == null)
                || !javaFile.getFileExtension().equals(JAVA_FILE_EXT)) {
            //we only care about java files
            return null;
        }

        try {
            ICompilationUnit compilationUnitMember = getJavaUnitFromFile(javaFile);

            IJavaElement element = compilationUnitMember.getElementAt(offset);
            LOG.debug("selected offset:" + offset);
            LOG.debug("selected java element:" + element);

            return element;
        } catch (JavaModelException e) {
            LOG.error("", e);
            return null;
        }
    }
    
    @SuppressWarnings("unchecked")
    public static SingleVariableDeclaration getMethodParamDeclaration(CompilationUnit compilationUnitAstNode,
                                                                      IMethod method,
                                                                      int offset) {
        MethodDeclaration methodDesc; 
        if (compilationUnitAstNode == null) {
            methodDesc = (MethodDeclaration)getBodyDeclaration(method);
        } else {
            methodDesc = (MethodDeclaration)getBodyDeclaration(compilationUnitAstNode, method);
        }
        Iterator paraItor = methodDesc.parameters().iterator();

        while (paraItor.hasNext()) {
            SingleVariableDeclaration desc = (SingleVariableDeclaration)paraItor.next();
            int srcRangeOffset = desc.getStartPosition();
            int srcRangeLentgh = desc.getLength();
            if (offset >= srcRangeOffset && offset <= srcRangeOffset + srcRangeLentgh) {
                return desc;
            }
        }

        return null;
    }

    /**
     * find the class with implements user assigned interface within project
     * @param project, the project to find
     * @param interfaceName, the interface class name
     * @return implementation class name or null
     */
    public static String findImplClsName(IProject project, String interfaceName) {
        try {
            IType ifType = findTypeByName(project, interfaceName);

            //ITypeHierarchy hierarchy = ifType.newSupertypeHierarchy(null);
            ITypeHierarchy hierarchy = ifType.newTypeHierarchy(null);
            IType[] types = hierarchy.getImplementingClasses(ifType);

            for (IType type : types) {
                //todo: find the top level impl class for the input interface
                //      instead of return the first one.
                return type.getPackageFragment().getElementName() + "." + type.getElementName();
            }
        } catch (Exception e) {
            LOG.error("", e);
        }

        return null;
    }

    public static IType findTypeByName(IProject project, String clsName) {
        IType type = null;

        try {
            IJavaProject javaProject = getJavaProjectByName(project.getName());
            LOG.debug("try to find type:" + clsName);
            return javaProject.findType(clsName);
        } catch (Exception e) {
            LOG.error("", e);
        }

        return type;
    }

    public static IJavaProject findJavaProject(String projectName) {

        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IJavaModel javaModel = JavaCore.create(workspace.getRoot());
        return javaModel.getJavaProject(projectName);
    }
    
    
    
    
    
    /**
     * remove 1 annotation from a member of a java compilation unit.
     * this method actually goes through all the process of updating the physical java file 
     */
    public static void removeAnnotationFromCu(ICompilationUnit compilationUnitMember,
                                              CompilationUnit compilationUnitAstNode,
                                              Annotation annotationNode,
                                              IMember member,
                                              SingleVariableDeclaration methodParam) throws JavaModelException,
                                                                                            MalformedTreeException,
                                                                                            BadLocationException {
        
        List<Annotation> annotationNodes = new ArrayList<Annotation>(1);
        annotationNodes.add(annotationNode);
        removeAnnotationsFromCu(compilationUnitMember,
                                compilationUnitAstNode,
                                annotationNodes,
                                member,
                                methodParam);
    }
    
    /**
     * remove a series of annotations from a member of a java compilation unit.
     * this method actually goes through all the process of updating the physical java file 
     */
    public static void removeAnnotationsFromCu(ICompilationUnit compilationUnitMember,
                                               CompilationUnit compilationUnitAstNode,
                                               List<Annotation> annotationNodes,
                                               IMember member,
                                               SingleVariableDeclaration methodParam) throws JavaModelException,
                                                                                                MalformedTreeException,
                                                                                                BadLocationException {
        //creation of a Document
        String source = compilationUnitMember.getBuffer().getContents();
        Document document = new Document(source);
    
        //creation of ASTRewrite
        ASTRewrite rewrite = ASTRewrite.create(compilationUnitAstNode.getAST());

        Iterator<Annotation> iter = annotationNodes.iterator();
        while (iter.hasNext()) {
            Annotation currentAnnotationNode = iter.next();
            if (member instanceof IField) {
                removeAnnotationOnField(compilationUnitAstNode,
                                        currentAnnotationNode,
                                        (IField)member, rewrite);
        
            } else if (methodParam != null) {
                removeAnnotationOnMethodParam(compilationUnitAstNode,
                                              currentAnnotationNode,
                                              methodParam,
                                              rewrite);
            
            } else if (member instanceof IMethod && methodParam == null) {
                removeAnnotationOnMethod(compilationUnitAstNode,
                                         currentAnnotationNode,
                                         (IMethod)member, rewrite);
            
            } else if (member instanceof IType) {
                removeAnnotationOnType(compilationUnitAstNode, currentAnnotationNode, (IType)member, rewrite);
            }
        }
    
//        // computation of the text edits
//        TextEdit edits = rewrite.rewriteAST(document, compilationUnitMember.getJavaProject().getOptions(true));
//    
//        // computation of the new source code
//        edits.apply(document);
//    
//        // update of the compilation unit
//        compilationUnitMember.getBuffer().setContents(document.get());
        
        TextEditorHelper.applyRewrite(compilationUnitMember, rewrite);
    }
    
    
    public static String getNamespace(String packageName) {
        if (packageName == null || packageName.length() == 0) {
            packageName = "default_package";
        }
        StringTokenizer tokenizer = new StringTokenizer(packageName, ".");
        String[] tokens;
        if (tokenizer.countTokens() == 0) {
            tokens = new String[0];
        } else {
            tokens = new String[tokenizer.countTokens()];
            for (int i = tokenizer.countTokens() - 1; i >= 0; i--) {
                tokens[i] = tokenizer.nextToken();
            }
        }
        StringBuffer namespace = new StringBuffer("http://");
        String dot = "";
        for (int i = 0; i < tokens.length; i++) {
            if (i == 1) {
                dot = ".";
            }
            namespace.append(dot + tokens[i]);
        }
        namespace.append('/');
        return namespace.toString();
    }
    
    public static TypeLiteral createTypeLiteralFromClass(AST ast,
			String classCanonicalName) {
		int previousIndex = -1;
		Type theType = null;
		if (classCanonicalName.indexOf(".") > 0) {
			int nextIndex = classCanonicalName.indexOf('.');
			theType = ast.newSimpleType(ast.newSimpleName(classCanonicalName
					.substring(1 + previousIndex, nextIndex)));
			previousIndex = nextIndex;
			nextIndex = classCanonicalName.indexOf('.', previousIndex + 1);
			while (nextIndex != -1) {
				String substring = classCanonicalName.substring(
						1 + previousIndex, nextIndex);
				theType = ast.newQualifiedType(theType, ast
						.newSimpleName(substring));
				previousIndex = nextIndex;
				nextIndex = classCanonicalName.indexOf('.', previousIndex + 1);
			}
			String substring = classCanonicalName.substring(1 + previousIndex);
			theType = ast.newQualifiedType(theType, ast
					.newSimpleName(substring));
		} else {
			theType = ast.newSimpleType(ast.newSimpleName(classCanonicalName));
		}

		TypeLiteral typeLiteral = ast.newTypeLiteral();
		typeLiteral.setType(theType);
		return typeLiteral;
	}
    
    @SuppressWarnings("unchecked")
    public static boolean hasAnnotationOnPrimaryType(IFile javaFile, Class cls) {
        ICompilationUnit compUnit = JDTUtils.getJavaUnitFromFile(javaFile);
    
        if (compUnit == null) {
            return false;
        }
    
        List<Annotation> annotations = JDTUtils.getAnnotations(compUnit.findPrimaryType());
    
        if (compUnit == null) {
            return false;
        }
    
        if (annotations == null) {
            return false;
        }
           
        
        for (Annotation an : annotations) {
        	if (an.getTypeName().getFullyQualifiedName().equals(cls.getSimpleName())
            		|| an.getTypeName().getFullyQualifiedName().equals(cls.getName())) {
        		return true;
        	}
        }
    
        return false;
    }
    
    @SuppressWarnings("unchecked")
    public static IFile importJavaFileToProject(String fullFilePath, IFolder srcFolder){
    	IFile returnValue = null;
    	String originalFile = fullFilePath;
    	StringBuffer buffer = new StringBuffer();
		FileReader reader = null;
		BufferedReader br = null; 
    	try{
    		File file = new File(originalFile);
    		reader = new FileReader(file); 
    		br = new BufferedReader(reader); 
        	String s1 = null;
        	while((s1 = br.readLine()) != null) {
        		buffer.append(s1 + "\n");
        	}
        	char[] charSet = new char[buffer.length()];
        	buffer.getChars(0, buffer.length() -1, charSet, 0);
        	
        	ASTParser parser = ASTParser.newParser(AST.JLS3);
            parser.setSource(charSet);
            org.eclipse.jdt.core.dom.CompilationUnit domCu = (org.eclipse.jdt.core.dom.CompilationUnit)parser.createAST(null);
            
            String packageName = domCu.getPackage().getName().getFullyQualifiedName();
            
            //IFolder srcFolder = JaxWsWorkspaceManager.getSrcFolder(project);
        
            IFolder targetFolder = srcFolder.getFolder((packageName.replace(".", File.separator)));
            
            File targetDir = targetFolder.getLocation().toFile();
            if(!targetDir.exists()){
            	targetDir.mkdirs();
            	LOG.info("create folder: " + targetDir);
            }
            
            FileUtils.copyFile(originalFile, targetFolder.getLocation().toOSString());
    		LOG.info("copy file " + originalFile + "to " + targetDir);
            
            srcFolder.refreshLocal(IFolder.DEPTH_INFINITE, null);
            
            returnValue = targetFolder.getFile(file.getName());
    	}catch (Exception ex){
    		LOG.error(ex);
    	}finally{
    		try{
    			reader.close();
    			br.close();
        		buffer = null;
    		}catch(Exception ex){
    			LOG.error(ex);
    		}
    		
    	}
    	
    	return returnValue;
    		
    }
}