blob: 7fd88acf35cb289359df666747fa2b9ab294dc67 [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.modules.java.source.queriesimpl;
import com.sun.source.tree.*;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Types;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.source.Comment;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.ElementUtilities;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.modules.java.source.queries.api.QueryException;
import org.netbeans.modules.java.source.queries.spi.ModelOperations;
import org.openide.filesystems.FileUtil;
/**
*
* @author Tomas Zezula
*/
class JavaOperationsImpl<T> implements ModelOperations {
private final CompilationController control;
JavaOperationsImpl(@NonNull final CompilationController control) {
assert control != null;
this.control = control;
}
@Override
@NonNull
public Collection<? extends String> getTopLevelClasses() throws QueryException {
try {
if (control.toPhase(Phase.ELEMENTS_RESOLVED) != Phase.ELEMENTS_RESOLVED) {
throw new QueryException("Cannot resolve file: " + //NOI18N
Optional.ofNullable(control.getFileObject())
.map((fo) -> FileUtil.getFileDisplayName(fo))
.orElse("<unkown>")); //NOI18N
}
} catch (IOException ioe) {
throw new QueryException(ioe);
}
final Collection<? extends Element> topLevels = control.getTopLevelElements();
final List<String> result = new ArrayList<String>(topLevels.size());
for (Element topLevel : topLevels) {
result.add(((TypeElement)topLevel).getQualifiedName().toString());
}
return result;
}
@Override
@CheckForNull
public String getSuperClass(@NonNull final String cls) throws QueryException {
final TypeElement te = findClass(cls);
if (te == null) {
return null;
}
final TypeMirror superType = te.getSuperclass();
if (superType.getKind() != TypeKind.DECLARED) {
return null;
}
return ((TypeElement)((DeclaredType)superType).asElement()).getQualifiedName().toString();
}
@NonNull
@Override
public final Collection<? extends String> getInterfaces(@NonNull final String cls) throws QueryException {
final TypeElement te = findClass(cls);
if (te == null) {
return null;
}
final List<? extends TypeMirror> interfaceTypes = te.getInterfaces();
final List<String> result = new ArrayList<String>(interfaceTypes.size());
for (TypeMirror tm : interfaceTypes) {
if (tm.getKind() == TypeKind.DECLARED) {
result.add(
((TypeElement)((DeclaredType)tm).asElement()).getQualifiedName().toString());
}
}
return Collections.unmodifiableCollection(result);
}
@Override
@CheckForNull
public String getClassBinaryName(@NonNull final String cls) throws QueryException {
final TypeElement te = findClass(cls);
return te == null ? null : ElementUtilities.getBinaryName(te);
}
@Override
@NonNull
public Collection<? extends String> getFieldNames(
@NonNull final String clz,
final boolean rt,
@NullAllowed final String type) throws QueryException {
final TypeElement te = findClass(clz);
if (te == null) {
return Collections.<String>emptyList();
}
final Types types = control.getTypes();
TypeMirror tm = null;
if (type != null) {
final List<? extends TypeElement> topLevels = control.getTopLevelElements();
tm = topLevels.isEmpty() ?
null :
control.getTreeUtilities().parseType(type, topLevels.get(0));
if (tm == null) {
return Collections.<String>emptyList();
} else if (rt) {
tm = types.erasure(tm);
}
}
final Collection<String> result = new ArrayList<String>();
for (VariableElement ve : ElementFilter.fieldsIn(te.getEnclosedElements())) {
if (isSameType(types,tm,ve.asType(),rt)) {
result.add(ve.getSimpleName().toString());
}
}
return Collections.unmodifiableCollection(result);
}
@Override
@NonNull
public Collection<? extends String> getMethodNames(
final @NonNull String clz,
final boolean rt,
final @NullAllowed String returnType,
final @NullAllowed String... parameterTypes) throws QueryException {
final List<? extends ExecutableElement> methods = getMethods(clz, null, rt, returnType, parameterTypes);
final List<String> result = new ArrayList<String>(methods.size());
for (ExecutableElement method : methods) {
result.add(method.getSimpleName().toString());
}
return Collections.unmodifiableCollection(result);
}
@Override
@CheckForNull
public int[] getMethodSpan(
@NonNull final String clz,
@NonNull final String methodName,
final boolean rt,
@NonNull final String returnType,
@NonNull final String... parameterTypes) throws QueryException {
final List<? extends ExecutableElement> methods = getMethods(clz, methodName, rt, returnType, parameterTypes);
if (methods.isEmpty()) {
return null;
}
//Todo: if size > 1 => 2 methods with same signature (invalid source) use the first one
final ExecutableElement method = methods.get(0);
final Trees trees = control.getTrees();
final TreePath tp = trees.getPath(method);
if (tp == null) {
return null;
}
int start = (int) trees.getSourcePositions().getStartPosition(tp.getCompilationUnit(),tp.getLeaf());
int end = (int) trees.getSourcePositions().getEndPosition(tp.getCompilationUnit(),tp.getLeaf());
List<Comment> cmts = control.getTreeUtilities().getComments(tp.getLeaf(), true);
for (Comment c : cmts) {
final int cp = c.pos();
if (cp >= 0) {
start = Math.min(start,cp);
}
}
cmts = control.getTreeUtilities().getComments(tp.getLeaf(), false);
for (Comment c : cmts) {
final int cp = c.endPos();
end = Math.max(end,cp);
}
return new int[] {start, end};
}
@Override
public void modifyInterfaces(
@NonNull final String clz,
@NonNull final Collection<? extends String> toAdd,
@NonNull final Collection<? extends String> toRemove) throws QueryException {
if (!(control instanceof WorkingCopy)) {
throw new IllegalStateException();
}
final WorkingCopy wcopy = (WorkingCopy) control;
final TreePath mainClassTreePath = findClassInCompilationUnit(clz);
if (mainClassTreePath == null) {
throw new IllegalArgumentException("No class: " + clz + " in source: " + //NOI18N
FileUtil.getFileDisplayName(control.getFileObject()));
}
final Element mainClassElm = wcopy.getTrees().getElement(mainClassTreePath);
assert mainClassElm != null;
ClassTree mainClassTree = (ClassTree) mainClassTreePath.getLeaf();
final ClassTree origMainTree = mainClassTree;
if (mainClassElm != null) {
Set<String> actualInterfaces = new HashSet<String>();
TreeMaker maker = wcopy.getTreeMaker();
// first take the current interfaces and exclude the removed ones
List<? extends TypeMirror> interfaces = ((TypeElement) mainClassElm).getInterfaces();
for (int infIndex = interfaces.size() - 1; infIndex >= 0; infIndex--) {
TypeMirror infMirror = interfaces.get(infIndex);
TypeElement infElm = (TypeElement) wcopy.getTypes().asElement(infMirror);
actualInterfaces.add(infElm.getQualifiedName().toString());
if (toRemove.contains(infElm.getQualifiedName().toString())) {
mainClassTree = maker.removeClassImplementsClause(mainClassTree, infIndex);
}
}
for (String name : toAdd) {
if (!actualInterfaces.contains(name)) {
TypeElement inf2add = wcopy.getElements().getTypeElement(name);
ExpressionTree infTree2add = inf2add != null
? maker.QualIdent(inf2add)
: maker.Identifier(name);
mainClassTree = maker.addClassImplementsClause(mainClassTree, infTree2add);
}
}
if (origMainTree != mainClassTree) {
wcopy.rewrite(origMainTree, mainClassTree);
}
}
}
@Override
public void setSuperClass(
@NonNull final String clz,
@NonNull final String superClz) throws QueryException {
if (!(control instanceof WorkingCopy)) {
throw new IllegalStateException();
}
final WorkingCopy wcopy = (WorkingCopy) control;
final TreePath mainClassTreePath = findClassInCompilationUnit(clz);
if (mainClassTreePath == null) {
throw new IllegalArgumentException("No class: " + clz + " in source: " + //NOI18N
FileUtil.getFileDisplayName(control.getFileObject()));
}
final Element mainClassElm = wcopy.getTrees().getElement(mainClassTreePath);
assert mainClassElm != null;
ClassTree mainClassTree = (ClassTree) mainClassTreePath.getLeaf();
final ClassTree origMainTree = mainClassTree;
if (mainClassElm != null) {
final TreeMaker maker = wcopy.getTreeMaker();
ExpressionTree superClsTree = null;
if (!Object.class.getName().equals(superClz)){
final TypeElement inf2add = wcopy.getElements().getTypeElement(superClz);
superClsTree = inf2add != null
? maker.QualIdent(inf2add)
: maker.Identifier(superClz);
}
mainClassTree = maker.setExtends(mainClassTree,superClsTree);
if (origMainTree != mainClassTree) {
wcopy.rewrite(origMainTree, mainClassTree);
}
}
}
@Override
public void renameField(
@NonNull final String clz,
@NonNull final String oldName,
@NonNull final String newName) throws QueryException {
final WorkingCopy wcopy = (WorkingCopy) control;
try {
if (control.toPhase(Phase.RESOLVED) != Phase.RESOLVED) {
throw new QueryException("Cannot resolve file: " + //NOI18N
Optional.ofNullable(control.getFileObject())
.map((fo) -> FileUtil.getFileDisplayName(fo))
.orElse("<unkown>")); //NOI18N
}
} catch (IOException ioe) {
throw new QueryException(ioe);
}
final TypeElement te = findClass(clz);
if (te == null) {
throw new IllegalArgumentException("No class: " + clz + " in source: " + //NOI18N
FileUtil.getFileDisplayName(control.getFileObject()));
}
VariableElement field = null;
for (VariableElement ve : ElementFilter.fieldsIn(te.getEnclosedElements())) {
if (oldName.contentEquals(ve.getSimpleName())) {
field = ve;
break;
}
}
if (field == null) {
throw new IllegalArgumentException("No field: " + clz +"."+oldName + " in source: " + //NOI18N
FileUtil.getFileDisplayName(control.getFileObject()));
}
final Trees trees = wcopy.getTrees();
final TreeMaker maker = wcopy.getTreeMaker();
final CompilationUnitTree cu = wcopy.getCompilationUnit();
final TreePath fieldPath = trees.getPath(field);
if (fieldPath == null || !cu.equals(fieldPath.getCompilationUnit())) {
throw new IllegalArgumentException("No field: " + clz +"."+oldName + " in source: " + //NOI18N
FileUtil.getFileDisplayName(control.getFileObject()));
}
final VariableTree oldVarTree = (VariableTree) fieldPath.getLeaf();
final VariableTree newVarTree = wcopy.getTreeMaker().Variable(
oldVarTree.getModifiers(),
newName,
oldVarTree.getType(),
oldVarTree.getInitializer());
wcopy.rewrite(oldVarTree, newVarTree);
final VariableElement fieldF = field;
final ErrorAwareTreePathScanner<Void,Void> scanner = new ErrorAwareTreePathScanner<Void, Void>(){
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
super.visitIdentifier(node, p);
if (shouldRename()) {
wcopy.rewrite(
getCurrentPath().getLeaf(),
maker.Identifier(newName));
}
return null;
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
super.visitMemberSelect(node, p);
if (shouldRename()) {
wcopy.rewrite(
getCurrentPath().getLeaf(),
maker.MemberSelect(node.getExpression(), newName));
}
return null;
}
private boolean shouldRename() {
final Element e = trees.getElement(getCurrentPath());
return fieldF.equals(e);
}
};
scanner.scan(cu, null);
}
@Override
public void fixImports(
final int[][] ranges) throws QueryException {
if (!(control instanceof WorkingCopy)) {
throw new IllegalStateException();
}
Arrays.sort(ranges, new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
final WorkingCopy wcopy = (WorkingCopy) control;
try {
if (control.toPhase(Phase.RESOLVED) != Phase.RESOLVED) {
throw new QueryException("Cannot resolve file: " + //NOI18N
Optional.ofNullable(control.getFileObject())
.map((fo) -> FileUtil.getFileDisplayName(fo))
.orElse("<unkown>")); //NOI18N
}
} catch (IOException ioe) {
throw new QueryException(ioe);
}
final CompilationUnitTree cu = wcopy.getCompilationUnit();
final Trees trees = wcopy.getTrees();
final GeneratorUtilities utils = GeneratorUtilities.get(wcopy);
final List<Tree> toImport = new ArrayList<Tree>();
final ErrorAwareTreePathScanner<Void,Void> scanner = new ErrorAwareTreePathScanner<Void, Void>(){
@Override
public Void scan(Tree node, Void p) {
final int start = (int) trees.getSourcePositions().getStartPosition(cu, node);
final int end = (int) trees.getSourcePositions().getEndPosition(cu, node);
final int status = contains(ranges,start,end);
switch (status) {
case -1:
super.scan(node, p);
break;
case 0:
break;
case 1:
toImport.add(node);
break;
}
return null;
}
};
scanner.scan(cu, null);
for (Tree tree : toImport) {
wcopy.rewrite(tree, utils.importFQNs(tree));
}
}
private List<? extends ExecutableElement> getMethods(
final @NonNull String clz,
final @NullAllowed String methodName,
final boolean rt,
final @NullAllowed String returnType,
final @NullAllowed String... parameterTypes) throws QueryException {
final TypeElement te = findClass(clz);
if (te == null) {
return Collections.<ExecutableElement>emptyList();
}
final TreeUtilities treeUtils = control.getTreeUtilities();
final Types types = control.getTypes();
final List<? extends TypeElement> topLevels = control.getTopLevelElements();
if (topLevels.isEmpty()) {
return Collections.<ExecutableElement>emptyList();
}
TypeMirror rType = null;
List<TypeMirror> pTypes = null;
if (returnType != null) {
rType = treeUtils.parseType(
returnType,
topLevels.get(0));
if (rType == null) {
return Collections.<ExecutableElement>emptyList();
} else if (rt) {
rType = types.erasure(rType);
}
}
if (parameterTypes != null) {
pTypes = new ArrayList<TypeMirror>(parameterTypes.length + 1);
for (final String parameterType : parameterTypes) {
TypeMirror tm = treeUtils.parseType(
parameterType,
topLevels.get(0));
if (tm == null) {
return Collections.<ExecutableElement>emptyList();
} else if (rt) {
tm = types.erasure(tm);
}
pTypes.add(tm);
}
}
final List<ExecutableElement> result = new ArrayList<ExecutableElement>();
nextM: for (ExecutableElement me : ElementFilter.methodsIn(te.getEnclosedElements())) {
if (methodName != null && !methodName.contentEquals(me.getSimpleName())) {
continue nextM;
}
if (pTypes != null) {
final List<? extends VariableElement> params = me.getParameters();
if (params.size() != pTypes.size()) {
continue nextM;
}
final Iterator<? extends VariableElement> paramsIt = params.iterator();
final Iterator<? extends TypeMirror> pTypesIt = pTypes.iterator();
for (;paramsIt.hasNext();) {
if (!isSameType(types, pTypesIt.next(), paramsIt.next().asType(), rt)) {
continue nextM;
}
}
}
if (!isSameType(types, rType, me.getReturnType(), rt)) {
continue nextM;
}
result.add(me);
}
return result;
}
private TypeElement findClass(
@NonNull final String clz) throws QueryException {
try {
if (control.toPhase(Phase.ELEMENTS_RESOLVED) != Phase.ELEMENTS_RESOLVED) {
throw new QueryException("Cannot resolve file: " + //NOI18N
Optional.ofNullable(control.getFileObject())
.map((fo) -> FileUtil.getFileDisplayName(fo))
.orElse("<unkown>")); //NOI18N
}
} catch (IOException ioe) {
throw new QueryException(ioe);
}
return control.getElements().getTypeElement(clz);
}
private TreePath findClassInCompilationUnit(
@NonNull final String clz) throws QueryException {
try {
if (control.toPhase(Phase.ELEMENTS_RESOLVED) != Phase.ELEMENTS_RESOLVED) {
throw new QueryException("Cannot resolve file: " + //NOI18N
Optional.ofNullable(control.getFileObject())
.map((fo) -> FileUtil.getFileDisplayName(fo))
.orElse("<unkown>")); //NOI18N
}
} catch (IOException ioe) {
throw new QueryException(ioe);
}
final Trees trees = control.getTrees();
final ErrorAwareTreePathScanner<TreePath,Void> visitor = new ErrorAwareTreePathScanner<TreePath, Void>() {
@Override
public TreePath visitClass(ClassTree node, Void p) {
final Element el = trees.getElement(getCurrentPath());
if (el != null &&
(el.getKind().isClass() || el.getKind().isInterface()) &&
clz.contentEquals(((TypeElement)el).getQualifiedName())) {
return getCurrentPath();
} else {
return super.visitClass(node, p);
}
}
@Override
public TreePath visitMethod(MethodTree node, Void p) {
return null;
}
};
return visitor.scan(control.getCompilationUnit(), null);
}
private static boolean isSameType (
@NonNull Types types,
@NullAllowed final TypeMirror t1,
@NonNull final TypeMirror t2,
final boolean rawType) {
return t1 == null ||
types.isSameType(
t1,
rawType ? types.erasure(t2) : t2);
}
private static int contains(
@NonNull final int[][] ranges,
final int start,
final int end) {
for (int[] range : ranges) {
if (start >= range[0] && end <= range[1]) {
return 1;
}
if (start < range[0] && end > range[1]) {
return -1;
}
}
return 0;
}
}