blob: fbc1ecb845fa363398f6e552369a067d4479d2ee [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.refactoring.java.plugins;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.*;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.*;
import javax.lang.model.util.Types;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/**
* Utility class for java plugins.
*/
public final class JavaPluginUtils {
private static final Logger LOG = Logger.getLogger(JavaPluginUtils.class.getName());
public static Problem isSourceElement(Element el, CompilationInfo info) {
if (el == null) {
// XXX: shouldn't we create a problem ? How ?
return null;
}
Problem preCheckProblem;
Element typeElement;
if(el.getKind() != ElementKind.PACKAGE) {
typeElement = info.getElementUtilities().enclosingTypeElement(el);
if(typeElement == null) {
typeElement = el;
}
} else {
typeElement = el;
}
ElementHandle<Element> handle = null;
try {
handle = ElementHandle.create(typeElement);
} catch (IllegalArgumentException ex) {
LOG.log(Level.WARNING, "Cannot create handle for source element", ex);
}
if (handle == null || JavaRefactoringUtils.isFromLibrary(handle, info.getClasspathInfo())) { //NOI18N
preCheckProblem = new Problem(true, NbBundle.getMessage(
JavaPluginUtils.class, "ERR_CannotRefactorLibraryClass",
el.getKind()==ElementKind.PACKAGE?el:el.getEnclosingElement()
));
return preCheckProblem;
}
FileObject file = SourceUtils.getFile(handle, info.getClasspathInfo());
// RefactoringUtils.isFromLibrary already checked file for null
if (!RefactoringUtils.isFileInOpenProject(file)) {
preCheckProblem =new Problem(true, NbBundle.getMessage(
JavaPluginUtils.class,
"ERR_ProjectNotOpened",
FileUtil.getFileDisplayName(file)));
return preCheckProblem;
}
return null;
}
public static Problem isSourceFile(FileObject fo, CompilationInfo info) {
Problem preCheckProblem;
if (fo == null || FileUtil.getArchiveFile(fo) != null) { //NOI18N
preCheckProblem = new Problem(true, NbBundle.getMessage(
JavaPluginUtils.class, "ERR_CannotRefactorLibraryClass",
FileUtil.getFileDisplayName(fo)
));
return preCheckProblem;
}
// RefactoringUtils.isFromLibrary already checked file for null
if (!RefactoringUtils.isFileInOpenProject(fo)) {
preCheckProblem =new Problem(true, NbBundle.getMessage(
JavaPluginUtils.class,
"ERR_ProjectNotOpened",
FileUtil.getFileDisplayName(fo)));
return preCheckProblem;
}
return null;
}
public static TreePath findMethod(TreePath path) {
while (path != null) {
if (path.getLeaf().getKind() == Kind.METHOD) {
return path;
}
if (path.getLeaf().getKind() == Kind.BLOCK
&& path.getParentPath() != null
&& TreeUtilities.CLASS_TREE_KINDS.contains(path.getParentPath().getLeaf().getKind())) {
//initializer:
return path;
}
path = path.getParentPath();
}
return null;
}
public static TreePath findStatement(TreePath statementPath) {
while (statementPath != null
&& (!StatementTree.class.isAssignableFrom(statementPath.getLeaf().getKind().asInterface())
|| (statementPath.getParentPath() != null
&& statementPath.getParentPath().getLeaf().getKind() != Kind.BLOCK))) {
if (TreeUtilities.CLASS_TREE_KINDS.contains(statementPath.getLeaf().getKind())) {
return null;
}
statementPath = statementPath.getParentPath();
}
return statementPath;
}
public static boolean isParentOf(TreePath parent, TreePath path) {
Tree parentLeaf = parent.getLeaf();
while (path != null && path.getLeaf() != parentLeaf) {
path = path.getParentPath();
}
return path != null;
}
public static boolean isParentOf(TreePath parent, List<? extends TreePath> candidates) {
for (TreePath tp : candidates) {
if (!isParentOf(parent, tp)) {
return false;
}
}
return true;
}
public static Problem chainProblems(Problem result, Problem problem) {
if (result == null) {
return problem;
}
if (problem == null) {
return result;
}
Problem value;
if(problem.isFatal()) {
problem.setNext(result);
value = problem;
} else {
Problem next = value = result;
while (next.getNext() != null) {
next = next.getNext();
}
next.setNext(problem);
}
return value;
}
/**
* Convert typemirror of an anonymous class to supertype/iface
*
* @return typemirror of supertype/iface, initial tm if not anonymous
*/
public static TypeMirror convertIfAnonymous(TypeMirror tm) {
//anonymous class?
Set<ElementKind> fm = EnumSet.of(ElementKind.METHOD, ElementKind.FIELD);
if (tm instanceof DeclaredType) {
Element el = ((DeclaredType) tm).asElement();
//XXX: the null check is needed for lambda type, not covered by test:
if (el != null && (el.getSimpleName().length() == 0 || fm.contains(el.getEnclosingElement().getKind()))) {
List<? extends TypeMirror> interfaces = ((TypeElement) el).getInterfaces();
if (interfaces.isEmpty()) {
tm = ((TypeElement) el).getSuperclass();
} else {
tm = interfaces.get(0);
}
}
}
return tm;
}
public static TypeMirror resolveCapturedType(CompilationInfo info, TypeMirror tm) {
TypeMirror type = resolveCapturedTypeInt(info, tm);
if (type.getKind() == TypeKind.WILDCARD) {
TypeMirror tmirr = ((WildcardType) type).getExtendsBound();
if (tmirr != null) {
return tmirr;
}
else { //no extends, just '?'
return info.getElements().getTypeElement("java.lang.Object").asType(); // NOI18N
}
}
return type;
}
private static TypeMirror resolveCapturedTypeInt(CompilationInfo info, TypeMirror tm) {
TypeMirror orig = SourceUtils.resolveCapturedType(tm);
if (orig != null) {
if (orig.getKind() == TypeKind.WILDCARD) {
TypeMirror extendsBound = ((WildcardType) orig).getExtendsBound();
TypeMirror rct = SourceUtils.resolveCapturedType(extendsBound != null ? extendsBound : ((WildcardType) orig).getSuperBound());
if (rct != null) {
return rct;
}
}
return orig;
}
if (tm.getKind() == TypeKind.DECLARED) {
DeclaredType dt = (DeclaredType) tm;
List<TypeMirror> typeArguments = new LinkedList<TypeMirror>();
for (TypeMirror t : dt.getTypeArguments()) {
typeArguments.add(resolveCapturedTypeInt(info, t));
}
final TypeMirror enclosingType = dt.getEnclosingType();
if (enclosingType.getKind() == TypeKind.DECLARED) {
return info.getTypes().getDeclaredType((DeclaredType) enclosingType, (TypeElement) dt.asElement(), typeArguments.toArray(new TypeMirror[0]));
} else {
return info.getTypes().getDeclaredType((TypeElement) dt.asElement(), typeArguments.toArray(new TypeMirror[0]));
}
}
if (tm.getKind() == TypeKind.ARRAY) {
ArrayType at = (ArrayType) tm;
return info.getTypes().getArrayType(resolveCapturedTypeInt(info, at.getComponentType()));
}
return tm;
}
public static JavaSource createSource(final FileObject file, final ClasspathInfo cpInfo, final TreePathHandle tph) throws IllegalArgumentException {
JavaSource source;
if (file != null) {
final ClassPath mergedPlatformPath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.BOOT), ClassPath.getClassPath(file, ClassPath.BOOT));
final ClassPath mergedModulePlatformPath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.MODULE_BOOT), ClassPath.getClassPath(file, JavaClassPathConstants.MODULE_BOOT_PATH));
final ClassPath mergedCompilePath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.COMPILE), ClassPath.getClassPath(file, ClassPath.COMPILE));
final ClassPath mergedModuleCompilePath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.MODULE_COMPILE), ClassPath.getClassPath(file, JavaClassPathConstants.MODULE_COMPILE_PATH));
final ClassPath mergedModuleClassPath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.MODULE_CLASS), ClassPath.getClassPath(file, JavaClassPathConstants.MODULE_CLASS_PATH));
final ClassPath mergedSourcePath = RefactoringUtils.merge(cpInfo.getClassPath(PathKind.SOURCE), ClassPath.getClassPath(file, ClassPath.SOURCE));
final ClasspathInfo mergedInfo = new ClasspathInfo.Builder(mergedPlatformPath)
.setModuleBootPath(mergedModulePlatformPath)
.setClassPath(mergedCompilePath)
.setModuleCompilePath(mergedModuleCompilePath)
.setModuleClassPath(mergedModuleClassPath)
.setSourcePath(mergedSourcePath)
.build();
source = JavaSource.create(mergedInfo, new FileObject[]{tph.getFileObject()});
} else {
source = JavaSource.create(cpInfo);
}
return source;
}
public static boolean hasGetter(CompilationInfo info, TypeElement typeElement, VariableElement field, Map<String, List<ExecutableElement>> methods, CodeStyle cs) {
CharSequence name = field.getSimpleName();
assert name.length() > 0;
TypeMirror type = field.asType();
boolean isStatic = field.getModifiers().contains(Modifier.STATIC);
String getterName = CodeStyleUtils.computeGetterName(name, type.getKind() == TypeKind.BOOLEAN, isStatic, cs);
Types types = info.getTypes();
List<ExecutableElement> candidates = methods.get(getterName);
if (candidates != null) {
for (ExecutableElement candidate : candidates) {
if ((!candidate.getModifiers().contains(Modifier.ABSTRACT) || candidate.getEnclosingElement() == typeElement)
&& candidate.getParameters().isEmpty()
&& types.isSameType(candidate.getReturnType(), type))
return true;
}
}
return false;
}
public static boolean hasSetter(CompilationInfo info, TypeElement typeElement, VariableElement field, Map<String, List<ExecutableElement>> methods, CodeStyle cs) {
CharSequence name = field.getSimpleName();
assert name.length() > 0;
TypeMirror type = field.asType();
boolean isStatic = field.getModifiers().contains(Modifier.STATIC);
String setterName = CodeStyleUtils.computeSetterName(name, isStatic, cs);
Types types = info.getTypes();
List<ExecutableElement> candidates = methods.get(setterName);
if (candidates != null) {
for (ExecutableElement candidate : candidates) {
if ((!candidate.getModifiers().contains(Modifier.ABSTRACT) || candidate.getEnclosingElement() == typeElement)
&& candidate.getReturnType().getKind() == TypeKind.VOID
&& candidate.getParameters().size() == 1
&& types.isSameType(candidate.getParameters().get(0).asType(), type))
return true;
}
}
return false;
}
/**
* Works as TreeUtilities.isSynthetic, but treats implicit annotation parameter (value) as
* non-synthetic. See defect #270036
*/
public static boolean isSyntheticPath(CompilationInfo ci, TreePath path) {
TreeUtilities tu = ci.getTreeUtilities();
if (path == null)
throw new NullPointerException();
while (path != null) {
SYNT: if (isSynthetic(ci, path.getCompilationUnit(), path.getLeaf())) {
if (path.getLeaf().getKind() == Tree.Kind.ASSIGNMENT &&
path.getParentPath() != null && path.getParentPath().getLeaf().getKind() == Tree.Kind.ANNOTATION) {
AssignmentTree aTree = (AssignmentTree)path.getLeaf();
if (aTree.getVariable().getKind() == Tree.Kind.IDENTIFIER &&
((IdentifierTree)aTree.getVariable()).getName().contentEquals("value")) { // implicit value is not synthetic
break SYNT;
}
}
return true;
}
path = path.getParentPath();
}
return false;
}
//<editor-fold defaultstate="collapsed" desc="TODO: Copied from java.source.base TreeUtilities">
static boolean isSynthetic(CompilationInfo info, CompilationUnitTree cut, Tree leaf) throws NullPointerException {
JCTree tree = (JCTree) leaf;
if (tree.pos == (-1))
return true;
if (leaf.getKind() == Kind.METHOD) {
//check for synthetic constructor:
return (((JCTree.JCMethodDecl)leaf).mods.flags & Flags.GENERATEDCONSTR) != 0L;
}
//check for synthetic superconstructor call:
if (leaf.getKind() == Kind.EXPRESSION_STATEMENT) {
ExpressionStatementTree est = (ExpressionStatementTree) leaf;
if (est.getExpression().getKind() == Kind.METHOD_INVOCATION) {
MethodInvocationTree mit = (MethodInvocationTree) est.getExpression();
if (mit.getMethodSelect().getKind() == Kind.IDENTIFIER) {
IdentifierTree it = (IdentifierTree) mit.getMethodSelect();
if ("super".equals(it.getName().toString())) {
SourcePositions sp = info.getTrees().getSourcePositions();
return sp.getEndPosition(cut, leaf) == (-1);
}
}
}
}
return false;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="TODO: Copy from org.netbeans.modules.java.hints.errors.Utilities">
public static final String DEFAULT_NAME = "par"; // NOI18N
public static String makeNameUnique(CompilationInfo info, Scope s, String name) {
return makeNameUnique(info, s, name, Collections.EMPTY_LIST, null, null);
}
public static String makeNameUnique(CompilationInfo info, Scope s, String name, List<String> definedIds) {
return makeNameUnique(info, s, name, definedIds, null, null);
}
public static String makeNameUnique(CompilationInfo info, Scope s, String name, String prefix, String suffix) {
return makeNameUnique(info, s, name, Collections.EMPTY_LIST, prefix, suffix);
}
public static String makeNameUnique(CompilationInfo info, Scope s, String name, List<String> definedIds, String prefix, String suffix) {
boolean cont;
String proposedName;
name = CodeStyleUtils.addPrefixSuffix(name, prefix, null);
int counter = 0;
do {
proposedName = name + (counter != 0 ? String.valueOf(counter) : "") + safeString(suffix);
cont = false;
if (s != null) {
for (String id : definedIds) {
if (proposedName.equals(id)) {
counter++;
cont = true;
break;
}
}
for (Element e : info.getElementUtilities().getLocalMembersAndVars(s, new VariablesFilter())) {
if (proposedName.equals(e.getSimpleName().toString())) {
counter++;
cont = true;
break;
}
}
}
} while(cont);
return proposedName;
}
private static String safeString(String str) {
return str == null ? "" : str;
}
public static String getName(TypeMirror tm) {
if (tm.getKind().isPrimitive()) {
return "" + Character.toLowerCase(tm.getKind().name().charAt(0));
}
switch (tm.getKind()) {
case DECLARED:
DeclaredType dt = (DeclaredType) tm;
return firstToLower(dt.asElement().getSimpleName().toString());
case ARRAY:
return getName(((ArrayType) tm).getComponentType());
default:
return DEFAULT_NAME;
}
}
public static String getName(ExpressionTree et) {
return getName((Tree) et);
}
public static String getName(Tree et) {
return adjustName(getNameRaw(et));
}
private static String getNameRaw(Tree et) {
if (et == null)
return null;
switch (et.getKind()) {
case IDENTIFIER:
return ((IdentifierTree) et).getName().toString();
case METHOD_INVOCATION:
return getNameRaw(((MethodInvocationTree) et).getMethodSelect());
case MEMBER_SELECT:
return ((MemberSelectTree) et).getIdentifier().toString();
case NEW_CLASS:
return firstToLower(getNameRaw(((NewClassTree) et).getIdentifier()));
case PARAMETERIZED_TYPE:
return firstToLower(getNameRaw(((ParameterizedTypeTree) et).getType()));
case STRING_LITERAL:
String name = guessLiteralName((String) ((LiteralTree) et).getValue());
if(name == null) {
return firstToLower(String.class.getSimpleName());
} else {
return firstToLower(name);
}
case VARIABLE:
return ((VariableTree) et).getName().toString();
default:
return null;
}
}
static String adjustName(String name) {
if (name == null) {
return null;
}
String shortName = null;
if (name.startsWith("get") && name.length() > 3) {
shortName = name.substring(3);
}
if (name.startsWith("is") && name.length() > 2) {
shortName = name.substring(2);
}
if (shortName != null) {
return firstToLower(shortName);
}
if (SourceVersion.isKeyword(name)) {
return "a" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
} else {
return name;
}
}
private static String firstToLower(String name) {
if (name.length() == 0) {
return null;
}
StringBuilder result = new StringBuilder();
boolean toLower = true;
char last = Character.toLowerCase(name.charAt(0));
for (int i = 1; i < name.length(); i++) {
if (toLower && (Character.isUpperCase(name.charAt(i)) || name.charAt(i) == '_')) {
result.append(Character.toLowerCase(last));
} else {
result.append(last);
toLower = false;
}
last = name.charAt(i);
}
result.append(toLower ? Character.toLowerCase(last) : last);
if (SourceVersion.isKeyword(result)) {
return "a" + name;
} else {
return result.toString();
}
}
private static String guessLiteralName(String str) {
if(str.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if(ch == ' ') {
sb.append('_');
} else if (sb.length() == 0 ? Character.isJavaIdentifierStart(ch) : Character.isJavaIdentifierPart(ch)) {
sb.append(ch);
}
if (sb.length() > 40) {
break;
}
}
if (sb.length() == 0) {
return null;
}
else {
return sb.toString();
}
}
public static CompilationUnitTree createCompilationUnit(FileObject sourceRoot, String relativePath, Tree typeDecl, WorkingCopy workingCopy, TreeMaker make) {
GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
CompilationUnitTree newCompilation;
try {
newCompilation = genUtils.createFromTemplate(sourceRoot, relativePath, ElementKind.CLASS);
List<? extends Tree> typeDecls = newCompilation.getTypeDecls();
if (typeDecls.isEmpty()) {
newCompilation = make.addCompUnitTypeDecl(newCompilation, typeDecl);
} else {
List<Tree> typeDeclarations = new LinkedList<Tree>(newCompilation.getTypeDecls());
Tree templateClazz = typeDeclarations.remove(0); // TODO: Check for class with correct name, template could start with another type.
// mark the template CU as removed; any untransfered comments will be (?) lost.
workingCopy.getTreeMaker().asRemoved(templateClazz);
if (workingCopy.getTreeUtilities().getComments(typeDecl, true).isEmpty()) {
genUtils.copyComments(templateClazz, typeDecl, true);
} else if (workingCopy.getTreeUtilities().getComments(typeDecl, false).isEmpty()) {
genUtils.copyComments(templateClazz, typeDecl, false);
}
typeDeclarations.add(0, typeDecl);
newCompilation = make.CompilationUnit(newCompilation.getPackageAnnotations(), sourceRoot, relativePath, newCompilation.getImports(), typeDeclarations);
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
newCompilation = make.CompilationUnit(sourceRoot, relativePath, null, Collections.singletonList(typeDecl));
}
return newCompilation;
}
public static final class VariablesFilter implements ElementUtilities.ElementAcceptor {
private static final Set<ElementKind> ACCEPTABLE_KINDS = EnumSet.of(ElementKind.ENUM_CONSTANT, ElementKind.EXCEPTION_PARAMETER, ElementKind.FIELD, ElementKind.LOCAL_VARIABLE, ElementKind.PARAMETER);
public boolean accept(Element e, TypeMirror type) {
return ACCEPTABLE_KINDS.contains(e.getKind());
}
}
//</editor-fold>
}