alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.java.tools;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.ListIterator;
import org.netbeans.api.mdr.MDRepository;
import org.netbeans.jmi.javamodel.Array;
import org.netbeans.jmi.javamodel.Resource;
import org.netbeans.jmi.javamodel.ClassDefinition;
import org.netbeans.jmi.javamodel.JavaClass;
import org.netbeans.jmi.javamodel.JavaModelPackage;
import org.netbeans.jmi.javamodel.JavaPackage;
import org.netbeans.jmi.javamodel.Method;
import org.netbeans.jmi.javamodel.MultipartId;
import org.netbeans.jmi.javamodel.NamedElement;
import org.netbeans.jmi.javamodel.Parameter;
import org.netbeans.jmi.javamodel.ParameterizedType;
import org.netbeans.jmi.javamodel.PrimitiveType;
import org.netbeans.jmi.javamodel.PrimitiveTypeKindEnum;
import org.netbeans.jmi.javamodel.Type;
import org.netbeans.jmi.javamodel.TypeParameter;
import org.netbeans.jmi.javamodel.TypeReference;
import org.netbeans.jmi.javamodel.UnresolvedClass;
import org.netbeans.modules.java.settings.JavaSynchronizationSettings;
import org.netbeans.modules.javacore.internalapi.JavaMetamodel;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.SharedClassObject;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.util.Utilities;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;

public class JMIInheritanceSupport implements Runnable, TaskListener {
    
    public static final String PROP_FINISHED = "JMIInheritanceSupport.finished"; //NOI18N
    public static final String ROOT_OBJECT_NAME = "java.lang.Object"; //NOI18N

    /** Strings suitable for parameter names for some well-known data types.
     */
    private static String NAME_ARRAY = getString("NAME_ArrayOfPrimitives"); // NOI18N
    private static String NAME_PARAM = getString("NAME_Primitive"); // NOI18N
    private static String NAME_OBJECT_VALUE = getString("NAME_javaLangValue"); // NOI18N
    private static String NAME_OBJECT = getString("NAME_javaLangObject"); // NOI18N
    private static String NAME_STRING = getString("NAME_javaLangString"); // NOI18N
    private static String NAME_CLASS_VALUE = getString("NAME_javaLangClass"); // NOI18N

    /** Mapping from well-known parameter types to appropriate names.
     */
    private static Map wellKnownClasses;

    static {
        // TODO: read well-known classes from some configuration file or
        // user settings accessible from the GUI
        wellKnownClasses = new HashMap();
        wellKnownClasses.put("java.lang.Object", NAME_OBJECT); // NOI18N
        wellKnownClasses.put("java.lang.String", NAME_STRING); // NOI18N
        wellKnownClasses.put("java.lang.Float", NAME_OBJECT_VALUE); // NOI18N
        wellKnownClasses.put("java.lang.Double", NAME_OBJECT_VALUE); // NOI18N
        wellKnownClasses.put("java.lang.Long", NAME_OBJECT_VALUE); // NOI18N
        wellKnownClasses.put("java.lang.Class", NAME_CLASS_VALUE); // NOI18N
    }

    private static String getString(String key) {
        return NbBundle.getMessage(JMIInheritanceSupport.class, key);
    }
    
    private ClassDefinition root;
    
    private JavaModelPackage modelPackage;
    
    private Resource resource;

    private FileObject owner;

    private Set classes;
    
    // 
    private Map methods;
    
    // >, (alias >)
    private Map tree;
    
    private List classesList;
    
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private RequestProcessor.Task task = RequestProcessor.getDefault().create(this);
    
    /** Creates a new instance of JMIInheritanceSupport. The root must be a class. */
    public JMIInheritanceSupport(ClassDefinition root) {
        DataObject dobj = (DataObject) JavaMetamodel.getManager().getDataObject(root.getResource());
        assert dobj != null;
        this.owner = dobj.getPrimaryFile();
        this.root = root;
        this.resource = root.getResource();
        this.modelPackage = (JavaModelPackage)root.refOutermostPackage();
        this.task.addTaskListener(this);
    }

    public ClassDefinition getRootClass() {
        return root;
    }
    
    public Collection getClasses(Collection result, boolean cls, boolean ifc) {
        if (!task.isFinished()) {
            task.waitFinished();
        }

        for (Iterator i = classesList.iterator(); i.hasNext(); ) {
            JavaClass jc = (JavaClass) i.next();
            boolean isInterface = jc.isInterface();
            if ((!isInterface && cls) || (isInterface && ifc)) {
                result.add(jc);
            }
        }
        
        return result;
    }
    
    public Collection getAllMethods(Collection result, boolean abstractOnly) {
        if (!task.isFinished()) {
            task.waitFinished();
        }

        for (Iterator i = methods.values().iterator(); i.hasNext(); ) {
            Method mte = (Method) i.next();
            if (!abstractOnly || (isAbstract(mte) && !isImplemented(root, mte, true))) {
                result.add(mte);
            }
        }
        
        return result;
    }
    
    public Collection getMethods(Collection result, ClassDefinition cd, boolean abstractOnly) {
        if (!task.isFinished()) {
            task.waitFinished();
        }

        Set meths = (Set) tree.get(cd);
        for (Iterator i = meths.iterator(); i.hasNext(); ) {
            MethodKey key = (MethodKey) i.next();
            Method m = (Method) methods.get(key);
            if (!abstractOnly || (isAbstract(m) && !isImplemented(cd, m, true))) {
                result.add(m);
            }
        }
        
        return result;
    }

    /**
     * Adds method that overrides source.
     * @param source method to be overridden
     * @param superCall generate super call?
     * @param javadoc generate super javadoc?
     * @return reference to method created in source file
     * @throws SourceException impossible to add the method
     */ 
    public Method overrideMethod(Method m, boolean superCall, boolean javadoc) {        
        boolean failed = true;
        MDRepository repository = JavaMetamodel.getDefaultRepository();
        repository.beginTrans(true);
        try {
            List params = computeParameters(m);
            List typeParams = computeTypeParameters(m);
            String bodyText = null;
            List excs = null;

            // remove ABSTRACT and NATIVE since the user probably wants to write the method body ;-)
            // SYNCHRONIZED is removed for safety reasons: if the user wants the method to be synchronized,
            // she will add the modifier.
            int mods = m.getModifiers() & ~(Modifier.NATIVE | Modifier.ABSTRACT | Modifier.SYNCHRONIZED);
            if (((JavaClass)m.getDeclaringClass()).isInterface())
                mods = mods | Modifier.PUBLIC;

            // to be formatted nicely in the source code...
            if (superCall) {
                bodyText = createSuperCall(m, params, isImplemented(root, m, false));
                excs = computeExceptions(m);
            }

            Method newMethod = modelPackage.getMethod().createMethod(
                m.getName(), null, mods, javadoc ? m.getJavadocText() : null, null, null,
                bodyText, typeParams, params, excs, getTypeRef(m.getType()), 0
            );
            
            addMethod(newMethod);
            failed = false;
            return newMethod;
        } finally {
            repository.endTrans(failed);
        }
    }
    
    private void addMethod(Method m) {
        Object insPoint = null;
        List contents = root.getContents();
        ListIterator iter;
        for (iter = contents.listIterator(); iter.hasNext(); ) {
            Object obj = iter.next();
            if (!(obj instanceof JavaClass)) {
                insPoint = obj;
            }
        }
        iter = contents.listIterator();
        if (insPoint == null) {
            iter.add(m);
        } else {
            while (iter.hasNext()) {
                if (iter.next() == insPoint) {
                    iter.add(m);
                    return;
                }
            }
        }
    }    
    
    public void addPropertyChangeListener(PropertyChangeListener l) {
        pcs.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        pcs.removePropertyChangeListener(l);
    }

    public boolean isFinished() {
        return task.isFinished();
    }
    
    public void reset() {
        classes = null;
        classesList = null;
        methods = null;
        tree = null;
        task.schedule(0);
    }

    // ---- Runnable implementation
    
    public void run() {
        classes = new HashSet();
        classesList = new ArrayList();
        methods = new HashMap();
        tree = new HashMap();

        // traverse all super classes
        JavaClass superClass = root.getSuperClass();
        while (superClass != null) {
            classes.add(superClass);
            classesList.add(superClass);
            HashSet meths = new HashSet();
            collectMethods(meths, superClass);
            tree.put(superClass, meths);
            superClass = superClass.getSuperClass();
        }

        // traverse all interfaces
        traverseInterfaces(classes, classesList, tree, root);
        
        // add java.lang.Object if it wasn't processed yet
        //[PENDING] -^
    }

    private void traverseInterfaces(Set classes, List classesList, Map tree, ClassDefinition root) {
        List interfaces = root.getInterfaces();
        for (Iterator iter = interfaces.iterator(); iter.hasNext(); ) {
            JavaClass jc = (JavaClass) iter.next();
            if (classes.add(jc)) {
                classesList.add(jc);
                HashSet meths = new HashSet();
                collectMethods(meths, jc);
                tree.put(jc, meths);
            }
            
            traverseInterfaces(classes, classesList, tree, jc);
        }
    }
    
