/* * The contents of this file are subject to the terms of the Common Development * and Distribution License (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.html * or http://www.netbeans.org/cddl.txt. * * When distributing Covered Code, include this CDDL Header Notice in each file * and include the License file at http://www.netbeans.org/cddl.txt. * If applicable, add the following below the CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Portions Copyrighted 2007 Sun Microsystems, Inc. */ package org.netbeans.modules.gwt4nb.hints; import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import com.sun.source.tree.ClassTree; 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.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.queries.UnitTestForSourceQuery; import org.netbeans.api.java.source.ClassIndex.NameKind; import org.netbeans.api.java.source.ClassIndex.SearchScope; 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.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.modules.gwt4nb.GWT4NBUtil; import org.netbeans.modules.gwt4nb.GWTProjectInfo; import org.netbeans.spi.editor.hints.ErrorDescription; import org.netbeans.spi.editor.hints.Fix; import org.netbeans.spi.editor.hints.Severity; import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.netbeans.spi.java.hints.Hint; import org.netbeans.spi.java.hints.HintContext; import org.netbeans.spi.java.hints.HintSeverity; import org.netbeans.spi.java.hints.TriggerTreeKind; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.filesystems.URLMapper; import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import static org.netbeans.modules.gwt4nb.hints.Bundle.*; /** * Hints classes unsupported by GWT. * * This file is modelled after * http://kenai.com/projects/nbappengine/sources/main/content/hints/src/org/netbeans/modules/j2ee/appengine/hints/UnsupportedClass.java?rev=51 * author Jan Lahoda */ @Hint(description="#DESC_UnsupportedClass", displayName="#DN_UnsupportedClass", severity= Severity.WARNING, hintKind= Hint.Kind.ACTION, id="org.netbeans.modules.gwt4nb.hints.UnsupportedClass", category="gwt") @Messages({"DN_UnsupportedClass=Unsupported Class", "DESC_UnsupportedClass=Unsupported Class"}) public class UnsupportedClass { private static final Set<String> jreWhitelist; static { Set<String> whitelist = new HashSet<String>(); URL url = UnsupportedClass.class.getResource("class-white-list"); // NOI18N FileObject file = URLMapper.findFileObject(url); try { for (String line : file.asLines("UTF-8")) { // NOI18N whitelist.add(line); } } catch (IOException ex) { Exceptions.printStackTrace(ex); } jreWhitelist = whitelist; } public Set<Kind> getTreeKinds() { return EnumSet.of(Kind.IDENTIFIER, Kind.MEMBER_SELECT); } private static final Map<CompilationInfo, Boolean> hasGWT_ = new WeakHashMap<CompilationInfo, Boolean>(); private static boolean hasGWT(CompilationInfo info) { Boolean val = hasGWT_.get(info); if (val != null) { return val; } Project p = FileOwnerQuery.getOwner(info.getFileObject()); if (p == null) return false; final GWTProjectInfo pi = GWTProjectInfo.get(p); boolean b = pi != null; if (b) { ExpressionTree pn = info.getCompilationUnit().getPackageName(); boolean foundMatchingModule=false; if (pn == null) b = false; else { String pn_ = pn.toString(); List<String> modules = pi.getModules(); for (String module: modules) { String clientPackage = GWTProjectInfo.getClientPackage(module); foundMatchingModule |= clientPackage.equals(pn_) || pn_.startsWith(clientPackage + "."); // NOI18N } } b = foundMatchingModule; } hasGWT_.put(info, b); return b; } private static final ClassPath EMPTY = ClassPathSupport.createClassPath( new FileObject[0]); private static final Map<CompilationInfo, Set<String>> info2ProjectWhitelist = new WeakHashMap<CompilationInfo, Set<String>>(); private static Set<String> getProjectBasedWhitelist(CompilationInfo info) { Set<String> cached = info2ProjectWhitelist.get(info); if (cached != null) { return cached; } long start = System.currentTimeMillis(); ClasspathInfo cpInfo = ClasspathInfo.create(EMPTY, info.getClasspathInfo().getClassPath(PathKind.COMPILE), info.getClasspathInfo().getClassPath(PathKind.SOURCE)); Set<String> result = new HashSet<String>(); Set<ElementHandle<TypeElement>> declaredTypes = cpInfo.getClassIndex().getDeclaredTypes("", // NOI18N NameKind.PREFIX, EnumSet.of(SearchScope.DEPENDENCIES, SearchScope.SOURCE)); if (declaredTypes == null) { return null; } for (ElementHandle<TypeElement> h : declaredTypes) { result.add(h.getBinaryName()); } long end = System.currentTimeMillis(); Logger.getLogger("TIMER").log(Level.FINE, // NOI18N "Project Based Whitelist", new Object[] { // NOI18N info.getFileObject(), (end - start)}); info2ProjectWhitelist.put(info, result); return result; } @TriggerTreeKind(Kind.IDENTIFIER) public static List<ErrorDescription> runIdentifier(final HintContext context){ return run(context.getInfo(), context.getPath()); } @TriggerTreeKind(Kind.MEMBER_SELECT) public static List<ErrorDescription> runMemberSelect(final HintContext context){ return run(context.getInfo(), context.getPath()); } @Messages({ "# {0} - classname", "ERR_UnsupportedClass=Class {0} not supported by the GWT" } ) private static List<ErrorDescription> run(final CompilationInfo info, final TreePath treePath){ if (!hasGWT(info) || isUnitTest(info)) { return null; } Element el = info.getTrees().getElement(treePath); if (el == null || (!el.getKind().isClass() && !el.getKind().isInterface())) { return null; } TypeMirror tm = info.getTrees().getTypeMirror(treePath); if (tm == null || tm.getKind() == TypeKind.ERROR) { return null; } TypeElement te = (TypeElement) el; String fqn = ElementHandle.create(te).getBinaryName(); if (jreWhitelist.contains(fqn)) { return null; } Set<String> projectBasedWhitelist = getProjectBasedWhitelist(info); if (projectBasedWhitelist == null) { return null; } if (projectBasedWhitelist.contains(fqn)) { return null; } return Collections.singletonList(forName(info, treePath.getLeaf(), ERR_UnsupportedClass(fqn))); // NOI18N } private static ErrorDescription forName(CompilationInfo info, Tree tree, String text, Fix... fixes) { int[] span = computeNameSpan(tree, info); if (span != null && span[0] != (-1) && span[1] != (-1)) { return org.netbeans.spi.editor.hints.ErrorDescriptionFactory.createErrorDescription(HintSeverity.WARNING.toEditorSeverity(), text, Arrays.asList(fixes), info.getFileObject(), span[0], span[1]); } return null; } private static int[] computeNameSpan(Tree tree, CompilationInfo info) { switch (tree.getKind()) { case METHOD: return info.getTreeUtilities().findNameSpan((MethodTree) tree); case CLASS: return info.getTreeUtilities().findNameSpan((ClassTree) tree); case VARIABLE: return info.getTreeUtilities().findNameSpan((VariableTree) tree); case MEMBER_SELECT: return info.getTreeUtilities().findNameSpan((MemberSelectTree) tree); case METHOD_INVOCATION: return computeNameSpan(((MethodInvocationTree) tree).getMethodSelect(), info); default: return new int[] { (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree), (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tree), }; } } private static boolean isUnitTest(CompilationInfo info) { ClassPath sourcePath = info.getClasspathInfo().getClassPath(PathKind.SOURCE); if (sourcePath == null) { GWT4NBUtil.LOGGER.log(Level.FINE, "No source path for {0}", // NOI18N FileUtil.getFileDisplayName(info.getFileObject())); return true; } FileObject ownerRoot = sourcePath.findOwnerRoot(info.getFileObject()); if (ownerRoot == null) { GWT4NBUtil.LOGGER.log(Level.FINE, "No owning root for {0} on its own source path", // NOI18N FileUtil.getFileDisplayName(info.getFileObject())); return true; } return UnitTestForSourceQuery.findSources(ownerRoot).length > 0; } }