001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.javaeditor;
011:
012: import java.util.List;
013:
014: import org.eclipse.core.runtime.IProgressMonitor;
015: import org.eclipse.core.runtime.ISafeRunnable;
016: import org.eclipse.core.runtime.IStatus;
017: import org.eclipse.core.runtime.OperationCanceledException;
018: import org.eclipse.core.runtime.Platform;
019: import org.eclipse.core.runtime.SafeRunner;
020: import org.eclipse.core.runtime.Status;
021:
022: import org.eclipse.ui.IPartListener2;
023: import org.eclipse.ui.IWindowListener;
024: import org.eclipse.ui.IWorkbenchPart;
025: import org.eclipse.ui.IWorkbenchPartReference;
026: import org.eclipse.ui.IWorkbenchWindow;
027: import org.eclipse.ui.PlatformUI;
028:
029: import org.eclipse.jdt.core.ICompilationUnit;
030: import org.eclipse.jdt.core.ITypeRoot;
031: import org.eclipse.jdt.core.JavaModelException;
032: import org.eclipse.jdt.core.dom.AST;
033: import org.eclipse.jdt.core.dom.ASTNode;
034: import org.eclipse.jdt.core.dom.ASTParser;
035: import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
036: import org.eclipse.jdt.core.dom.CompilationUnit;
037:
038: import org.eclipse.jdt.internal.corext.dom.ASTNodes;
039:
040: import org.eclipse.jdt.ui.JavaUI;
041: import org.eclipse.jdt.ui.SharedASTProvider;
042: import org.eclipse.jdt.ui.SharedASTProvider.WAIT_FLAG;
043:
044: import org.eclipse.jdt.internal.ui.JavaPlugin;
045:
046: /**
047: * Provides a shared AST for clients. The shared AST is
048: * the AST of the active Java editor's input element.
049: *
050: * @since 3.0
051: */
052: public final class ASTProvider {
053:
054: /**
055: * @deprecated Use {@link SharedASTProvider#WAIT_YES} instead.
056: */
057: public static final WAIT_FLAG WAIT_YES = SharedASTProvider.WAIT_YES;
058:
059: /**
060: * @deprecated Use {@link SharedASTProvider#WAIT_ACTIVE_ONLY} instead.
061: */
062: public static final WAIT_FLAG WAIT_ACTIVE_ONLY = SharedASTProvider.WAIT_ACTIVE_ONLY;
063:
064: /**
065: * @deprecated Use {@link SharedASTProvider#WAIT_NO} instead.
066: */
067: public static final WAIT_FLAG WAIT_NO = SharedASTProvider.WAIT_NO;
068:
069: /**
070: * Tells whether this class is in debug mode.
071: * @since 3.0
072: */
073: private static final boolean DEBUG = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jdt.ui/debug/ASTProvider")); //$NON-NLS-1$//$NON-NLS-2$
074:
075: /**
076: * Internal activation listener.
077: *
078: * @since 3.0
079: */
080: private class ActivationListener implements IPartListener2,
081: IWindowListener {
082:
083: /*
084: * @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference)
085: */
086: public void partActivated(IWorkbenchPartReference ref) {
087: if (isJavaEditor(ref) && !isActiveEditor(ref))
088: activeJavaEditorChanged(ref.getPart(true));
089: }
090:
091: /*
092: * @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference)
093: */
094: public void partBroughtToTop(IWorkbenchPartReference ref) {
095: if (isJavaEditor(ref) && !isActiveEditor(ref))
096: activeJavaEditorChanged(ref.getPart(true));
097: }
098:
099: /*
100: * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
101: */
102: public void partClosed(IWorkbenchPartReference ref) {
103: if (isActiveEditor(ref)) {
104: if (DEBUG)
105: System.out
106: .println(getThreadName()
107: + " - " + DEBUG_PREFIX + "closed active editor: " + ref.getTitle()); //$NON-NLS-1$ //$NON-NLS-2$
108:
109: activeJavaEditorChanged(null);
110: }
111: }
112:
113: /*
114: * @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference)
115: */
116: public void partDeactivated(IWorkbenchPartReference ref) {
117: }
118:
119: /*
120: * @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
121: */
122: public void partOpened(IWorkbenchPartReference ref) {
123: if (isJavaEditor(ref) && !isActiveEditor(ref))
124: activeJavaEditorChanged(ref.getPart(true));
125: }
126:
127: /*
128: * @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference)
129: */
130: public void partHidden(IWorkbenchPartReference ref) {
131: }
132:
133: /*
134: * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference)
135: */
136: public void partVisible(IWorkbenchPartReference ref) {
137: if (isJavaEditor(ref) && !isActiveEditor(ref))
138: activeJavaEditorChanged(ref.getPart(true));
139: }
140:
141: /*
142: * @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference)
143: */
144: public void partInputChanged(IWorkbenchPartReference ref) {
145: if (isJavaEditor(ref) && isActiveEditor(ref))
146: activeJavaEditorChanged(ref.getPart(true));
147: }
148:
149: /*
150: * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow)
151: */
152: public void windowActivated(IWorkbenchWindow window) {
153: IWorkbenchPartReference ref = window.getPartService()
154: .getActivePartReference();
155: if (isJavaEditor(ref) && !isActiveEditor(ref))
156: activeJavaEditorChanged(ref.getPart(true));
157: }
158:
159: /*
160: * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow)
161: */
162: public void windowDeactivated(IWorkbenchWindow window) {
163: }
164:
165: /*
166: * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow)
167: */
168: public void windowClosed(IWorkbenchWindow window) {
169: if (fActiveEditor != null
170: && fActiveEditor.getSite() != null
171: && window == fActiveEditor.getSite()
172: .getWorkbenchWindow()) {
173: if (DEBUG)
174: System.out
175: .println(getThreadName()
176: + " - " + DEBUG_PREFIX + "closed active editor: " + fActiveEditor.getTitle()); //$NON-NLS-1$ //$NON-NLS-2$
177:
178: activeJavaEditorChanged(null);
179: }
180: window.getPartService().removePartListener(this );
181: }
182:
183: /*
184: * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow)
185: */
186: public void windowOpened(IWorkbenchWindow window) {
187: window.getPartService().addPartListener(this );
188: }
189:
190: private boolean isActiveEditor(IWorkbenchPartReference ref) {
191: return ref != null && isActiveEditor(ref.getPart(false));
192: }
193:
194: private boolean isActiveEditor(IWorkbenchPart part) {
195: return part != null && (part == fActiveEditor);
196: }
197:
198: private boolean isJavaEditor(IWorkbenchPartReference ref) {
199: if (ref == null)
200: return false;
201:
202: String id = ref.getId();
203:
204: // The instanceof check is not need but helps clients, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=84862
205: return JavaUI.ID_CF_EDITOR.equals(id)
206: || JavaUI.ID_CU_EDITOR.equals(id)
207: || ref.getPart(false) instanceof JavaEditor;
208: }
209: }
210:
211: public static final int SHARED_AST_LEVEL = AST.JLS3;
212: public static final boolean SHARED_AST_STATEMENT_RECOVERY = true;
213: public static final boolean SHARED_BINDING_RECOVERY = true;
214:
215: private static final String DEBUG_PREFIX = "ASTProvider > "; //$NON-NLS-1$
216:
217: private ITypeRoot fReconcilingJavaElement;
218: private ITypeRoot fActiveJavaElement;
219: private CompilationUnit fAST;
220: private ActivationListener fActivationListener;
221: private Object fReconcileLock = new Object();
222: private Object fWaitLock = new Object();
223: private boolean fIsReconciling;
224: private IWorkbenchPart fActiveEditor;
225:
226: /**
227: * Returns the Java plug-in's AST provider.
228: *
229: * @return the AST provider
230: * @since 3.2
231: */
232: public static ASTProvider getASTProvider() {
233: return JavaPlugin.getDefault().getASTProvider();
234: }
235:
236: /**
237: * Creates a new AST provider.
238: */
239: public ASTProvider() {
240: install();
241: }
242:
243: /**
244: * Installs this AST provider.
245: */
246: void install() {
247: // Create and register activation listener
248: fActivationListener = new ActivationListener();
249: PlatformUI.getWorkbench()
250: .addWindowListener(fActivationListener);
251:
252: // Ensure existing windows get connected
253: IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
254: .getWorkbenchWindows();
255: for (int i = 0, length = windows.length; i < length; i++)
256: windows[i].getPartService().addPartListener(
257: fActivationListener);
258: }
259:
260: private void activeJavaEditorChanged(IWorkbenchPart editor) {
261:
262: ITypeRoot javaElement = null;
263: if (editor instanceof JavaEditor)
264: javaElement = ((JavaEditor) editor).getInputJavaElement();
265:
266: synchronized (this ) {
267: fActiveEditor = editor;
268: fActiveJavaElement = javaElement;
269: cache(null, javaElement);
270: }
271:
272: if (DEBUG)
273: System.out
274: .println(getThreadName()
275: + " - " + DEBUG_PREFIX + "active editor is: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$
276:
277: synchronized (fReconcileLock) {
278: if (fIsReconciling
279: && (fReconcilingJavaElement == null || !fReconcilingJavaElement
280: .equals(javaElement))) {
281: fIsReconciling = false;
282: fReconcilingJavaElement = null;
283: } else if (javaElement == null) {
284: fIsReconciling = false;
285: fReconcilingJavaElement = null;
286: }
287: }
288: }
289:
290: /**
291: * Returns whether the given compilation unit AST is
292: * cached by this AST provided.
293: *
294: * @param ast the compilation unit AST
295: * @return <code>true</code> if the given AST is the cached one
296: */
297: public boolean isCached(CompilationUnit ast) {
298: return ast != null && fAST == ast;
299: }
300:
301: /**
302: * Returns whether this AST provider is active on the given
303: * compilation unit.
304: *
305: * @param cu the compilation unit
306: * @return <code>true</code> if the given compilation unit is the active one
307: * @since 3.1
308: */
309: public boolean isActive(ICompilationUnit cu) {
310: return cu != null && cu.equals(fActiveJavaElement);
311: }
312:
313: /**
314: * Informs that reconciling for the given element is about to be started.
315: *
316: * @param javaElement the Java element
317: * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#aboutToBeReconciled()
318: */
319: void aboutToBeReconciled(ITypeRoot javaElement) {
320:
321: if (javaElement == null)
322: return;
323:
324: if (DEBUG)
325: System.out
326: .println(getThreadName()
327: + " - " + DEBUG_PREFIX + "about to reconcile: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$
328:
329: synchronized (fReconcileLock) {
330: fIsReconciling = true;
331: fReconcilingJavaElement = javaElement;
332: }
333: cache(null, javaElement);
334: }
335:
336: /**
337: * Disposes the cached AST.
338: */
339: private synchronized void disposeAST() {
340:
341: if (fAST == null)
342: return;
343:
344: if (DEBUG)
345: System.out
346: .println(getThreadName()
347: + " - " + DEBUG_PREFIX + "disposing AST: " + toString(fAST) + " for: " + toString(fActiveJavaElement)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
348:
349: fAST = null;
350:
351: cache(null, null);
352: }
353:
354: /**
355: * Returns a string for the given Java element used for debugging.
356: *
357: * @param javaElement the compilation unit AST
358: * @return a string used for debugging
359: */
360: private String toString(ITypeRoot javaElement) {
361: if (javaElement == null)
362: return "null"; //$NON-NLS-1$
363: else
364: return javaElement.getElementName();
365:
366: }
367:
368: /**
369: * Returns a string for the given AST used for debugging.
370: *
371: * @param ast the compilation unit AST
372: * @return a string used for debugging
373: */
374: private String toString(CompilationUnit ast) {
375: if (ast == null)
376: return "null"; //$NON-NLS-1$
377:
378: List types = ast.types();
379: if (types != null && types.size() > 0)
380: return ((AbstractTypeDeclaration) types.get(0)).getName()
381: .getIdentifier();
382: else
383: return "AST without any type"; //$NON-NLS-1$
384: }
385:
386: /**
387: * Caches the given compilation unit AST for the given Java element.
388: *
389: * @param ast
390: * @param javaElement
391: */
392: private synchronized void cache(CompilationUnit ast,
393: ITypeRoot javaElement) {
394:
395: if (fActiveJavaElement != null
396: && !fActiveJavaElement.equals(javaElement)) {
397: if (DEBUG && javaElement != null) // don't report call from disposeAST()
398: System.out
399: .println(getThreadName()
400: + " - " + DEBUG_PREFIX + "don't cache AST for inactive: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$
401: return;
402: }
403:
404: if (DEBUG && (javaElement != null || ast != null)) // don't report call from disposeAST()
405: System.out
406: .println(getThreadName()
407: + " - " + DEBUG_PREFIX + "caching AST: " + toString(ast) + " for: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
408:
409: if (fAST != null)
410: disposeAST();
411:
412: fAST = ast;
413:
414: // Signal AST change
415: synchronized (fWaitLock) {
416: fWaitLock.notifyAll();
417: }
418: }
419:
420: /**
421: * Returns a shared compilation unit AST for the given
422: * Java element.
423: * <p>
424: * Clients are not allowed to modify the AST and must
425: * synchronize all access to its nodes.
426: * </p>
427: *
428: * @param input
429: * the Java element, must not be <code>null</code>
430: * @param waitFlag
431: * {@link SharedASTProvider#WAIT_YES}, {@link SharedASTProvider#WAIT_NO} or {@link SharedASTProvider#WAIT_ACTIVE_ONLY}
432: * @param progressMonitor
433: * the progress monitor or <code>null</code>
434: * @return
435: * the AST or <code>null</code> if the AST is not available
436: */
437: public CompilationUnit getAST(ITypeRoot input, WAIT_FLAG waitFlag,
438: IProgressMonitor progressMonitor) {
439: if (input == null || waitFlag == null)
440: throw new IllegalArgumentException(
441: "input or wait flag are null"); //$NON-NLS-1$
442:
443: if (progressMonitor != null && progressMonitor.isCanceled())
444: return null;
445:
446: boolean isActiveElement;
447: synchronized (this ) {
448: isActiveElement = input.equals(fActiveJavaElement);
449: if (isActiveElement) {
450: if (fAST != null) {
451: if (DEBUG)
452: System.out
453: .println(getThreadName()
454: + " - " + DEBUG_PREFIX + "returning cached AST:" + toString(fAST) + " for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
455:
456: return fAST;
457: }
458: if (waitFlag == SharedASTProvider.WAIT_NO) {
459: if (DEBUG)
460: System.out
461: .println(getThreadName()
462: + " - " + DEBUG_PREFIX + "returning null (WAIT_NO) for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
463:
464: return null;
465:
466: }
467: }
468: }
469: if (isActiveElement && isReconciling(input)) {
470: try {
471: final ITypeRoot activeElement = fReconcilingJavaElement;
472:
473: // Wait for AST
474: synchronized (fWaitLock) {
475: if (DEBUG)
476: System.out
477: .println(getThreadName()
478: + " - " + DEBUG_PREFIX + "waiting for AST for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
479:
480: fWaitLock.wait();
481: }
482:
483: // Check whether active element is still valid
484: synchronized (this ) {
485: if (activeElement == fActiveJavaElement
486: && fAST != null) {
487: if (DEBUG)
488: System.out
489: .println(getThreadName()
490: + " - " + DEBUG_PREFIX + "...got AST for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
491:
492: return fAST;
493: }
494: }
495: return getAST(input, waitFlag, progressMonitor);
496: } catch (InterruptedException e) {
497: return null; // thread has been interrupted don't compute AST
498: }
499: } else if (waitFlag == SharedASTProvider.WAIT_NO
500: || (waitFlag == SharedASTProvider.WAIT_ACTIVE_ONLY && !(isActiveElement && fAST == null)))
501: return null;
502:
503: if (isActiveElement)
504: aboutToBeReconciled(input);
505:
506: CompilationUnit ast = null;
507: try {
508: ast = createAST(input, progressMonitor);
509: if (progressMonitor != null && progressMonitor.isCanceled()) {
510: ast = null;
511: if (DEBUG)
512: System.out
513: .println(getThreadName()
514: + " - " + DEBUG_PREFIX + "Ignore created AST for: " + input.getElementName() + " - operation has been cancelled"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
515: }
516: } finally {
517: if (isActiveElement) {
518: if (fAST != null) {
519: // in the meantime, reconcile created a new AST. Return that one
520: if (DEBUG)
521: System.out
522: .println(getThreadName()
523: + " - " + DEBUG_PREFIX + "Ignore created AST for " + input.getElementName() + " - AST from reconciler is newer"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
524: reconciled(fAST, input, null);
525: return fAST;
526: } else
527: reconciled(ast, input, null);
528: }
529: }
530: return ast;
531: }
532:
533: /**
534: * Tells whether the given Java element is the one
535: * reported as currently being reconciled.
536: *
537: * @param javaElement the Java element
538: * @return <code>true</code> if reported as currently being reconciled
539: */
540: private boolean isReconciling(ITypeRoot javaElement) {
541: synchronized (fReconcileLock) {
542: return javaElement != null
543: && javaElement.equals(fReconcilingJavaElement)
544: && fIsReconciling;
545: }
546: }
547:
548: /**
549: * Creates a new compilation unit AST.
550: *
551: * @param input the Java element for which to create the AST
552: * @param progressMonitor the progress monitor
553: * @return AST
554: */
555: private static CompilationUnit createAST(final ITypeRoot input,
556: final IProgressMonitor progressMonitor) {
557: if (!hasSource(input))
558: return null;
559:
560: if (progressMonitor != null && progressMonitor.isCanceled())
561: return null;
562:
563: final ASTParser parser = ASTParser.newParser(SHARED_AST_LEVEL);
564: parser.setResolveBindings(true);
565: parser.setStatementsRecovery(SHARED_AST_STATEMENT_RECOVERY);
566: parser.setBindingsRecovery(SHARED_BINDING_RECOVERY);
567: parser.setSource(input);
568:
569: if (progressMonitor != null && progressMonitor.isCanceled())
570: return null;
571:
572: final CompilationUnit root[] = new CompilationUnit[1];
573:
574: SafeRunner.run(new ISafeRunnable() {
575: public void run() {
576: try {
577: if (progressMonitor != null
578: && progressMonitor.isCanceled())
579: return;
580: if (DEBUG)
581: System.err
582: .println(getThreadName()
583: + " - " + DEBUG_PREFIX + "creating AST for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$
584: root[0] = (CompilationUnit) parser
585: .createAST(progressMonitor);
586:
587: //mark as unmodifiable
588: ASTNodes.setFlagsToAST(root[0], ASTNode.PROTECT);
589: } catch (OperationCanceledException ex) {
590: return;
591: }
592: }
593:
594: public void handleException(Throwable ex) {
595: IStatus status = new Status(IStatus.ERROR,
596: JavaUI.ID_PLUGIN, IStatus.OK,
597: "Error in JDT Core during AST creation", ex); //$NON-NLS-1$
598: JavaPlugin.getDefault().getLog().log(status);
599: }
600: });
601: return root[0];
602: }
603:
604: /**
605: * Checks whether the given Java element has accessible source.
606: *
607: * @param je the Java element to test
608: * @return <code>true</code> if the element has source
609: * @since 3.2
610: */
611: private static boolean hasSource(ITypeRoot je) {
612: if (je == null || !je.exists())
613: return false;
614:
615: try {
616: return je.getBuffer() != null;
617: } catch (JavaModelException ex) {
618: IStatus status = new Status(IStatus.ERROR,
619: JavaUI.ID_PLUGIN, IStatus.OK,
620: "Error in JDT Core during AST creation", ex); //$NON-NLS-1$
621: JavaPlugin.getDefault().getLog().log(status);
622: }
623: return false;
624: }
625:
626: /**
627: * Disposes this AST provider.
628: */
629: public void dispose() {
630:
631: // Dispose activation listener
632: PlatformUI.getWorkbench().removeWindowListener(
633: fActivationListener);
634: fActivationListener = null;
635:
636: disposeAST();
637:
638: synchronized (fWaitLock) {
639: fWaitLock.notifyAll();
640: }
641: }
642:
643: /*
644: * @see org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#reconciled(org.eclipse.jdt.core.dom.CompilationUnit)
645: */
646: void reconciled(CompilationUnit ast, ITypeRoot javaElement,
647: IProgressMonitor progressMonitor) {
648: if (DEBUG)
649: System.out
650: .println(getThreadName()
651: + " - " + DEBUG_PREFIX + "reconciled: " + toString(javaElement) + ", AST: " + toString(ast)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
652:
653: synchronized (fReconcileLock) {
654: fIsReconciling = progressMonitor != null
655: && progressMonitor.isCanceled();
656: if (javaElement == null
657: || !javaElement.equals(fReconcilingJavaElement)) {
658:
659: if (DEBUG)
660: System.out
661: .println(getThreadName()
662: + " - " + DEBUG_PREFIX + " ignoring AST of out-dated editor"); //$NON-NLS-1$ //$NON-NLS-2$
663:
664: // Signal - threads might wait for wrong element
665: synchronized (fWaitLock) {
666: fWaitLock.notifyAll();
667: }
668:
669: return;
670: }
671:
672: cache(ast, javaElement);
673: }
674: }
675:
676: private static String getThreadName() {
677: String name = Thread.currentThread().getName();
678: if (name != null)
679: return name;
680: else
681: return Thread.currentThread().toString();
682: }
683:
684: }
|