    // ---- TaskListener implementation
    
    public void taskFinished(Task task) {
        pcs.firePropertyChange(PROP_FINISHED, Boolean.FALSE, Boolean.TRUE);
    }
    
    // ---- private implementation

    private boolean isAbstract(Method mte) {
        ClassDefinition cd = mte.getDeclaringClass();
        return Modifier.isAbstract(mte.getModifiers()) || (cd instanceof JavaClass && ((JavaClass)cd).isInterface());
    }
        
    /** Determines, if the given method is accessible from the root class. */
    private boolean isAccessibleMethod(Method m) {
        ClassDefinition cls = m.getDeclaringClass();
        int modifs = cls instanceof JavaClass ? ((JavaClass)cls).getModifiers() : 0;
        
        if ((modifs & Modifier.PRIVATE) != 0 ||
            ((modifs & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE)) == 0 &&
             !isSamePackage(cls))) {
            return false;
        }
        if ((cls instanceof JavaClass) && ((JavaClass)cls).isInterface())
            return true;
        modifs = m.getModifiers();
        if ((modifs & Modifier.PRIVATE) != 0) {
            return false;
        }
        if ((modifs & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0)
            return true;
        return isSamePackage(cls);
    }

    /** Determines if the method is implemented in some super class of the
     * passed class.
     */
    private boolean isImplemented(ClassDefinition rootCl, Method m, boolean includeClass) {
        String name = m.getName();
        List params = m.getParameters();
        List paramTypes = new ArrayList(params.size());
        for (Iterator iter = params.iterator(); iter.hasNext(); ) {
            paramTypes.add(((Parameter)iter.next()).getType());
        }
        
        // traverse all super classes
        ClassDefinition jc = includeClass ? rootCl : rootCl.getSuperClass();
        while (jc != null) {
            Method mte = jc.getMethod(name, paramTypes, false);
            if (mte != null && !Modifier.isAbstract(mte.getModifiers())) {
                return true;
            }
            jc = jc.getSuperClass();
        }
        return false;
    }
    
    /** Determines if the given method can be overriden in the root class. */
    private boolean isOverridable(Method m) {
        if (!isAccessibleMethod(m)) {
            return false;
        }
        
        int mods = m.getModifiers();
        return !(Modifier.isFinal(mods) || Modifier.isStatic(mods));
    }

    /**
     * Determines if the given method is overriden in the hierarchy.
     *
     * @param  method  method for which overriding methods are looked for
     * @return true, if given method is overriden in subclasses
     */
    private boolean isOverriden(Method m) {
        List params = m.getParameters();
        String name = m.getName();
        List paramTypes = new ArrayList(params.size());
        for (Iterator iter = params.iterator(); iter.hasNext(); ) {
            paramTypes.add(((Parameter)iter.next()).getType());
        }
        ClassDefinition cd = root;
        while (cd != null) {
            Method meth = root.getMethod(name, paramTypes, false);
            if (meth == m)
                return false;
            else if (meth != null)
                return true;
            cd = cd.getSuperClass();
        }
        return false;
    }
    
    /** Deteremines if the class is in the same package as the root class */
    private boolean isSamePackage(ClassDefinition cd) {
        String p1 = root.getResource().getPackageName();
        String p2 = cd.getResource().getPackageName();
        return p1 == null ? p2 == null : p1.equals(p2);
    }

    /** Collects all methods declared by the class and all its interfaces. It doesn't
     * traverse super classes.
     */
    private void collectMethods(Set meths, JavaClass jc) {
        for (Iterator iter = jc.getContents().iterator(); iter.hasNext(); ) {
            Object obj = iter.next();
            if (!(obj instanceof Method))
                continue;
            Method m = (Method)obj;
            if (isOverridable(m) && !isOverriden(m)) {
                MethodKey key = new MethodKey(m);
                methods.put(key, m);
                meths.add(key);
            }
        }
        
        for (Iterator iter = jc.getInterfaces().iterator(); iter.hasNext(); ) {
            collectMethods(meths, (JavaClass) iter.next());
        }
    }
    
    // ---- private implementation of method generator
    
    private List computeParameters(Method orig) {
        List params = orig.getParameters();
        List result = new ArrayList(params.size());
        Set synthesized = new HashSet();

        if (params.size() == 0) return result;
        Iterator iter = params.iterator();
        for (int i = 0; iter.hasNext(); i++) {
            Parameter p = (Parameter) iter.next();
            String parName = p.getName();
            if (parName == null || parName.equals("")) { // NOI18N
                String s = createName(p.getType());
                if (synthesized.contains(s)) {
                    s = s + i;
                } else {
                    synthesized.add(s);
                }
                parName = s;
            }	    
            // public Parameter createParameter(java.lang.String name, java.util.List annotations, 
            //      boolean isFinal, org.netbeans.jmi.javamodel.TypeReference typeName, int dimCount, 
            //      boolean isVarArg);
            Parameter newPar = modelPackage.getParameter().createParameter(
                parName, null, false, getTypeRef(p.getType()), 0, p.isVarArg()
            );
            result.add(newPar);
        }
        return result;
    }

    private List computeTypeParameters(Method orig) {
        if (true) return null;
        List params = orig.getTypeParameters();
        List result = new ArrayList(params.size());

        if (params.size() == 0) return result;
        Iterator iter = params.iterator();
        for (int i = 0; iter.hasNext(); i++) {
            TypeParameter p = (TypeParameter) iter.next();
            result.add(p.duplicate());
        }
        return result;
    }
    
    private List computeExceptions(Method orig) {
        List excs = orig.getExceptions();
        List result = new ArrayList(excs.size());
        for (Iterator iter = excs.iterator(); iter.hasNext(); ) {
            result.add(getTypeRef((JavaClass) iter.next()));
        }
        return result;
    }
    
    private TypeReference getTypeRef(Type type) {
        if (type instanceof PrimitiveType)
            return modelPackage.getMultipartId().createMultipartId(type.getName(), null, null);
        if (type instanceof Array) {
	    int dimCount = 1;
            type = ((Array) type).getType();
            while (type instanceof Array) {
                dimCount++;
                type = ((Array) type).getType();
            }
            MultipartId elemTypeRef = (MultipartId)getTypeRef(type);
            StringBuffer sb = new StringBuffer();
            sb.append(elemTypeRef.getName());
            for (int x = 0; x < dimCount; x++) {
                sb.append("[]"); // NOI18N
            }
            return modelPackage.getArrayReference().createArrayReference(sb.toString(), elemTypeRef, dimCount);
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType) type;
            MultipartId def = (MultipartId)getTypeRef(ptype.getDefinition());
            List pars = new ArrayList(ptype.getParameters().size());
            for (Iterator iter = ptype.getParameters().iterator(); iter.hasNext(); ) {
                pars.add(getTypeRef((Type)iter.next()));
            }
            def.getTypeArguments().addAll(pars);
            return def;
        }
        if (type instanceof TypeParameter) {
            return modelPackage.getMultipartId().createMultipartId(type.getName(), null, null);
        }
        // JavaClass
        String fullName = ((JavaClass)type).getName();
        String name = isVisible(fullName) ? ((JavaClass)type).getSimpleName() : fullName;
        return modelPackage.getMultipartId().createMultipartId(name, null, null);
    }
    
    private boolean isVisible(String fullName) { // [PENDING] change fullName to JavaClass
        JavaMetamodel.getDefaultRepository().beginTrans(false);
        try {
            Object obj = null;
            try {
                obj = ((JavaModelPackage)resource.refImmediatePackage()).getType().resolve(fullName);
            } catch (NullPointerException e) {
            } catch (javax.jmi.reflect.InvalidObjectException e) {
            }
            JavaClass jc = (obj instanceof JavaClass) ? (JavaClass)obj : null;
            if (jc == null || jc instanceof UnresolvedClass)
                return false;
            Resource res = jc.getResource();
            if (res == null)
                return false;
            String resPName = resource.getPackageName();
            String pName = res.getPackageName();
            if (pName == null) {
                return resPName == null;
            } 
            if (pName.equals("java.lang") || pName.equals(resPName)) { // NOI18N
                return true;
            }
            ClassDefinition outerClass = jc.getDeclaringClass();
            for (Iterator iter = resource.getImports().iterator(); iter.hasNext();) {
                org.netbeans.jmi.javamodel.Import imp = (org.netbeans.jmi.javamodel.Import) iter.next();
                NamedElement impElem = imp.getImportedElement();
                if (impElem instanceof JavaPackage) {
                    String pn = ((JavaPackage) impElem).getName();
                    if (pName.equals(pn))
                        return true;
                } else if (impElem == (imp.isOnDemand() ? outerClass : jc)) {
                    return true;
                }
            } // for
            return false;
        } finally {
            JavaMetamodel.getDefaultRepository().endTrans(false);
        }
    }
    
    /** Synthesizes suitable name for type `t'.
      If the type is an array, the name will be based on its element's type.
      If the type is primitive, the name will be `param' or `value'.
      In case of Reference types, the name will be constructed from the simple
      name of the type. If the simple name is composed from several words using
      uppercase (titlecase) letters, the last such name component will be taken.
      The returned name always begins with lowercase letter.
     */
    private String createName(Type t) {
        String lastComponent;
        boolean array = false;

        if (t instanceof Array) {
            array = true;
            do {
                t = ((Array)t).getType();
            } while (t instanceof Array);
        }
        if (t instanceof PrimitiveType) {
            if (array) {
                return NAME_ARRAY;
            } else {
                return NAME_PARAM;
            }
        }
        if (t instanceof ParameterizedType) {
            t = ((ParameterizedType) t).getDefinition();
        }
        int i;
        String fullName = t.getName();

        if (wellKnownClasses.containsKey(fullName)) {
            return (String)wellKnownClasses.get(fullName);
        }
        lastComponent = t instanceof JavaClass ? ((JavaClass) t).getSimpleName() : t.getName();
        for (i = lastComponent.length() - 1; i > 0; i--) {
            if (Character.isTitleCase(lastComponent.charAt(i))) {
                break;
            }
        }
        String n;
        if (i < lastComponent.length() - 1) {
            n = Character.toLowerCase(lastComponent.charAt(i)) + lastComponent.substring(i + 1);
        } else {
            n = lastComponent.toLowerCase();
        }
        if (Utilities.isJavaIdentifier(n))
            return n;
        // it is an identifier because it is a class' name. But it is not a java identifier
        // ==> it is a keyword append a Param which will make the string just an ordinary
        // identifier.
        return java.text.MessageFormat.format(NbBundle.getMessage(JMIInheritanceSupport.class, "NAME_typeParam"), new Object[] {
            n
        });
    }

    private String createSuperCall(Method target, List params, boolean hasSuper) {
        JavaSynchronizationSettings syncSettings = (JavaSynchronizationSettings)SharedClassObject.findObject(JavaSynchronizationSettings.class, true);

        String body;
        
        if (hasSuper) {
            // create a parameter list for the superclass' call:
            String format;
            
            StringBuffer str = new StringBuffer();
            for (Iterator iter = params.iterator(); iter.hasNext(); ) {
                Parameter par = (Parameter) iter.next();
                if (str.length() > 0)
                    str.append(", "); // NOI18N
                str.append(par.getName());
            }
            Type t = target.getType();
            if (t instanceof PrimitiveType && PrimitiveTypeKindEnum.VOID.equals(((PrimitiveType)t).getKind())) {
                format = NbBundle.getMessage(JMIInheritanceSupport.class, "FMT_CallSuper"); // NOI18N
            } else {
                format = NbBundle.getMessage(JMIInheritanceSupport.class, "FMT_ReturnCallSuper"); // NOI18N
            }
            body = java.text.MessageFormat.format(format,
                new Object[] {
                    getTypeRef(target.getType()).getName(),
                    target.getName(),
                    str
            });
        } else {
            // no super, let JavaSyncSettings generate a default return:
            body = syncSettings.getGenerateReturnAsString(target.getType());
        }
        return body;
    }
    
    // ..........................................................................
    
    public static class MethodKey extends Object {
        
        private String name;
        private Type[] paramTypes;
        
        public MethodKey (Method m) {
            name = m.getName();
            List params = m.getParameters();
            paramTypes = new Type[params.size()];
            Iterator iter = params.iterator();
            for (int x = 0; iter.hasNext(); x++) {
                paramTypes[x] = ((Parameter)iter.next()).getType();
            }
        }

        /* Returns true if parameters are the same */
        public boolean equals (Object obj) {
            if (!(obj instanceof MethodKey)) return false;
            return name.equals(((MethodKey)obj).name) && Arrays.equals(paramTypes, ((MethodKey)obj).paramTypes);
        }

        /* Computes hashcode as exclusive or of first and
        * last parameter's names
        * (or only from the first or return some constant
        * for special cases) */
        public int hashCode () {
            int length = paramTypes.length;
            if (length == 0) return 0;
            if (length == 1) return paramTypes[0].getName().hashCode();
            return paramTypes[0].getName().hashCode() ^
                   paramTypes[length - 1].getName().hashCode();
        }

    }
    
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.