001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.java.editor.imports;
042:
043: import com.sun.source.tree.IdentifierTree;
044: import com.sun.source.tree.MemberSelectTree;
045: import com.sun.source.tree.MethodInvocationTree;
046: import com.sun.source.tree.Scope;
047: import com.sun.source.tree.Tree.Kind;
048: import com.sun.source.tree.VariableTree;
049: import com.sun.source.util.TreePath;
050: import java.util.ArrayList;
051: import java.util.Collections;
052: import java.util.Comparator;
053: import java.util.EnumSet;
054: import java.util.HashMap;
055: import java.util.HashSet;
056: import java.util.List;
057: import java.util.Map;
058: import java.util.Set;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061: import javax.lang.model.SourceVersion;
062: import javax.lang.model.element.Element;
063: import javax.lang.model.element.ElementKind;
064: import javax.lang.model.element.PackageElement;
065: import javax.lang.model.element.TypeElement;
066: import javax.lang.model.type.DeclaredType;
067: import javax.lang.model.type.TypeKind;
068: import javax.lang.model.type.TypeMirror;
069: import javax.lang.model.util.Types;
070: import org.netbeans.api.java.source.ClassIndex;
071: import org.netbeans.api.java.source.CompilationInfo;
072: import org.netbeans.api.java.source.ClassIndex.NameKind;
073: import org.netbeans.api.java.source.ElementHandle;
074: import org.netbeans.api.java.source.support.CancellableTreePathScanner;
075: import org.openide.util.Union2;
076:
077: /**
078: *
079: * @author Jan Lahoda
080: */
081: public class ComputeImports {
082:
083: private static final String ERROR = "<error>";
084:
085: /** Creates a new instance of JavaFixAllImports */
086: public ComputeImports() {
087: }
088:
089: private boolean cancelled;
090:
091: public synchronized void cancel() {
092: cancelled = true;
093:
094: if (visitor != null)
095: visitor.cancel();
096: }
097:
098: private synchronized boolean isCancelled() {
099: return cancelled;
100: }
101:
102: public Pair<Map<String, List<TypeElement>>, Map<String, List<TypeElement>>> computeCandidates(
103: CompilationInfo info) {
104: return computeCandidates(info, Collections.<String> emptySet());
105: }
106:
107: private TreeVisitorImpl visitor;
108:
109: private synchronized void setVisitor(TreeVisitorImpl visitor) {
110: this .visitor = visitor;
111: }
112:
113: Pair<Map<String, List<TypeElement>>, Map<String, List<TypeElement>>> computeCandidates(
114: CompilationInfo info, Set<String> forcedUnresolved) {
115: Map<String, List<TypeElement>> candidates = new HashMap<String, List<TypeElement>>();
116: Map<String, List<TypeElement>> notFilteredCandidates = new HashMap<String, List<TypeElement>>();
117: TreeVisitorImpl v = new TreeVisitorImpl(info);
118:
119: setVisitor(v);
120:
121: v
122: .scan(info.getCompilationUnit(),
123: new HashMap<String, Object>());
124:
125: setVisitor(null);
126:
127: Set<String> unresolvedNames = new HashSet<String>(v.unresolved);
128:
129: unresolvedNames.addAll(forcedUnresolved);
130:
131: for (String unresolved : unresolvedNames) {
132: if (isCancelled())
133: return null;
134:
135: List<TypeElement> classes = new ArrayList<TypeElement>();
136: Set<ElementHandle<TypeElement>> typeNames = info
137: .getJavaSource()
138: .getClasspathInfo()
139: .getClassIndex()
140: .getDeclaredTypes(unresolved, NameKind.SIMPLE_NAME,
141: EnumSet.allOf(ClassIndex.SearchScope.class));
142: if (typeNames == null) {
143: //Canceled
144: return null;
145: }
146: for (ElementHandle<TypeElement> typeName : typeNames) {
147: TypeElement te = info.getElements().getTypeElement(
148: typeName.getQualifiedName());
149:
150: if (te == null) {
151: Logger.getLogger(ComputeImports.class.getName())
152: .log(
153: Level.INFO,
154: "Cannot resolve type element \""
155: + typeName + "\".");
156: continue;
157: }
158:
159: classes.add(te);
160: }
161: Collections.sort(classes, new Comparator<TypeElement>() {
162: public int compare(TypeElement te1, TypeElement te2) {
163: return (te1 == te2) ? 0 : te1.getQualifiedName()
164: .toString().compareTo(
165: te2.getQualifiedName().toString());
166: }
167: });
168:
169: candidates.put(unresolved, new ArrayList(classes));
170: notFilteredCandidates.put(unresolved, classes);
171: }
172:
173: boolean wasChanged = true;
174:
175: while (wasChanged) {
176: if (isCancelled())
177: return new Pair(Collections.emptyMap(), Collections
178: .emptyMap());
179:
180: wasChanged = false;
181:
182: for (Hint hint : v.hints) {
183: wasChanged |= hint.filter(info, notFilteredCandidates,
184: candidates);
185: }
186: }
187:
188: return new Pair<Map<String, List<TypeElement>>, Map<String, List<TypeElement>>>(
189: candidates, notFilteredCandidates);
190: }
191:
192: private static boolean filter(Types types, List<TypeElement> left,
193: List<TypeElement> right, boolean leftReadOnly,
194: boolean rightReadOnly) {
195: boolean changed = false;
196: Map<TypeElement, List<TypeElement>> validPairs = new HashMap<TypeElement, List<TypeElement>>();
197:
198: for (TypeElement l : left) {
199: List<TypeElement> valid = new ArrayList<TypeElement>();
200:
201: for (TypeElement r : right) {
202: TypeMirror t1 = types.erasure(l.asType());
203: TypeMirror t2 = types.erasure(r.asType());
204:
205: // System.err.println("t2 = " + t2);
206: // System.err.println("t1 = " + t1);
207: // System.err.println("types= " + types.getClass());
208: // System.err.println("types.isAssignable(t2, t1) = " + types.isAssignable(t2, t1));
209: // System.err.println("types.isSubtype(t2, t1) = " + types.isSubtype(t2, t1));
210: // System.err.println("types.isAssignable(t1,t2) = " + types.isAssignable(t1,t2));
211: // System.err.println("types.isSubtype(t1, t2) = " + types.isSubtype(t1, t2));
212: if (types.isAssignable(t2, t1))
213: valid.add(r);
214: }
215:
216: // System.err.println("l = " + l );
217: // System.err.println("valid = " + valid );
218: validPairs.put(l, valid);
219: }
220:
221: Set<TypeElement> validRights = new HashSet<TypeElement>();
222:
223: for (TypeElement l : validPairs.keySet()) {
224: List<TypeElement> valid = validPairs.get(l);
225:
226: if (valid.isEmpty() && !leftReadOnly) {
227: //invalid left:
228: left.remove(l);
229: changed = true;
230: }
231:
232: validRights.addAll(valid);
233: }
234:
235: if (!rightReadOnly)
236: changed = right.retainAll(validRights) | changed;
237:
238: return changed;
239: }
240:
241: private static EnumSet<TypeKind> INVALID_TYPES = EnumSet.of(
242: TypeKind.NULL, TypeKind.NONE, TypeKind.OTHER,
243: TypeKind.ERROR);
244:
245: private static class TreeVisitorImpl extends
246: CancellableTreePathScanner<Void, Map<String, Object>> {
247:
248: private CompilationInfo info;
249: private Set<String> unresolved;
250:
251: private List<Hint> hints;
252:
253: public TreeVisitorImpl(CompilationInfo info) {
254: this .info = info;
255: unresolved = new HashSet<String>();
256: hints = new ArrayList<Hint>();
257: }
258:
259: @Override
260: public Void visitMemberSelect(MemberSelectTree tree,
261: Map<String, Object> p) {
262: if (tree.getExpression().getKind() == Kind.IDENTIFIER) {
263: p.put("request", null);
264: }
265:
266: scan(tree.getExpression(), p);
267:
268: Union2<String, DeclaredType> leftSide = (Union2<String, DeclaredType>) p
269: .remove("result");
270:
271: p.remove("request");
272:
273: if (leftSide != null && leftSide.hasFirst()) {
274: String rightSide = tree.getIdentifier().toString();
275:
276: if (ERROR.equals(rightSide))
277: rightSide = "";
278:
279: boolean isMethodInvocation = getCurrentPath()
280: .getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION;
281:
282: //Ignore .class (which will not help us much):
283: if (!"class".equals(rightSide))
284: hints.add(new EnclosedHint(leftSide.first(),
285: rightSide, !isMethodInvocation));
286: }
287:
288: return null;
289: }
290:
291: @Override
292: public Void visitVariable(VariableTree tree,
293: Map<String, Object> p) {
294: scan(tree.getModifiers(), p);
295:
296: if (tree.getType().getKind() == Kind.IDENTIFIER) {
297: p.put("request", null);
298: }
299:
300: scan(tree.getType(), p);
301:
302: Union2<String, DeclaredType> leftSide = (Union2<String, DeclaredType>) p
303: .remove("result");
304:
305: p.remove("request");
306:
307: Union2<String, DeclaredType> rightSide = null;
308:
309: if (leftSide != null && tree.getInitializer() != null) {
310: Element el = info.getTrees().getElement(
311: new TreePath(getCurrentPath(), tree
312: .getInitializer()));
313: TypeMirror rightType = el != null ? el.asType() : null;
314:
315: // System.err.println("rightType = " + rightType );
316: // System.err.println("tree.getInitializer()=" + tree.getInitializer());
317: // System.err.println("rightType.getKind()=" + rightType.getKind());
318: // System.err.println("INVALID_TYPES.contains(rightType.getKind())=" + INVALID_TYPES.contains(rightType.getKind()));
319: if (rightType != null
320: && rightType.getKind() == TypeKind.DECLARED) {
321: rightSide = Union2
322: .<String, DeclaredType> createSecond((DeclaredType) rightType);
323: } else {
324: if (tree.getInitializer().getKind() == Kind.NEW_CLASS
325: || tree.getInitializer().getKind() == Kind.NEW_ARRAY) {
326: p.put("request", null);
327: }
328: }
329: }
330:
331: scan(tree.getInitializer(), p);
332:
333: rightSide = rightSide == null ? (Union2<String, DeclaredType>) p
334: .remove("result")
335: : rightSide;
336:
337: p.remove("result");
338:
339: // System.err.println("rightSide = " + rightSide );
340:
341: p.remove("request");
342:
343: if (leftSide != null && rightSide != null) {
344: if (!(leftSide instanceof TypeMirror)
345: || !(rightSide instanceof TypeMirror)) {
346: hints.add(new TypeHint(leftSide, rightSide));
347: }
348: }
349:
350: return null;
351: }
352:
353: @Override
354: public Void visitIdentifier(IdentifierTree tree,
355: Map<String, Object> p) {
356: super .visitIdentifier(tree, p);
357:
358: if (getCurrentPath().getParentPath() != null
359: && getCurrentPath().getParentPath().getLeaf()
360: .getKind() == Kind.METHOD_INVOCATION) {
361: MethodInvocationTree mit = (MethodInvocationTree) getCurrentPath()
362: .getParentPath().getLeaf();
363:
364: if (mit.getMethodSelect() == tree) {
365: return null;
366: }
367: }
368:
369: // System.err.println("tree=" + tree);
370: Element el = info.getTrees().getElement(getCurrentPath());
371: if (el != null
372: && (el.getKind().isClass()
373: || el.getKind().isInterface() || el
374: .getKind() == ElementKind.PACKAGE)) {
375: TypeMirror type = el.asType();
376: String simpleName = null;
377:
378: if (type.getKind() == TypeKind.ERROR) {
379: simpleName = el.getSimpleName().toString();
380: }
381:
382: if (type.getKind() == TypeKind.PACKAGE) {
383: //does the package really exists?
384: String s = ((PackageElement) el).getQualifiedName()
385: .toString();
386: if (info.getElements().getPackageElement(s) == null) {
387: //probably situation like:
388: //Map.Entry e;
389: //where Map is not imported
390: simpleName = el.getSimpleName().toString();
391: }
392: }
393:
394: if (simpleName == null
395: || !SourceVersion.isIdentifier(simpleName)
396: || SourceVersion.isKeyword(simpleName)) {
397: simpleName = null;
398: }
399:
400: if (simpleName != null) {
401: unresolved.add(simpleName);
402:
403: Scope currentScope = info.getTrees().getScope(
404: getCurrentPath());
405:
406: hints.add(new AccessibleHint(simpleName,
407: currentScope));
408:
409: if (p.containsKey("request")) {
410: p
411: .put(
412: "result",
413: Union2
414: .<String, DeclaredType> createFirst(simpleName));
415: }
416: } else {
417: if (p.containsKey("request")
418: && type.getKind() == TypeKind.DECLARED) {
419: p
420: .put(
421: "result",
422: Union2
423: .<String, DeclaredType> createSecond((DeclaredType) type));
424: }
425: }
426: }
427:
428: p.remove("request");
429:
430: return null;
431: }
432:
433: }
434:
435: public static interface Hint {
436:
437: public abstract boolean filter(CompilationInfo info,
438: Map<String, List<TypeElement>> rawCandidates,
439: Map<String, List<TypeElement>> candidates);
440:
441: }
442:
443: public static final class TypeHint implements Hint {
444:
445: private Union2<String, DeclaredType> left;
446: private Union2<String, DeclaredType> right;
447:
448: public TypeHint(Union2<String, DeclaredType> left,
449: Union2<String, DeclaredType> right) {
450: this .left = left;
451: this .right = right;
452: }
453:
454: public boolean filter(CompilationInfo info,
455: Map<String, List<TypeElement>> rawCandidates,
456: Map<String, List<TypeElement>> candidates) {
457: return false;
458: }
459:
460: //IZ 102613 -- bugous 'discouraged' hints
461: public boolean Xfilter(CompilationInfo info,
462: Map<String, List<TypeElement>> rawCandidates,
463: Map<String, List<TypeElement>> candidates) {
464: List<TypeElement> left = null;
465: List<TypeElement> right = null;
466: boolean leftReadOnly = false;
467: boolean rightReadOnly = false;
468:
469: if (this .left.hasSecond()) {
470: Element el = this .left.second().asElement();
471:
472: //TODO do not use instanceof!
473: if (el instanceof TypeElement) {
474: left = Collections.singletonList((TypeElement) el);
475: leftReadOnly = true;
476: }
477: } else {
478: left = candidates.get(this .left.first());
479: }
480:
481: if (this .right.hasSecond()) {
482: Element el = this .right.second().asElement();
483:
484: //TODO do not use instanceof!
485: if (el instanceof TypeElement) {
486: right = Collections.singletonList((TypeElement) el);
487: rightReadOnly = true;
488: }
489: } else {
490: right = candidates.get(this .right.first());
491: }
492:
493: if (left != null && right != null && !left.isEmpty()
494: && !right.isEmpty()) {
495: return ComputeImports.filter(info.getTypes(), left,
496: right, leftReadOnly, rightReadOnly);
497: }
498:
499: return false;
500: }
501:
502: }
503:
504: public static final class EnclosedHint implements Hint {
505:
506: private String simpleName;
507: private String methodName;
508: private boolean allowPrefix;
509:
510: public EnclosedHint(String simpleName, String methodName,
511: boolean allowPrefix) {
512: this .simpleName = simpleName;
513: this .methodName = methodName;
514: this .allowPrefix = allowPrefix;
515: }
516:
517: public boolean filter(CompilationInfo info,
518: Map<String, List<TypeElement>> rawCandidates,
519: Map<String, List<TypeElement>> candidates) {
520: return false;
521: }
522:
523: //IZ 102613 -- bugous 'discouraged' hints
524: private boolean Xfilter(CompilationInfo info,
525: Map<String, List<TypeElement>> rawCandidates,
526: Map<String, List<TypeElement>> candidates) {
527: List<TypeElement> cands = candidates.get(simpleName);
528:
529: if (cands == null || cands.isEmpty())
530: return false;
531:
532: List<TypeElement> toRemove = new ArrayList<TypeElement>();
533:
534: for (TypeElement te : cands) {
535: boolean found = false;
536:
537: for (Element e : te.getEnclosedElements()) {
538: String simpleName = e.getSimpleName().toString();
539:
540: if (methodName.contentEquals(simpleName)) {
541: found = true;
542: break;
543: }
544:
545: if (allowPrefix
546: && simpleName.startsWith(methodName)) {
547: found = true;
548: break;
549: }
550: }
551:
552: if (!found) {
553: toRemove.add(te);
554: }
555: }
556:
557: return cands.removeAll(toRemove);
558: }
559:
560: }
561:
562: public static final class AccessibleHint implements Hint {
563:
564: private String simpleName;
565: private Scope scope;
566:
567: public AccessibleHint(String simpleName, Scope scope) {
568: this .simpleName = simpleName;
569: this .scope = scope;
570: }
571:
572: public boolean filter(CompilationInfo info,
573: Map<String, List<TypeElement>> rawCandidates,
574: Map<String, List<TypeElement>> candidates) {
575: List<TypeElement> cands = candidates.get(simpleName);
576:
577: if (cands == null || cands.isEmpty())
578: return false;
579:
580: List<TypeElement> toRemove = new ArrayList<TypeElement>();
581:
582: for (TypeElement te : cands) {
583: if (!info.getTrees().isAccessible(scope, te)) {
584: toRemove.add(te);
585: }
586: }
587:
588: //remove it from the raw candidates too:
589: rawCandidates.get(simpleName).removeAll(toRemove);
590:
591: return cands.removeAll(toRemove);
592: }
593:
594: }
595:
596: public static class Pair<A, B> {
597:
598: public A a;
599: public B b;
600:
601: public Pair(A a, B b) {
602: this.a = a;
603: this.b = b;
604: }
605: }
606:
607: }
|