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

org.netbeans.spi.java.hints.JavaFixUtilities Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.spi.java.hints;

import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SinceTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.Scope;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.TreeScanner;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.ClassPath.PathConversionMode;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.TypeMirrorHandle;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.java.source.matching.Occurrence;
import org.netbeans.api.java.source.matching.Pattern;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.java.hints.spiimpl.Hacks;
import org.netbeans.modules.java.hints.spiimpl.Utilities;
import org.netbeans.modules.java.hints.spiimpl.ipi.upgrade.ProjectDependencyUpgrader;
import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.java.hints.JavaFix.TransformationContext;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.modules.SpecificationVersion;
import org.openide.text.PositionBounds;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbCollections;

/**Factory methods for various predefined {@link JavaFix} implementations.
 *
 * @author lahvac
 */
public class JavaFixUtilities {

    /**Prepare a fix that will replace the given tree node ({@code what}) with the
     * given code. Any variables in the {@code to} pattern will be replaced with their
     * values from {@link HintContext#getVariables() }, {@link HintContext#getMultiVariables() }
     * and {@link HintContext#getVariableNames() }.
     *
     * @param ctx basic context for which the fix should be created
     * @param displayName the display name of the fix
     * @param what the tree node that should be replaced
     * @param to the new code that should replaced the {@code what} tree node
     * @return an editor fix that performs the required transformation
     */
    public static Fix rewriteFix(HintContext ctx, String displayName, TreePath what, final String to) {
        return rewriteFix(ctx.getInfo(), displayName, what, to, ctx.getVariables(), ctx.getMultiVariables(), ctx.getVariableNames(), ctx.getConstraints(), Collections.emptyMap());
    }

