All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.api.java.source.ScanUtils Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.api.java.source;

import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import javax.swing.SwingUtilities;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.openide.util.Exceptions;
import org.openide.util.Parameters;

/**
 * Utility methods, which help JavaSource processing tasks to coordinate with the indexing/scanning process.
 * There are two variants of {@link JavaSource#runUserActionTask}, which support abort and restart of the user task,
 * if the task indicates incomplete data and the scanning is in progress. The restart is done in a hope that, after scanning
 * completes, the missing types or elements will appear.
 * 

* The user task may use other provided wrapper methods to: *

    *
  • {@link #checkElement} to assert element's presence and validity *
  • {@link #signalIncompleteData} to request restart if running parallel to scan *
* Note that even though the user task run when all the parsing/scanning is over, there's no guarantee that data obtained from * the parser structures will be complete and valid - the source code may be really broken, dependency could be missing etc. *

* An example pattern how to use this class is: *

{@code
 * final TreePath tp = ... ; // obtain a tree path
 * ScanUtils.waitUserActionTask(new Task<{@link CompilationController}>() {
 *      public void run(CompilationController ctrl) {
 * 
 *          // possibly abort and wait for all info to become available
 *          Element e = ScanUtils.checkElement(tp.resolve(ctrl));
 *          if (!ctrl.getElementUtilities().isErroneous(e)) {
 *              // report the error; element is not found or is otherwise unusable
 *          } else {
 *              // do the really useful work on Element e
 *          }
 *      }
 * };
 * }
* * @author Svata Dedic * @since 0.95 */ public final class ScanUtils { private ScanUtils() {} /** * Runs the user task through {@link JavaSource#runUserActionTask}, and returns Future completion handle for it. * The executed Task may indicate that it does not have enough data when scan is in progress, through e.g. {@link #checkElement}. * If so, processing of the Task will abort, and will be restarted when the scan finishes. *

* For more details, see description of {@link #postUserTask(org.netbeans.modules.parsing.api.Source, org.netbeans.modules.parsing.api.UserTask) }. * * @param src JavaSource to process * @param uat action task that will be executed * * @return Future that allows to wait for task's completion. * * @see #postUserTask(org.netbeans.modules.parsing.api.Source, org.netbeans.modules.parsing.api.UserTask) * @see JavaSource#runUserActionTask */ public static Future postUserActionTask(@NonNull final JavaSource src, @NonNull final Task uat) { Parameters.notNull("src", src); Parameters.notNull("uat", uat); AtomicReference status = new AtomicReference<>(null); Future f = postJavaTask(src, uat, status); Throwable t = status.get(); if (t != null) { Exceptions.printStackTrace(t); } return f; } /** * Runs the user task, and returns Future completion handle for it. * The executed Task may indicate that it does not have enough data when scan is in progress, through e.g. {@link #checkElement}. * If so, processing of the Task will abort, and will be restarted when the scan finishes. *

* The task may run asynchronously, pay attention to synchronization of task's output * data. The first attempt to run the task MAY execute synchronously. Do not call the method from * Swing EDT, if the task typically takes non-trivial time to complete (see {@link JavaSource#runUserActionTask} * for discussion). *

* As {@code postUserTask} may decide to defer the task execution, {@code postUserTask} cannot reliably report exceptions thrown * from the parsing infrastructure. The Task itself is responsible for handling exceptions and propagating them * to the caller, the {@code post} method will just log the exception. * * @param src Source to process * @param task action task that will be executed * @return Future that allows to wait for task's completion. * * @see JavaSource#runUserActionTask */ public static Future postUserTask(@NonNull final Source src, @NonNull final UserTask task) { Parameters.notNull("src", src); Parameters.notNull("task", task); AtomicReference status = new AtomicReference<>(null); Future f = postUserTask(src, task, status); Throwable t = status.get(); if (t != null) { Exceptions.printStackTrace(t); } return f; } /** * Runs user action over source 'src' using {@link JavaSource#runUserActionTask} and waits for its completion. * The executed Task may indicate that it does not have enough data when scan is in progress, through e.g. {@link #checkElement}. * If so, processing of the Task will abort, and will be restarted when the scan finishes. The {@code waitUserActionTask} method * will wait until the rescheduled task completes. *

* Unlike {@link #postUserActionTask}, this method propagates exceptiosn from the Task to the caller, even if the Task * is postponed and waited for. *

* Calling this method from Swing ED thread is prohibited. * * @param src java source to process * @param uat task to execute * * @throws IOException in the case of a failure in the user task, or scheduling failure (propagated from {@link JavaSource#runUserActionTask}, * {@link JavaSource#runWhenScanFinished} * * @see JavaSource#runUserActionTask */ public static void waitUserActionTask(@NonNull final JavaSource src, @NonNull final Task uat) throws IOException { Parameters.notNull("src", src); Parameters.notNull("uat", uat); if (SwingUtilities.isEventDispatchThread()) { throw new IllegalStateException("Illegal to call within EDT"); } AtomicReference status = new AtomicReference<>(null); Future f = postJavaTask(src, uat, status); if (f.isDone()) { return; } try { f.get(); } catch (InterruptedException ex) { IOException ioex = new IOException("Interrupted", ex); throw ioex; } catch (ExecutionException ex) { Throwable cause = ex.getCause(); if (cause instanceof IOException) { throw (IOException)cause; } IOException ioex = new IOException("Failed", ex); throw ioex; } // propagate the 'retry' instruction as an exception - was not thrown in an appropriate context. Throwable t = status.get(); if (t != null) { if (t instanceof IOException) { throw (IOException) t; } IOException ioex = new IOException("Exception during processing", t); throw ioex; } } /** * Runs user action over source 'src' using {@link ParserManager#parse} and waits for its completion. * The executed Task may indicate that it does not have enough data when scan is in progress, through e.g. {@link #checkElement}. * If so, processing of the Task will abort, and will be restarted when the scan finishes. The {@code waitUserTask} method * will wait until the rescheduled task completes. *

* Unlike {@link #postUserTask}, this method propagates exceptiosn from the Task to the caller, even if the Task * is postponed and waited for. *

* Calling this method from Swing ED thread is prohibited. * * @param src java source to process * @param task task to execute * * @throws ParseException in the case of a failure in the user task, or scheduling failure (propagated from {@link JavaSource#runUserActionTask}, * {@link ParserManager#parseWhenScanFinished(java.util.Collection, org.netbeans.modules.parsing.api.UserTask) } * * @see ParserManager#parse(java.util.Collection, org.netbeans.modules.parsing.api.UserTask) * @see ParserManager#parseWhenScanFinished(java.util.Collection, org.netbeans.modules.parsing.api.UserTask) */ public static void waitUserTask(@NonNull final Source src, @NonNull final UserTask task) throws ParseException { Parameters.notNull("src", src); Parameters.notNull("task", task); if (SwingUtilities.isEventDispatchThread()) { throw new IllegalStateException("Illegal to call within EDT"); } AtomicReference status = new AtomicReference<>(null); Future f = postUserTask(src, task, status); if (f.isDone()) { Throwable t = status.get(); if (t != null) { if (t instanceof ParseException) { throw (ParseException)t; } else { throw new ParseException("User task failure", t); } } return; } try { f.get(); } catch (InterruptedException ex) { ParseException err = new ParseException("Interrupted", ex); throw err; } catch (ExecutionException ex) { Throwable cause = ex.getCause(); if (cause instanceof ParseException) { throw (ParseException)cause; } ParseException ioex = new ParseException("User task failure", ex); throw ioex; } // propagate the 'retry' instruction as an exception - was not thrown in an appropriate context. Throwable t = status.get(); if (t != null) { if (t instanceof ParseException) { throw (ParseException) t; } ParseException err = new ParseException("User task failure", t); throw err; } } /** * Checks that the Element is valid, is not erroneous. If the Element is {@code null} or erroneous and * scanning is running, the method throws a {@link ScanUtils.RetryWhenScanFinished} exception to * indicate that the action should be retried. The method should be only used in code, which * is directly or indirectly executed by {@link #waitUserActionTask}, otherwise {@link IllegalStateException} * will be thrown. *

* If scan is not running, the method always returns the value of 'e' parameter. *

* An example usage is as follows: * * TreePath tp = ...; * Element e = checkElement(cu.getTrees().getElement(tp)); * *

* Note: the method may be only called from within {@link #waitUserActionTask}, it aborts * the current processing under assumption the user task will be restarted. It is illegal to call the * method if not running as user task - IllegalStateException will be thrown. * * @param e the Element to check * @param info the source of the Element * @return the original Element, to support 'fluent' pattern * * @throws IllegalStateException if not called from within user task * */ public static T checkElement(@NonNull CompilationInfo info, @NullAllowed T e) { checkRetryContext(); if (e == null) { signalIncompleteData(info, null); return e; } if (!info.getElementUtilities().isErroneous(e)) { return e; } if (shouldSignal()) { signalIncompleteData(info, ElementHandle.create(e)); } return e; } private static boolean shouldSignal() { return SourceUtils.isScanInProgress() && Boolean.TRUE.equals(retryGuard.get()); } /** * Aborts the user task and calls it again when parsing finishes, if a typename is not available. * * @param ci the Element which causes the trouble * @param handle of the Element * @throws IllegalStateException if not called from within {@link #waitUserActionTask} and the like * */ public static void signalIncompleteData(@NonNull CompilationInfo ci, @NullAllowed ElementHandle handle) { checkRetryContext(); if (shouldSignal()) { throw new RetryWhenScanFinished(ci, handle); } } private static void checkRetryContext() throws IllegalStateException { Boolean b = retryGuard.get(); if (b == null) { throw new IllegalStateException("The method may be only called within SourceUtils.waitUserActionTask"); } } private static Future postJavaTask( final JavaSource javaSrc, final Task javaTask, final AtomicReference status) { boolean retry = false; Boolean b = retryGuard.get(); boolean mode = b == null || b.booleanValue(); try { retryGuard.set(mode); javaSrc.runUserActionTask(javaTask, true); // the action passed ;) } catch (RuntimeException e) { status.set(e); } catch (IOException e) { // log and swallow status.set(e); } catch (RetryWhenScanFinished e) { // expected, will retry in runWhenParseFinished retry = true; } finally { if (b == null) { retryGuard.remove(); } else { retryGuard.set(b); } } if (!retry) { return new FinishedFuture(); } final TaskWrapper wrapper; Future handle; wrapper = new TaskWrapper(javaTask, status, mode); try { handle = javaSrc.runWhenScanFinished(wrapper, true); } catch (IOException ex) { status.set(ex); handle = new FinishedFuture(); } return handle; } private static Future postUserTask( final Source src, final UserTask task, final AtomicReference status) { boolean retry = false; Boolean b = retryGuard.get(); boolean mode = b == null || b.booleanValue(); try { retryGuard.set(mode); ParserManager.parse(Collections.singleton(src), task); } catch (ParseException ex) { status.set(ex); } catch (RetryWhenScanFinished e) { // expected, will retry in runWhenParseFinished retry = true; } finally { if (b == null) { retryGuard.remove(); } else { retryGuard.set(b); } } if (!retry) { return new FinishedFuture(); } final TaskWrapper wrapper; Future handle; wrapper = new TaskWrapper(task, status, mode); try { handle = ParserManager.parseWhenScanFinished(Collections.singletonList(src), wrapper); } catch (ParseException ex) { status.set(ex); handle = new FinishedFuture(); } return handle; } /** * Guards usage of the abortAndRetry methods; since they throw an exception, which is * only caught on specific places */ private static final ThreadLocal retryGuard = new ThreadLocal(); private static final class FinishedFuture implements Future { @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public Void get() throws InterruptedException, ExecutionException { return null; } @Override public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return null; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public String toString() { return "FinishedFuture"; } } private static class TaskWrapper extends UserTask implements Task { private Task javaTask; private UserTask task; private AtomicReference status; private boolean mode; public TaskWrapper(UserTask task, AtomicReference status, boolean mode) { this.task = task; this.status = status; this.mode = mode; } public TaskWrapper(Task javaTask, AtomicReference status, boolean mode) { this.javaTask = javaTask; this.status = status; this.mode = mode; } @Override public void run(ResultIterator resultIterator) throws Exception { Boolean b = retryGuard.get(); try { retryGuard.set(mode); task.run(resultIterator); } catch (RetryWhenScanFinished ex) { // swallow Error, but signal error status.set(ex); } catch (Exception ex) { status.set(ex); throw ex; } finally { if (b == null) { retryGuard.remove(); } else { retryGuard.set(b); } } } @Override public void run(CompilationController parameter) throws Exception { Boolean b = retryGuard.get(); try { retryGuard.set(mode); javaTask.run(parameter); } catch (RetryWhenScanFinished ex) { // swallow Error, but signal error status.set(ex); } catch (Exception ex) { status.set(ex); throw ex; } finally { if (b == null) { retryGuard.remove(); } else { retryGuard.set(b); } } } } /** * An Exception which indicates that the operation should be aborted, and * retried when parsing is finished. The exception derives from the {@link Error} * class to bypass ill-written code, which caught {@link RuntimeException} or * even {@link Exception}. *

* The exception is interpreted by the Java Source infrastructure and is not * meant to be ever thrown or caught by regular application code. It's deliberately * package-private so users are not tempted to throw or catch it. */ static class RetryWhenScanFinished extends Error { private ElementHandle elHandle; private Source source; public RetryWhenScanFinished(CompilationInfo ci, ElementHandle elHandle) { if (ci != null && ci.getSnapshot() != null) { source = ci.getSnapshot().getSource(); } this.elHandle = elHandle; } @Override public String toString() { return "RetryWhenScanFinished{" + "elHandle=" + elHandle + ", source=" + source + '}'; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy