/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.java.hints.errors;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.classpath.ClassPath;
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.SourceUtils;
import org.netbeans.modules.java.hints.errors.CreateClassFix.CreateInnerClassFix;
import org.netbeans.modules.java.hints.errors.CreateClassFix.CreateOuterClassFix;
import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
import org.netbeans.modules.java.hints.infrastructure.Pair;
import org.netbeans.modules.java.hints.spi.ErrorRule;
import org.netbeans.spi.editor.hints.Fix;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import static org.netbeans.modules.java.hints.errors.CreateElementUtilities.*;
/**
*
* @author Jan Lahoda
*/
public final class CreateElement implements ErrorRule<Void> {
/** Creates a new instance of CreateElement */
public CreateElement() {
}
public Set<String> getCodes() {
return new HashSet<String>(Arrays.asList("compiler.err.cant.resolve.location", "compiler.err.cant.apply.symbol", "compiler.err.cant.resolve")); // NOI18N
}
public List<Fix> run(CompilationInfo info, String diagnosticKey, int offset, TreePath treePath, Data<Void> data) {
try {
return analyze(info, offset);
} catch (IOException e) {
Exceptions.printStackTrace(e);
return null;
} catch (ClassCastException e) {
Logger.getLogger(CreateElement.class.getName()).log(Level.FINE, null, e);
return null;
}
}
static List<Fix> analyze(CompilationInfo info, int offset) throws IOException {
TreePath errorPath = ErrorHintsProvider.findUnresolvedElement(info, offset);
if (errorPath == null) {
return Collections.<Fix>emptyList();
}
if (info.getElements().getTypeElement("java.lang.Object") == null) { // NOI18N
// broken java platform
return Collections.<Fix>emptyList();
}
TreePath parent = null;
TreePath firstClass = null;
TreePath firstMethod = null;
TreePath firstInitializer = null;
TreePath methodInvocation = null;
TreePath newClass = null;
boolean lookupMethodInvocation = true;
boolean lookupNCT = true;
TreePath path = info.getTreeUtilities().pathFor(offset + 1);
while(path != null) {
Tree leaf = path.getLeaf();
Kind leafKind = leaf.getKind();
if (parent != null && parent.getLeaf() == errorPath.getLeaf())
parent = path;
if (leaf == errorPath.getLeaf() && parent == null)
parent = path;
if (leafKind == Kind.CLASS && firstClass == null)
firstClass = path;
if (leafKind == Kind.METHOD && firstMethod == null && firstClass == null)
firstMethod = path;
//static/dynamic initializer:
if ( leafKind == Kind.BLOCK && path.getParentPath().getLeaf().getKind() == Kind.CLASS
&& firstMethod == null && firstClass == null)
firstInitializer = path;
if (lookupMethodInvocation && leafKind == Kind.METHOD_INVOCATION) {
methodInvocation = path;
}
if (lookupNCT && leafKind == Kind.NEW_CLASS) {
newClass = path;
}
if (leafKind == Kind.MEMBER_SELECT) {
lookupMethodInvocation = leaf == errorPath.getLeaf();
}
if (leafKind != Kind.MEMBER_SELECT && leafKind != Kind.IDENTIFIER) {
lookupMethodInvocation = false;
}
if (leafKind != Kind.MEMBER_SELECT && leafKind != Kind.IDENTIFIER && leafKind != Kind.PARAMETERIZED_TYPE) {
lookupNCT = false;
}
path = path.getParentPath();
}
if (parent == null || parent.getLeaf() == errorPath.getLeaf() || firstClass == null)
return Collections.<Fix>emptyList();
Element e = info.getTrees().getElement(errorPath);
if (e == null) {
return Collections.<Fix>emptyList();
}
Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
String simpleName = e.getSimpleName().toString();
TypeElement source = (TypeElement) info.getTrees().getElement(firstClass);
TypeElement target = null;
boolean wasMemberSelect = false;
if (errorPath.getLeaf().getKind() == Kind.MEMBER_SELECT) {
TreePath exp = new TreePath(errorPath, ((MemberSelectTree) errorPath.getLeaf()).getExpression());
Element targetElement = info.getTrees().getElement(exp);
TypeMirror targetType = info.getTrees().getTypeMirror(exp);
if (targetElement != null && targetType != null && targetType.getKind() != TypeKind.ERROR) {
switch (targetElement.getKind()) {
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
//situation like <something>.ClassName.<identifier>,
//targetElement representing <something>.ClassName:
//the new element needs to be static
target = (TypeElement) targetElement;
modifiers.add(Modifier.STATIC);
break;
case FIELD:
case ENUM_CONSTANT:
case LOCAL_VARIABLE:
case PARAMETER:
case EXCEPTION_PARAMETER:
TypeMirror tm = targetElement.asType();
if (tm.getKind() == TypeKind.DECLARED) {
target = (TypeElement)((DeclaredType)tm).asElement();
}
break;
case METHOD:
Element el = info.getTypes().asElement(((ExecutableElement) targetElement).getReturnType());
if (el != null && (el.getKind().isClass() || el.getKind().isInterface())) {
target = (TypeElement) el;
}
break;
case CONSTRUCTOR:
target = (TypeElement) targetElement.getEnclosingElement();
break;
//TODO: type parameter?
}
}
wasMemberSelect = true;
} else {
Element enclosingElement = e.getEnclosingElement();
if(enclosingElement != null && enclosingElement.getKind() == ElementKind.ANNOTATION_TYPE) //unresolved element inside annot.
target = (TypeElement) enclosingElement;
else
if (errorPath.getLeaf().getKind() == Kind.IDENTIFIER) {
//TODO: Handle Annotations
target = source;
if (firstMethod != null) {
if (((MethodTree)firstMethod.getLeaf()).getModifiers().getFlags().contains(Modifier.STATIC)) {
modifiers.add(Modifier.STATIC);
}
} else {
if (firstInitializer != null) {
if (((BlockTree) firstInitializer.getLeaf()).isStatic()) {
modifiers.add(Modifier.STATIC);
}
} else {
//TODO: otherwise.
}
}
}
}
if (target == null) {
if (ErrorHintsProvider.ERR.isLoggable(ErrorManager.INFORMATIONAL)) {
ErrorHintsProvider.ERR.log(ErrorManager.INFORMATIONAL, "target=null"); // NOI18N
ErrorHintsProvider.ERR.log(ErrorManager.INFORMATIONAL, "offset=" + offset); // NOI18N
ErrorHintsProvider.ERR.log(ErrorManager.INFORMATIONAL, "errorTree=" + errorPath.getLeaf()); // NOI18N
}
return Collections.<Fix>emptyList();
}
modifiers.addAll(getAccessModifiers(info, source, target));
List<Fix> result = new ArrayList<Fix>();
if (methodInvocation != null) {
//create method:
MethodInvocationTree mit = (MethodInvocationTree) methodInvocation.getLeaf();
//return type:
Set<ElementKind> fixTypes = EnumSet.noneOf(ElementKind.class);
List<? extends TypeMirror> types = resolveType(fixTypes, info, methodInvocation.getParentPath(), methodInvocation.getLeaf(), offset, null, null);
if (types == null || types.isEmpty()) {
return Collections.<Fix>emptyList();
}
result.addAll(prepareCreateMethodFix(info, methodInvocation, modifiers, target, simpleName, mit.getArguments(), types));
}
if (newClass != null) {
//create method:
NewClassTree nct = (NewClassTree) newClass.getLeaf();
Element clazz = info.getTrees().getElement(new TreePath(newClass, nct.getIdentifier()));
if (clazz == null || clazz.asType().getKind() == TypeKind.ERROR || (!clazz.getKind().isClass() && !clazz.getKind().isInterface())) {
//the class does not exist...
ExpressionTree ident = nct.getIdentifier();
int numTypeArguments = 0;
if (ident.getKind() == Kind.PARAMETERIZED_TYPE) {
numTypeArguments = ((ParameterizedTypeTree) ident).getTypeArguments().size();
}
if (wasMemberSelect) {
return prepareCreateInnerClassFix(info, newClass, target, modifiers, simpleName, nct.getArguments(), null, ElementKind.CLASS, numTypeArguments);
} else {
return prepareCreateOuterClassFix(info, newClass, source, EnumSet.noneOf(Modifier.class), simpleName, nct.getArguments(), null, ElementKind.CLASS, numTypeArguments);
}
}
if (nct.getClassBody() != null) {
return Collections.<Fix>emptyList();
}
target = (TypeElement) clazz;
result.addAll(prepareCreateMethodFix(info, newClass, getAccessModifiers(info, source, target), target, "<init>", nct.getArguments(), null));
}
//field like or class (type):
Set<ElementKind> fixTypes = EnumSet.noneOf(ElementKind.class);
TypeMirror[] superType = new TypeMirror[1];
int[] numTypeParameters = new int[1];
List<? extends TypeMirror> types = resolveType(fixTypes, info, parent, errorPath.getLeaf(), offset, superType, numTypeParameters);
ElementKind classType = getClassType(fixTypes);
if (classType != null) {
if (wasMemberSelect) {
result.addAll(prepareCreateInnerClassFix(info, null, target, modifiers, simpleName, null, superType[0], classType, numTypeParameters[0]));
} else {
result.addAll(prepareCreateOuterClassFix(info, null, source, EnumSet.noneOf(Modifier.class), simpleName, null, superType[0], classType, numTypeParameters[0]));
}
}
if (types == null || types.isEmpty()) {
return result;
}
//XXX: should reasonably consider all the found type candidates, not only the one:
TypeMirror type = types.get(0);
if (type == null || type.getKind() == TypeKind.VOID || type.getKind() == TypeKind.EXECUTABLE) {
return result;
}
//currently, we cannot handle error types, TYPEVARs and WILDCARDs:
if (containsErrorsOrTypevarsRecursively(type)) {
return result;
}
if (fixTypes.contains(ElementKind.FIELD) && isTargetWritable(target, info)) { //IZ 111048 -- don't offer anything if target file isn't writable
Element enclosingElement = e.getEnclosingElement();
if(enclosingElement != null && enclosingElement.getKind() == ElementKind.ANNOTATION_TYPE) {
FileObject targetFile = SourceUtils.getFile(target, info.getClasspathInfo());
if (targetFile != null) {
result.add(new CreateMethodFix(info, simpleName, modifiers, target, type, types, Collections.<String>emptyList(), targetFile));
}
return result;
}
else {
FileObject targetFile = SourceUtils.getFile(target, info.getClasspathInfo());
if (targetFile != null) {
result.add(new CreateFieldFix(info, simpleName, modifiers, target, type, targetFile));
}
}
}
if (!wasMemberSelect && (fixTypes.contains(ElementKind.LOCAL_VARIABLE) || types.contains(ElementKind.PARAMETER))) {
ExecutableElement ee = null;
if (firstMethod != null) {
ee = (ExecutableElement) info.getTrees().getElement(firstMethod);
}
if ((ee != null) && type != null) {
int identifierPos = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), errorPath.getLeaf());
if (ee != null && fixTypes.contains(ElementKind.PARAMETER) && !Utilities.isMethodHeaderInsideGuardedBlock(info, (MethodTree) firstMethod.getLeaf()))
result.add(new AddParameterOrLocalFix(info, type, simpleName, true, identifierPos));
if (fixTypes.contains(ElementKind.LOCAL_VARIABLE) && ErrorFixesFakeHint.enabled(ErrorFixesFakeHint.FixKind.CREATE_LOCAL_VARIABLE))
result.add(new AddParameterOrLocalFix(info, type, simpleName, false, identifierPos));
}
}
return result;
}
private static List<Fix> prepareCreateMethodFix(CompilationInfo info, TreePath invocation, Set<Modifier> modifiers, TypeElement target, String simpleName, List<? extends ExpressionTree> arguments, List<? extends TypeMirror> returnTypes) {
//create method:
Pair<List<? extends TypeMirror>, List<String>> formalArguments = resolveArguments(info, invocation, arguments);
//return type:
//XXX: should reasonably consider all the found type candidates, not only the one:
TypeMirror returnType = returnTypes != null ? returnTypes.get(0) : null;
//currently, we cannot handle error types, TYPEVARs and WILDCARDs:
if (formalArguments == null || returnType != null && containsErrorsOrTypevarsRecursively(returnType)) {
return Collections.<Fix>emptyList();
}
//IZ 111048 -- don't offer anything if target file isn't writable
if(!isTargetWritable(target, info))
return Collections.<Fix>emptyList();
FileObject targetFile = SourceUtils.getFile(target, info.getClasspathInfo());
if (targetFile == null)
return Collections.<Fix>emptyList();
return Collections.<Fix>singletonList(new CreateMethodFix(info, simpleName, modifiers, target, returnType, formalArguments.getA(), formalArguments.getB(), targetFile));
}
private static Pair<List<? extends TypeMirror>, List<String>> resolveArguments(CompilationInfo info, TreePath invocation, List<? extends ExpressionTree> realArguments) {
List<TypeMirror> argumentTypes = new LinkedList<TypeMirror>();
List<String> argumentNames = new LinkedList<String>();
Set<String> usedArgumentNames = new HashSet<String>();
for (ExpressionTree arg : realArguments) {
TypeMirror tm = info.getTrees().getTypeMirror(new TreePath(invocation, arg));
if (tm == null || containsErrorsOrTypevarsRecursively(tm)) {
return null;
}
if (tm.getKind() == TypeKind.NULL) {
tm = info.getElements().getTypeElement("java.lang.Object").asType(); // NOI18N
}
argumentTypes.add(tm);
String proposedName = org.netbeans.modules.java.hints.errors.Utilities.getName(arg);
if (proposedName == null) {
proposedName = org.netbeans.modules.java.hints.errors.Utilities.getName(tm);
}
if (proposedName == null) {
proposedName = "arg"; // NOI18N
}
if (usedArgumentNames.contains(proposedName)) {
int num = 0;
while (usedArgumentNames.contains(proposedName + num)) {
num++;
}
proposedName = proposedName + num;
}
usedArgumentNames.add(proposedName);
argumentNames.add(proposedName);
}
return new Pair<List<? extends TypeMirror>, List<String>>(argumentTypes, argumentNames);
}
private static List<Fix> prepareCreateOuterClassFix(CompilationInfo info, TreePath invocation, TypeElement source, Set<Modifier> modifiers, String simpleName, List<? extends ExpressionTree> realArguments, TypeMirror superType, ElementKind kind, int numTypeParameters) {
Pair<List<? extends TypeMirror>, List<String>> formalArguments = invocation != null ? resolveArguments(info, invocation, realArguments) : new Pair<List<? extends TypeMirror>, List<String>>(null, null);
if (formalArguments == null) {
return Collections.<Fix>emptyList();
}
ClassPath cp = info.getClasspathInfo().getClassPath(PathKind.SOURCE);
FileObject root = cp.findOwnerRoot(info.getFileObject());
TypeElement outer = info.getElementUtilities().outermostTypeElement(source);
PackageElement packageElement = (PackageElement) outer.getEnclosingElement();
return Collections.<Fix>singletonList(new CreateOuterClassFix(info, root, packageElement.getQualifiedName().toString(), simpleName, modifiers, formalArguments.getA(), formalArguments.getB(), superType, kind, numTypeParameters));
}
private static List<Fix> prepareCreateInnerClassFix(CompilationInfo info, TreePath invocation, TypeElement target, Set<Modifier> modifiers, String simpleName, List<? extends ExpressionTree> realArguments, TypeMirror superType, ElementKind kind, int numTypeParameters) {
Pair<List<? extends TypeMirror>, List<String>> formalArguments = invocation != null ? resolveArguments(info, invocation, realArguments) : new Pair<List<? extends TypeMirror>, List<String>>(null, null);
if (formalArguments == null) {
return Collections.<Fix>emptyList();
}
//IZ 111048 -- don't offer anything if target file isn't writable
if (!isTargetWritable(target, info))
return Collections.<Fix>emptyList();
FileObject targetFile = SourceUtils.getFile(target, info.getClasspathInfo());
if (targetFile == null)
return Collections.<Fix>emptyList();
return Collections.<Fix>singletonList(new CreateInnerClassFix(info, simpleName, modifiers, target, formalArguments.getA(), formalArguments.getB(), superType, kind, numTypeParameters, targetFile));
}
private static ElementKind getClassType(Set<ElementKind> types) {
if (types.contains(ElementKind.CLASS))
return ElementKind.CLASS;
if (types.contains(ElementKind.ANNOTATION_TYPE))
return ElementKind.ANNOTATION_TYPE;
if (types.contains(ElementKind.INTERFACE))
return ElementKind.INTERFACE;
if (types.contains(ElementKind.ENUM))
return ElementKind.ENUM;
return null;
}
public void cancel() {
//XXX: not done yet
}
public String getId() {
return CreateElement.class.getName();
}
public String getDisplayName() {
return NbBundle.getMessage(CreateElement.class, "LBL_Create_Field");
}
public String getDescription() {
return NbBundle.getMessage(CreateElement.class, "DSC_Create_Field");
}
//XXX: currently we cannot fix:
//xxx = new ArrayList<Unknown>();
//=>
//ArrayList<Unknown> xxx;
//xxx = new ArrayList<Unknown>();
private static boolean containsErrorsOrTypevarsRecursively(TypeMirror tm) {
switch (tm.getKind()) {
case WILDCARD:
case TYPEVAR:
case ERROR:
return true;
case DECLARED:
DeclaredType type = (DeclaredType) tm;
for (TypeMirror t : type.getTypeArguments()) {
if (containsErrorsOrTypevarsRecursively(t))
return true;
}
return false;
case ARRAY:
return containsErrorsOrTypevarsRecursively(((ArrayType) tm).getComponentType());
default:
return false;
}
}
/**
* Detects if targets file is non-null and writable
* @return true if target's file is writable
*/
private static boolean isTargetWritable(TypeElement target, CompilationInfo info) {
FileObject fo = SourceUtils.getFile(ElementHandle.create(target.getEnclosingElement()), info.getClasspathInfo());
if(fo != null && fo.canWrite())
return true;
else
return false;
}
static EnumSet<Modifier> getAccessModifiers(CompilationInfo info, TypeElement source, TypeElement target) {
if (target.getKind().isInterface()) {
return EnumSet.of(Modifier.PUBLIC);
}
TypeElement outterMostSource = info.getElementUtilities().outermostTypeElement(source);
TypeElement outterMostTarget = info.getElementUtilities().outermostTypeElement(target);
if (outterMostSource.equals(outterMostTarget)) {
return EnumSet.of(Modifier.PRIVATE);
}
Element sourcePackage = outterMostSource.getEnclosingElement();
Element targetPackage = outterMostTarget.getEnclosingElement();
if (sourcePackage.equals(targetPackage)) {
return EnumSet.noneOf(Modifier.class);
}
//TODO: protected?
return EnumSet.of(Modifier.PUBLIC);
}
}
|