    static Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, final String to, Map parameters, Map> parametersMulti, final Map parameterNames, Map constraints, Map options, String... imports) {
        final Map params = new HashMap<>();
        final Map extraParamsData = new HashMap<>();
        final Map> implicitThis = new HashMap<>();

        for (Entry e : parameters.entrySet()) {
            TreePath tp = e.getValue();
            if (tp.getParentPath() != null && !immediateChildren(tp.getParentPath().getLeaf()).contains(tp.getLeaf())) {
                Element el = info.getTrees().getElement(tp);
                if (el != null && el.getSimpleName().contentEquals("this")) {
                    implicitThis.put(e.getKey(), ElementHandle.create(el.getEnclosingElement()));
                    continue;
                }
            }
            params.put(e.getKey(), TreePathHandle.create(e.getValue(), info));
            if (e.getValue() instanceof Callable) {
                try {
                    extraParamsData.put(e.getKey(), ((Callable) e.getValue()).call());
                } catch (Exception ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }

        final Map> paramsMulti = new HashMap<>();

        for (Entry> e : parametersMulti.entrySet()) {
            Collection tph = new LinkedList<>();

            for (TreePath tp : e.getValue()) {
                TreePathHandle x = TreePathHandle.create(tp, info);
                // resolve back, to save some problems later:
                if (x.resolve(info) == null) {
                    continue;
                }
                tph.add(x);
            }

            paramsMulti.put(e.getKey(), tph);
        }

        final Map> constraintsHandles = new HashMap<>();

        for (Entry c : constraints.entrySet()) {
            constraintsHandles.put(c.getKey(), TypeMirrorHandle.create(c.getValue()));
        }

        if (displayName == null) {
            displayName = defaultFixDisplayName(info, parameters, to);
        }

        return new JavaFixRealImpl(info, what, options, displayName, to, params, extraParamsData, implicitThis, paramsMulti, parameterNames, constraintsHandles, Arrays.asList(imports)).toEditorFix();
    }

    private static Set immediateChildren(Tree t) {
        Set children = new HashSet<>();

        t.accept(new TreeScanner() {
            @Override
            public Void scan(Tree tree, Void p) {
                if (tree != null)
                    children.add(tree);
                return null;
            }
        }, null);

        return children;
    }

    /**Creates a fix that removes the given code corresponding to the given tree
     * node from the source code.
     * 
     * @param ctx basic context for which the fix should be created
     * @param displayName the display name of the fix
     * @param what the tree node that should be removed
     * @return an editor fix that removes the give tree from the source code
     */
    public static Fix removeFromParent(HintContext ctx, String displayName, TreePath what) {
        return new RemoveFromParent(displayName, ctx.getInfo(), what, false).toEditorFix();
    }

    /**Creates a fix that removes the given code corresponding to the given tree
     * node together with all its usages from the source code
     * 
     * @param ctx basic context for which the fix should be created
     * @param displayName the display name of the fix
     * @param what the tree node that should be removed
     * @return an editor fix that removes the give tree from the source code
     * 
     * @since 1.48
     */
    public static Fix safelyRemoveFromParent(HintContext ctx, String displayName, TreePath what) {
        return RemoveFromParent.canSafelyRemove(ctx.getInfo(), what) ? new RemoveFromParent(displayName, ctx.getInfo(), what, true).toEditorFix() : null;
    }

    private static String defaultFixDisplayName(CompilationInfo info, Map variables, String replaceTarget) {
        Map stringsForVariables = new HashMap<>();

        for (Entry e : variables.entrySet()) {
            Tree t = e.getValue().getLeaf();
            SourcePositions sp = info.getTrees().getSourcePositions();
            int startPos = (int) sp.getStartPosition(info.getCompilationUnit(), t);
            int endPos = (int) sp.getEndPosition(info.getCompilationUnit(), t);

            if (startPos >= 0 && endPos >= 0) {
                stringsForVariables.put(e.getKey(), info.getText().substring(startPos, endPos));
            } else {
                stringsForVariables.put(e.getKey(), "");
            }
        }

        if (!stringsForVariables.containsKey("$this")) {
            //XXX: is this correct?
            stringsForVariables.put("$this", "this");
        }

        for (Entry e : stringsForVariables.entrySet()) {
            String quotedVariable = java.util.regex.Pattern.quote(e.getKey());
            String quotedTarget = Matcher.quoteReplacement(e.getValue());
            replaceTarget = replaceTarget.replaceAll(quotedVariable, quotedTarget);
        }

        return "Rewrite to " + replaceTarget;
    }

    private static void checkDependency(CompilationInfo copy, Element e, boolean canShowUI) {
        SpecificationVersion sv = computeSpecVersion(copy, e);

        while (sv == null && e.getKind() != ElementKind.PACKAGE) {
            e = e.getEnclosingElement();
            sv = computeSpecVersion(copy, e);
        }

        if (sv == null) {
            return ;
        }

        Project currentProject = FileOwnerQuery.getOwner(copy.getFileObject());

        if (currentProject == null) {
            return ;
        }

        FileObject file = getFile(copy, e);

        if (file == null) {
            return ;
        }

        FileObject root = findRootForFile(file, copy.getClasspathInfo());

        if (root == null) {
            return ;
        }

        Project referedProject = FileOwnerQuery.getOwner(file);

        if (referedProject != null && currentProject.getProjectDirectory().equals(referedProject.getProjectDirectory())) {
            return ;
        }

        for (ProjectDependencyUpgrader pdu : Lookup.getDefault().lookupAll(ProjectDependencyUpgrader.class)) {
            if (pdu.ensureDependency(currentProject, root, sv, canShowUI)) {
                return ;
            }
        }
    }

    private static java.util.regex.Pattern SPEC_VERSION = java.util.regex.Pattern.compile("[0-9]+(\\.[0-9]+)+");

    static SpecificationVersion computeSpecVersion(CompilationInfo info, Element el) {
        if (!Utilities.isJavadocSupported(info)) return null;

        DocCommentTree javaDoc = info.getDocTrees().getDocCommentTree(el);

        if (javaDoc == null) return null;

        for (DocTree tag : javaDoc.getBlockTags()) {
            if (tag.getKind() != DocTree.Kind.SINCE) {
                continue;
            }

            String text = ((SinceTree) tag).getBody().toString();

            Matcher m = SPEC_VERSION.matcher(text);

            if (!m.find()) {
                continue;
            }

            return new SpecificationVersion(m.group()/*ver.toString()*/);
        }

        return null;
    }

    @SuppressWarnings("deprecation")
    private static FileObject getFile(CompilationInfo copy, Element e) {
        return SourceUtils.getFile(e, copy.getClasspathInfo());
    }

    private static FileObject findRootForFile(final FileObject file, final ClasspathInfo cpInfo) {
        ClassPath cp = ClassPathSupport.createProxyClassPath(
            new ClassPath[] {
                cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE),
                cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT),
                cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE),
            });

        FileObject root = cp.findOwnerRoot(file);

        if (root != null) {
            return root;
        }

        for (ClassPath.Entry e : cp.entries()) {
            FileObject[] sourceRoots = SourceForBinaryQuery.findSourceRoots(e.getURL()).getRoots();

            if (sourceRoots.length == 0) continue;

            ClassPath sourcePath = ClassPathSupport.createClassPath(sourceRoots);

            root = sourcePath.findOwnerRoot(file);

            if (root != null) {
                return root;
            }
        }
        return null;
    }

    private static boolean isStaticElement(Element el) {
        if (el == null) return false;

        if (el.asType() == null || el.asType().getKind() == TypeKind.ERROR) {
            return false;
        }

        if (el.getModifiers().contains(Modifier.STATIC)) {
            //XXX:
            if (!el.getKind().isClass() && !el.getKind().isInterface()) {
                return false;
            }

            return true;
        }

        if (el.getKind().isClass() || el.getKind().isInterface()) {
            return el.getEnclosingElement().getKind() == ElementKind.PACKAGE;
        }

        return false;
    }
    
    private static class JavaFixRealImpl extends JavaFix {
        private final String displayName;
        private final Map params;
        private final Map extraParamsData;
        private final Map> implicitThis;
        private final Map> paramsMulti;
        private final Map parameterNames;
        private final Map> constraintsHandles;
        private final Iterable imports;
        private final String to;

        public JavaFixRealImpl(CompilationInfo info, TreePath what, Map options, String displayName, String to, Map params, Map extraParamsData, Map> implicitThis, Map> paramsMulti, final Map parameterNames, Map> constraintsHandles, Iterable imports) {
            super(info, what, options);

            this.displayName = displayName;
            this.to = to;
            this.params = params;
            this.extraParamsData = extraParamsData;
            this.implicitThis = implicitThis;
            this.paramsMulti = paramsMulti;
            this.parameterNames = parameterNames;
            this.constraintsHandles = constraintsHandles;
            this.imports = imports;
        }

        @Override
        protected String getText() {
            return displayName;
        }

        @Override
        protected void performRewrite(TransformationContext ctx) {
            final WorkingCopy wc = ctx.getWorkingCopy();
            TreePath tp = ctx.getPath();
            
            final GeneratorUtilities gen = GeneratorUtilities.get(wc);
            tp = new TreePath(tp.getParentPath(), gen.importComments(tp.getLeaf(), tp.getCompilationUnit()));
            final Map parameters = new HashMap<>();

            for (Entry e : params.entrySet()) {
                TreePath p = e.getValue().resolve(wc);

                if (p == null) {
                    Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", e.getValue());
                }

                parameters.put(e.getKey(), p);
            }

            Map implicitThis = new HashMap<>();

            for (Entry> e : this.implicitThis.entrySet()) {
                Element clazz = e.getValue().resolve(wc);

                if (clazz == null) {
                    Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", e.getValue());
                    continue;
                }

                implicitThis.put(e.getKey(), clazz);
            }

            final Map> parametersMulti = new HashMap<>();

            for (Entry> e : paramsMulti.entrySet()) {
                Collection tps = new LinkedList<>();

                for (TreePathHandle tph : e.getValue()) {
                    TreePath p = tph.resolve(wc);
                    if (p == null) {
                        Logger.getLogger(JavaFix.class.getName()).log(Level.SEVERE, "Cannot resolve handle={0}", e.getValue());
                        continue;
                    }

                    tps.add(p);
                }

                parametersMulti.put(e.getKey(), tps);
            }

            Map constraints = new HashMap<>();

            for (Entry> c : constraintsHandles.entrySet()) {
                constraints.put(c.getKey(), c.getValue().resolve(wc));
            }

            Scope scope = Utilities.constructScope(wc, constraints, imports);

            assert scope != null;

            Tree parsed = Utilities.parseAndAttribute(wc, to, scope);
            
            if (parsed.getKind() == Kind.EXPRESSION_STATEMENT && ExpressionTree.class.isAssignableFrom(tp.getLeaf().getKind().asInterface())) {
                parsed = ((ExpressionStatementTree) parsed).getExpression();
            }
            
            Map rewriteFromTo = new IdentityHashMap<>();
            List order = new ArrayList<>(7);
            Tree original;

            if (Utilities.isFakeBlock(parsed)) {
                TreePath parent = tp.getParentPath();
                List statements = ((BlockTree) parsed).getStatements();
                
                if (tp.getLeaf().getKind() == Kind.BLOCK) {
                    BlockTree real = (BlockTree) tp.getLeaf();
                    rewriteFromTo.put(original = real, wc.getTreeMaker().Block(statements, real.isStatic()));
                } else {
                    statements = statements.subList(1, statements.size() - 1);

                    if (parent.getLeaf().getKind() == Kind.BLOCK) {
                        List newStatements = new LinkedList<>();

                        for (StatementTree st : ((BlockTree) parent.getLeaf()).getStatements()) {
                            if (st == tp.getLeaf()) {
                                newStatements.addAll(statements);
                            } else {
                                newStatements.add(st);
                            }
                        }

                        rewriteFromTo.put(original = parent.getLeaf(), wc.getTreeMaker().Block(newStatements, ((BlockTree) parent.getLeaf()).isStatic()));
                    } else {
                        rewriteFromTo.put(original = tp.getLeaf(), wc.getTreeMaker().Block(statements, false));
                    }
                }
            } else if (Utilities.isFakeClass(parsed)) {
                TreePath parent = tp.getParentPath();
                List members = ((ClassTree) parsed).getMembers();

                members = members.subList(1, members.size());

                assert parent.getLeaf().getKind() == Kind.CLASS;

                List newMembers = new LinkedList<>();

                ClassTree ct = (ClassTree) parent.getLeaf();

                for (Tree t : ct.getMembers()) {
                    if (t == tp.getLeaf()) {
                        newMembers.addAll(members);
                    } else {
                        newMembers.add(t);
                    }
                }

                rewriteFromTo.put(original = parent.getLeaf(), wc.getTreeMaker().Class(ct.getModifiers(), ct.getSimpleName(), ct.getTypeParameters(), ct.getExtendsClause(), ct.getImplementsClause(), newMembers));
            } else if (tp.getLeaf().getKind() == Kind.BLOCK && parametersMulti.containsKey("$$1$") && parsed.getKind() != Kind.BLOCK && StatementTree.class.isAssignableFrom(parsed.getKind().asInterface())) {
                List newStatements = new LinkedList<>();

                newStatements.add(wc.getTreeMaker().ExpressionStatement(wc.getTreeMaker().Identifier("$$1$")));
                newStatements.add((StatementTree) parsed);
                newStatements.add(wc.getTreeMaker().ExpressionStatement(wc.getTreeMaker().Identifier("$$2$")));

                parsed = wc.getTreeMaker().Block(newStatements, ((BlockTree) tp.getLeaf()).isStatic());

                rewriteFromTo.put(original = tp.getLeaf(), parsed);
            } else {
                while (   tp.getParentPath().getLeaf().getKind() == Kind.PARENTHESIZED
                       && tp.getLeaf().getKind() != parsed.getKind()
                       && tp.getParentPath() != null
                       && tp.getParentPath().getParentPath() != null
                       && !requiresParenthesis(parsed, tp.getParentPath().getLeaf(), tp.getParentPath().getParentPath().getLeaf())
                       && requiresParenthesis(tp.getLeaf(), tp.getParentPath().getLeaf(), tp.getParentPath().getParentPath().getLeaf()))
                    tp = tp.getParentPath();
                rewriteFromTo.put(original = tp.getLeaf(), parsed);
            }
            order.add(original);

            //prevent generating QualIdents inside import clauses - might be better to solve that inside ImportAnalysis2,
            //but that seems not to be straightforward:
            boolean inImport = parsed.getKind() == Kind.IMPORT;
            boolean inPackage = false;
            TreePath w = tp;

            while (!inImport && w != null) {
                inImport |= w.getLeaf().getKind() == Kind.IMPORT;
                inPackage |= w.getParentPath() != null && w.getParentPath().getLeaf().getKind() == Kind.COMPILATION_UNIT && ((CompilationUnitTree) w.getParentPath().getLeaf()).getPackageName() == w.getLeaf();
                w = w.getParentPath();
            }

            final Set originalTrees = Collections.newSetFromMap(new IdentityHashMap<>());
            
            new ErrorAwareTreeScanner() {
                @Override public Void scan(Tree tree, Void p) {
                    originalTrees.add(tree);
                    return super.scan(tree, p);
                }
            }.scan(original, null);
            
            new ReplaceParameters(wc, ctx.isCanShowUI(), inImport, parameters, extraParamsData, implicitThis, parametersMulti, parameterNames, rewriteFromTo, order, originalTrees).scan(new TreePath(tp.getParentPath(), rewriteFromTo.get(original)), null);

            if (inPackage) {
                String newPackage = wc.getTreeUtilities().translate(wc.getCompilationUnit().getPackageName(), new IdentityHashMap<>(rewriteFromTo))./*XXX: not correct*/toString();

                ClassPath source = wc.getClasspathInfo().getClassPath(PathKind.SOURCE);
                FileObject ownerRoot = source.findOwnerRoot(wc.getFileObject());

                if (ownerRoot != null) {
                    ctx.getFileChanges().add(new MoveFile(wc.getFileObject(), ownerRoot, newPackage.replace('.', '/')));
                } else {
                    Logger.getLogger(JavaFix.class.getName()).log(Level.WARNING, "{0} not on its source path ({1})", new Object[] {FileUtil.getFileDisplayName(wc.getFileObject()), source.toString(PathConversionMode.PRINT)});
                }
            }
            for (Tree from : order) {
                Tree to = rewriteFromTo.get(from);
//                gen.copyComments(from, to, true);
//                gen.copyComments(from, to, false);
                wc.rewrite(from, to);
            }
        }
    }
    
    private static class IK {
        private final Object o;

        public IK(Object o) {
            this.o = o;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            if (o != null) {
                hash = 59 * hash + System.identityHashCode(o);
            }
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IK)) {
                return false;
            }
            final IK other = (IK) obj;
            return other.o == this.o;
        }
    }

    private static final Set NUMBER_LITERAL_KINDS = EnumSet.of(Kind.FLOAT_LITERAL, Kind.DOUBLE_LITERAL, Kind.INT_LITERAL, Kind.LONG_LITERAL);

    private static class ReplaceParameters extends ErrorAwareTreePathScanner {

        private final CompilationInfo info;
        private final TreeMaker make;
        private final boolean canShowUI;
        private final boolean inImport;
        private final Map parameters;
        private final Map extraParamsData;
        private final Map implicitThis;
        private final Map> parametersMulti;
        private final Map parameterNames;
        private final Map rewriteFromTo;
        private final Set originalTrees;
        private final List order;
        private final List nestedTypes = new ArrayList<>();

        public ReplaceParameters(WorkingCopy wc, boolean canShowUI, boolean inImport, Map parameters, Map extraParamsData, Map implicitThis, Map> parametersMulti, Map parameterNames, Map rewriteFromTo, List order, Set originalTrees) {
            this.parameters = parameters;
            this.info = wc;
            this.make = wc.getTreeMaker();
            this.canShowUI = canShowUI;
            this.inImport = inImport;
            this.extraParamsData = extraParamsData;
            this.implicitThis = implicitThis;
            this.parametersMulti = parametersMulti;
            this.parameterNames = parameterNames;
            this.rewriteFromTo = rewriteFromTo;
            this.order = order;
            this.originalTrees = originalTrees;
        }

        @Override
        public Number visitIdentifier(IdentifierTree node, Void p) {
            String name = node.getName().toString();
            Tree newNode = handleIdentifier(name, node);
            
            if (newNode != null) {
                rewrite(node, newNode);
                if (NUMBER_LITERAL_KINDS.contains(newNode.getKind())) {
                    return (Number) ((LiteralTree) newNode).getValue();
                }
            } else {
                Element implicitThisClass = implicitThis.get(name);
                if (implicitThisClass != null) {
                    Element enclClass = findEnclosingClass();
                    if (enclClass == implicitThisClass) {
                        rewrite(node, make.Identifier("this"));
                    } else {
                        rewrite(node, make.MemberSelect(make.QualIdent(implicitThisClass), "this"));
                    }
                    return null;
                }
            }

            Element e = info.getTrees().getElement(getCurrentPath());

            if (e != null && isStaticElement(e) && !inImport) {
                rewrite(node, make.QualIdent(e));
            }

            return super.visitIdentifier(node, p);
        }

        private Element findEnclosingClass() {
            TreePath findClass = getCurrentPath();
            while (findClass != null && !TreeUtilities.CLASS_TREE_KINDS.contains(findClass.getLeaf().getKind())) {
                findClass = findClass.getParentPath();
            }
            return findClass != null ? info.getTrees().getElement(findClass) : null;
        }

        @Override
        public Number visitTypeParameter(TypeParameterTree node, Void p) {
            String name = node.getName().toString();
            Tree newNode = handleIdentifier(name, node);
            
            if (newNode != null) {
                rewrite(node, newNode);
                if (NUMBER_LITERAL_KINDS.contains(newNode.getKind())) {
                    return (Number) ((LiteralTree) newNode).getValue();
                }
            }
            
            return super.visitTypeParameter(node, p);
        }
        
        private static final EnumSet  COMPLEX_OPS = EnumSet.of(Kind.CONDITIONAL_AND, Kind.CONDITIONAL_OR);
        
        private Tree handleIdentifier(String name, Tree node) {
            TreePath tp = parameters.get(name);

            if (tp != null) {
                if (tp.getLeaf() instanceof Hacks.RenameTree) {
                    Hacks.RenameTree rt = (Hacks.RenameTree) tp.getLeaf();
                    return make.setLabel(rt.originalTree, rt.newName);
                }
                if (!parameterNames.containsKey(name)) {
                    Tree target = tp.getLeaf();
                    if (NUMBER_LITERAL_KINDS.contains(target.getKind())) {
                        return target;
                    }
                    //TODO: might also remove parenthesis, but needs to ensure the diff will still be minimal
//                    while (target.getKind() == Kind.PARENTHESIZED
//                           && !requiresParenthesis(((ParenthesizedTree) target).getExpression(), getCurrentPath().getParentPath().getLeaf())) {
//                        target = ((ParenthesizedTree) target).getExpression();
//                    }
                    if (   getCurrentPath().getParentPath() != null
                        && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.LOGICAL_COMPLEMENT) {
                        boolean rewriteNegated;
                        
                        if (tp.getParentPath() == null) {
                            rewriteNegated = true;
                        } else {
                            Tree parent = tp.getParentPath().getLeaf();
                            TreePath aboveNewComplement = getCurrentPath().getParentPath().getParentPath();
                            // Do not try to optimize too complex expressions, following the principle of the least surprise.
                            // 1/ optimization is OK if the target is without parenthesis - extra level of parens can be avoided
                            // 2/ if both target and original are parenthesized, then optimization should be done to avoid one extra paren level
                            // 3/ do not optimize complex expressions - least surprise
                            rewriteNegated = (parent.getKind() != Kind.LOGICAL_COMPLEMENT) && 
                                             (!COMPLEX_OPS.contains(target.getKind()) || 
                                                parent.getKind() != Kind.PARENTHESIZED || 
                                                (aboveNewComplement != null && aboveNewComplement.getLeaf().getKind() == Kind.PARENTHESIZED));
                        }
                        
                        if (rewriteNegated) {
                            Tree negated = negate((ExpressionTree) tp.getLeaf(), getCurrentPath().getParentPath().getParentPath().getLeaf(), true);

                            if (negated != null) {
                                rewrite(getCurrentPath().getParentPath().getLeaf(), negated);
                            }
                        }
                    }
                    if (requiresParenthesis(target, node, getCurrentPath().getParentPath().getLeaf())) {
                        target = make.Parenthesized((ExpressionTree) target);
                    }
                    return target;
                }
            }

            String variableName = parameterNames.get(name);

            if (variableName != null) {
                return make.Identifier(variableName);
            }
            
            return null;
        }

        @Override
        public Number visitMemberSelect(MemberSelectTree node, Void p) {
            Element e = info.getTrees().getElement(getCurrentPath());

            if (e != null && (e.getKind() != ElementKind.CLASS || ((TypeElement) e).asType().getKind() != TypeKind.ERROR)) {
                //check correct dependency:
                checkDependency(info, e, canShowUI);

                if (isStaticElement(e) && !inImport) {
                    rewrite(node, make.QualIdent(e));

                    return null;
                }
            }
            
            MemberSelectTree nue = node;
            String selectedName = node.getIdentifier().toString();

            if (selectedName.startsWith("$") && parameterNames.get(selectedName) != null) {
                nue = make.MemberSelect(node.getExpression(), parameterNames.get(selectedName));
            }

            if (nue.getExpression().getKind() == Kind.IDENTIFIER) {
                String name = ((IdentifierTree) nue.getExpression()).getName().toString();

                if (name.startsWith("$") && parameters.get(name) == null) {
                    Element implicitThisClass = implicitThis.get(name);
                    if (implicitThisClass != null) {
                        TreePath findClass = getCurrentPath();
                        OUTER: while (findClass != null) {
                            if (TreeUtilities.CLASS_TREE_KINDS.contains(findClass.getLeaf().getKind())) {
                                Element clazz = info.getTrees().getElement(findClass);
                                if (implicitThisClass.equals(clazz)) {
                                    //this.<...>, the this may be implicit:
                                    rewrite(node, make.Identifier(nue.getIdentifier()));
                                    return null;
                                }
                                if (clazz.getKind().isClass() || clazz.getKind().isInterface()) {
                                    for (Element currentClassElement : info.getElements().getAllMembers((TypeElement) clazz)) {
                                        if (currentClassElement.getSimpleName().equals(node.getIdentifier())) {
                                            //there may be a resolution conflict, let the member select be qualified
                                            //TODO: no conflicts between fields and methods of the same name
                                            //but we current still qualify the name
                                            break OUTER;
                                        }
                                    }
                                }
                            }
                            findClass = findClass.getParentPath();
                        }
                        //let visitIdent handle this
                    } else {
                        //XXX: unbound variable, use identifier instead of member select - may cause problems?
                        rewrite(node, make.Identifier(nue.getIdentifier()));
                        return null;
                    }
                }
            }

            if (nue != node) {
                rewrite(node, nue);
            }
            
            return super.visitMemberSelect(node, p);
        }

        @Override
        public Number visitVariable(VariableTree node, Void p) {
            String name = node.getName().toString();

            if (name.startsWith("$")) {
                String nueName = parameterNames.get(name);

                if (nueName != null) {
                    name = nueName;
                }
            }
            
            VariableTree nue = make.Variable(node.getModifiers(), name, node.getType(), resolveOptionalValue(node.getInitializer()));

            rewrite(node, nue);

            return super.visitVariable(nue, p);
        }

        @Override
        public Number visitIf(IfTree node, Void p) {
            IfTree nue = make.If(node.getCondition(), node.getThenStatement(), resolveOptionalValue(node.getElseStatement()));
            
            rewrite(node, nue);
            
            return super.visitIf(nue, p);
        }

        @Override
        public Number visitMethod(MethodTree node, Void p) {
            String name = node.getName().toString();
            String newName = name;

            if (name.startsWith("$")) {
                if (parameterNames.containsKey(name)) {
                    newName = parameterNames.get(name);
                }
            }

            List typeParams = resolveMultiParameters(node.getTypeParameters());
            List params = resolveMultiParameters(node.getParameters());
            List thrown = resolveMultiParameters(node.getThrows());
            
            MethodTree nue = make.Method(node.getModifiers(), newName, node.getReturnType(), typeParams, params, thrown, node.getBody(), (ExpressionTree) node.getDefaultValue());
            
            rewrite(node, nue);
            
            return super.visitMethod(nue, p);
        }

        @Override
        public Number visitClass(ClassTree node, Void p) {
            String name = node.getSimpleName().toString();
            String newName = name;

            if (name.startsWith("$")) {
                if (parameterNames.containsKey(name)) {
                    newName = parameterNames.get(name);
                }
            }

            List typeParams = resolveMultiParameters(node.getTypeParameters());
            List implementsClauses = resolveMultiParameters(node.getImplementsClause());
            List members = resolveMultiParameters(Utilities.filterHidden(getCurrentPath(), node.getMembers()));
            Tree extend = resolveOptionalValue(node.getExtendsClause());
            ClassTree nue = make.Class(node.getModifiers(), newName, typeParams, extend, implementsClauses, members);
            
            rewrite(node, nue);
            
            Element el = info.getTrees().getElement(getCurrentPath());

            nestedTypes.add(el);

            try {
                return super.visitClass(nue, p);
            } finally {
                nestedTypes.remove(nestedTypes.size() - 1);
            }
        }

        @Override
        public Number visitExpressionStatement(ExpressionStatementTree node, Void p) {
            CharSequence name = Utilities.getWildcardTreeName(node);

            if (name != null) {
                TreePath tp = parameters.get(name.toString());

                if (tp != null && StatementTree.class.isAssignableFrom(tp.getLeaf().getKind().asInterface())) {
                    rewrite(node, tp.getLeaf());
                    return null;
                }
            }

            return super.visitExpressionStatement(node, p);
        }

        @Override
        public Number visitLiteral(LiteralTree node, Void p) {
            if (node.getValue() instanceof Number) {
                return (Number) node.getValue();
            }

            return super.visitLiteral(node, p);
        }

        @Override
        public Number visitBinary(BinaryTree node, Void p) {
            Number left  = scan(node.getLeftOperand(), p);
            Number right = scan(node.getRightOperand(), p);

            if (left != null && right != null) {
                Number result = null;
                switch (node.getKind()) {
                    case MULTIPLY:
                            if (left instanceof Double || right instanceof Double) {
                                result = left.doubleValue() * right.doubleValue();
                            } else if (left instanceof Float || right instanceof Float) {
                                result = left.floatValue() * right.floatValue();
                            } else if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() * right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() * right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case DIVIDE:
                            if (left instanceof Double || right instanceof Double) {
                                result = left.doubleValue() / right.doubleValue();
                            } else if (left instanceof Float || right instanceof Float) {
                                result = left.floatValue() / right.floatValue();
                            } else if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() / right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() / right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case REMAINDER:
                            if (left instanceof Double || right instanceof Double) {
                                result = left.doubleValue() % right.doubleValue();
                            } else if (left instanceof Float || right instanceof Float) {
                                result = left.floatValue() % right.floatValue();
                            } else if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() % right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() % right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case PLUS:
                            if (left instanceof Double || right instanceof Double) {
                                result = left.doubleValue() + right.doubleValue();
                            } else if (left instanceof Float || right instanceof Float) {
                                result = left.floatValue() + right.floatValue();
                            } else if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() + right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() + right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case MINUS:
                            if (left instanceof Double || right instanceof Double) {
                                result = left.doubleValue() - right.doubleValue();
                            } else if (left instanceof Float || right instanceof Float) {
                                result = left.floatValue() - right.floatValue();
                            } else if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() - right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() - right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case LEFT_SHIFT:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() << right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() << right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case RIGHT_SHIFT:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() >> right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() >> right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case UNSIGNED_RIGHT_SHIFT:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() >>> right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() >>> right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case AND:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() & right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() & right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case XOR:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() ^ right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() ^ right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;

                    case OR:
                            if (left instanceof Long || right instanceof Long) {
                                result = left.longValue() | right.longValue();
                            } else if (left instanceof Integer || right instanceof Integer) {
                                result = left.intValue() | right.intValue();
                            } else {
                                throw new IllegalStateException("left=" + left.getClass() + ", right=" + right.getClass());
                            }
                            break;
                }

                if (result != null) {
                    rewrite(node, make.Literal(result));

                    return result;
                }
            }

            return null;
        }

        @Override
        public Number visitUnary(UnaryTree node, Void p) {
            Number op  = scan(node.getExpression(), p);

            if (op != null) {
                Number result = null;
                switch (node.getKind()) {
                    case UNARY_MINUS:
                            if (op instanceof Double) {
                                result = -op.doubleValue();
                            } else if (op instanceof Float) {
                                result = -op.floatValue();
                            } else if (op instanceof Long) {
                                result = -op.longValue();
                            } else if (op instanceof Integer) {
                                result = -op.intValue();
                            } else {
                                throw new IllegalStateException("op=" + op.getClass());
                            }
                            break;
                    case UNARY_PLUS:
                        result = op;
                        break;
                }

                if (result != null) {
                    rewrite(node, make.Literal(result));

                    return result;
                }
            }

            return super.visitUnary(node, p);
        }

        @Override
        public Number visitBlock(BlockTree node, Void p) {
            List nueStatement = resolveMultiParameters(node.getStatements());
            BlockTree nue = make.Block(nueStatement, node.isStatic());

            rewrite(node, nue);

            return super.visitBlock(nue, p);
        }

        @Override
        public Number visitCase(CaseTree node, Void p) {
            List statements = (List) resolveMultiParameters(node.getStatements());
            CaseTree nue = make.Case(node.getExpression(), statements);

            rewrite(node, nue);
            return super.visitCase(node, p);
        }

        @Override
        @SuppressWarnings("unchecked")
        public Number visitMethodInvocation(MethodInvocationTree node, Void p) {
            List typeArgs = (List) resolveMultiParameters(node.getTypeArguments());
            List args = resolveMultiParameters(node.getArguments());
            MethodInvocationTree nue = make.MethodInvocation(typeArgs, node.getMethodSelect(), args);

            rewrite(node, nue);

            return super.visitMethodInvocation(nue, p);
        }

        @Override
        @SuppressWarnings("unchecked")
        public Number visitNewClass(NewClassTree node, Void p) {
            List typeArgs = (List) resolveMultiParameters(node.getTypeArguments());
            List args = resolveMultiParameters(node.getArguments());
            NewClassTree nue = make.NewClass(node.getEnclosingExpression(), typeArgs, node.getIdentifier(), args, node.getClassBody());

            rewrite(node, nue);
            return super.visitNewClass(nue, p);
        }

        @Override
        @SuppressWarnings("unchecked")
        public Number visitParameterizedType(ParameterizedTypeTree node, Void p) {
            List typeArgs = (List) resolveMultiParameters(node.getTypeArguments());
            ParameterizedTypeTree nue = make.ParameterizedType(node.getType(), typeArgs);

            rewrite(node, nue);
            return super.visitParameterizedType(node, p);
        }

        @Override
        public Number visitSwitch(SwitchTree node, Void p) {
            List cases = (List) resolveMultiParameters(node.getCases());
            SwitchTree nue = make.Switch(node.getExpression(), cases);

            rewrite(node, nue);
            return super.visitSwitch(node, p);
        }

        @Override
        public Number visitTry(TryTree node, Void p) {
            List resources = (List) resolveMultiParameters(node.getResources());
            List catches = (List) resolveMultiParameters(node.getCatches());
            TryTree nue = make.Try(resources, node.getBlock(), catches, node.getFinallyBlock());

            rewrite(node, nue);
            return super.visitTry(node, p);
        }

        @Override
        public Number visitModifiers(ModifiersTree node, Void p) {
            List annotations = new ArrayList<>(node.getAnnotations());
            IdentifierTree ident = !annotations.isEmpty() && annotations.get(0).getAnnotationType().getKind() == Kind.IDENTIFIER ? (IdentifierTree) annotations.get(0).getAnnotationType() : null;

            if (ident != null) {
                annotations.remove(0);
                
                String name = ident.getName().toString();
                TreePath orig = parameters.get(name);
                ModifiersTree nue;
                
                if (orig != null && orig.getLeaf().getKind() == Kind.MODIFIERS) {
                    ModifiersTree origMods = (ModifiersTree) orig.getLeaf();
                    Object actualContent = extraParamsData.get(name);
                    Set actualFlags = EnumSet.noneOf(Modifier.class);
                    boolean[] actualAnnotationsMask = new boolean[0];
                    
                    if (actualContent instanceof Object[] && ((Object[]) actualContent)[0] instanceof Set) {
                        actualFlags.addAll(NbCollections.checkedSetByFilter((Set) ((Object[]) actualContent)[0], Modifier.class, false));
                    }
                    
                    if (actualContent instanceof Object[] && ((Object[]) actualContent)[1] instanceof boolean[]) {
                        actualAnnotationsMask = (boolean[]) ((Object[]) actualContent)[1];
                    }
                    
                    nue = origMods;
                    
                    for (Modifier m : origMods.getFlags()) {
                        if (actualFlags.contains(m)) continue;
                        nue = make.removeModifiersModifier(nue, m);
                    }
                    
                    for (Modifier m : node.getFlags()) {
                        nue = make.addModifiersModifier(nue, m);
                    }
                    
                    int ai = 0;
                    
                    OUTER: for (AnnotationTree a : origMods.getAnnotations()) {
                        if (actualAnnotationsMask.length <= ai || actualAnnotationsMask[ai++]) continue;
                        for (Iterator it = annotations.iterator(); it.hasNext();) {
                            AnnotationTree toCheck = it.next();
                            Collection match = org.netbeans.api.java.source.matching.Matcher.create(info).setTreeTopSearch().setSearchRoot(new TreePath(getCurrentPath(), a)).match(Pattern.createSimplePattern(new TreePath(getCurrentPath(), toCheck)));
                            
                            if (!match.isEmpty()) {
                                //should be kept:
                                it.remove();
                                break OUTER;
                            }
                        }
                        
                        nue = make.removeModifiersAnnotation(nue, a);
                    }
                    
                    for (AnnotationTree a : annotations) {
                        nue = make.addModifiersAnnotation(nue, a);
                        scan(a, p);
                    }
                } else {
                    nue = make.removeModifiersAnnotation(node, 0);
                }
                
                rewrite(node, nue);
                
                return null;
            }
            
            return super.visitModifiers(node, p);
        }

        @Override
        public Number visitNewArray(NewArrayTree node, Void p) {
            List dimensions = (List) resolveMultiParameters(node.getDimensions());
            List initializers = (List) resolveMultiParameters(node.getInitializers());
            NewArrayTree nue = make.NewArray(node.getType(), dimensions, initializers);

            rewrite(node, nue);
            return super.visitNewArray(node, p);
        }

        @Override
        public Number visitLambdaExpression(LambdaExpressionTree node, Void p) {
            List args = resolveMultiParameters(node.getParameters());
            LambdaExpressionTree nue = make.LambdaExpression(args, node.getBody());

            Hacks.copyLambdaKind(node, nue);

            rewrite(node, nue);

            return super.visitLambdaExpression(node, p);
        }

        @Override
        public Number visitAnnotation(AnnotationTree node, Void p) {
            List args = resolveMultiParameters(node.getArguments());
            AnnotationTree nue = make.Annotation(node.getAnnotationType(), args);

            rewrite(node, nue);

            return super.visitAnnotation(node, p);
        }

        @SuppressWarnings("unchecked")
        private  List resolveMultiParameters(List list) {
            if (list == null) return null;
            if (!Utilities.containsMultistatementTrees(list)) return list;

            List result = new LinkedList<>();

            for (T t : list) {
                if (Utilities.isMultistatementWildcardTree(t)) {
                    Collection embedded = parametersMulti.get(Utilities.getWildcardTreeName(t).toString());

                    if (embedded != null) {
                        for (TreePath tp : embedded) {
                            if (tp != null) {
                                result.add((T) tp.getLeaf());
                            }
                        }
                    }
                } else {
                    result.add(t);
                }
            }

            return result;
        }
        
        @SuppressWarnings("unchecked")
        private  T resolveOptionalValue(T in) {
            if (in != null && Utilities.isMultistatementWildcardTree(in)) {
                TreePath out = parameters.get(Utilities.getWildcardTreeName(in).toString());
                if (out != null) return (T) out.getLeaf();
                return null;
            }
            
            return in;
        }

        private ExpressionTree negate(ExpressionTree original, Tree parent, boolean nullOnPlainNeg) {
            ExpressionTree newTree;
            switch (original.getKind()) {
                case PARENTHESIZED:
                    ExpressionTree expr = ((ParenthesizedTree) original).getExpression();
                    ExpressionTree negatedOrNull = negate(expr, original, nullOnPlainNeg);
                    if (negatedOrNull != null) {
                        if (negatedOrNull.getKind() != Kind.PARENTHESIZED) {
                            negatedOrNull = make.Parenthesized(negatedOrNull);
                        }
                    }
                    return negatedOrNull;
                    /**
                    if (nullOnPlainNeg) {
                        return null;
                    } else {
                        return make.Unary(Kind.LOGICAL_COMPLEMENT, original);
                    }
                    */
                    
                case INSTANCE_OF:
                    return make.Unary(Kind.LOGICAL_COMPLEMENT, make.Parenthesized(original));
                    
                case LOGICAL_COMPLEMENT:
                    newTree = ((UnaryTree) original).getExpression();
                    while (newTree.getKind() == Kind.PARENTHESIZED && !JavaFixUtilities.requiresParenthesis(((ParenthesizedTree) newTree).getExpression(), original, parent)) {
                        newTree = ((ParenthesizedTree) newTree).getExpression();
                    }
                    break;
                case NOT_EQUAL_TO:
                    newTree = negateBinaryOperator(original, Kind.EQUAL_TO, false);
                    break;
                case EQUAL_TO:
                    newTree = negateBinaryOperator(original, Kind.NOT_EQUAL_TO, false);
                    break;
                case BOOLEAN_LITERAL:
                    newTree = make.Literal(!(Boolean) ((LiteralTree) original).getValue());
                    break;
                case CONDITIONAL_AND:
                    newTree = negateBinaryOperator(original, Kind.CONDITIONAL_OR, true);
                    break;
                case CONDITIONAL_OR:
                    newTree = negateBinaryOperator(original, Kind.CONDITIONAL_AND, true);
                    break;
                case LESS_THAN:
                    newTree = negateBinaryOperator(original, Kind.GREATER_THAN_EQUAL, false);
                    break;
                case LESS_THAN_EQUAL:
                    newTree = negateBinaryOperator(original, Kind.GREATER_THAN, false);
                    break;
                case GREATER_THAN:
                    newTree = negateBinaryOperator(original, Kind.LESS_THAN_EQUAL, false);
                    break;
                case GREATER_THAN_EQUAL:
                    newTree = negateBinaryOperator(original, Kind.LESS_THAN, false);
                    break;
                default:
                    if (nullOnPlainNeg)
                        return null;
                    newTree = make.Unary(Kind.LOGICAL_COMPLEMENT, original);
            }
         
            if (JavaFixUtilities.requiresParenthesis(newTree, original, parent)) {
                newTree = make.Parenthesized(newTree);
            }
            
            return newTree;
        }
        
        private ExpressionTree negateBinaryOperator(Tree original, Kind newKind, boolean negateOperands) {
            BinaryTree bt = (BinaryTree) original;
            BinaryTree nonNegated = make.Binary(newKind,
                                                bt.getLeftOperand(),
                                                bt.getRightOperand());
            if (negateOperands) {
                ExpressionTree lo = negate(bt.getLeftOperand(), nonNegated, false);
                ExpressionTree ro = negate(bt.getRightOperand(), nonNegated, false);
                return make.Binary(newKind,
                                   lo != null ? lo : bt.getLeftOperand(),
                                   ro != null ? ro : bt.getRightOperand());
            }
            return nonNegated;
        }
        
        private void rewrite(Tree from, Tree to) {
            if (originalTrees.contains(from)) return ;
            rewriteFromTo.put(from, to);
            order.add(from);
        }
    }

    private static final Map OPERATOR_PRIORITIES;
    
    static {
        OPERATOR_PRIORITIES = new EnumMap<>(Kind.class);

        OPERATOR_PRIORITIES.put(Kind.IDENTIFIER, 0);

        for (Kind k : Kind.values()) {
            if (k.asInterface() == LiteralTree.class) {
                OPERATOR_PRIORITIES.put(k, 0);
            }
        }

        OPERATOR_PRIORITIES.put(Kind.ARRAY_ACCESS, 1);
        OPERATOR_PRIORITIES.put(Kind.METHOD_INVOCATION, 1);
        OPERATOR_PRIORITIES.put(Kind.MEMBER_REFERENCE, 1);
        OPERATOR_PRIORITIES.put(Kind.MEMBER_SELECT, 1);
        OPERATOR_PRIORITIES.put(Kind.POSTFIX_DECREMENT, 1);
        OPERATOR_PRIORITIES.put(Kind.POSTFIX_INCREMENT, 1);
        OPERATOR_PRIORITIES.put(Kind.NEW_ARRAY, 1);
        OPERATOR_PRIORITIES.put(Kind.NEW_CLASS, 1);

        OPERATOR_PRIORITIES.put(Kind.BITWISE_COMPLEMENT, 2);
        OPERATOR_PRIORITIES.put(Kind.LOGICAL_COMPLEMENT, 2);
        OPERATOR_PRIORITIES.put(Kind.PREFIX_DECREMENT, 2);
        OPERATOR_PRIORITIES.put(Kind.PREFIX_INCREMENT, 2);
        OPERATOR_PRIORITIES.put(Kind.UNARY_MINUS, 2);
        OPERATOR_PRIORITIES.put(Kind.UNARY_PLUS, 2);

        OPERATOR_PRIORITIES.put(Kind.TYPE_CAST, 3);

        OPERATOR_PRIORITIES.put(Kind.DIVIDE, 4);
        OPERATOR_PRIORITIES.put(Kind.MULTIPLY, 4);
        OPERATOR_PRIORITIES.put(Kind.REMAINDER, 4);

        OPERATOR_PRIORITIES.put(Kind.MINUS, 5);
        OPERATOR_PRIORITIES.put(Kind.PLUS, 5);

        OPERATOR_PRIORITIES.put(Kind.LEFT_SHIFT, 6);
        OPERATOR_PRIORITIES.put(Kind.RIGHT_SHIFT, 6);
        OPERATOR_PRIORITIES.put(Kind.UNSIGNED_RIGHT_SHIFT, 6);

        OPERATOR_PRIORITIES.put(Kind.INSTANCE_OF, 7);
        OPERATOR_PRIORITIES.put(Kind.GREATER_THAN, 7);
        OPERATOR_PRIORITIES.put(Kind.GREATER_THAN_EQUAL, 7);
        OPERATOR_PRIORITIES.put(Kind.LESS_THAN, 7);
        OPERATOR_PRIORITIES.put(Kind.LESS_THAN_EQUAL, 7);

        OPERATOR_PRIORITIES.put(Kind.EQUAL_TO, 8);
        OPERATOR_PRIORITIES.put(Kind.NOT_EQUAL_TO, 8);

        OPERATOR_PRIORITIES.put(Kind.AND, 9);
        OPERATOR_PRIORITIES.put(Kind.OR, 11);
        OPERATOR_PRIORITIES.put(Kind.XOR, 10);

        OPERATOR_PRIORITIES.put(Kind.CONDITIONAL_AND, 12);
        OPERATOR_PRIORITIES.put(Kind.CONDITIONAL_OR, 13);

        OPERATOR_PRIORITIES.put(Kind.CONDITIONAL_EXPRESSION, 14);

        OPERATOR_PRIORITIES.put(Kind.AND_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.DIVIDE_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.LEFT_SHIFT_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.MINUS_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.MULTIPLY_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.OR_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.PLUS_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.REMAINDER_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.RIGHT_SHIFT_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, 15);
        OPERATOR_PRIORITIES.put(Kind.XOR_ASSIGNMENT, 15);
    }

    /**
     * Checks whether {@code tree} can be used in places where a Primary is
     * required (for instance, as the receiver expression of a method invocation).
     * 

This is a friend API intended to be used by the java.hints module. * Other modules should not use this API because it might not be stabilized. * @param tree the tree to check * @return {@code true} iff {@code tree} can be used where a Primary is * required. * @since 1.31 */ public static boolean isPrimary(@NonNull Tree tree) { final Integer treePriority = OPERATOR_PRIORITIES.get(tree.getKind()); return (treePriority != null && treePriority <= 1); } /**Checks whether putting {@code inner} tree into {@code outter} tree, * when {@code original} is being replaced with {@code inner} requires parentheses. * * @param inner the new tree node that will be placed under {@code outter} * @param original the tree node that is being replaced with {@code inner} * @param outter the future parent node of {@code inner} * @return true if and only if inner needs to be wrapped using {@link TreeMaker#Parenthesized(com.sun.source.tree.ExpressionTree) } * to keep the original meaning. */ public static boolean requiresParenthesis(Tree inner, Tree original, Tree outter) { if (!ExpressionTree.class.isAssignableFrom(inner.getKind().asInterface()) || outter == null) return false; if (!ExpressionTree.class.isAssignableFrom(outter.getKind().asInterface())) { boolean condition = false; switch (outter.getKind()) { case IF: condition = original == ((IfTree)outter).getCondition(); break; case WHILE_LOOP: condition = original == ((WhileLoopTree)outter).getCondition(); break; case DO_WHILE_LOOP: condition = original == ((DoWhileLoopTree)outter).getCondition(); break; } return condition && inner.getKind() != Tree.Kind.PARENTHESIZED; } if (outter.getKind() == Kind.PARENTHESIZED || inner.getKind() == Kind.PARENTHESIZED) return false; if (outter.getKind() == Kind.METHOD_INVOCATION) { if (((MethodInvocationTree) outter).getArguments().contains(original)) return false; } if (outter.getKind() == Kind.LAMBDA_EXPRESSION) { LambdaExpressionTree lt = ((LambdaExpressionTree)outter); if (lt.getParameters().contains(original)) { return false; } if (lt.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT) { return false; } return original.getKind() == Tree.Kind.PARENTHESIZED; } if (outter.getKind() == Kind.NEW_CLASS) { if (((NewClassTree) outter).getArguments().contains(original)) return false; } Integer innerPriority = OPERATOR_PRIORITIES.get(inner.getKind()); Integer outterPriority = OPERATOR_PRIORITIES.get(outter.getKind()); if (innerPriority == null || outterPriority == null) { Logger.getLogger(JavaFix.class.getName()).log(Level.WARNING, "Unknown tree kind(s): {0}/{1}", new Object[] {inner.getKind(), outter.getKind()}); return true; } if (innerPriority > outterPriority) { return true; } if (innerPriority < outterPriority) { return false; } //associativity if (BinaryTree.class.isAssignableFrom(outter.getKind().asInterface())) { BinaryTree ot = (BinaryTree) outter; //TODO: for + it might be possible to skip the parenthesis: return ot.getRightOperand() == original; } if (CompoundAssignmentTree.class.isAssignableFrom(outter.getKind().asInterface())) { CompoundAssignmentTree ot = (CompoundAssignmentTree) outter; return ot.getVariable() == original; } if (AssignmentTree.class.isAssignableFrom(outter.getKind().asInterface())) { AssignmentTree ot = (AssignmentTree) outter; return ot.getVariable() == original; } return false; } private static final class RemoveFromParent extends JavaFix { private final String displayName; private final boolean safely; public RemoveFromParent(String displayName, CompilationInfo info, TreePath toRemove, boolean safely) { super(info, toRemove); this.displayName = displayName; this.safely = safely; } @Override protected String getText() { return displayName; } @Override protected void performRewrite(TransformationContext ctx) { WorkingCopy wc = ctx.getWorkingCopy(); TreePath tp = ctx.getPath(); doRemoveFromParent(wc, tp); if (safely) { Element el = wc.getTrees().getElement(tp); if (el != null) { new TreePathScanner() { @Override public Void scan(Tree tree, Void p) { if (tree != null && tree != tp.getLeaf()) { TreePath treePath = new TreePath(getCurrentPath(), tree); Element e = wc.getTrees().getElement(treePath); if (el == e) { doRemoveFromParent(wc, treePath); } } return super.scan(tree, p); } }.scan(new TreePath(wc.getCompilationUnit()), null); } } } private void doRemoveFromParent(WorkingCopy wc, TreePath what) { TreeMaker make = wc.getTreeMaker(); Tree leaf = what.getLeaf(); Tree parentLeaf = what.getParentPath().getLeaf(); switch (parentLeaf.getKind()) { case ANNOTATION: AnnotationTree at = (AnnotationTree) parentLeaf; AnnotationTree newAnnot; newAnnot = make.removeAnnotationAttrValue(at, (ExpressionTree) leaf); wc.rewrite(at, newAnnot); break; case BLOCK: BlockTree bt = (BlockTree) parentLeaf; wc.rewrite(bt, make.removeBlockStatement(bt, (StatementTree) leaf)); break; case CASE: CaseTree caseTree = (CaseTree) parentLeaf; wc.rewrite(caseTree, make.removeCaseStatement(caseTree, (StatementTree) leaf)); break; case CLASS: ClassTree classTree = (ClassTree) parentLeaf; ClassTree nueClassTree; if (classTree.getTypeParameters().contains(leaf)) { nueClassTree = make.removeClassTypeParameter(classTree, (TypeParameterTree) leaf); } else if (classTree.getExtendsClause() == leaf) { nueClassTree = make.Class(classTree.getModifiers(), classTree.getSimpleName(), classTree.getTypeParameters(), null, classTree.getImplementsClause(), classTree.getMembers()); } else if (classTree.getImplementsClause().contains(leaf)) { nueClassTree = make.removeClassImplementsClause(classTree, leaf); } else if (classTree.getMembers().contains(leaf)) { nueClassTree = make.removeClassMember(classTree, leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(classTree, nueClassTree); break; case UNION_TYPE: UnionTypeTree disjunct = (UnionTypeTree) parentLeaf; List alternatives = new LinkedList(disjunct.getTypeAlternatives()); alternatives.remove(leaf); wc.rewrite(disjunct, make.UnionType(alternatives)); break; case METHOD: MethodTree mTree = (MethodTree) parentLeaf; MethodTree newMethod; if (mTree.getTypeParameters().contains(leaf)) { newMethod = make.removeMethodTypeParameter(mTree, (TypeParameterTree) leaf); } else if (mTree.getParameters().contains(leaf)) { newMethod = make.removeMethodParameter(mTree, (VariableTree) leaf); } else if (mTree.getThrows().contains(leaf)) { newMethod = make.removeMethodThrows(mTree, (ExpressionTree) leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(mTree, newMethod); break; case METHOD_INVOCATION: MethodInvocationTree iTree = (MethodInvocationTree) parentLeaf; MethodInvocationTree newInvocation; if (iTree.getTypeArguments().contains(leaf)) { newInvocation = make.removeMethodInvocationTypeArgument(iTree, (ExpressionTree) leaf); } else if (iTree.getArguments().contains(leaf)) { newInvocation = make.removeMethodInvocationArgument(iTree, (ExpressionTree) leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(iTree, newInvocation); break; case MODIFIERS: ModifiersTree modsTree = (ModifiersTree) parentLeaf; wc.rewrite(modsTree, make.removeModifiersAnnotation(modsTree, (AnnotationTree) leaf)); break; case NEW_CLASS: NewClassTree newCTree = (NewClassTree) parentLeaf; NewClassTree newNCT; if (newCTree.getTypeArguments().contains(leaf)) { newNCT = make.removeNewClassTypeArgument(newCTree, (ExpressionTree) leaf); } else if (newCTree.getArguments().contains(leaf)) { newNCT = make.removeNewClassArgument(newCTree, (ExpressionTree) leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(newCTree, newNCT); break; case PARAMETERIZED_TYPE: ParameterizedTypeTree parTree = (ParameterizedTypeTree) parentLeaf; wc.rewrite(parTree, make.removeParameterizedTypeTypeArgument(parTree, (ExpressionTree) leaf)); break; case SWITCH: SwitchTree switchTree = (SwitchTree) parentLeaf; SwitchTree newSwitch; if (switchTree.getCases().contains(leaf)) { newSwitch = make.removeSwitchCase(switchTree, (CaseTree) leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(switchTree, newSwitch); break; case TRY: TryTree tryTree = (TryTree) parentLeaf; TryTree newTry; if (tryTree.getResources().contains(leaf)) { LinkedList resources = new LinkedList<>(tryTree.getResources()); resources.remove(leaf); newTry = make.Try(resources, tryTree.getBlock(), tryTree.getCatches(), tryTree.getFinallyBlock()); } else if (tryTree.getCatches().contains(leaf)) { newTry = make.removeTryCatch(tryTree, (CatchTree) leaf); } else { throw new UnsupportedOperationException(); } wc.rewrite(tryTree, newTry); break; case EXPRESSION_STATEMENT: doRemoveFromParent(wc, what.getParentPath()); break; case ASSIGNMENT: AssignmentTree assignmentTree = (AssignmentTree) parentLeaf; if (leaf == assignmentTree.getVariable()) { if (wc.getTreeUtilities().isExpressionStatement(assignmentTree.getExpression())) { wc.rewrite(parentLeaf, assignmentTree.getExpression()); } else { doRemoveFromParent(wc, what.getParentPath()); } } else { throw new UnsupportedOperationException(); } break; case AND_ASSIGNMENT: case DIVIDE_ASSIGNMENT: case LEFT_SHIFT_ASSIGNMENT: case MINUS_ASSIGNMENT: case MULTIPLY_ASSIGNMENT: case OR_ASSIGNMENT: case PLUS_ASSIGNMENT: case REMAINDER_ASSIGNMENT: case RIGHT_SHIFT_ASSIGNMENT: case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: case XOR_ASSIGNMENT: CompoundAssignmentTree compoundAssignmentTree = (CompoundAssignmentTree) parentLeaf; if (leaf == compoundAssignmentTree.getVariable()) { if (wc.getTreeUtilities().isExpressionStatement(compoundAssignmentTree.getExpression())) { wc.rewrite(parentLeaf, compoundAssignmentTree.getExpression()); } else { doRemoveFromParent(wc, what.getParentPath()); } } else { throw new UnsupportedOperationException(); } break; default: wc.rewrite(what.getLeaf(), make.Block(Collections.emptyList(), false)); break; } } private static boolean canSafelyRemove(CompilationInfo info, TreePath tp) { AtomicBoolean ret = new AtomicBoolean(true); Element el = info.getTrees().getElement(tp); if (el != null) { new TreePathScanner() { @Override public Void scan(Tree tree, Void p) { if (tree != null && tree != tp.getLeaf()) { TreePath treePath = new TreePath(getCurrentPath(), tree); Element e = info.getTrees().getElement(treePath); if (el == e) { Tree parentLeaf = treePath.getParentPath().getLeaf(); switch (parentLeaf.getKind()) { case ASSIGNMENT: AssignmentTree assignmentTree = (AssignmentTree) parentLeaf; if (tree == assignmentTree.getVariable()) { if (!info.getTreeUtilities().isExpressionStatement(assignmentTree.getExpression()) && canHaveSideEffects(assignmentTree.getExpression())) { ret.set(false); } } else { ret.set(false); } break; case AND_ASSIGNMENT: case DIVIDE_ASSIGNMENT: case LEFT_SHIFT_ASSIGNMENT: case MINUS_ASSIGNMENT: case MULTIPLY_ASSIGNMENT: case OR_ASSIGNMENT: case PLUS_ASSIGNMENT: case REMAINDER_ASSIGNMENT: case RIGHT_SHIFT_ASSIGNMENT: case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: case XOR_ASSIGNMENT: CompoundAssignmentTree compoundAssignmentTree = (CompoundAssignmentTree) parentLeaf; if (tree == compoundAssignmentTree.getVariable()) { if (!info.getTreeUtilities().isExpressionStatement(compoundAssignmentTree.getExpression()) && canHaveSideEffects(compoundAssignmentTree.getExpression())) { ret.set(false); } } else { ret.set(false); } break; default: ret.set(false); } } } return super.scan(tree, p); } }.scan(new TreePath(info.getCompilationUnit()), null); } return ret.get(); } private static boolean canHaveSideEffects(Tree tree) { AtomicBoolean ret = new AtomicBoolean(); new TreeScanner() { @Override public Void scan(Tree tree, Void p) { if (tree != null) { switch (tree.getKind()) { case METHOD_INVOCATION: case NEW_CLASS: case POSTFIX_DECREMENT: case POSTFIX_INCREMENT: case PREFIX_DECREMENT: case PREFIX_INCREMENT: ret.set(true); break; } } return super.scan(tree, p); } }.scan(tree, null); return ret.get(); } } //TODO: from FileMovePlugin private static class MoveFile extends SimpleRefactoringElementImplementation { private FileObject toMove; private final FileObject sourceRoot; private final String targetFolderName; public MoveFile(FileObject toMove, FileObject sourceRoot, String targetFolderName) { this.toMove = toMove; this.sourceRoot = sourceRoot; this.targetFolderName = targetFolderName; } @Override @Messages({"#{0} - original file name", "TXT_MoveFile=Move {0}"}) public String getText() { return Bundle.TXT_MoveFile(toMove.getNameExt()); } @Override public String getDisplayText() { return getText(); } DataFolder sourceFolder; DataObject source; @Override public void performChange() { try { FileObject target = FileUtil.createFolder(sourceRoot, targetFolderName); DataFolder targetFolder = DataFolder.findFolder(target); if (!toMove.isValid()) { String path = FileUtil.getFileDisplayName(toMove); Logger.getLogger(JavaFix.class.getName()).fine("Invalid FileObject " + path + "trying to recreate..."); toMove = FileUtil.toFileObject(FileUtil.toFile(toMove)); if (toMove==null) { Logger.getLogger(JavaFix.class.getName()).severe("Invalid FileObject " + path + "\n. File not found."); return; } } source = DataObject.find(toMove); sourceFolder = source.getFolder(); source.move(targetFolder); } catch (DataObjectNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } @Override public void undoChange() { try { source.move(sourceFolder); } catch (DataObjectNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } @Override public Lookup getLookup() { return Lookup.EMPTY; } @Override public FileObject getParentFile() { return toMove; } @Override public PositionBounds getPosition() { return null; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy