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:
042: package org.netbeans.api.java.source;
043:
044: import com.sun.source.tree.Tree;
045: import com.sun.source.tree.Tree.Kind;
046: import com.sun.source.util.TreePath;
047: import com.sun.tools.javac.code.Symbol;
048: import com.sun.tools.javac.tree.JCTree;
049: import java.io.IOException;
050: import java.net.MalformedURLException;
051: import java.net.URL;
052: import java.util.ArrayList;
053: import java.util.Arrays;
054: import java.util.StringTokenizer;
055: import java.util.logging.Logger;
056: import javax.lang.model.element.Element;
057: import javax.lang.model.element.ElementKind;
058: import javax.swing.text.Position;
059: import javax.swing.text.Position.Bias;
060: import org.netbeans.modules.java.source.parsing.FileObjects;
061: import org.netbeans.modules.java.source.usages.Index;
062: import org.openide.filesystems.FileObject;
063: import org.openide.filesystems.FileStateInvalidException;
064: import org.openide.filesystems.FileUtil;
065: import org.openide.filesystems.URLMapper;
066: import org.openide.loaders.DataObject;
067: import org.openide.loaders.DataObjectNotFoundException;
068: import org.openide.text.CloneableEditorSupport;
069: import org.openide.text.EditorSupport;
070: import org.openide.text.PositionRef;
071: import org.openide.util.Exceptions;
072:
073: /**
074: * Represents a handle for {@link TreePath} which can be kept and later resolved
075: * by another javac. The Javac {@link Element}s are valid only in the single
076: * {@link javax.tools.CompilationTask} or single run of the
077: * {@link org.netbeans.api.java.source.CancellableTask}. If the client needs to
078: * keep a reference to the {@link TreePath} and use it in the other CancellableTask
079: * he has to serialize it into the {@link TreePathHandle}.
080: * <div class="nonnormative">
081: * <p>
082: * Typical usage of TreePathHandle enclElIsCorrespondingEl:
083: * </p>
084: * <pre>
085: * final TreePathHandle[] tpHandle = new TreePathHandle[1];
086: * javaSource.runCompileControlTask(new CancellableTask<CompilationController>() {
087: * public void run(CompilationController compilationController) {
088: * parameter.toPhase(Phase.RESOLVED);
089: * CompilationUnitTree cu = compilationController.getTree ();
090: * TreePath treePath = getInterestingTreePath (cu);
091: * treePathHandle[0] = TreePathHandle.create (element, compilationController);
092: * }
093: * },priority);
094: *
095: * otherJavaSource.runCompileControlTask(new CancellableTask<CompilationController>() {
096: * public void run(CompilationController compilationController) {
097: * parameter.toPhase(Phase.RESOLVED);
098: * TreePath treePath = treePathHanlde[0].resolve (compilationController);
099: * ....
100: * }
101: * },priority);
102: * </pre>
103: * </div>
104: *
105: *
106: * @author Jan Becicka
107: */
108: public final class TreePathHandle {
109:
110: private final Delegate delegate;
111:
112: private TreePathHandle(Delegate d) {
113: if (d == null) {
114: throw new IllegalArgumentException();
115: }
116:
117: this .delegate = d;
118: }
119:
120: /**
121: * getter for FileObject from give TreePathHandle
122: * @return FileObject for which was this handle created
123: */
124: public FileObject getFileObject() {
125: return this .delegate.getFileObject();
126: }
127:
128: /**
129: * Resolves an {@link TreePath} from the {@link TreePathHandle}.
130: * @param compilationInfo representing the {@link javax.tools.CompilationTask}
131: * @return resolved subclass of {@link Element} or null if the elment does not exist on
132: * the classpath/sourcepath of {@link javax.tools.CompilationTask}.
133: * @throws {@link IllegalArgumentException} when this {@link TreePathHandle} is not created for a source
134: * represented by the compilationInfo.
135: */
136: public TreePath resolve(final CompilationInfo compilationInfo)
137: throws IllegalArgumentException {
138: return this .delegate.resolve(compilationInfo);
139: }
140:
141: @Override
142: public boolean equals(Object obj) {
143: if (obj == null && !(obj instanceof TreePathHandle)) {
144: return false;
145: }
146:
147: if (delegate.getClass() != ((TreePathHandle) obj).delegate
148: .getClass()) {
149: return false;
150: }
151:
152: return delegate.equals(((TreePathHandle) obj).delegate);
153: }
154:
155: @Override
156: public int hashCode() {
157: return delegate.hashCode();
158: }
159:
160: /**
161: * Resolves an {@link Element} from the {@link TreePathHandle}.
162: * @param compilationInfo representing the {@link javax.tools.CompilationTask}
163: * @return resolved subclass of {@link Element} or null if the elment does not exist on
164: * the classpath/sourcepath of {@link javax.tools.CompilationTask}.
165: */
166: public Element resolveElement(final CompilationInfo info) {
167: return this .delegate.resolveElement(info);
168: }
169:
170: /**
171: * Returns the {@link Tree.Kind} of this TreePathHandle,
172: * it returns the kind of the {@link Tree} from which the handle
173: * was created.
174: *
175: * @return {@link Tree.Kind}
176: */
177: public Tree.Kind getKind() {
178: return this .delegate.getKind();
179: }
180:
181: /**
182: * Factory method for creating {@link TreePathHandle}.
183: *
184: * @param treePath for which the {@link TrePathHandle} should be created.
185: * @param info
186: * @return a new {@link TreePathHandle}
187: * @throws java.lang.IllegalArgumentException if arguments are not supported
188: */
189: public static TreePathHandle create(final TreePath treePath,
190: CompilationInfo info) throws IllegalArgumentException {
191: FileObject file;
192: try {
193: file = URLMapper.findFileObject(treePath
194: .getCompilationUnit().getSourceFile().toUri()
195: .toURL());
196: } catch (MalformedURLException e) {
197: throw (RuntimeException) new RuntimeException()
198: .initCause(e);
199: }
200: int position = ((JCTree) treePath.getLeaf()).pos;
201: PositionRef pos = createPositionRef(file, position,
202: Bias.Forward);
203: TreePath current = treePath;
204: Element element;
205: boolean enclElIsCorrespondingEl = true;
206: do {
207: element = info.getTrees().getElement(current);
208: current = current.getParentPath();
209: if (element != null && !isSupported(element)) {
210: enclElIsCorrespondingEl = false;
211: }
212: } while ((element == null || !isSupported(element))
213: && current != null);
214: return new TreePathHandle(new TreeDelegate(pos,
215: new TreeDelegate.KindPath(treePath), file,
216: ElementHandle.create(element), enclElIsCorrespondingEl));
217: }
218:
219: /**
220: * Factory method for creating {@link TreePathHandle}.
221: *
222: * @param element for which the {@link TrePathHandle} should be created.
223: * @param info
224: * @return a new {@link TreePathHandle}
225: * @throws java.lang.IllegalArgumentException if arguments are not supported
226: */
227: public static TreePathHandle create(Element element,
228: CompilationInfo info) throws IllegalArgumentException {
229: URL u = null;
230: String qualName = null;
231: Symbol.ClassSymbol clsSym;
232: if (element instanceof Symbol.ClassSymbol) {
233: clsSym = (Symbol.ClassSymbol) element;
234: } else {
235: clsSym = (Symbol.ClassSymbol) SourceUtils
236: .getEnclosingTypeElement(element);
237: }
238: if (clsSym != null && clsSym.classfile != null) {
239: try {
240: u = clsSym.classfile.toUri().toURL();
241: } catch (MalformedURLException ex) {
242: Exceptions.printStackTrace(ex);
243: }
244:
245: qualName = clsSym.getEnclosingElement().getQualifiedName()
246: .toString();
247: }
248:
249: return new TreePathHandle(new ElementDelegate(ElementHandle
250: .create(element), u, qualName, info.getClasspathInfo()));
251: }
252:
253: private static boolean isSupported(Element el) {
254: switch (el.getKind()) {
255: case PACKAGE:
256: case CLASS:
257: case INTERFACE:
258: case ENUM:
259: case METHOD:
260: case CONSTRUCTOR:
261: case INSTANCE_INIT:
262: case STATIC_INIT:
263: case FIELD:
264: case ANNOTATION_TYPE:
265: case ENUM_CONSTANT:
266: return true;
267: default:
268: return false;
269: }
270: }
271:
272: private static PositionRef createPositionRef(FileObject file,
273: int position, Position.Bias bias) {
274: try {
275: CloneableEditorSupport ces;
276: DataObject dob = DataObject.find(file);
277: Object obj = dob
278: .getCookie(org.openide.cookies.OpenCookie.class);
279: if (obj instanceof CloneableEditorSupport) {
280: return ((CloneableEditorSupport) obj)
281: .createPositionRef(position, bias);
282: }
283: obj = dob.getCookie(org.openide.cookies.EditorCookie.class);
284: if (obj instanceof CloneableEditorSupport) {
285: return ((CloneableEditorSupport) obj)
286: .createPositionRef(position, bias);
287: }
288: @SuppressWarnings("deprecation")
289: EditorSupport es = dob.getCookie(EditorSupport.class);
290: if (es != null) {
291: return es.createPositionRef(position, bias);
292: }
293: } catch (DataObjectNotFoundException ex) {
294: ex.printStackTrace();
295: }
296: throw new IllegalStateException(
297: "Cannot create PositionRef for file " + file.getPath()
298: + ". CloneableEditorSupport not found");
299: }
300:
301: @Override
302: public String toString() {
303: return "TreePathHandle[kind:" + getKind();// + ", enclosingElement:" + enclosingElement + "]";
304: }
305:
306: static interface Delegate {
307: public FileObject getFileObject();
308:
309: public TreePath resolve(final CompilationInfo compilationInfo)
310: throws IllegalArgumentException;
311:
312: public boolean equalsHandle(Delegate obj);
313:
314: public int hashCode();
315:
316: public Element resolveElement(final CompilationInfo info);
317:
318: public Tree.Kind getKind();
319: }
320:
321: private static final class TreeDelegate implements Delegate {
322:
323: private PositionRef position;
324:
325: private KindPath kindPath;
326:
327: private FileObject file;
328:
329: private ElementHandle enclosingElement;
330:
331: private boolean enclElIsCorrespondingEl;
332:
333: private Tree.Kind kind;
334:
335: private TreeDelegate(PositionRef position, KindPath kindPath,
336: FileObject file, ElementHandle element,
337: boolean enclElIsCorrespondingEl) {
338: this .kindPath = kindPath;
339: this .position = position;
340: this .file = file;
341: this .enclosingElement = element;
342: this .enclElIsCorrespondingEl = enclElIsCorrespondingEl;
343: if (kindPath != null) {
344: this .kind = kindPath.kindPath.get(0);
345: } else {
346: if (enclElIsCorrespondingEl) {
347: ElementKind k = element.getKind();
348: if (k.isClass() || k.isInterface()) {
349: kind = Tree.Kind.CLASS;
350: } else if (k.isField()) {
351: kind = Tree.Kind.VARIABLE;
352: } else if (k == ElementKind.METHOD
353: || k == ElementKind.CONSTRUCTOR) {
354: kind = Tree.Kind.METHOD;
355: }
356: }
357: }
358: }
359:
360: /**
361: * getter for FileObject from give TreePathHandle
362: * @return FileObject for which was this handle created
363: */
364: public FileObject getFileObject() {
365: return file;
366: }
367:
368: /**
369: * Resolves an {@link TreePath} from the {@link TreePathHandle}.
370: * @param compilationInfo representing the {@link javax.tools.CompilationTask}
371: * @return resolved subclass of {@link Element} or null if the elment does not exist on
372: * the classpath/sourcepath of {@link javax.tools.CompilationTask}.
373: * @throws {@link IllegalArgumentException} when this {@link TreePathHandle} is not created for a source
374: * represented by the compilationInfo.
375: */
376: public TreePath resolve(final CompilationInfo compilationInfo)
377: throws IllegalArgumentException {
378: assert compilationInfo != null;
379: if (!compilationInfo.getFileObject()
380: .equals(getFileObject())) {
381: throw new IllegalArgumentException("TreePathHandle ["
382: + FileUtil.getFileDisplayName(getFileObject())
383: + "] was not created from "
384: + FileUtil.getFileDisplayName(compilationInfo
385: .getFileObject()));
386: }
387: Element element = enclosingElement.resolve(compilationInfo);
388: TreePath tp = null;
389: if (element != null) {
390: TreePath startPath = compilationInfo.getTrees()
391: .getPath(element);
392: if (startPath == null) {
393: Logger.getLogger(TreePathHandle.class.getName())
394: .fine(
395: "compilationInfo.getTrees().getPath(element) returned null for element %s "
396: + element + "("
397: + file.getPath() + ")"); //NOI18N
398: } else {
399: tp = compilationInfo.getTreeUtilities().pathFor(
400: startPath, position.getOffset() + 1);
401: }
402: }
403: if (tp != null && new KindPath(tp).equals(kindPath)) {
404: return tp;
405: }
406: tp = compilationInfo.getTreeUtilities().pathFor(
407: position.getOffset() + 1);
408: while (tp != null) {
409: if (new KindPath(tp).equals(kindPath)) {
410: return tp;
411: }
412: tp = tp.getParentPath();
413: }
414: return null;
415: }
416:
417: public boolean equalsHandle(Delegate obj) {
418: TreeDelegate other = (TreeDelegate) obj;
419:
420: try {
421: if (this .position == null && other.position == null) {
422: assert this .enclElIsCorrespondingEl;
423: assert other.enclElIsCorrespondingEl;
424: return this .enclosingElement
425: .equals(other.enclosingElement);
426: }
427: if (this .position.getPosition().getOffset() != this .position
428: .getPosition().getOffset()) {
429: return false;
430: }
431: if (this .file != other.file
432: && (this .file == null || !this .file
433: .equals(other.file))) {
434: return false;
435: }
436: } catch (IOException ex) {
437: Exceptions.printStackTrace(ex);
438: return false;
439: }
440: return true;
441: }
442:
443: @Override
444: public int hashCode() {
445: if (this .position == null) {
446: return 553 + enclosingElement.hashCode();
447: }
448: int hash = 7;
449: hash = 79 * hash + this .position.getOffset();
450: hash = 79 * hash
451: + (this .file != null ? this .file.hashCode() : 0);
452: return hash;
453: }
454:
455: /**
456: * Resolves an {@link Element} from the {@link TreePathHandle}.
457: * @param compilationInfo representing the {@link javax.tools.CompilationTask}
458: * @return resolved subclass of {@link Element} or null if the elment does not exist on
459: * the classpath/sourcepath of {@link javax.tools.CompilationTask}.
460: */
461: public Element resolveElement(final CompilationInfo info) {
462: TreePath tp = null;
463: IllegalStateException ise = null;
464: try {
465: if ((this .file != null && info.getFileObject() != null)
466: && info.getFileObject().equals(this .file)
467: && this .position != null) {
468: tp = this .resolve(info);
469: }
470: } catch (IllegalStateException i) {
471: ise = i;
472: }
473: if (tp == null) {
474: if (enclElIsCorrespondingEl) {
475: Element e = enclosingElement.resolve(info);
476: if (e == null) {
477: Logger
478: .getLogger(
479: TreePathHandle.class.getName())
480: .severe(
481: "Cannot resolve"
482: + enclosingElement
483: + " in "
484: + info
485: .getClasspathInfo()); //NOI18N
486: }
487: return e;
488: } else {
489: if (ise == null) {
490: return null;
491: }
492: throw ise;
493: }
494: }
495: Element el = info.getTrees().getElement(tp);
496: if (el == null) {
497: Logger.getLogger(TreePathHandle.class.toString()).fine(
498: "info.getTrees().getElement(tp) returned null for "
499: + tp);
500: if (enclElIsCorrespondingEl) {
501: Element e = enclosingElement.resolve(info);
502: if (e == null) {
503: Logger
504: .getLogger(
505: TreePathHandle.class.getName())
506: .fine(
507: "Cannot resolve"
508: + enclosingElement
509: + " in "
510: + info
511: .getClasspathInfo()); //NOI18N
512: }
513: return e;
514: } else {
515: return null;
516: }
517: } else {
518: return el;
519: }
520: }
521:
522: /**
523: * Returns the {@link Tree.Kind} of this TreePathHandle,
524: * it returns the kind of the {@link Tree} from which the handle
525: * was created.
526: *
527: * @return {@link Tree.Kind}
528: */
529: public Tree.Kind getKind() {
530: return kind;
531: }
532:
533: @Override
534: public String toString() {
535: return "TreePathHandle[kind:" + kind
536: + ", enclosingElement:" + enclosingElement + "]";
537: }
538:
539: static class KindPath {
540: private ArrayList<Tree.Kind> kindPath = new ArrayList();
541:
542: KindPath(TreePath treePath) {
543: while (treePath != null) {
544: kindPath.add(treePath.getLeaf().getKind());
545: treePath = treePath.getParentPath();
546: }
547: }
548:
549: public int hashCode() {
550: return kindPath.hashCode();
551: }
552:
553: public boolean equals(Object object) {
554: if (object instanceof KindPath) {
555: return kindPath
556: .equals(((KindPath) object).kindPath);
557: }
558: return false;
559: }
560: }
561:
562: }
563:
564: private static final class ElementDelegate implements Delegate {
565:
566: private ElementHandle<? extends Element> el;
567: private URL source;
568: private String qualName;
569: private ClasspathInfo cpInfo;
570:
571: public ElementDelegate(ElementHandle<? extends Element> el,
572: URL source, String qualName, ClasspathInfo cpInfo) {
573: this .el = el;
574: this .source = source;
575: this .qualName = qualName;
576: this .cpInfo = cpInfo;
577: }
578:
579: public FileObject getFileObject() {
580: //source does not exist
581: FileObject file = SourceUtils.getFile(el, cpInfo);
582:
583: if (file == null && source != null) {
584: FileObject fo = URLMapper.findFileObject(source);
585: file = fo;
586: if (fo.getNameExt().endsWith("sig")) {
587: //NOI18N
588: //conversion sig -> class
589: String pkgName = FileObjects
590: .convertPackage2Folder(qualName);
591: StringTokenizer tk = new StringTokenizer(pkgName,
592: "/"); //NOI18N
593: for (int i = 0; fo != null && i <= tk.countTokens(); i++) {
594: fo = fo.getParent();
595: }
596: if (fo != null) {
597: try {
598: URL url = fo.getURL();
599: URL sourceRoot = Index
600: .getSourceRootForClassFolder(url);
601: if (sourceRoot != null) {
602: FileObject root = URLMapper
603: .findFileObject(sourceRoot);
604: String resourceName = FileUtil
605: .getRelativePath(fo, URLMapper
606: .findFileObject(source));
607: file = root.getFileObject(resourceName
608: .replace(".sig", ".class")); //NOI18N
609: } else {
610: Logger.getLogger(
611: TreePathHandle.class.getName())
612: .fine(
613: "Index.getSourceRootForClassFolder(url) returned null for url="
614: + url); //NOI18N
615: }
616: } catch (FileStateInvalidException ex) {
617: Exceptions.printStackTrace(ex);
618: }
619: }
620: }
621: }
622:
623: return file;
624: }
625:
626: public TreePath resolve(CompilationInfo compilationInfo)
627: throws IllegalArgumentException {
628: Element e = resolveElement(compilationInfo);
629:
630: if (e == null) {
631: return null;
632: }
633: return compilationInfo.getTrees().getPath(e);
634: }
635:
636: public Element resolveElement(CompilationInfo info) {
637: return el.resolve(info);
638: }
639:
640: public Kind getKind() {
641: switch (el.getKind()) {
642: case PACKAGE:
643: return Kind.COMPILATION_UNIT;
644:
645: case ENUM:
646: case CLASS:
647: case ANNOTATION_TYPE:
648: case INTERFACE:
649: return Kind.CLASS;
650:
651: case ENUM_CONSTANT:
652: case FIELD:
653: case PARAMETER:
654: case LOCAL_VARIABLE:
655: case EXCEPTION_PARAMETER:
656: return Kind.VARIABLE;
657:
658: case METHOD:
659: case CONSTRUCTOR:
660: return Kind.METHOD;
661:
662: case STATIC_INIT:
663: case INSTANCE_INIT:
664: return Kind.BLOCK;
665:
666: case TYPE_PARAMETER:
667: return Kind.TYPE_PARAMETER;
668:
669: case OTHER:
670: default:
671: return Kind.OTHER;
672: }
673: }
674:
675: public boolean equalsHandle(Delegate obj) {
676: ElementDelegate other = (ElementDelegate) obj;
677:
678: return el.signatureEquals(other.el)
679: && cpInfo.equals(other.cpInfo);
680: }
681:
682: @Override
683: public int hashCode() {
684: return Arrays.hashCode(el.getSignature());
685: }
686:
687: }
688:
689: }
|