| /******************************************************************************* |
| * Copyright (c) 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.manipulation; |
| |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| |
| import org.eclipse.jdt.core.ITypeRoot; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTParser; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| |
| import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; |
| import org.eclipse.jdt.internal.corext.dom.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; |
| |
| /** |
| * The {@link CoreASTProvider} provides access to the {@link CompilationUnit AST root} used by |
| * the current active Java editor. |
| * |
| * The {@link CoreASTProvider} contains all methods/functionality that are |
| * not dependent on the UI, from org.eclipse.jdt.internal.ui.javaeditor.ASTProvider |
| * for the purpose of reuse by non-UI bundles. |
| * |
| * <p> |
| * This class is not intended to be subclassed or instantiated by clients. |
| * </p> |
| * |
| * @since 1.10 |
| * |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| */ |
| public final class CoreASTProvider { |
| |
| private static CoreASTProvider instance = new CoreASTProvider(); |
| |
| public static final String DEBUG_PREFIX= "ASTProvider > "; //$NON-NLS-1$ |
| |
| private volatile ITypeRoot fReconcilingJavaElement; |
| private ITypeRoot fActiveJavaElement; |
| private CompilationUnit fAST; |
| private Object fReconcileLock= new Object(); |
| private Object fWaitLock= new Object(); |
| private volatile boolean fIsReconciling; |
| |
| /** |
| * Wait flag class. |
| */ |
| public static final class WAIT_FLAG { |
| |
| private String fName; |
| |
| private WAIT_FLAG(String name) { |
| fName= name; |
| } |
| |
| /* |
| * @see java.lang.Object#toString() |
| */ |
| @Override |
| public String toString() { |
| return fName; |
| } |
| } |
| |
| /** |
| * Wait flag indicating that a client requesting an AST |
| * wants to wait until an AST is ready. |
| * <p> |
| * An AST will be created by this AST provider if the shared |
| * AST is not for the given Java element. |
| * </p> |
| */ |
| public static final WAIT_FLAG WAIT_YES= new WAIT_FLAG("wait yes"); //$NON-NLS-1$ |
| |
| /** |
| * Wait flag indicating that a client requesting an AST |
| * only wants to wait for the shared AST of the active editor. |
| * <p> |
| * No AST will be created by the AST provider. |
| * </p> |
| */ |
| public static final WAIT_FLAG WAIT_ACTIVE_ONLY= new WAIT_FLAG("wait active only"); //$NON-NLS-1$ |
| |
| /** |
| * Wait flag indicating that a client requesting an AST |
| * only wants the already available shared AST. |
| * <p> |
| * No AST will be created by the AST provider. |
| * </p> |
| */ |
| public static final WAIT_FLAG WAIT_NO= new WAIT_FLAG("don't wait"); //$NON-NLS-1$ |
| |
| /** |
| * Returns a shared compilation unit AST for the given Java element. |
| * <p> |
| * Clients are not allowed to modify the AST and must synchronize all access to its nodes. |
| * </p> |
| * |
| * @param input the Java element, must not be <code>null</code> |
| * @param waitFlag org.eclipse.jdt.ui.SharedASTProvider#WAIT_YES, |
| * org.eclipse.jdt.ui.SharedASTProvider#WAIT_NO or |
| * org.eclipse.jdt.ui.SharedASTProvider#WAIT_ACTIVE_ONLY |
| * @param progressMonitor the progress monitor or <code>null</code> |
| * @return the AST or <code>null</code> if the AST is not available |
| */ |
| public CompilationUnit getAST(final ITypeRoot input, WAIT_FLAG waitFlag, IProgressMonitor progressMonitor) { |
| if (input == null || waitFlag == null) |
| throw new IllegalArgumentException("input or wait flag are null"); //$NON-NLS-1$ |
| |
| if (progressMonitor != null && progressMonitor.isCanceled()) |
| return null; |
| |
| boolean isActiveElement; |
| synchronized (this) { |
| isActiveElement= input.equals(fActiveJavaElement); |
| if (isActiveElement) { |
| if (fAST != null) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "returning cached AST:" + toString(fAST) + " for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| return fAST; |
| } |
| if (waitFlag == CoreASTProvider.WAIT_NO) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "returning null (WAIT_NO) for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| return null; |
| |
| } |
| } |
| } |
| |
| final boolean canReturnNull= waitFlag == CoreASTProvider.WAIT_NO || (waitFlag == CoreASTProvider.WAIT_ACTIVE_ONLY && (!isActiveElement || fAST != null)); |
| boolean isReconciling= false; |
| final ITypeRoot activeElement; |
| if (isActiveElement) { |
| synchronized (fReconcileLock) { |
| activeElement= fReconcilingJavaElement; |
| isReconciling= isReconciling(input); |
| if (!isReconciling && !canReturnNull) |
| aboutToBeReconciled(input); |
| } |
| } else |
| activeElement= null; |
| |
| if (isReconciling) { |
| try { |
| // Wait for AST |
| synchronized (fWaitLock) { |
| if (isReconciling(input)) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "waiting for AST for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| fWaitLock.wait(30000); // XXX: The 30 seconds timeout is an attempt to at least avoid a deadlock. See https://bugs.eclipse.org/366048#c21 |
| } |
| } |
| |
| // Check whether active element is still valid |
| synchronized (this) { |
| if (activeElement == fActiveJavaElement && fAST != null) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "...got AST: " + toString(fAST) + " for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| return fAST; |
| } |
| } |
| return getAST(input, waitFlag, progressMonitor); |
| } catch (InterruptedException e) { |
| return null; // thread has been interrupted don't compute AST |
| } |
| } else if (canReturnNull) |
| return null; |
| |
| |
| CompilationUnit ast= null; |
| try { |
| ast= createAST(input, progressMonitor); |
| if (progressMonitor != null && progressMonitor.isCanceled()) { |
| ast= null; |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "Ignore created AST for: " + input.getElementName() + " - operation has been cancelled"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } finally { |
| if (isActiveElement) { |
| if (fAST != null) { |
| // in the meantime, reconcile created a new AST. Return that one |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "Ignore created AST for " + input.getElementName() + " - AST from reconciler is newer"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| reconciled(fAST, input, null); |
| return fAST; |
| } else |
| reconciled(ast, input, null); |
| } |
| } |
| return ast; |
| } |
| |
| /** |
| * Informs that reconciling for the given element is about to be started. |
| * |
| * @param javaElement the Java element |
| * See org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#aboutToBeReconciled() |
| */ |
| public void aboutToBeReconciled(ITypeRoot javaElement) { |
| |
| if (javaElement == null) |
| return; |
| |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "about to reconcile: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| synchronized (fReconcileLock) { |
| fReconcilingJavaElement= javaElement; |
| fIsReconciling= true; |
| } |
| cache(null, javaElement); |
| } |
| |
| /** |
| * Creates a new compilation unit AST. |
| * |
| * @param input the Java element for which to create the AST |
| * @param progressMonitor the progress monitor |
| * @return AST |
| */ |
| private static CompilationUnit createAST(final ITypeRoot input, final IProgressMonitor progressMonitor) { |
| if (!hasSource(input)) |
| return null; |
| |
| if (progressMonitor != null && progressMonitor.isCanceled()) |
| return null; |
| |
| final ASTParser parser = ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL); |
| parser.setResolveBindings(true); |
| parser.setStatementsRecovery(IASTSharedValues.SHARED_AST_STATEMENT_RECOVERY); |
| parser.setBindingsRecovery(IASTSharedValues.SHARED_BINDING_RECOVERY); |
| parser.setSource(input); |
| |
| if (progressMonitor != null && progressMonitor.isCanceled()) |
| return null; |
| |
| final CompilationUnit root[]= new CompilationUnit[1]; |
| |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void run() { |
| try { |
| if (progressMonitor != null && progressMonitor.isCanceled()) |
| return; |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.err.println(getThreadName() + " - " + DEBUG_PREFIX + "creating AST for: " + input.getElementName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| root[0]= (CompilationUnit)parser.createAST(progressMonitor); |
| |
| //mark as unmodifiable |
| ASTNodes.setFlagsToAST(root[0], ASTNode.PROTECT); |
| } catch (OperationCanceledException ex) { |
| return; |
| } |
| } |
| @Override |
| public void handleException(Throwable ex) { |
| IStatus status= new Status(IStatus.ERROR, JavaManipulation.ID_PLUGIN, IStatus.OK, "Error in JDT Core during AST creation", ex); //$NON-NLS-1$ |
| Platform.getLog(CoreASTProvider.class).log(status); |
| } |
| }); |
| return root[0]; |
| } |
| |
| /** |
| * Update internal structures after reconcile. |
| * |
| * @param ast the compilation unit AST or <code>null</code> if the working copy was consistent or |
| * reconciliation has been cancelled |
| * @param javaElement the Java element for which the AST was built |
| * @param progressMonitor the progress monitor |
| * See org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener#reconciled(CompilationUnit, |
| * boolean, IProgressMonitor) |
| */ |
| public void reconciled(CompilationUnit ast, ITypeRoot javaElement, IProgressMonitor progressMonitor) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "reconciled: " + toString(javaElement) + ", AST: " + toString(ast)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| synchronized (fReconcileLock) { |
| fIsReconciling= false; |
| if (javaElement == null || !javaElement.equals(fReconcilingJavaElement)) { |
| |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + " ignoring AST of out-dated editor"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| // Signal - threads might wait for wrong element |
| synchronized (fWaitLock) { |
| fWaitLock.notifyAll(); |
| } |
| |
| return; |
| } |
| cache(ast, javaElement); |
| } |
| } |
| |
| /** |
| * Tells whether the given Java element is the one reported as currently being reconciled. |
| * |
| * @param javaElement the Java element |
| * @return <code>true</code> if reported as currently being reconciled |
| */ |
| private boolean isReconciling(ITypeRoot javaElement) { |
| return javaElement != null && javaElement.equals(fReconcilingJavaElement) && fIsReconciling; |
| } |
| |
| /** |
| * Caches the given compilation unit AST for the given Java element. |
| * |
| * @param ast the ast |
| * @param javaElement the java element |
| */ |
| public synchronized void cache(CompilationUnit ast, ITypeRoot javaElement) { |
| |
| if (fActiveJavaElement != null && !fActiveJavaElement.equals(javaElement)) { |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER && javaElement != null) // don't report call from disposeAST() |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "don't cache AST for inactive: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| } |
| |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER && (javaElement != null || ast != null)) // don't report call from disposeAST() |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "caching AST: " + toString(ast) + " for: " + toString(javaElement)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| if (fAST != null) |
| disposeAST(); |
| |
| fAST= ast; |
| |
| // Signal AST change |
| synchronized (fWaitLock) { |
| fWaitLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Checks whether the given Java element has accessible source. |
| * |
| * @param je the Java element to test |
| * @return <code>true</code> if the element has source |
| */ |
| private static boolean hasSource(ITypeRoot je) { |
| if (je == null || !je.exists()) |
| return false; |
| |
| try { |
| return je.getBuffer() != null; |
| } catch (JavaModelException ex) { |
| IStatus status= new Status(IStatus.ERROR, JavaManipulation.ID_PLUGIN, IStatus.OK, "Error in JDT Core during AST creation", ex); //$NON-NLS-1$ |
| Platform.getLog(CoreASTProvider.class).log(status); |
| } |
| return false; |
| } |
| |
| /** |
| * Disposes the cached AST. |
| */ |
| public synchronized void disposeAST() { |
| |
| if (fAST == null) |
| return; |
| |
| if (JavaManipulationPlugin.DEBUG_AST_PROVIDER) |
| System.out.println(getThreadName() + " - " + DEBUG_PREFIX + "disposing AST: " + toString(fAST) + " for: " + toString(fActiveJavaElement)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| |
| fAST= null; |
| |
| cache(null, null); |
| } |
| |
| /** |
| * Returns a string for the given Java element used for debugging. |
| * |
| * @param javaElement the compilation unit AST |
| * @return a string used for debugging |
| */ |
| public String toString(ITypeRoot javaElement) { |
| if (javaElement == null) |
| return "null"; //$NON-NLS-1$ |
| else |
| return javaElement.getElementName(); |
| |
| } |
| |
| /** |
| * Returns a string for the given AST used for debugging. |
| * |
| * @param ast the compilation unit AST |
| * @return a string used for debugging |
| */ |
| private String toString(CompilationUnit ast) { |
| if (ast == null) |
| return "null"; //$NON-NLS-1$ |
| |
| List<AbstractTypeDeclaration> types= ast.types(); |
| if (types != null && types.size() > 0) |
| return types.get(0).getName().getIdentifier() + "(" + ast.hashCode() + ")"; //$NON-NLS-1$//$NON-NLS-2$ |
| else |
| return "AST without any type"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * @return The name of the current thread if not null. |
| * Otherwise this returns the string representation of the |
| * current thread. |
| */ |
| public static String getThreadName() { |
| String name= Thread.currentThread().getName(); |
| if (name != null) |
| return name; |
| else |
| return Thread.currentThread().toString(); |
| } |
| |
| /** |
| * @return The singleton instance of this class. |
| */ |
| public static CoreASTProvider getInstance() { |
| return instance; |
| } |
| |
| private CoreASTProvider() { |
| // Prevent instantiation. |
| } |
| |
| /** |
| * @return Whether the current java element is being reconciled. |
| */ |
| public boolean isReconciling () { |
| return fIsReconciling; |
| } |
| |
| /** |
| * @return The java element currently being reconciled. |
| */ |
| public ITypeRoot getReconcilingJavaElement () { |
| return fReconcilingJavaElement; |
| } |
| |
| /** |
| * @return The active java element. |
| */ |
| public ITypeRoot getActiveJavaElement () { |
| return fActiveJavaElement; |
| } |
| |
| /** |
| * Set the active java element that is currently active. |
| * @param activeJavaElement the java element. |
| */ |
| public void setActiveJavaElement (ITypeRoot activeJavaElement) { |
| fActiveJavaElement = activeJavaElement; |
| } |
| |
| /** |
| * @return The compilation unit's cached AST. |
| */ |
| public CompilationUnit getCachedAST () { |
| return fAST; |
| } |
| |
| /** |
| * Notify all waiting threads that the AST has changed. |
| */ |
| public void waitLockNotifyAll () { |
| synchronized (fWaitLock) { |
| fWaitLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * Clear the reconciliation state. |
| */ |
| public void clearReconciliation () { |
| synchronized (fReconcileLock) { |
| fIsReconciling = false; |
| fReconcilingJavaElement = null; |
| } |
| } |
| |
| } |