blob: 22699f472d2c0a032e21f5d368b56f6c3c34458a [file] [log] [blame]
/*
* 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.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.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.<String, String>emptyMap());
}
static Fix rewriteFix(CompilationInfo info, String displayName, TreePath what, final String to, Map<String, TreePath> parameters, Map<String, Collection<? extends TreePath>> parametersMulti, final Map<String, String> parameterNames, Map<String, TypeMirror> constraints, Map<String, String> options, String... imports) {
final Map<String, TreePathHandle> params = new HashMap<String, TreePathHandle>();
final Map<String, Object> extraParamsData = new HashMap<String, Object>();
final Map<String, ElementHandle<?>> implicitThis = new HashMap<>();
for (Entry<String, TreePath> 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<String, Collection<TreePathHandle>> paramsMulti = new HashMap<String, Collection<TreePathHandle>>();
for (Entry<String, Collection<? extends TreePath>> e : parametersMulti.entrySet()) {
Collection<TreePathHandle> tph = new LinkedList<TreePathHandle>();
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<String, TypeMirrorHandle<?>> constraintsHandles = new HashMap<String, TypeMirrorHandle<?>>();
for (Entry<String, TypeMirror> 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<Tree> immediateChildren(Tree t) {
Set<Tree> children = new HashSet<>();
t.accept(new TreeScanner<Void, Void>() {
@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).toEditorFix();
}
private static String defaultFixDisplayName(CompilationInfo info, Map<String, TreePath> variables, String replaceTarget) {
Map<String, String> stringsForVariables = new HashMap<String, String>();
for (Entry<String, TreePath> 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<String, String> 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<String, TreePathHandle> params;
private final Map<String, Object> extraParamsData;
private final Map<String, ElementHandle<?>> implicitThis;
private final Map<String, Collection<TreePathHandle>> paramsMulti;
private final Map<String, String> parameterNames;
private final Map<String, TypeMirrorHandle<?>> constraintsHandles;
private final Iterable<? extends String> imports;
private final String to;
public JavaFixRealImpl(CompilationInfo info, TreePath what, Map<String, String> options, String displayName, String to, Map<String, TreePathHandle> params, Map<String, Object> extraParamsData, Map<String, ElementHandle<?>> implicitThis, Map<String, Collection<TreePathHandle>> paramsMulti, final Map<String, String> parameterNames, Map<String, TypeMirrorHandle<?>> constraintsHandles, Iterable<? extends String> 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<String, TreePath> parameters = new HashMap<String, TreePath>();
for (Entry<String, TreePathHandle> 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<String, Element> implicitThis = new HashMap<>();
for (Entry<String, ElementHandle<?>> 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<String, Collection<TreePath>> parametersMulti = new HashMap<String, Collection<TreePath>>();
for (Entry<String, Collection<TreePathHandle>> e : paramsMulti.entrySet()) {
Collection<TreePath> tps = new LinkedList<TreePath>();
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<String, TypeMirror> constraints = new HashMap<String, TypeMirror>();
for (Entry<String, TypeMirrorHandle<?>> 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<Tree, Tree> rewriteFromTo = new IdentityHashMap<Tree, Tree>();
List<Tree> order = new ArrayList<Tree>(7);
Tree original;
if (Utilities.isFakeBlock(parsed)) {
TreePath parent = tp.getParentPath();
List<? extends StatementTree> 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<StatementTree> newStatements = new LinkedList<StatementTree>();
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<? extends Tree> members = ((ClassTree) parsed).getMembers();
members = members.subList(1, members.size());
assert parent.getLeaf().getKind() == Kind.CLASS;
List<Tree> newMembers = new LinkedList<Tree>();
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<StatementTree> newStatements = new LinkedList<StatementTree>();
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<Tree> originalTrees = Collections.newSetFromMap(new IdentityHashMap<Tree, Boolean>());
new ErrorAwareTreeScanner<Void, Void>() {
@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<Tree, Tree>(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<Kind> NUMBER_LITERAL_KINDS = EnumSet.of(Kind.FLOAT_LITERAL, Kind.DOUBLE_LITERAL, Kind.INT_LITERAL, Kind.LONG_LITERAL);
private static class ReplaceParameters extends ErrorAwareTreePathScanner<Number, Void> {
private final CompilationInfo info;
private final TreeMaker make;
private final boolean canShowUI;
private final boolean inImport;
private final Map<String, TreePath> parameters;
private final Map<String, Object> extraParamsData;
private final Map<String, Element> implicitThis;
private final Map<String, Collection<TreePath>> parametersMulti;
private final Map<String, String> parameterNames;
private final Map<Tree, Tree> rewriteFromTo;
private final Set<Tree> originalTrees;
private final List<Tree> order;
private final List<Element> nestedTypes = new ArrayList<>();
public ReplaceParameters(WorkingCopy wc, boolean canShowUI, boolean inImport, Map<String, TreePath> parameters, Map<String, Object> extraParamsData, Map<String, Element> implicitThis, Map<String, Collection<TreePath>> parametersMulti, Map<String, String> parameterNames, Map<Tree, Tree> rewriteFromTo, List<Tree> order, Set<Tree> 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<Kind> 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<? extends TypeParameterTree> typeParams = resolveMultiParameters(node.getTypeParameters());
List<? extends VariableTree> params = resolveMultiParameters(node.getParameters());
List<? extends ExpressionTree> 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<? extends TypeParameterTree> typeParams = resolveMultiParameters(node.getTypeParameters());
List<? extends Tree> implementsClauses = resolveMultiParameters(node.getImplementsClause());
List<? extends Tree> 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<? extends StatementTree> 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<? extends StatementTree> statements = (List<? extends StatementTree>) resolveMultiParameters(node.getStatements());
CaseTree nue = make.Case(node.getExpression(), statements);
rewrite(node, nue);
return super.visitCase(node, p);
}
@Override
public Number visitMethodInvocation(MethodInvocationTree node, Void p) {
List<? extends ExpressionTree> typeArgs = (List<? extends ExpressionTree>) resolveMultiParameters(node.getTypeArguments());
List<? extends ExpressionTree> args = resolveMultiParameters(node.getArguments());
MethodInvocationTree nue = make.MethodInvocation(typeArgs, node.getMethodSelect(), args);
rewrite(node, nue);
return super.visitMethodInvocation(nue, p);
}
@Override
public Number visitNewClass(NewClassTree node, Void p) {
List<? extends ExpressionTree> typeArgs = (List<? extends ExpressionTree>) resolveMultiParameters(node.getTypeArguments());
List<? extends ExpressionTree> 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
public Number visitParameterizedType(ParameterizedTypeTree node, Void p) {
List<? extends ExpressionTree> typeArgs = (List<? extends ExpressionTree>) 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<? extends CaseTree> cases = (List<? extends CaseTree>) 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<? extends Tree> resources = (List<? extends Tree>) resolveMultiParameters(node.getResources());
List<? extends CatchTree> catches = (List<? extends CatchTree>) 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<AnnotationTree> annotations = new ArrayList<AnnotationTree>(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<Modifier> 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<AnnotationTree> it = annotations.iterator(); it.hasNext();) {
AnnotationTree toCheck = it.next();
Collection<? extends Occurrence> 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<? extends ExpressionTree> dimensions = (List<? extends ExpressionTree>) resolveMultiParameters(node.getDimensions());
List<? extends ExpressionTree> initializers = (List<? extends ExpressionTree>) 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<? extends VariableTree> 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<? extends ExpressionTree> args = resolveMultiParameters(node.getArguments());
AnnotationTree nue = make.Annotation(node.getAnnotationType(), args);
rewrite(node, nue);
return super.visitAnnotation(node, p);
}
private <T extends Tree> List<T> resolveMultiParameters(List<T> list) {
if (list == null) return null;
if (!Utilities.containsMultistatementTrees(list)) return list;
List<T> result = new LinkedList<T>();
for (T t : list) {
if (Utilities.isMultistatementWildcardTree(t)) {
Collection<TreePath> 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;
}
private <T extends Tree> 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<Kind, Integer> OPERATOR_PRIORITIES;
static {
OPERATOR_PRIORITIES = new EnumMap<Kind, Integer>(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).
* <p>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;
public RemoveFromParent(String displayName, CompilationInfo info, TreePath toRemove) {
super(info, toRemove);
this.displayName = displayName;
}
@Override
protected String getText() {
return displayName;
}
@Override
protected void performRewrite(TransformationContext ctx) {
WorkingCopy wc = ctx.getWorkingCopy();
TreePath tp = ctx.getPath();
doRemoveFromParent(wc, tp);
}
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<? extends Tree> alternatives = new LinkedList<Tree>(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<Tree> resources = new LinkedList<Tree>(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;
default:
wc.rewrite(what.getLeaf(), make.Block(Collections.<StatementTree>emptyList(), false));
break;
}
}
}
//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;
}
}
}