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.refactoring.api;

import java.lang.reflect.Modifier;
import org.netbeans.jmi.javamodel.*;
import org.netbeans.modules.javacore.jmiimpl.javamodel.MethodImpl;
import org.netbeans.modules.refactoring.CheckUtils;
import org.netbeans.modules.refactoring.NbAbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.openide.text.PositionBounds;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import javax.jmi.reflect.RefObject;
import java.text.MessageFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import org.netbeans.modules.javacore.internalapi.JavaMetamodel;
import org.netbeans.modules.javacore.internalapi.ProgressListener;
import org.netbeans.modules.javacore.jmiimpl.javamodel.CallableFeatureImpl;

/**
 * Refactoring used for changing method signature. It changes method declaration
 * and also all its references (callers).
 *
 * @author  Pavel Flaska
 * @author  Tomas Hurka
 */
public class ChangeParameters extends NbAbstractRefactoring implements ProgressListener {
    
    RefObject selectedObject;
    // refatorected object - method or constructor
    CallableFeature method;
    // table of all the changes - it contains all the new parameters and also
    // changes in order
    ParameterInfo[] paramTable;
    // new modifier
    int modifier;
    
    /**
     * Creates a new instance of change parameters refactoring.
     *
     * @param method  refactored object, i.e. method or constructor
     */
    public ChangeParameters(RefObject method) {
        selectedObject = method;
    }
    
    public void setClassPath() {
        if (method != null && method instanceof Method) {
            Collection c = ((MethodImpl) method).getOverridenMethods();
            if (!c.isEmpty()) {
                setClassPath(c);
                return;
            }
        }
        setClassPath((Element) selectedObject); 
    }
    
    /**
     * Returns list of problems. For the change method signature, there are two
     * possible warnings - if the method is overriden or if it overrides
     * another method.
     *
     * @return  overrides or overriden problem or both
     */
    public Problem preCheck() {
        Problem result = null;

        if (!(selectedObject instanceof CallableFeature)) {
            return createProblem(result, true, NbBundle.getMessage(ChangeParameters.class, "ERR_ChangeParamsWrongType"));
        }
        Collection overridesMethod = null;
        Collection overridenMethod = null;
        
        method = (CallableFeature)selectedObject;
        // for the method, check, if the method overrides another method and
        // if the method is overriden by another method
        // todo (#pf): what about constructors?
        List pars = (List) method.getParameters();
        List typeList = new ArrayList();
        for (Iterator parIt = pars.iterator(); parIt.hasNext(); ) {
            Parameter par = (Parameter) parIt.next();
            typeList.add(par.getType());
        }
        if (method instanceof Method) {
             overridesMethod = CheckUtils.overrides((Method) method,
                method.getName(), typeList, true);
             overridenMethod = CheckUtils.isOverridden((Method) method,
                method.getName(), typeList);
        }
        // for 4.0, we not support methods with variable arguments. It is a
        // fatal error for the time being.
        if (CheckUtils.hasVarArgs(method)) {
            String msg = getString("ERR_HasVarArg");// NOI18N
            result = createProblem(result, true, msg);
        }
        if (overridesMethod != null) {
            for (Iterator iter = overridesMethod.iterator(); iter.hasNext(); ){
                String msg = new MessageFormat(getString("ERR_MethodOverrides")).format( // NOI18N
                new Object[] { getDefClassName(((Method) iter.next()).getDeclaringClass()) }
                );
                result = createProblem(result, false, msg);
            }
        }
        if (overridenMethod != null) {
            for (Iterator iter = overridenMethod.iterator(); iter.hasNext(); ){
                String msg = new MessageFormat(getString("ERR_MethodIsOverridden")).format( // NOI18N
                new Object[] { getDefClassName(((Method) iter.next()).getDeclaringClass()) }
                );
                result = createProblem(result, false, msg);
            }
        }
        return result;
    }
    
    public Problem checkParameters(ParameterInfo[] aParamTable, int modifier) {
        ParameterInfo[] oldParamTable = paramTable;
        int oldModifier = this.modifier;
        paramTable = aParamTable;
        this.modifier = modifier;
        // checks
        Problem emptyArgs = checkParameterAttributes();
        // Note (#pf): little hacking - because of reverse problems sorting 
        // (last fatal reported is first in the chain, not first fatal reported),
        // we have to use two problem chains. First for empty arguments and 
        // second for invalid identifiers. Then they are joined together 
        // at the end of method.
        Problem result = null;
        // check, if there is is varArg. Check also the parameter's name clash
        Set paramNames = new HashSet(3);
        for (int i = 0; i < paramTable.length; i++) {
            Type type = paramTable[i].getType();
            if (type != null && type.getName() != null && type.getName().endsWith("...")) { // NOI18N
                // varargs are not supported in change parameters in 4.0.
                result = createProblem(result, true, getString("ERR_HasVarArg"));
            }
            String name = null;
            int orIdx = paramTable[i].getOrigIndex();
            if (orIdx > -1) { 
                // existing parameter
                name = ((Parameter) method.getParameters().get(orIdx)).getName();
            } else {
                // new parameter
                name = paramTable[i].getName();
            }
            if (name != null && paramNames.add(name) == false) {
                // set already contains the same name!
                result = createProblem(result, true,
                    new MessageFormat(getString("ERR_DuplicateName")).format( // NOI18N
                        new Object[] { name }
                ));
            }
        }
        
        // if there exists duplicate method (i.e. the method with the
        // name and parameters, create fatal problem - be careful and
        // do not create problem when found method is the same -
        // example: you have method 'int swap(int a, int b)'. When
        // user only swaps parameters in the method (i.e. new signature is
        // 'swap(int b, int a)', you have to allow him to do it!
        if (method instanceof Method) {
            Method duplicateMethod = methodClashes(paramTable);
            if (duplicateMethod != null && !method.equals(duplicateMethod)) {
                result = createProblem(result, true,
                    new MessageFormat(getString("ERR_existingMethod")).format( // NOI18N
                        new Object[] {
                            duplicateMethod.getName(),
                            getDefClassName(duplicateMethod.getDeclaringClass())
                        }
                    )
                );
            }
        // otherwise it has to be constructor
        } else {
            Constructor duplicateConstr = constructorClashes(paramTable);
            if (duplicateConstr != null && !method.equals(duplicateConstr)) {
                result = createProblem(result, true,
                    new MessageFormat(getString("ERR_existingConstr")).format( // NOI18N
                        new Object[] {
                            getDefClassName(duplicateConstr.getDeclaringClass())
                        }
                    )
                );
            }
        }
        // sorting problems in correct order (different of createProblem method)
        if (emptyArgs != null) {
            Problem toAdd = emptyArgs;
            while (toAdd.getNext() != null) {
                toAdd = toAdd.getNext();
            }
            toAdd.setNext(result);
            result = toAdd;
        }
        paramTable=oldParamTable;
        this.modifier=oldModifier;
        return result;
    }
    
    /**
     * Sets the parameters for refactoring. You can add new parameters or change
     * order of parameters or change existing parameter.
     *
     * @param  aParamTable  table representing new parameters list
     * @param  modifier  new modifier for the method
     *
     * @return  problem or a chain of problems
     */
    public Problem setParameters(ParameterInfo[] aParamTable, int modifier) {
        paramTable = aParamTable;
        this.modifier = modifier;
        
        return checkParameters(aParamTable, modifier);
    }

    /**
     * Prepares the collection of refactoring elements. It will be add
     * to elements collection, which is provided as a parameter.
     *
     * @param  elements collection to which the elements will be append t
     * @return  list of problems
     */
    public Problem prepare(Collection elements) {
        JavaMetamodel.getManager().getProgressSupport().addProgressListener(this);
        fireProgressListenerStart(PREPARE, 9);
        try {
            fireProgressListenerStep();
            // get all the callers and usages of the callable and add them
            // the the collection of refactored elements
            referencesIterator = ((CallableFeatureImpl) method).findDependencies(true, true, true).iterator();
            if (method instanceof Constructor) {
                // we have to add declaration element in case of constructor.
                // For methods, usages return original declaration because they
                // are looking for overriden/overriding methods. (not case of
                // constructor)
                elements.add(new SignatureElement(method, paramTable, modifier));
            }
            while (referencesIterator.hasNext()) {
                if (cancelRequest) {
                    return null;
                }
                Object ref = referencesIterator.next();
                if (ref instanceof Invocation) {
                    // callers
                    elements.add(new CallerElement((Invocation) ref, paramTable));
                } else {
                    // declaration/declarations (in case of overriden or overrides)
                    elements.add(new SignatureElement((CallableFeature) ref, paramTable, modifier));
                }
            }
            return null;
        } 
        finally {
            referencesIterator = null;
            JavaMetamodel.getManager().getProgressSupport().removeProgressListener(this);
            fireProgressListenerStop();
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // INNER CLASSES
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Represents one item for setParameters(List params) list parameter.
     * Item contains information about changes in method parameters.
     * Parameter can be added, changed or moved to another position.
     */
    public static class ParameterInfo {
        int origIndex;
        String name;
        Type type;
        String defaultVal;

        /**
         * Creates a new instanceof of ParameterInfo. This constructor can be
         * used for newly added parameters or changed original parameters.
         * When you call method with -1 origIndex, you have to provide not
         * null values in all other pamarameters, otherwise it throws an
         * IllegalArgumentException.
         *
         * @param  origIndex  for newly added parameters, use -1, otherwise
         *                    use index in original parameters list
         * @param  name       parameter name 
         * @param  type       parameter type
         * @param  defaultVal should be provided for the all new parameters.
         *                    For changed parameters, it is ignored.
         */
        public ParameterInfo(int origIndex, String name, Type type, String defaultVal) {
            // new parameter
            // if (origIndex == -1 && (name == null || defaultVal == null || type == null || name.length() == 0 || defaultVal.length() == 0)) {
            //    throw new IllegalArgumentException(NbBundle.getMessage(ChangeParameters.class, "ERR_NoValues"));
            // }
            this.origIndex = origIndex;
            this.name = name;
            this.type = type;
            // do not set default value for existing parameters
            this.defaultVal = origIndex == -1 ? defaultVal : null;
        }
        
        /**
         * Creates a new instance of ParameterInfo. This constructor is used
         * for existing non-changed parameters. All the values except original
         * position in parameters list is set to null.
         *
         * @param  origIndex  position index in original parameters list
         */
        public ParameterInfo(int origIndex) {
            this(origIndex, null, null, null);
        }
        
        /**
         * Returns value of original parameter index.
         *
         * @return  original index of parameter in parameters list
         */
        public int getOrigIndex() { return origIndex; }
        
        /**
         * Returns value of the name of parameter. If the name was not
         * changed, returns null.
         *
         * @return  new name for parameter or null in case that it was not changed.
         */
        public String getName() { return name; }

        /**
         * Returns value of the type of parameter. If the name was not
         * changed, returns null.
         *
         * @return new type for parameter or null if it was not changed.
         */
        public Type getType() { return type; }

        /**
         * Returns value of the default value in case of the new parameter.
         * Otherwise, it returns null.
         *
         * @return default value for new parameter, otherwise null.
         */
        public String getDefaultVal() { return defaultVal; }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Instance of this class represents call to refactored method, i.e. method,
     * whose signature is changed.
     */
    static class CallerElement implements RefactoringElement {
        private PositionBounds bounds;
        private boolean enabled;
        private final Invocation element;
        private ParameterInfo[] paramTable;
        private final String text;
        private final String displayText;
        
        private int status;
        
        // contains bounds for all original parameters. Used for obtaining original
        // parameter value, when the parameter was moved to another place.
        private List paramList;
        // cache
        private RefObject comp = null;

        /**
         * Creates new CallerElement. It represents Invocation to be
         * refactored when signature is changed.
         *
         * @param element represents method invocation
         * @param paramTable array of reordered, changed and added parameters
         */    
        public CallerElement(Invocation element,
                             ParameterInfo[] paramTable)
        {
            paramList = Collections.EMPTY_LIST;
            this.element = element;
            this.paramTable = paramTable;
            this.bounds = null;
            enabled = true;
            Element disp = getDisplayElement(element);
            int b = disp.getStartOffset();
            int e = disp.getEndOffset();
            int bb = element.getPartStartOffset(ElementPartKindEnum.NAME);
            int be = element.getEndOffset();
            String src = element.getResource().getSourceText();
            text = src.substring(b, e);
            displayText = src.substring(b, bb) + "" + src.substring(bb, be) + "" + src.substring(be, e); //NOI18N
            if (JavaMetamodel.getManager().isElementGuarded(element)) {
                status = RefactoringElement.GUARDED;
            } else {
                status = RefactoringElement.NORMAL;
            }
        }
        
        /**
         * Returns the text describing the change provided by element.
         *
         * @return  the text describing element functionality
         */        
        public String getText() { return text; }

        /**
         * Returns text containing the description of the element
         * (i.e. 'Change declaration' and the current declaration of the method.
         *
         * @return description text with method declaration change text
         */
        public String getDisplayText() { return displayText; }

        /**
         * If the refactoring of this element is enabled, it returns true.
         *
         * @return true, if the refactoring of this element is enabled
         */        
        public boolean isEnabled() { return enabled; }

        /**
         * Sets the enabled flag. It allows to stop refactoring on the
         * element, when you call setEnabled(false);
         *
         * @param enabled tell if the element can be refactored
         */
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        
        /**
         * Performs change on element. It is method invocation, it goes through
         * the parameters provide in constructor in an array and creates new
         * parameters in model.
         */
        public void performChange() {
            List parameters = element.getParameters();
            Element[] origParameters = (Element[]) parameters.toArray(new Element[0]);
            parameters.clear();
            for (int i = 0; i < paramTable.length; i++) {
                ParameterInfo parInfo = paramTable[i];
                int origIndex = parInfo.getOrigIndex();
                Element par;
                if (origIndex == -1) {
                    JavaModelPackage jmp = (JavaModelPackage) getJavaElement().refImmediatePackage();
                    par = jmp.getMultipartId().createMultipartId(parInfo.getDefaultVal(), null, null);
                }
                else {
                    par = origParameters[origIndex];
                }
                parameters.add(par);
            }
        }

        /**
         * Do undo on the element.
         */
        public void undoChange() {
            throw new UnsupportedOperationException();
        }

        /** 
         * Returns Java element associated with this refactoring element.
         *
         * @return MDR Java element.
         */
        public Element getJavaElement() {
            if (comp == null) {
                comp = element;
                while (!((comp instanceof Feature) || (comp instanceof Resource))) {
                    comp = (RefObject) comp.refImmediateComposite();
                }
            }
            return (Element) comp;
        }

        /**
         *
         * @return bounds bordering the element
         */    
        public PositionBounds getPosition() {
            if (bounds == null) {
                bounds = getPositionBounds(element);
            }
            return bounds;
        }

        public int getStatus() {
            return status;
        }

        ////////////////////////////////////////////////////////////////////////////
        // PRIVATE MEMBERS
        ////////////////////////////////////////////////////////////////////////////
        private static Element getDisplayElement(Element obj) {
            Element result = obj;
            while (!((result instanceof Feature) || (result.refImmediateComposite() instanceof StatementBlock))) {
                result = (Element) result.refImmediateComposite();
            }
            return result;
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Instance of this class is part of the change method signature refactoring.
     * It is responsible for changing method declaration - it doesn't change
     * references.
     */
    static class SignatureElement implements RefactoringElement {
        
        private PositionBounds bounds;
        private final CallableFeature element;
        boolean enabled;
        private ParameterInfo[] paramTable;
        private int modifier;
        private final String displayText;
        private final String text;

        private int status;
        
        /**
         * Creates refactoring elements, which is responsible for changing
         * method (constructor) declaration.
         *
         * @param element     element, which will be changed
         * @param paramTable  array of Lists, which contains information about
         *                    parameters changes
         * @param modifier    contains information about modifier change
         */
        public SignatureElement(CallableFeature element,
                                ParameterInfo[] paramTable,
                                int modifier)
        {
            this.element = element;
            this.paramTable = paramTable;
            this.bounds = null;
            this.modifier = modifier;
            enabled = true;

            // initialize text and displayText
            String decl = ChangeParameters.getString("LBL_chngsigdecl"); //NOI18N
            Object[] args = new Object[2];
            String key = element instanceof Method ? "LBL_Method" : "LBL_Constructor"; //NOI18N
            args[0] = ChangeParameters.getString(key);
            int b = element.getPartStartOffset(ElementPartKindEnum.HEADER);
            int e = element.getPartEndOffset(ElementPartKindEnum.HEADER);
            args[1] = element.getResource().getSourceText().substring(b, e);
            text = MessageFormat.format(decl, args);
            args[1] = "" + args[1] + ""; //NOI18N
            displayText = MessageFormat.format(decl, args);
            
            if (JavaMetamodel.getManager().isElementGuarded(element)) {
                status = RefactoringElement.GUARDED;
            } else {
                status = RefactoringElement.NORMAL;
            }
        }

        /**
         * Returns the text describing the change provided by element.
         *
         * @return  the text describing element functionality
         */        
        public String getText() { return text; }

        /**
         * Returns text containing the description of the element
         * (i.e. 'Change declaration' and the current declaration of the method.
         *
         * @return description text with method declaration change text
         */
        public String getDisplayText() { return displayText; }

        /**
         * If the refactoring of this element is enabled, it returns true.
         *
         * @return true, if the refactoring of this element is enabled
         */        
        public boolean isEnabled() { return enabled; }

        /**
         * Sets the enabled flag. It allows to stop refactoring on the
         * element, when you call setEnabled(false);
         *
         * @param enabled tell if the element can be refactored
         */
        public void setEnabled(boolean enabled) { this.enabled = enabled; }

        /**
         * Performs the change on header. Change the parameter if the modifier
         * was change and also do the parameters changes in header.
         */
        public void performChange() {
            List parameters = element.getParameters();
            Parameter[] origParameters = (Parameter[]) parameters.toArray(new Parameter[0]);
            // set new access modifier
            int oldMod = element.getModifiers();
            if ((oldMod & modifier) == 0) {
                int newMod = oldMod & ~(Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE) | modifier;
                element.setModifiers(newMod);
            }
            parameters.clear();
            JavaModelPackage modelPackage = (JavaModelPackage) getJavaElement().refImmediatePackage();
            for (int i = 0; i < paramTable.length; i++) {
                ParameterInfo parInfo = paramTable[i];
                int origIndex = parInfo.getOrigIndex();
                Parameter parameter = null;
                // for the new parameter
                if (origIndex == -1) {
                    parameter = modelPackage.getParameter().createParameter();
                    parameter.setName(parInfo.getName());
                    parameter.setType(parInfo.getType());
                }
                else {
                    parameter = origParameters[origIndex];
                    Object val;
                    if ((val = parInfo.getName()) != null)
                        parameter.setName((String) val);
                    if ((val = parInfo.getType()) != null)
                        parameter.setType((Type) val);
                }
                parameters.add(parameter);
            }
        }
        
        /**
         * Do undo on the element.
         */
        public void undoChange() {
            throw new UnsupportedOperationException();
        }

        /**
         * Returns refactored java element. (method or constructor)
         *
         * @return  java element, which is refactored. It is always
         *          CallableFeature, i.e. method or constructor .
         */        
        public Element getJavaElement() { return element; }

        /**
         * Returns bounds of the declarion, e.g. testMethod(int a, int b).
         *
         * @return bounds of the method declaration
         */        
        public PositionBounds getPosition() {
            if (bounds == null) {
                bounds = getPositionBounds(element);
            }
            return bounds;
        }

        public int getStatus() {
            return status;
        }

    }
    
    ////////////////////////////////////////////////////////////////////////////
    // PRIVATE MEMBERS
    ////////////////////////////////////////////////////////////////////////////
    private Problem checkParameterAttributes() {
        // check, if the default values for all new added parameters are present
        Problem p = null;
        
        for (int i = 0; i < paramTable.length; i++) {
            int origIndex = paramTable[i].getOrigIndex();
     
            // check parameter name
            String s;
            s = paramTable[i].getName();
            if (origIndex == -1 && (s == null || s.length() < 1))
                p = createProblem(p, true, newParMessage("ERR_parname")); // NOI18N

            // check parameter type
            Type t = paramTable[i].getType();
            if (origIndex == -1 && t == null)
                p = createProblem(p, true, newParMessage("ERR_partype")); // NOI18N

            // check the default value
            s = paramTable[i].getDefaultVal();
            if (origIndex == -1 && (s == null || s.length() < 1))
                p = createProblem(p, true, newParMessage("ERR_pardefv")); // NOI18N
        }
        return p;
    }
    
    private Method methodClashes(ParameterInfo[] parInfo) {
        // check, if there is any existing method with the same signature
        if (!(method instanceof Method))
            return null;
        List paramTypes = new ArrayList(parInfo.length);
        List parameters = method.getParameters();
        for (int i = 0; i < parInfo.length; i++) {
            Type t = parInfo[i].getType();
            if (t == null) t = ((Parameter) parameters.get(parInfo[i].getOrigIndex())).getType();
            paramTypes.add(t);
        }
        return CheckUtils.getMethod(method.getDeclaringClass(), method.getName(), paramTypes);
    }

    private Constructor constructorClashes(ParameterInfo[] parInfo) {
        // check, if there is any existing constructor with the same signature
        if (!(method instanceof Constructor))
            return null;
        List paramTypes = new ArrayList(parInfo.length);
        List parameters = method.getParameters();
        for (int i = 0; i < parInfo.length; i++) {
            Type t = parInfo[i].getType();
            if (t == null) t = ((Parameter) parameters.get(parInfo[i].getOrigIndex())).getType();
            paramTypes.add(t);
        }
        return CheckUtils.getConstructor(method.getDeclaringClass(), paramTypes);
    }
    
    private static String newParMessage(String par) {
        return new MessageFormat(
                getString("ERR_newpar")).format(new Object[] { getString(par) } // NOI18N
            );
    }
    
    private static String getString(String key) {
        return NbBundle.getMessage(ChangeParameters.class, key);
    }

    private String getDefClassName(ClassDefinition dc) {
        if (dc instanceof JavaClass) {
            return ((JavaClass) dc).getName();
        } 
        else {
            return "";
        }
    }
    
    public void start(org.netbeans.modules.javacore.internalapi.ProgressEvent event) {
        fireProgressListenerStart(event.getOperationType(), event.getCount());
    }
    
    public void step(org.netbeans.modules.javacore.internalapi.ProgressEvent event) {
        fireProgressListenerStep();
    }
    
    public void stop(org.netbeans.modules.javacore.internalapi.ProgressEvent event) {
        fireProgressListenerStop();
    }
    
    // end private members
}
... 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.