Index: apichanges.xml =================================================================== RCS file: /cvs/j2ee/metadata/apichanges.xml,v retrieving revision 1.1 diff -u -r1.1 apichanges.xml --- apichanges.xml 16 Apr 2007 12:35:14 -0000 1.1 +++ apichanges.xml 13 May 2007 19:28:59 -0000 @@ -84,6 +84,24 @@ + Added MetadataModel.isReady() and MetadataModel.runReadActionWhenReady() + + + + + +

+ Added methods to check if a model is ready (metadata in the model + correspond exactly to their source) and to run a read action + when a model becomes ready. +

+
+ + + +
+ + Initial version released Index: manifest.mf =================================================================== RCS file: /cvs/j2ee/metadata/manifest.mf,v retrieving revision 1.6 diff -u -r1.6 manifest.mf --- manifest.mf 4 May 2007 11:18:45 -0000 1.6 +++ manifest.mf 13 May 2007 19:28:59 -0000 @@ -1,4 +1,4 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.j2ee.metadata/0 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/j2ee/metadata/Bundle.properties -OpenIDE-Module-Specification-Version: 1.2 +OpenIDE-Module-Specification-Version: 1.3 Index: src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModel.java =================================================================== RCS file: /cvs/j2ee/metadata/src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModel.java,v retrieving revision 1.2 diff -u -r1.2 MetadataModel.java --- src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModel.java 13 Apr 2007 17:09:23 -0000 1.2 +++ src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModel.java 13 May 2007 19:28:59 -0000 @@ -20,6 +20,7 @@ package org.netbeans.modules.j2ee.metadata.model.api; import java.io.IOException; +import java.util.concurrent.Future; import org.netbeans.modules.j2ee.metadata.model.MetadataModelAccessor; import org.netbeans.modules.j2ee.metadata.model.spi.MetadataModelImplementation; import org.openide.util.Parameters; @@ -50,7 +51,8 @@ } /** - * Executes an action in the context of this model. This method is used to provide + * Executes an action in the context of this model and in the calling thread. + * This method is used to provide * the model client with access to the metadata contained in the model. * *

This method provides safe access to the model in the presence of concurrency. @@ -70,6 +72,14 @@ * that are not explicitly documented as immutable are not allowed to escape * the action's run() method.

* + *

This method may take a long time to execute. It is + * recommended that the method not be called from the AWT event thread. In some + * situations, though, the call needs to be made from the event thread, such + * as when computing the enabled state of an action. In this case it is + * recommended that the call not take place if the {@link #isReady} method + * returns false (and a default value be returned as the result of + * the computation).

+ * * @param action the action to be executed. * @return the value returned by the action's run() method. * @throws MetadataModelException if a checked exception was thrown by @@ -85,5 +95,68 @@ public R runReadAction(MetadataModelAction action) throws MetadataModelException, IOException { Parameters.notNull("action", action); // NOI18N return impl.runReadAction(action); + } + + /** + * Returns true if the metadata contained in the model correspond exactly + * to their source. For example, for a model containing metadata expressed + * in annotations in Java files, the model could be considered ready if + * no classpath scanning is taking place. + * + *

It is not guaranteed that if this method returns true, a + * subsequent invocation of {@link #runReadAction runReadAction()} will see the model in a + * ready state. Therefore this method is intended just as a hint useful + * in best-effort scenarios. For example the method might be used by a client + * which needs immediate access to the model to make its best effort to + * ensure that the model will at least not be accessed when not ready.

+ * + * @return true if the model is ready, false otherwise. + * + * @since 1.3 + */ + public boolean isReady() { + return impl.isReady(); + } + + /** + * Executes an action in the context of this model either immediately + * if the model is ready, or at a later point in time when the model becomes + * ready. The action is executed in the calling thread if executed immediately, + * otherwise it is executed in another, unspecified thread. + * + *

The same guarantees with respect to concurrency and constraints + * with respect to re-readability of metadata that apply to + * {@link #runReadAction runReadAction()} apply to this method too. + * Furthermore, it is guaranteed that the action will see the model + * in a ready state, that is, when invoked by the action, the + * {@link #isReady} method will return true.

+ * + *

This method may take a long time to execute (in the case + * the action is executed immediately). It is recommended that + * the method not be called from the AWT event thread.

+ * + * @param action the action to be executed. + * @return a {@link Future} encapsulating the result of the action's + * run() method. If the action was not run + * immediately and it threw an exception (checked or unchecked), + * the future's get() methods will throw an + * {@link java.util.concurrent.ExecutionException} encapsulating + * that exception. + * @throws MetadataModelException if the action was run immediately + * and a checked exception was thrown by + * the action's run() method. That checked exception + * will be available as the return value of the {@link MetadataModelException#getCause getCause()} + * method. This only applies to checked exceptions; unchecked exceptions + * are propagated from the run() method unwrapped. + * @throws IOException if there was a problem reading the model from its storage (for + * example an exception occured while reading the disk files + * which constitute the source for the model's metadata). + * @throws NullPointerException if the action parameter was null. + * + * @since 1.3 + */ + public Future runReadActionWhenReady(MetadataModelAction action) throws MetadataModelException, IOException { + Parameters.notNull("action", action); // NOI18N + return impl.runReadActionWhenReady(action); } } Index: src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelImplementation.java =================================================================== RCS file: /cvs/j2ee/metadata/src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelImplementation.java,v retrieving revision 1.2 diff -u -r1.2 MetadataModelImplementation.java --- src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelImplementation.java 13 Apr 2007 17:09:24 -0000 1.2 +++ src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelImplementation.java 13 May 2007 19:28:59 -0000 @@ -20,6 +20,7 @@ package org.netbeans.modules.j2ee.metadata.model.spi; import java.io.IOException; +import java.util.concurrent.Future; import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction; import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelException; @@ -43,4 +44,22 @@ * @throws IOException if an error occured while reading the model from its storage. */ R runReadAction(MetadataModelAction action) throws MetadataModelException, IOException; + + /** + * Corresponds to {@link org.netbeans.modules.j2ee.metadata.model.api.MetadataModel#isReady}. + * + * @return true if the model is ready, false otherwise. + */ + boolean isReady(); + + /** + * Corresponds to {@link org.netbeans.modules.j2ee.metadata.model.api.MetadataModel#runReadActionWhenReady}. + * + * @param action the action to be executed; never null. + * @return a {@link Future} encapsulating the value returned by the action's {@link MetadataModelAction#run} method. + * @throws MetadataModelException if the action's run() method + * threw a checked exception. + * @throws IOException if an error occured while reading the model from its storage. + */ + Future runReadActionWhenReady(MetadataModelAction action) throws MetadataModelException, IOException; } Index: test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModelCompatibilityTest.java =================================================================== RCS file: /cvs/j2ee/metadata/test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModelCompatibilityTest.java,v retrieving revision 1.2 diff -u -r1.2 MetadataModelCompatibilityTest.java --- test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModelCompatibilityTest.java 13 Apr 2007 17:09:32 -0000 1.2 +++ test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/MetadataModelCompatibilityTest.java 13 May 2007 19:28:59 -0000 @@ -21,10 +21,14 @@ import java.io.IOException; import java.sql.SQLException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.j2ee.metadata.model.spi.MetadataModelFactory; /** + * Compatibility test for all MetadataModel implementations. Subclasses + * should override createModel() to return a ready or non-ready model. * * @author Andrei Badea */ @@ -34,18 +38,48 @@ super(name); } - protected MetadataModel createModel() { - return MetadataModelFactory.createMetadataModel(new SimpleMetadataModelImpl()); + /** + * Return a metadata model to be tested for compatibility. This method + * will only be called once for each test case. + */ + protected MetadataModel createModel(boolean ready) { + return MetadataModelFactory.createMetadataModel(new SimpleMetadataModelImpl(ready)); } - public void testExceptions() throws Exception { - MetadataModel model = createModel(); - doTestRuntimeExceptionsArePropagated(model); - doTestCheckedExceptionsAreWrapped(model); - doTestIOExceptionsAreWrapped(model); + /** + * Called after createModel() with a ready + * parameter equal to true to make the model become ready. + */ + protected void makeReady(MetadataModel model) { + // not needed here, but needed by subclasses } - private void doTestRuntimeExceptionsArePropagated(MetadataModel model) throws IOException { + public void testUncheckedExceptionsPropagatedWhenModelReady() throws IOException { + doTestUncheckedExceptionsPropagatedWhenReady(createModel(true)); + } + + public void testCheckedExceptionsWrappedWhenModelReady() throws IOException { + doTestCheckedExceptionsWrappedWhenModelReady(createModel(true)); + } + + public void testIOExceptionsWrappedWhenModelReady() throws IOException { + doTestIOExceptionsWrappedWhenModelReady(createModel(true)); + } + + public void testFutureRethrowsUncheckedExceptionsWhenModelNotReady() throws IOException { + doTestFutureRethrowsUncheckedExceptionsWhenModelNotReady(createModel(false)); + } + + public void testFutureRethrowsCheckedExceptions() throws IOException { + doTestFutureRethrowsCheckedExceptions(createModel(false)); + } + + public void testFutureRethrowsIOExceptions() throws IOException { + doTestFutureRethrowsIOExceptions(createModel(false)); + } + + private void doTestUncheckedExceptionsPropagatedWhenReady(MetadataModel model) throws IOException { + assertTrue(model.isReady()); try { model.runReadAction(new MetadataModelAction() { public Void run(T test) { @@ -56,9 +90,20 @@ } catch (RuntimeException re) { assertEquals("foo", re.getMessage()); } + try { + model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) { + throw new RuntimeException("foo"); + } + }); + fail(); + } catch (RuntimeException re) { + assertEquals("foo", re.getMessage()); + } } - private void doTestCheckedExceptionsAreWrapped(MetadataModel model) throws IOException { + private void doTestCheckedExceptionsWrappedWhenModelReady(MetadataModel model) throws IOException { + assertTrue(model.isReady()); try { model.runReadAction(new MetadataModelAction() { public Void run(T test) throws SQLException { @@ -70,9 +115,21 @@ SQLException cause = (SQLException)mme.getCause(); assertEquals("foo", cause.getMessage()); } + try { + model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) throws SQLException { + throw new SQLException("foo"); + } + }); + fail(); + } catch (MetadataModelException mme) { + SQLException cause = (SQLException)mme.getCause(); + assertEquals("foo", cause.getMessage()); + } } - private void doTestIOExceptionsAreWrapped(MetadataModel model) throws IOException { + private void doTestIOExceptionsWrappedWhenModelReady(MetadataModel model) throws IOException { + assertTrue(model.isReady()); try { model.runReadAction(new MetadataModelAction() { public Void run(T test) throws IOException { @@ -84,5 +141,67 @@ IOException cause = (IOException)mme.getCause(); assertEquals("foo", cause.getMessage()); } + try { + model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) throws IOException { + throw new IOException("foo"); + } + }); + fail(); + } catch (MetadataModelException mme) { + IOException cause = (IOException)mme.getCause(); + assertEquals("foo", cause.getMessage()); + } + } + + private void doTestFutureRethrowsUncheckedExceptionsWhenModelNotReady(MetadataModel model) throws IOException { + assertFalse(model.isReady()); + Future future = model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) { + throw new RuntimeException("foo"); + } + }); + makeReady(model); // otherwise future.get() will never finish + try { + future.get(); + fail(); + } catch (ExecutionException e) { + RuntimeException cause = (RuntimeException)e.getCause(); + assertEquals("foo", cause.getMessage()); + } catch (InterruptedException e) {} + } + + private void doTestFutureRethrowsCheckedExceptions(MetadataModel model) throws IOException { + assertFalse(model.isReady()); + Future future = model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) throws SQLException { + throw new SQLException("foo"); + } + }); + makeReady(model); // otherwise future.get() will never finish + try { + future.get(); + fail(); + } catch (ExecutionException e) { + SQLException cause = (SQLException)e.getCause(); + assertEquals("foo", cause.getMessage()); + } catch (InterruptedException e) {} + } + + private void doTestFutureRethrowsIOExceptions(MetadataModel model) throws IOException { + assertFalse(model.isReady()); + Future future = model.runReadActionWhenReady(new MetadataModelAction() { + public Void run(T test) throws IOException { + throw new IOException("foo"); + } + }); + makeReady(model); // otherwise future.get() will never finish + try { + future.get(); + fail(); + } catch (ExecutionException e) { + IOException cause = (IOException)e.getCause(); + assertEquals("foo", cause.getMessage()); + } catch (InterruptedException e) {} } } Index: test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/SimpleMetadataModelImpl.java =================================================================== RCS file: /cvs/j2ee/metadata/test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/SimpleMetadataModelImpl.java,v retrieving revision 1.2 diff -u -r1.2 SimpleMetadataModelImpl.java --- test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/SimpleMetadataModelImpl.java 13 Apr 2007 17:09:33 -0000 1.2 +++ test/unit/src/org/netbeans/modules/j2ee/metadata/model/api/SimpleMetadataModelImpl.java 13 May 2007 19:28:59 -0000 @@ -20,6 +20,10 @@ package org.netbeans.modules.j2ee.metadata.model.api; import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.netbeans.modules.j2ee.metadata.model.spi.MetadataModelImplementation; /** @@ -28,6 +32,12 @@ */ public class SimpleMetadataModelImpl implements MetadataModelImplementation { + private final boolean ready; + + public SimpleMetadataModelImpl(boolean ready) { + this.ready = ready; + } + public R runReadAction(MetadataModelAction action) throws IOException { try { return action.run(null); @@ -37,6 +47,67 @@ } else { throw new MetadataModelException(t); } + } + } + + public boolean isReady() { + return ready; + } + + public Future runReadActionWhenReady(MetadataModelAction action) throws IOException { + if (ready) { + try { + return new SimpleFuture(action.run(null), null); + } catch (Throwable t) { + if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } else { + throw new MetadataModelException(t); + } + } + } else { + R result = null; + ExecutionException executionException = null; + try { + result = action.run(null); + } catch (Throwable t) { + executionException = new ExecutionException(t); + } + return new SimpleFuture(result, executionException); + } + } + + private static final class SimpleFuture implements Future { + + private final R result; + private final ExecutionException executionException; + + public SimpleFuture(R result, ExecutionException executionException) { + this.result = result; + this.executionException = executionException; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + public boolean isCancelled() { + return false; + } + + public boolean isDone() { + return true; + } + + public R get() throws InterruptedException, ExecutionException { + if (executionException != null) { + throw executionException; + } + return result; + } + + public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); } } } Index: test/unit/src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelFactoryTest.java =================================================================== RCS file: /cvs/j2ee/metadata/test/unit/src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelFactoryTest.java,v retrieving revision 1.2 diff -u -r1.2 MetadataModelFactoryTest.java --- test/unit/src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelFactoryTest.java 13 Apr 2007 17:09:33 -0000 1.2 +++ test/unit/src/org/netbeans/modules/j2ee/metadata/model/spi/MetadataModelFactoryTest.java 13 May 2007 19:28:59 -0000 @@ -35,7 +35,7 @@ } public void testCreateMetadataModel() throws Exception { - MetadataModel model = MetadataModelFactory.createMetadataModel(new SimpleMetadataModelImpl()); + MetadataModel model = MetadataModelFactory.createMetadataModel(new SimpleMetadataModelImpl(true)); assertEquals("foo", model.runReadAction(new MetadataModelAction() { public String run(Void metadata) throws Exception { return "foo";