From 267dda422e19750a8746f265dc0d6469e3aac014 Mon Sep 17 00:00:00 2001 From: bvosburgh Date: Wed, 28 Oct 2009 17:46:10 +0000 Subject: rework JPA project locking --- .../property_files/jpa_core.properties | 1 + .../src/org/eclipse/jpt/core/GenericJpaModel.java | 1076 ++++++++++++++++++++ .../src/org/eclipse/jpt/core/JpaFactory.java | 5 +- .../src/org/eclipse/jpt/core/JpaModel.java | 47 +- .../src/org/eclipse/jpt/core/JpaProject.java | 4 +- .../src/org/eclipse/jpt/core/JptCorePlugin.java | 117 ++- .../jpt/core/internal/AbstractJpaFactory.java | 3 +- .../jpt/core/internal/AbstractJpaProject.java | 38 +- .../eclipse/jpt/core/internal/GenericJpaModel.java | 528 ---------- .../eclipse/jpt/core/internal/JpaModelManager.java | 590 ----------- .../eclipse/jpt/core/internal/JptCoreMessages.java | 1 + .../jpt/core/internal/jpa1/GenericJpaProject.java | 3 +- .../AbstractJpaFileCreationDataModelProvider.java | 5 +- .../AbstractJpaFileCreationOperation.java | 25 +- .../internal/utility/SimpleSchedulingRule.java | 22 +- .../jpt/core/internal/validation/JpaValidator.java | 4 + .../core/resource/AbstractXmlResourceProvider.java | 2 +- .../jpt/core/resource/xml/JpaXmlResource.java | 21 +- .../core/internal/EclipseLinkJpaFactory.java | 3 +- .../core/internal/EclipseLinkJpaProjectImpl.java | 3 +- .../src/org/eclipse/jpt/ui/JptUiPlugin.java | 6 +- .../properties/JpaProjectPropertiesPage.java | 17 +- .../internal/AsynchronousCommandExecutor.java | 164 +++ .../internal/CallbackStatefulCommandExecutor.java | 51 + .../jpt/utility/internal/CompositeCommand.java | 39 + .../internal/ConsumerThreadCoordinator.java | 225 ++++ .../org/eclipse/jpt/utility/internal/Queue.java | 75 ++ .../utility/internal/SimpleCommandExecutor.java | 65 ++ .../eclipse/jpt/utility/internal/SimpleQueue.java | 90 ++ .../eclipse/jpt/utility/internal/SimpleStack.java | 15 +- .../org/eclipse/jpt/utility/internal/Stack.java | 11 +- .../utility/internal/StatefulCommandExecutor.java | 33 + .../jpt/utility/internal/SynchronizedQueue.java | 348 +++++++ .../jpt/utility/internal/SynchronizedStack.java | 142 ++- .../utility/internal/iterables/QueueIterable.java | 51 + .../utility/internal/iterables/StackIterable.java | 51 + .../utility/internal/iterators/QueueIterator.java | 58 ++ .../utility/internal/iterators/StackIterator.java | 58 ++ .../synchronizers/AsynchronousSynchronizer.java | 149 +-- .../CallbackAsynchronousSynchronizer.java | 29 +- .../META-INF/MANIFEST.MF | 7 +- .../eclipse/jpt/core/tests/JptCoreTestsPlugin.java | 56 + .../src/org/eclipse/jpt/core/tests/MiscTests.java | 65 ++ .../internal/context/ContextModelTestCase.java | 13 +- .../core/tests/internal/model/JpaModelTests.java | 26 +- .../utility/tests/internal/SimpleQueueTests.java | 149 +++ .../utility/tests/internal/SimpleStackTests.java | 23 +- .../tests/internal/SynchronizedQueueTests.java | 285 ++++++ .../tests/internal/SynchronizedStackTests.java | 20 +- .../AsynchronousSynchronizerTests.java | 7 +- 50 files changed, 3350 insertions(+), 1476 deletions(-) create mode 100644 jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java delete mode 100644 jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/GenericJpaModel.java delete mode 100644 jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JpaModelManager.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/AsynchronousCommandExecutor.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CallbackStatefulCommandExecutor.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CompositeCommand.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/ConsumerThreadCoordinator.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Queue.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleCommandExecutor.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleQueue.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StatefulCommandExecutor.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedQueue.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/QueueIterable.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/StackIterable.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/QueueIterator.java create mode 100644 jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/StackIterator.java create mode 100644 jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/JptCoreTestsPlugin.java create mode 100644 jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/MiscTests.java create mode 100644 jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleQueueTests.java create mode 100644 jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedQueueTests.java diff --git a/jpa/plugins/org.eclipse.jpt.core/property_files/jpa_core.properties b/jpa/plugins/org.eclipse.jpt.core/property_files/jpa_core.properties index cf03e9e32c..be5a0dc0f6 100644 --- a/jpa/plugins/org.eclipse.jpt.core/property_files/jpa_core.properties +++ b/jpa/plugins/org.eclipse.jpt.core/property_files/jpa_core.properties @@ -45,3 +45,4 @@ REGISTRY_FAILED_INSTANTIATION=Unable to instantiate the class ''{0}'' for the ex UPDATE_JOB_NAME=Update JPA project: ''{0}'' SYNCHRONIZE_METAMODEL_JOB_NAME=Synchronize JPA Canonical Metamodel: ''{0}'' PLATFORM_ID_DOES_NOT_EXIST=No JPA platform exists for the id: ''{0}''. The JPA project was not created for project ''{1}''. Ensure that the platform''s plugin has been added to this Eclipse installation. +DALI_EVENT_HANDLER_THREAD_NAME=Dali Event Handler diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java new file mode 100644 index 0000000000..913b48d54d --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java @@ -0,0 +1,1076 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.core; + +import java.util.Vector; + +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.ElementChangedEvent; +import org.eclipse.jdt.core.IElementChangedListener; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jpt.core.internal.AsynchronousJpaProjectUpdater; +import org.eclipse.jpt.core.internal.JptCoreMessages; +import org.eclipse.jpt.core.internal.SimpleJpaProjectConfig; +import org.eclipse.jpt.core.internal.facet.JpaFacetInstallDataModelProperties; +import org.eclipse.jpt.core.internal.operations.JpaFileCreationDataModelProperties; +import org.eclipse.jpt.core.internal.operations.OrmFileCreationDataModelProvider; +import org.eclipse.jpt.core.internal.operations.PersistenceFileCreationDataModelProvider; +import org.eclipse.jpt.utility.internal.AsynchronousCommandExecutor; +import org.eclipse.jpt.utility.internal.BitTools; +import org.eclipse.jpt.utility.internal.CallbackStatefulCommandExecutor; +import org.eclipse.jpt.utility.internal.SimpleCommandExecutor; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.SynchronizedBoolean; +import org.eclipse.jpt.utility.internal.iterables.LiveCloneIterable; +import org.eclipse.jpt.utility.internal.model.AbstractModel; +import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory; +import org.eclipse.wst.common.frameworks.datamodel.IDataModel; +import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework; +import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent; +import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener; +import org.eclipse.wst.common.project.facet.core.events.IProjectFacetActionEvent; + +/** + * The JPA model maintains a list of all JPA projects in the workspace. + * It keeps the list (and the state of the JPA projects themselves) + * synchronized with the workspace by listening for various + * changes: + * We use an Eclipse {@link ILock lock} to synchronize access to the JPA + * projects when dealing with these events. In an effort to reduce deadlocks, + * the simple Resource and Java change events are dispatched to a background + * thread, allowing us to handle the events outside of the workspace lock held + * during resource and Java change notifications. + *

+ * Events that trigger either the adding or removing of a JPA project (e.g. + * {@link IFacetedProjectEvent.Type#POST_INSTALL}) are handled "synchronously" + * by allowing the background thread to handle any outstanding events before + * updating the list of JPA projects and returning execution to the event + * source. + *

+ * Various things that cause us to add or remove a JPA project:

+ */ +class GenericJpaModel + extends AbstractModel + implements JpaModel +{ + /** + * All the JPA projects in the workspace. + */ + private final Vector jpaProjects = new Vector(); + + /** + * Synchronize access to the JPA projects. + */ + /* private */ final ILock lock = this.getJobManager().newLock(); + + /** + * Determine how Resource and Java change events are + * handled (i.e. synchronously or asynchronously). + */ + private volatile CallbackStatefulCommandExecutor eventHandler = new AsynchronousCommandExecutor(JptCoreMessages.DALI_EVENT_HANDLER_THREAD_NAME); + + /** + * Listen for + */ + private final IResourceChangeListener resourceChangeListener = new ResourceChangeListener(); + + /** + * The types of resource change events that interest + * {@link #resourceChangeListener}. + */ + private static final int RESOURCE_CHANGE_EVENT_TYPES = + IResourceChangeEvent.PRE_DELETE | + IResourceChangeEvent.POST_CHANGE | + IResourceChangeEvent.POST_BUILD; + + /** + * Listen for the JPA facet being added to or removed from a "faceted" project. + */ + private final IFacetedProjectListener facetedProjectListener = new FacetedProjectListener(); + + /** + * Listen for Java changes (unless the Dali UI is active). + * @see #javaElementChangeListenerIsActive + */ + private final JavaElementChangeListener javaElementChangeListener = new JavaElementChangeListener(); + + + // ********** constructor ********** + + /** + * internal - called by the Dali plug-in + */ + GenericJpaModel() { + super(); + } + + + // ********** plug-in controlled life-cycle ********** + + /** + * internal - called by the Dali plug-in + */ + void start() { + try { + this.lock.acquire(); + this.start_(); + } finally { + this.lock.release(); + } + } + + private void start_() { + debug("*** JPA model START ***"); //$NON-NLS-1$ + try { + this.buildJpaProjects(); + this.eventHandler.start(); + this.getWorkspace().addResourceChangeListener(this.resourceChangeListener, RESOURCE_CHANGE_EVENT_TYPES); + FacetedProjectFramework.addListener(this.facetedProjectListener, IFacetedProjectEvent.Type.values()); + JavaCore.addElementChangedListener(this.javaElementChangeListener); + } catch (RuntimeException ex) { + JptCorePlugin.log(ex); + this.stop_(); + } + } + + private void buildJpaProjects() { + try { + this.buildJpaProjects_(); + } catch (CoreException ex) { + // our visitor does not throw any exceptions + throw new RuntimeException(ex); + } + } + + private void buildJpaProjects_() throws CoreException { + this.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE); + } + + /** + * internal - called by the Dali plug-in + */ + void stop() throws Exception { + try { + this.lock.acquire(); + this.stop_(); + } finally { + this.lock.release(); + } + } + + private void stop_() { + debug("*** JPA model STOP ***"); //$NON-NLS-1$ + JavaCore.removeElementChangedListener(this.javaElementChangeListener); + FacetedProjectFramework.removeListener(this.facetedProjectListener); + this.getWorkspace().removeResourceChangeListener(this.resourceChangeListener); + this.eventHandler.stop(); + this.clearJpaProjects(); + } + + private void clearJpaProjects() { + // clone to prevent concurrent modification exceptions + for (JpaProject jpaProject : this.getJpaProjects_()) { + this.removeJpaProject(jpaProject); + } + } + + + // ********** JpaModel implementation ********** + + public Iterable getJpaProjects() { + try { + this.lock.acquire(); + return this.getJpaProjects_(); + } finally { + this.lock.release(); + } + } + + private Iterable getJpaProjects_() { + return new LiveCloneIterable(this.jpaProjects); + } + + public int getJpaProjectsSize() { + return this.jpaProjects.size(); + } + + public JpaProject getJpaProject(IProject project) { + try { + this.lock.acquire(); + return this.getJpaProject_(project); + } finally { + this.lock.release(); + } + } + + private JpaProject getJpaProject_(IProject project) { + for (JpaProject jpaProject : this.jpaProjects) { + if (jpaProject.getProject().equals(project)) { + return jpaProject; + } + } + return null; + } + + public JpaFile getJpaFile(IFile file) { + JpaProject jpaProject = this.getJpaProject(file.getProject()); + return (jpaProject == null) ? null : jpaProject.getJpaFile(file); + } + + public void rebuildJpaProject(IProject project) { + try { + this.lock.acquire(); + this.rebuildJpaProject_(project); + } finally { + this.lock.release(); + } + } + + /** + * assumption: the JPA project holder exists + */ + private void rebuildJpaProject_(IProject project) { + this.removeJpaProject(this.getJpaProject_(project)); + this.addJpaProject(project); + } + + public boolean javaElementChangeListenerIsActive() { + return this.javaElementChangeListener.isActive(); + } + + public void setJavaElementChangeListenerIsActive(boolean javaElementChangeListenerIsActive) { + this.javaElementChangeListener.setActive(javaElementChangeListenerIsActive); + } + + public IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } + + public IJobManager getJobManager() { + return Job.getJobManager(); + } + + + // ********** adding/removing JPA projects ********** + + /* private */ void addJpaProject(IProject project) { + this.addJpaProject(this.buildJpaProject(project)); + } + + private void addJpaProject(JpaProject jpaProject) { + // figure out exactly when JPA projects are added + dumpStackTrace("add: ", jpaProject); //$NON-NLS-1$ + // the jpa project can be null if we have problems getting the jpa platform + if (jpaProject != null) { + this.addItemToCollection(jpaProject, this.jpaProjects, JPA_PROJECTS_COLLECTION); + } + } + + private JpaProject buildJpaProject(IProject project) { + return this.buildJpaProject(this.buildJpaProjectConfig(project)); + } + + private JpaProject buildJpaProject(JpaProject.Config config) { + JpaPlatform jpaPlatform = config.getJpaPlatform(); + if (jpaPlatform == null) { + return null; + } + JpaProject result = jpaPlatform.getJpaFactory().buildJpaProject(config); + result.setUpdater(new AsynchronousJpaProjectUpdater(result)); + return result; + } + + private JpaProject.Config buildJpaProjectConfig(IProject project) { + SimpleJpaProjectConfig config = new SimpleJpaProjectConfig(); + config.setProject(project); + config.setJpaPlatform(JptCorePlugin.getJpaPlatform(project)); + config.setConnectionProfileName(JptCorePlugin.getConnectionProfileName(project)); + config.setUserOverrideDefaultCatalog(JptCorePlugin.getUserOverrideDefaultCatalog(project)); + config.setUserOverrideDefaultSchema(JptCorePlugin.getUserOverrideDefaultSchema(project)); + config.setDiscoverAnnotatedClasses(JptCorePlugin.discoverAnnotatedClasses(project)); + config.setMetamodelSourceFolderName(JptCorePlugin.getMetamodelSourceFolderName(project)); + return config; + } + + private void removeJpaProject(JpaProject jpaProject) { + // figure out exactly when JPA projects are removed + dumpStackTrace("remove: ", jpaProject); //$NON-NLS-1$ + this.removeItemFromCollection(jpaProject, this.jpaProjects, JPA_PROJECTS_COLLECTION); + jpaProject.dispose(); + } + + + // ********** Project POST_CHANGE ********** + + /** + * Forward the specified resource delta to all our JPA projects; + * they will each determine whether the event is significant. + */ + /* private */ void projectChanged(IResourceDelta delta) { + this.eventHandler.execute(this.buildProjectChangedCommand(delta)); + } + + private Command buildProjectChangedCommand(final IResourceDelta delta) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.projectChanged_(delta); + } + }; + } + + /* private */ void projectChanged_(IResourceDelta delta) { + for (JpaProject jpaProject : this.jpaProjects) { + jpaProject.projectChanged(delta); + } + } + + + // ********** Project POST_BUILD (CLEAN_BUILD) ********** + + /* private */ void projectPostCleanBuild(IProject project) { + this.waitForExecution(this.buildProjectPostCleanBuildCommand(project)); + } + + private Command buildProjectPostCleanBuildCommand(final IProject project) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.projectPostCleanBuild_(project); + } + }; + } + + /* private */ void projectPostCleanBuild_(IProject project) { + JpaProject jpaProject = this.getJpaProject_(project); + if (jpaProject != null) { + this.removeJpaProject(jpaProject); + this.addJpaProject(project); + } + } + + + // ********** Project PRE_DELETE ********** + + /** + * A project is being deleted. Remove its corresponding + * JPA project if appropriate. + */ + /* private */ void projectPreDelete(IProject project) { + this.waitForExecution(this.buildProjectPreDeleteCommand(project)); + } + + private Command buildProjectPreDeleteCommand(final IProject project) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.projectPreDelete_(project); + } + }; + } + + /* private */ void projectPreDelete_(IProject project) { + JpaProject jpaProject = this.getJpaProject(project); + if (jpaProject != null) { + this.removeJpaProject(jpaProject); + } + } + + + // ********** Resource and/or Facet events ********** + + /** + * Check whether the JPA facet has been added or removed. + */ + /* private */ void checkForJpaFacetTransition(IProject project) { + this.waitForExecution(this.buildCheckForJpaFacetTransitionCommand(project)); + } + + private Command buildCheckForJpaFacetTransitionCommand(final IProject project) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.checkForJpaFacetTransition_(project); + } + }; + } + + /* private */ void checkForJpaFacetTransition_(IProject project) { + JpaProject jpaProject = this.getJpaProject_(project); + + if (JptCorePlugin.projectHasJpaFacet(project)) { + if (jpaProject == null) { // JPA facet added + this.addJpaProject(project); + } + } else { + if (jpaProject != null) { // JPA facet removed + this.removeJpaProject(jpaProject); + } + } + } + + + // ********** FacetedProject POST_INSTALL ********** + + /* private */ void jpaFacetedProjectPostInstall(IProjectFacetActionEvent event) { + this.waitForExecution(this.buildJpaFacetedProjectPostInstallCommand(event)); + } + + private Command buildJpaFacetedProjectPostInstallCommand(final IProjectFacetActionEvent event) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.jpaFacetedProjectPostInstall_(event); + } + }; + } + + /* private */ void jpaFacetedProjectPostInstall_(IProjectFacetActionEvent event) { + IProject project = event.getProject().getProject(); + IDataModel dataModel = (IDataModel) event.getActionConfig(); + + // assume(?) this is the first event to indicate we need to add the JPA project to the JPA model + this.addJpaProject(project); + + boolean buildOrmXml = dataModel.getBooleanProperty(JpaFacetInstallDataModelProperties.CREATE_ORM_XML); + this.createProjectXml(project, buildOrmXml); + } + + private void createProjectXml(IProject project, boolean buildOrmXml) { + this.createPersistenceXml(project); + + if (buildOrmXml) { + this.createOrmXml(project); + } + } + + private void createPersistenceXml(IProject project) { + IDataModel config = DataModelFactory.createDataModel(new PersistenceFileCreationDataModelProvider()); + config.setProperty(JpaFileCreationDataModelProperties.PROJECT_NAME, project.getName()); + // default values for all other properties should suffice + try { + config.getDefaultOperation().execute(null, null); + } catch (ExecutionException ex) { + JptCorePlugin.log(ex); + } + } + + private void createOrmXml(IProject project) { + IDataModel config = DataModelFactory.createDataModel(new OrmFileCreationDataModelProvider()); + config.setProperty(JpaFileCreationDataModelProperties.PROJECT_NAME, project.getName()); + // default values for all other properties should suffice + try { + config.getDefaultOperation().execute(null, null); + } catch (ExecutionException ex) { + JptCorePlugin.log(ex); + } + } + + + // ********** FacetedProject PRE_UNINSTALL ********** + + /* private */ void jpaFacetedProjectPreUninstall(IProjectFacetActionEvent event) { + IProject project = event.getProject().getProject(); + this.waitForExecution(this.buildJpaFacetedProjectPreUninstallCommand(project)); + } + + private Command buildJpaFacetedProjectPreUninstallCommand(final IProject project) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.jpaFacetedProjectPreUninstall_(project); + } + }; + } + + /* private */ void jpaFacetedProjectPreUninstall_(IProject project) { + // assume(?) this is the first event to indicate we need to remove the JPA project to the JPA model + this.removeJpaProject(this.getJpaProject_(project)); + } + + + // ********** Java element changed ********** + + /** + * Forward the Java element changed event to all the JPA projects + * because the event could affect multiple projects. + */ + /* private */ void javaElementChanged(ElementChangedEvent event) { + this.eventHandler.execute(this.buildJavaElementChangedCommand(event)); + } + + private Command buildJavaElementChangedCommand(final ElementChangedEvent event) { + return new Command() { + @Override + void execute_() { + GenericJpaModel.this.javaElementChanged_(event); + } + }; + } + + /* private */ void javaElementChanged_(ElementChangedEvent event) { + for (JpaProject jpaProject : this.jpaProjects) { + jpaProject.javaElementChanged(event); + } + } + + + // ********** miscellaneous ********** + + @Override + public void toString(StringBuilder sb) { + sb.append(this.jpaProjects); + } + + + // ********** event handler ********** + + private void waitForExecution(Command command) { + SynchronizedBoolean flag = new SynchronizedBoolean(false); + org.eclipse.jpt.utility.Command markerCommand = this.buildMarkerCommand(); + EventHandlerListener listener = new EventHandlerListener(markerCommand, flag); + this.eventHandler.addListener(listener); + this.eventHandler.execute(markerCommand); + try { + flag.waitUntilTrue(); + } catch (InterruptedException ex) { + // ignore - not sure why this thread would be interrupted + } + this.eventHandler.removeListener(listener); + command.execute(); + } + + private org.eclipse.jpt.utility.Command buildMarkerCommand() { + return new org.eclipse.jpt.utility.Command() { + public void execute() { + // do nothing + } + }; + } + + private static class EventHandlerListener implements CallbackStatefulCommandExecutor.Listener { + private final org.eclipse.jpt.utility.Command command; + private final SynchronizedBoolean flag; + + EventHandlerListener(org.eclipse.jpt.utility.Command command, SynchronizedBoolean flag) { + super(); + this.command = command; + this.flag = flag; + } + + public void commandExecuted(org.eclipse.jpt.utility.Command c) { + if (c == this.command) { + this.flag.setTrue(); + } + } + } + + public void handleEventsSynchronously() { + try { + this.lock.acquire(); + this.handleEventsSynchronously_(); + } finally { + this.lock.release(); + } + } + + private void handleEventsSynchronously_() { + this.eventHandler.stop(); + this.eventHandler = new SimpleCommandExecutor(); + this.eventHandler.start(); + } + + + // ********** resource proxy visitor ********** + + /** + * Visit the workspace resource tree, adding a JPA project to the + * JPA model for each open Eclipse project that has a JPA facet. + */ + private class ResourceProxyVisitor implements IResourceProxyVisitor { + ResourceProxyVisitor() { + super(); + } + + public boolean visit(IResourceProxy resourceProxy) { + switch (resourceProxy.getType()) { + case IResource.ROOT : + return true; // all projects are in the "root" + case IResource.PROJECT : + this.checkProject(resourceProxy); + return false; // no nested projects + case IResource.FOLDER : + return false; // ignore + case IResource.FILE : + return false; // ignore + default : + return false; + } + } + + private void checkProject(IResourceProxy resourceProxy) { + if (resourceProxy.isAccessible()) { // the project exists and is open + IProject project = (IProject) resourceProxy.requestResource(); + if (JptCorePlugin.projectHasJpaFacet(project)) { + GenericJpaModel.this.addJpaProject(project); + } + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + } + + + // ********** command ********** + + /** + * Abstract class that holds the JPA model lock while + * executing. + */ + private abstract class Command + implements org.eclipse.jpt.utility.Command + { + Command() { + super(); + } + + public final void execute() { + try { + GenericJpaModel.this.lock.acquire(); + this.execute_(); + } finally { + GenericJpaModel.this.lock.release(); + } + } + + abstract void execute_(); + + } + + + // ********** resource change listener ********** + + private class ResourceChangeListener implements IResourceChangeListener { + + ResourceChangeListener() { + super(); + } + + /** + * Check for:
    + *
  • project close/delete + *
  • file add/remove + *
  • project open/rename + *
+ */ + public void resourceChanged(IResourceChangeEvent event) { + switch (event.getType()) { + case IResourceChangeEvent.POST_CHANGE : + this.resourcePostChange(event); + break; + + // workspace or project events + case IResourceChangeEvent.PRE_REFRESH : + break; // ignore + case IResourceChangeEvent.PRE_BUILD : + break; // ignore + case IResourceChangeEvent.POST_BUILD : + this.resourcePostBuild(event); + break; + + // project-only events + case IResourceChangeEvent.PRE_CLOSE : + break; // ignore + case IResourceChangeEvent.PRE_DELETE : + this.resourcePreDelete(event); + break; + default : + break; + } + } + + /** + * A resource has changed somehow. + * Check for files being added or removed. + * (The JPA project only handles added and removed files here + * File changes are handled via the Java Element Changed event.) + * Also check for opened projects. + */ + private void resourcePostChange(IResourceChangeEvent event) { + debug("Resource POST_CHANGE"); //$NON-NLS-1$ + this.resourceChanged(event.getDelta()); + } + + private void resourceChanged(IResourceDelta delta) { + IResource resource = delta.getResource(); + switch (resource.getType()) { + case IResource.ROOT : + this.resourceChangedChildren(delta); + break; + case IResource.PROJECT : + this.projectChanged((IProject) resource, delta); + break; + case IResource.FOLDER : + break; // ignore + case IResource.FILE : + break; // ignore + default : + break; + } + } + + private void resourceChangedChildren(IResourceDelta delta) { + for (IResourceDelta child : delta.getAffectedChildren()) { + this.resourceChanged(child); // recurse + } + } + + private void projectChanged(IProject project, IResourceDelta delta) { + GenericJpaModel.this.projectChanged(delta); + this.checkForOpenedProject(project, delta); + } + + + /** + * Crawl the specified delta, looking for projects being opened. + * Projects being deleted are handled in {@link IResourceChangeEvent#PRE_DELETE}. + * Projects being closed are handled in {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}. + */ + private void checkForOpenedProject(IProject project, IResourceDelta delta) { + switch (delta.getKind()) { + case IResourceDelta.ADDED : // all but project import and rename handled with the facet POST_INSTALL event + this.checkDeltaFlagsForOpenedProject(project, delta); + this.checkDeltaFlagsForRenamedProject(project, delta); + break; + case IResourceDelta.REMOVED : // already handled with the PRE_DELETE event + break; + case IResourceDelta.CHANGED : + this.checkDeltaFlagsForOpenedProject(project, delta); + break; + case IResourceDelta.ADDED_PHANTOM : + break; // ignore + case IResourceDelta.REMOVED_PHANTOM : + break; // ignore + default : + break; + } + } + + /** + * We don't get any events from the Facets Framework when a pre-existing + * project is imported, so we need to check for the newly imported project here. + *

+ * This event also occurs when a project is simply opened. Project opening + * also triggers a {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED} event + * and that is where we add the JPA project, not here. + */ + private void checkDeltaFlagsForOpenedProject(IProject project, IResourceDelta delta) { + if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.OPEN) && project.isOpen()) { + debug("\tProject CHANGED - OPEN: ", project.getName()); //$NON-NLS-1$ + GenericJpaModel.this.checkForJpaFacetTransition(project); + } + } + + /** + * We don't get any events from the Facets Framework when a project is renamed, + * so we need to check for the renamed projects here. + */ + private void checkDeltaFlagsForRenamedProject(IProject project, IResourceDelta delta) { + if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.MOVED_FROM) && project.isOpen()) { + debug("\tProject ADDED - MOVED_FROM: ", delta.getMovedFromPath()); //$NON-NLS-1$ + GenericJpaModel.this.checkForJpaFacetTransition(project); + } + } + + /** + * A post build event has occurred. + * Check for whether the build was a "clean" build and trigger project update. + */ + private void resourcePostBuild(IResourceChangeEvent event) { + debug("Resource POST_BUILD: ", event.getResource()); //$NON-NLS-1$ + if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) { + this.resourcePostCleanBuild(event.getDelta()); + } + } + + private void resourcePostCleanBuild(IResourceDelta delta) { + IResource resource = delta.getResource(); + switch (resource.getType()) { + case IResource.ROOT : + this.resourcePostCleanBuildChildren(delta); + break; + case IResource.PROJECT : + this.projectPostCleanBuild((IProject) resource); + break; + case IResource.FOLDER : + break; // ignore + case IResource.FILE : + break; // ignore + default : + break; + } + } + + private void resourcePostCleanBuildChildren(IResourceDelta delta) { + for (IResourceDelta child : delta.getAffectedChildren()) { + this.resourcePostCleanBuild(child); // recurse + } + } + + private void projectPostCleanBuild(IProject project) { + debug("\tProject CLEAN: ", project.getName()); //$NON-NLS-1$ + GenericJpaModel.this.projectPostCleanBuild(project); + } + + /** + * A project is being deleted. Remove its corresponding + * JPA project if appropriate. + */ + private void resourcePreDelete(IResourceChangeEvent event) { + IProject project = (IProject) event.getResource(); + debug("Resource (Project) PRE_DELETE: ", project); //$NON-NLS-1$ + GenericJpaModel.this.projectPreDelete(project); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + } + + + // ********** faceted project listener ********** + + /** + * Forward the Faceted project change event back to the JPA model. + */ + private class FacetedProjectListener implements IFacetedProjectListener { + + FacetedProjectListener() { + super(); + } + + /** + * Check for:

    + *
  • install of JPA facet + *
  • un-install of JPA facet + *
  • any other appearance or disappearance of the JPA facet + *
+ */ + public void handleEvent(IFacetedProjectEvent event) { + switch (event.getType()) { + case POST_INSTALL : + this.facetedProjectPostInstall((IProjectFacetActionEvent) event); + break; + case PRE_UNINSTALL : + this.facetedProjectPreUninstall((IProjectFacetActionEvent) event); + break; + case PROJECT_MODIFIED : + this.facetedProjectModified(event.getProject().getProject()); + break; + default : + break; + } + } + + private void facetedProjectPostInstall(IProjectFacetActionEvent event) { + debug("Facet POST_INSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ + if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) { + GenericJpaModel.this.jpaFacetedProjectPostInstall(event); + } + } + + private void facetedProjectPreUninstall(IProjectFacetActionEvent event) { + debug("Facet PRE_UNINSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ + if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) { + GenericJpaModel.this.jpaFacetedProjectPreUninstall(event); + } + } + + /** + * This event is triggered for any change to a faceted project. + * We use the event to watch for the following:
    + *
  • an open project is closed + *
  • a closed project is opened + *
  • one of a project's (facet) metadata files is edited directly + *
+ */ + private void facetedProjectModified(IProject project) { + debug("Facet PROJECT_MODIFIED: ", project.getName()); //$NON-NLS-1$ + GenericJpaModel.this.checkForJpaFacetTransition(project); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + } + + + // ********** Java element change listener ********** + + /** + * Forward the Java element change event back to the JPA model. + */ + private class JavaElementChangeListener implements IElementChangedListener { + /** + * A flag to activate/deactivate the listener + * so we can ignore Java events whenever Dali is manipulating the Java + * source code via the Dali model. We do this because the 0.5 sec delay + * between the Java source being changed and the corresponding event + * being fired causes us no end of pain. + */ + private volatile boolean active = true; + + JavaElementChangeListener() { + super(); + } + + public void elementChanged(ElementChangedEvent event) { + if (this.active) { + GenericJpaModel.this.javaElementChanged(event); + } + } + + void setActive(boolean active) { + this.active = active; + } + + boolean isActive() { + return this.active; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + + } + + + // ********** DEBUG ********** + + // @see JpaModelTests#testDEBUG() + private static final boolean DEBUG = false; + + /** + * trigger #toString() call and string concatenation only if DEBUG is true + */ + /* private */ static void debug(String message, Object object) { + if (DEBUG) { + debug_(message + object); + } + } + + /* private */ static void debug(String message) { + if (DEBUG) { + debug_(message); + } + } + + private static void debug_(String message) { + System.out.println(Thread.currentThread().getName() + ": " + message); //$NON-NLS-1$ + } + + /* private */ static void dumpStackTrace() { + dumpStackTrace(null); + } + + /* private */ static void dumpStackTrace(String message, Object object) { + if (DEBUG) { + dumpStackTrace_(message + object); + } + } + + /* private */ static void dumpStackTrace(String message) { + if (DEBUG) { + dumpStackTrace_(message); + } + } + + private static void dumpStackTrace_(String message) { + // lock System.out so the stack elements are printed out contiguously + synchronized (System.out) { + if (message != null) { + debug_(message); + } + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + // skip the first 3 elements - those are this method and 2 methods in Thread + for (int i = 3; i < stackTrace.length; i++) { + StackTraceElement element = stackTrace[i]; + if (element.getMethodName().equals("invoke0")) { //$NON-NLS-1$ + break; // skip all elements outside of the JUnit test + } + System.out.println("\t" + element); //$NON-NLS-1$ + } + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaFactory.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaFactory.java index 63133feab0..ed3693e965 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaFactory.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaFactory.java @@ -10,7 +10,6 @@ package org.eclipse.jpt.core; import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jpt.core.context.AssociationOverride; import org.eclipse.jpt.core.context.AssociationOverrideContainer; @@ -68,8 +67,6 @@ import org.eclipse.jpt.core.context.java.JavaTransientMapping; import org.eclipse.jpt.core.context.java.JavaTypeMapping; import org.eclipse.jpt.core.context.java.JavaUniqueConstraint; import org.eclipse.jpt.core.context.java.JavaVersionMapping; -import org.eclipse.jpt.core.context.orm.EntityMappings; -import org.eclipse.jpt.core.context.persistence.PersistenceUnit; import org.eclipse.jpt.core.resource.java.AssociationOverrideAnnotation; import org.eclipse.jpt.core.resource.java.JavaResourcePersistentAttribute; import org.eclipse.jpt.core.resource.java.JavaResourcePersistentMember; @@ -111,7 +108,7 @@ public interface JpaFactory * added to the specified JPA project. Return null if unable to create * the JPA file (e.g. the content type is unrecognized). */ - JpaProject buildJpaProject(JpaProject.Config config) throws CoreException; + JpaProject buildJpaProject(JpaProject.Config config); JpaDataSource buildJpaDataSource(JpaProject jpaProject, String connectionProfileName); diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaModel.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaModel.java index 97d817d02c..09518e5a70 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaModel.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaModel.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2008 Oracle. All rights reserved. + * Copyright (c) 2006, 2009 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. @@ -9,15 +9,13 @@ ******************************************************************************/ package org.eclipse.jpt.core; -import java.util.Iterator; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; import org.eclipse.jpt.utility.model.Model; /** - * The JPA model holds all the JPA projects. - * + * The JPA model holds all the JPA projects in the workspace. + *

* Provisional API: This interface is part of an interim API that is still * under development and expected to change significantly before reaching * stability. It is available at this early stage to solicit feedback from @@ -26,35 +24,46 @@ import org.eclipse.jpt.utility.model.Model; */ public interface JpaModel extends Model { + /** + * Return the JPA model's JPA projects. + */ + Iterable getJpaProjects(); + public static final String JPA_PROJECTS_COLLECTION = "jpaProjects"; //$NON-NLS-1$ + + /** + * Return the size of the JPA model's list of JPA projects. + */ + int getJpaProjectsSize(); + /** * Return the JPA project corresponding to the specified Eclipse project. * Return null if unable to associate the specified Eclipse project * with a JPA project. */ - JpaProject getJpaProject(IProject project) throws CoreException; + JpaProject getJpaProject(IProject project); /** - * Return whether the JPA model contains a JPA project corresponding - * to the specified Eclipse project. + * Return the JPA file corresponding to the specified Eclipse file, + * or null if unable to associate the specified file with a JPA file. */ - boolean containsJpaProject(IProject project); + JpaFile getJpaFile(IFile file); /** - * Return the JPA model's JPA projects. This has performance implications, - * it will build all the JPA projects. + * The JPA settings associated with the specified Eclipse project + * have changed in such a way as to require the associated + * JPA project to be completely rebuilt + * (e.g. when the user changes a project's JPA platform). */ - Iterator jpaProjects() throws CoreException; - public static final String JPA_PROJECTS_COLLECTION = "jpaProjects"; //$NON-NLS-1$ + void rebuildJpaProject(IProject project); /** - * Return the size of the JPA model's list of JPA projects. + * Return whether the model's Java change listener is active. */ - int jpaProjectsSize(); - + boolean javaElementChangeListenerIsActive(); + /** - * Return the JPA file corresponding to the specified Eclipse file, - * or null if unable to associate the specified file with a JPA file. + * Set whether the model's Java change listener is active. */ - JpaFile getJpaFile(IFile file) throws CoreException; + void setJavaElementChangeListenerIsActive(boolean javaElementChangeListenerIsActive); } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaProject.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaProject.java index 55a7ca4bf5..4f63da8621 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaProject.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JpaProject.java @@ -10,10 +10,10 @@ package org.eclipse.jpt.core; import java.util.Iterator; + import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.ElementChangedEvent; @@ -202,7 +202,7 @@ public interface JpaProject * Synchronize the JPA project with the specified project resource * delta, watching for added and removed files in particular. */ - void projectChanged(IResourceDelta delta) throws CoreException; + void projectChanged(IResourceDelta delta); /** * Synchronize the JPA project with the specified Java change. diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JptCorePlugin.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JptCorePlugin.java index cc92e43820..98260f81f8 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JptCorePlugin.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/JptCorePlugin.java @@ -10,6 +10,7 @@ package org.eclipse.jpt.core; import javax.xml.parsers.SAXParserFactory; + import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; @@ -28,10 +29,8 @@ import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jpt.core.internal.GenericJpaPlatformProvider; -import org.eclipse.jpt.core.internal.JpaModelManager; import org.eclipse.jpt.core.internal.JpaPlatformRegistry; import org.eclipse.jpt.core.internal.jpa2.Generic2_0JpaPlatformProvider; -import org.eclipse.jpt.core.internal.prefs.JpaPreferenceInitializer; import org.eclipse.jpt.utility.internal.StringTools; import org.eclipse.jst.j2ee.internal.J2EEConstants; import org.eclipse.wst.common.componentcore.ComponentCore; @@ -44,20 +43,21 @@ import org.osgi.service.prefs.Preferences; import org.osgi.util.tracker.ServiceTracker; /** - * The JPT plug-in lifecycle implementation. + * The Dali core plug-in lifecycle implementation. * A number of globally-available constants and methods. - * + *

* Provisional API: This class is part of an interim API that is still * under development and expected to change significantly before reaching * stability. It is available at this early stage to solicit feedback from * pioneering adopters on the understanding that any code that uses this API * will almost certainly be broken (repeatedly) as the API evolves. */ -// TODO keep preferences in synch with the JPA project -// (connection profile name, "discover" flag) -// use listeners? public class JptCorePlugin extends Plugin { + private volatile GenericJpaModel jpaModel; + private volatile ServiceTracker parserTracker; + + // ********** public constants ********** /** @@ -145,18 +145,18 @@ public class JptCorePlugin extends Plugin { public static final JpaResourceType JAVA_SOURCE_RESOURCE_TYPE = new JpaResourceType(JAVA_SOURCE_CONTENT_TYPE); /** - * The content type for persistence.xml files. + * The content type for persistence.xml files. */ public static final IContentType PERSISTENCE_XML_CONTENT_TYPE = getJpaContentType("persistence"); //$NON-NLS-1$ /** - * The resource type for persistence.xml version 1.0 files + * The resource type for persistence.xml version 1.0 files */ public static final JpaResourceType PERSISTENCE_XML_1_0_RESOURCE_TYPE = new JpaResourceType(PERSISTENCE_XML_CONTENT_TYPE, org.eclipse.jpt.core.resource.persistence.JPA.SCHEMA_VERSION); /** - * The resource type for persistence.xml version 2.0 files + * The resource type for persistence.xml version 2.0 files */ public static final JpaResourceType PERSISTENCE_XML_2_0_RESOURCE_TYPE = new JpaResourceType(PERSISTENCE_XML_CONTENT_TYPE, org.eclipse.jpt.core.resource.persistence.v2_0.JPA2_0.SCHEMA_VERSION); @@ -167,18 +167,18 @@ public class JptCorePlugin extends Plugin { public static final IContentType MAPPING_FILE_CONTENT_TYPE = getJpaContentType("mappingFile"); //$NON-NLS-1$ /** - * The content type for orm.xml mapping files. + * The content type for orm.xml mapping files. */ public static final IContentType ORM_XML_CONTENT_TYPE = getJpaContentType("orm"); //$NON-NLS-1$ /** - * The resource type for orm.xml version 1.0 mapping files + * The resource type for orm.xml version 1.0 mapping files */ public static final JpaResourceType ORM_XML_1_0_RESOURCE_TYPE = new JpaResourceType(ORM_XML_CONTENT_TYPE, org.eclipse.jpt.core.resource.orm.JPA.SCHEMA_VERSION); /** - * The resource type for orm.xml version 2.0 mapping files + * The resource type for orm.xml version 2.0 mapping files */ public static final JpaResourceType ORM_XML_2_0_RESOURCE_TYPE = new JpaResourceType(ORM_XML_CONTENT_TYPE, org.eclipse.jpt.core.resource.orm.v2_0.JPA2_0.SCHEMA_VERSION); @@ -189,7 +189,7 @@ public class JptCorePlugin extends Plugin { public static final IContentType JAR_CONTENT_TYPE = getJpaContentType("jar"); //$NON-NLS-1$ /** - * The resource type for Java archives (JARs) + * The resource type for Java archives (JARs). */ public static final JpaResourceType JAR_RESOURCE_TYPE = new JpaResourceType(JAR_CONTENT_TYPE); @@ -220,14 +220,12 @@ public class JptCorePlugin extends Plugin { } - private ServiceTracker parserTracker; - // ********** singleton ********** private static JptCorePlugin INSTANCE; /** - * Return the singleton JPT plug-in. + * Return the singleton Dali core plug-in. */ public static JptCorePlugin instance() { return INSTANCE; @@ -240,34 +238,34 @@ public class JptCorePlugin extends Plugin { * Return the singular JPA model corresponding to the current workspace. */ public static JpaModel getJpaModel() { - return JpaModelManager.instance().getJpaModel(); + return INSTANCE.getJpaModel_(); } /** * Return the JPA project corresponding to the specified Eclipse project, - * or null if unable to associate the specified project with a + * or null if unable to associate the specified project with a * JPA project. */ public static JpaProject getJpaProject(IProject project) { - try { - return JpaModelManager.instance().getJpaProject(project); - } catch (CoreException ex) { - log(ex); - return null; - } + return getJpaModel().getJpaProject(project); } /** * Return the JPA file corresponding to the specified Eclipse file, - * or null if unable to associate the specified file with a JPA file. + * or null if unable to associate the specified file with a JPA file. */ public static JpaFile getJpaFile(IFile file) { - try { - return JpaModelManager.instance().getJpaFile(file); - } catch (CoreException ex) { - log(ex); - return null; - } + return getJpaModel().getJpaFile(file); + } + + /** + * The JPA settings associated with the specified Eclipse project + * have changed in such a way as to require the associated + * JPA project to be completely rebuilt + * (e.g. when the user changes a project's JPA platform). + */ + public static void rebuildJpaProject(IProject project) { + getJpaModel().rebuildJpaProject(project); } /** @@ -278,7 +276,7 @@ public class JptCorePlugin extends Plugin { } /** - * Return whether the specified Eclipse project has a JPA facet. + * Return whether the specified Eclipse project has a Web facet. */ public static boolean projectHasWebFacet(IProject project) { return projectHasFacet(project, WEB_PROJECT_FACET_ID); @@ -297,7 +295,7 @@ public class JptCorePlugin extends Plugin { } /** - * Return the persistence.xml (specified as "META-INF/persistence.xml") + * Return the persistence.xml (specified as "META-INF/persistence.xml") * deployment URI for the specified project. */ public static String getPersistenceXmlDeploymentURI(IProject project) { @@ -305,7 +303,7 @@ public class JptCorePlugin extends Plugin { } /** - * Return the default mapping file (specified as "META-INF/orm.xml") + * Return the default mapping file (specified as "META-INF/orm.xml") * deployment URI for the specified project. */ public static String getDefaultOrmXmlDeploymentURI(IProject project) { @@ -313,7 +311,7 @@ public class JptCorePlugin extends Plugin { } /** - * Return the mapping file (specified as "META-INF/") + * Return the mapping file (specified as {@code"META-INF/"}) * deployment URI for the specified project. */ public static String getOrmXmlDeploymentURI(IProject project, String mappingFileName) { @@ -322,7 +320,7 @@ public class JptCorePlugin extends Plugin { /** * Tweak the specified deployment URI if the specified project - * has a web facet. + * has a Web facet. */ public static String getDeploymentURI(IProject project, String defaultURI) { return projectHasWebFacet(project) ? @@ -332,9 +330,9 @@ public class JptCorePlugin extends Plugin { } /** - * Return the deployment path to which jars are relatively specified for - * the given project - * (Web projects have a different deployment structure than non-web projects) + * Return the deployment path to which JARs are relatively specified for + * the given project. + * (Web projects have a different deployment structure than non-web projects.) */ public static IPath getJarDeploymentRootPath(IProject project) { return new Path(getJarDeploymentRootPathName(project)); @@ -415,12 +413,10 @@ public class JptCorePlugin extends Plugin { if (jpaFacetVersion.equals(JPA_FACET_VERSION_1_0)) { return GenericJpaPlatformProvider.ID; } - else if (jpaFacetVersion.equals(JPA_FACET_VERSION_2_0)) { + if (jpaFacetVersion.equals(JPA_FACET_VERSION_2_0)) { return Generic2_0JpaPlatformProvider.ID; } - else { - throw new IllegalArgumentException("Illegal JPA facet version: " + jpaFacetVersion); - } + throw new IllegalArgumentException("Illegal JPA facet version: " + jpaFacetVersion); //$NON-NLS-1$ } private static String getDefaultJpaPlatformId(Preferences... nodes) { @@ -601,6 +597,20 @@ public class JptCorePlugin extends Plugin { } } + /** + * Return whether the model manager's Java change listener is active. + */ + public static boolean javaElementChangeListenerIsActive() { + return getJpaModel().javaElementChangeListenerIsActive(); + } + + /** + * Set whether the model manager's Java change listener is active. + */ + public static void setJavaElementChangeListenerIsActive(boolean javaElementChangeListenerIsActive) { + getJpaModel().setJavaElementChangeListenerIsActive(javaElementChangeListenerIsActive); + } + /** * Log the specified status. */ @@ -638,13 +648,16 @@ public class JptCorePlugin extends Plugin { @Override public void start(BundleContext context) throws Exception { super.start(context); - JpaModelManager.instance().start(); + // nothing yet... } @Override public void stop(BundleContext context) throws Exception { try { - JpaModelManager.instance().stop(); + if (this.jpaModel != null) { + this.jpaModel.stop(); + this.jpaModel = null; + } if (this.parserTracker != null) { this.parserTracker.close(); this.parserTracker = null; @@ -654,6 +667,18 @@ public class JptCorePlugin extends Plugin { } } + private synchronized GenericJpaModel getJpaModel_() { + if (this.jpaModel == null) { + this.jpaModel = this.buildJpaModel(); + this.jpaModel.start(); + } + return this.jpaModel; + } + + private GenericJpaModel buildJpaModel() { + return new GenericJpaModel(); + } + public synchronized SAXParserFactory getSAXParserFactory() { SAXParserFactory factory = (SAXParserFactory) this.getParserTracker().getService(); if (factory != null) { diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaFactory.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaFactory.java index efacae6803..4ce3c05476 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaFactory.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaFactory.java @@ -10,7 +10,6 @@ package org.eclipse.jpt.core.internal; import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jpt.core.JpaDataSource; import org.eclipse.jpt.core.JpaFile; @@ -155,7 +154,7 @@ public abstract class AbstractJpaFactory // ********** Core Model ********** - public JpaProject buildJpaProject(JpaProject.Config config) throws CoreException { + public JpaProject buildJpaProject(JpaProject.Config config) { return new GenericJpaProject((JpaProject2_0.Config) config); } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaProject.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaProject.java index ca0ed219c7..0058ec7e0d 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaProject.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/AbstractJpaProject.java @@ -191,7 +191,7 @@ public abstract class AbstractJpaProject // ********** constructor/initialization ********** - protected AbstractJpaProject(JpaProject2_0.Config config) throws CoreException { + protected AbstractJpaProject(JpaProject2_0.Config config) { super(null); // JPA project is the root of the containment tree if ((config.getProject() == null) || (config.getJpaPlatform() == null)) { throw new NullPointerException(); @@ -207,7 +207,8 @@ public abstract class AbstractJpaProject this.resourceModelListener = this.buildResourceModelListener(); // build the JPA files corresponding to the Eclipse project's files - this.project.accept(this.buildInitialResourceProxyVisitor(), IResource.NONE); + InitialResourceProxyVisitor visitor = this.buildInitialResourceProxyVisitor(); + visitor.visitProject(this.project); this.externalJavaResourcePersistentTypeCache = this.buildExternalJavaResourcePersistentTypeCache(); @@ -243,7 +244,7 @@ public abstract class AbstractJpaProject return new DefaultResourceModelListener(); } - protected IResourceProxyVisitor buildInitialResourceProxyVisitor() { + protected InitialResourceProxyVisitor buildInitialResourceProxyVisitor() { return new InitialResourceProxyVisitor(); } @@ -286,8 +287,16 @@ public abstract class AbstractJpaProject protected InitialResourceProxyVisitor() { super(); } + protected void visitProject(IProject p) { + try { + p.accept(this, IResource.NONE); + } catch (CoreException ex) { + // we don't throw any CoreExceptions + throw new RuntimeException(ex); + } + } // add a JPA file for every [appropriate] file encountered by the visitor - public boolean visit(IResourceProxy resource) throws CoreException { + public boolean visit(IResourceProxy resource) { switch (resource.getType()) { case IResource.ROOT : // shouldn't happen return true; // visit children @@ -1227,7 +1236,7 @@ public abstract class AbstractJpaProject // ********** resource events ********** // TODO need to do the same thing for external projects and compilation units - public void projectChanged(IResourceDelta delta) throws CoreException { + public void projectChanged(IResourceDelta delta) { if (delta.getResource().equals(this.getProject())) { this.internalProjectChanged(delta); } else { @@ -1235,9 +1244,9 @@ public abstract class AbstractJpaProject } } - protected void internalProjectChanged(IResourceDelta delta) throws CoreException { + protected void internalProjectChanged(IResourceDelta delta) { ResourceDeltaVisitor resourceDeltaVisitor = this.buildInternalResourceDeltaVisitor(); - delta.accept(resourceDeltaVisitor); + resourceDeltaVisitor.visitDelta(delta); // at this point, if we have added and/or removed JpaFiles, an "update" will have been triggered; // any changes to the resource model during the "resolve" will trigger further "updates"; // there should be no need to "resolve" external Java types (they can't have references to @@ -1304,10 +1313,10 @@ public abstract class AbstractJpaProject } } - protected void externalProjectChanged(IResourceDelta delta) throws CoreException { + protected void externalProjectChanged(IResourceDelta delta) { if (this.getJavaProject().isOnClasspath(delta.getResource())) { ResourceDeltaVisitor resourceDeltaVisitor = this.buildExternalResourceDeltaVisitor(); - delta.accept(resourceDeltaVisitor); + resourceDeltaVisitor.visitDelta(delta); // force an "update" here since adding and/or removing an external Java type // will only trigger an "update" if the "resolve" causes something in the resource model to change if (resourceDeltaVisitor.encounteredSignificantChange()) { @@ -1396,7 +1405,16 @@ public abstract class AbstractJpaProject super(); } - public boolean visit(IResourceDelta delta) throws CoreException { + protected void visitDelta(IResourceDelta delta) { + try { + delta.accept(this); + } catch (CoreException ex) { + // we don't throw any CoreExceptions + throw new RuntimeException(ex); + } + } + + public boolean visit(IResourceDelta delta) { IResource res = delta.getResource(); switch (res.getType()) { case IResource.ROOT : diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/GenericJpaModel.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/GenericJpaModel.java deleted file mode 100644 index 9bdaceb461..0000000000 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/GenericJpaModel.java +++ /dev/null @@ -1,528 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2009 Oracle. All rights reserved. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0, which accompanies this distribution - * and is available at http://www.eclipse.org/legal/epl-v10.html. - * - * Contributors: - * Oracle - initial API and implementation - ******************************************************************************/ -package org.eclipse.jpt.core.internal; - -import java.util.ArrayList; -import java.util.Iterator; -import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceProxy; -import org.eclipse.core.resources.IResourceProxyVisitor; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.ElementChangedEvent; -import org.eclipse.jpt.core.JpaFile; -import org.eclipse.jpt.core.JpaModel; -import org.eclipse.jpt.core.JpaPlatform; -import org.eclipse.jpt.core.JpaProject; -import org.eclipse.jpt.core.JptCorePlugin; -import org.eclipse.jpt.core.JpaProject.Config; -import org.eclipse.jpt.core.internal.facet.JpaFacetInstallDataModelProperties; -import org.eclipse.jpt.core.internal.operations.OrmFileCreationDataModelProperties; -import org.eclipse.jpt.core.internal.operations.OrmFileCreationDataModelProvider; -import org.eclipse.jpt.core.internal.operations.PersistenceFileCreationDataModelProperties; -import org.eclipse.jpt.core.internal.operations.PersistenceFileCreationDataModelProvider; -import org.eclipse.jpt.utility.internal.ClassTools; -import org.eclipse.jpt.utility.internal.StringTools; -import org.eclipse.jpt.utility.internal.model.AbstractModel; -import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory; -import org.eclipse.wst.common.frameworks.datamodel.IDataModel; -import org.eclipse.wst.common.project.facet.core.events.IProjectFacetActionEvent; - -/** - * The JPA model is synchronized so all changes to the list of JPA projects - * are thread-safe. - * - * The JPA model holds on to a list of JPA project configs and only instantiates - * their associated JPA projects when necessary. Other than performance, - * this should be transparent to clients. - */ -public class GenericJpaModel - extends AbstractModel - implements JpaModel -{ - - /** maintain a list of all the current JPA projects */ - private final ArrayList jpaProjectHolders = new ArrayList(); - - - // ********** constructor ********** - - /** - * Construct a JPA model and populate it with JPA projects for all the - * current Eclipse projects with JPA facets. - * The JPA model can only be instantiated by the JPA model manager. - */ - GenericJpaModel() throws CoreException { - super(); - ResourcesPlugin.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE); - } - - - // ********** IJpaModel implementation ********** - - /** - * This will trigger the instantiation of the JPA project associated with the - * specified Eclipse project. - */ - public synchronized JpaProject getJpaProject(IProject project) throws CoreException { - return this.getJpaProjectHolder(project).jpaProject(); - } - - /** - * We can answer this question without instantiating the - * associated JPA project. - */ - public synchronized boolean containsJpaProject(IProject project) { - return this.getJpaProjectHolder(project).holdsJpaProjectFor(project); - } - - /** - * This will trigger the instantiation of all the JPA projects. - */ - public synchronized Iterator jpaProjects() throws CoreException { - // force the CoreException to occur here (instead of later, in Iterator#next()) - ArrayList jpaProjects = new ArrayList(this.jpaProjectHolders.size()); - for (JpaProjectHolder holder : this.jpaProjectHolders) { - jpaProjects.add(holder.jpaProject()); - } - return jpaProjects.iterator(); - } - - /** - * We can answer this question without instantiating any JPA projects. - */ - public synchronized int jpaProjectsSize() { - return this.jpaProjectHolders.size(); - } - - /** - * This will trigger the instantiation of the JPA project associated with the - * specified file. - */ - public synchronized JpaFile getJpaFile(IFile file) throws CoreException { - JpaProject jpaProject = this.getJpaProject(file.getProject()); - return (jpaProject == null) ? null : jpaProject.getJpaFile(file); - } - - - // ********** internal methods ********** - - /** - * never return null - */ - private JpaProjectHolder getJpaProjectHolder(IProject project) { - for (JpaProjectHolder holder : this.jpaProjectHolders) { - if (holder.holdsJpaProjectFor(project)) { - return holder; - } - } - return NullJpaProjectHolder.instance(); - } - - private JpaProject.Config buildJpaProjectConfig(IProject project) { - SimpleJpaProjectConfig config = new SimpleJpaProjectConfig(); - config.setProject(project); - config.setJpaPlatform(JptCorePlugin.getJpaPlatform(project)); - config.setConnectionProfileName(JptCorePlugin.getConnectionProfileName(project)); - config.setUserOverrideDefaultCatalog(JptCorePlugin.getUserOverrideDefaultCatalog(project)); - config.setUserOverrideDefaultSchema(JptCorePlugin.getUserOverrideDefaultSchema(project)); - config.setDiscoverAnnotatedClasses(JptCorePlugin.discoverAnnotatedClasses(project)); - config.setMetamodelSourceFolderName(JptCorePlugin.getMetamodelSourceFolderName(project)); - return config; - } - - /* private */ void addJpaProject(IProject project) { - this.addJpaProject(this.buildJpaProjectConfig(project)); - } - - /** - * Add a JPA project to the JPA model for the specified Eclipse project. - * JPA projects can only be added by the JPA model manager. - * The JPA project will only be instantiated later, on demand. - */ - private void addJpaProject(JpaProject.Config config) { - dumpStackTrace(); // figure out exactly when JPA projects are added - this.jpaProjectHolders.add(this.getJpaProjectHolder(config.getProject()).buildJpaProjectHolder(this, config)); - } - - /** - * Remove the JPA project corresponding to the specified Eclipse project - * from the JPA model. Return whether the removal actually happened. - * JPA projects can only be removed by the JPA model manager. - */ - private void removeJpaProject(IProject project) { - dumpStackTrace(); // figure out exactly when JPA projects are removed - this.getJpaProjectHolder(project).remove(); - } - - - // ********** Resource events ********** - - /** - * A project is being deleted. Remove its corresponding - * JPA project if appropriate. - */ - synchronized void projectPreDelete(IProject project) { - this.removeJpaProject(project); - } - - /** - * Forward the specified resource delta to all our JPA projects; - * they will each determine whether the event is significant. - */ - synchronized void projectChanged(IResourceDelta delta) throws CoreException { - for (JpaProjectHolder holder : this.jpaProjectHolders) { - holder.projectChanged(delta); - } - } - - - // ********** Resource and/or Facet events ********** - - /** - * Check whether the JPA facet has been added or removed. - */ - synchronized void checkForTransition(IProject project) { - boolean jpaFacet = JptCorePlugin.projectHasJpaFacet(project); - boolean jpaProject = this.containsJpaProject(project); - - if (jpaFacet) { - if ( ! jpaProject) { // JPA facet added - this.addJpaProject(project); - } - } else { - if (jpaProject) { // JPA facet removed - this.removeJpaProject(project); - } - } - } - - - // ********** Facet events ********** - - synchronized void jpaFacetedProjectPostInstall(IProjectFacetActionEvent event) { - IProject project = event.getProject().getProject(); - IDataModel dataModel = (IDataModel) event.getActionConfig(); - - boolean buildOrmXml = dataModel.getBooleanProperty(JpaFacetInstallDataModelProperties.CREATE_ORM_XML); - this.createProjectXml(project, buildOrmXml); - - // assume(?) this is the first event to indicate we need to add the JPA project to the JPA model - this.addJpaProject(project); - } - - private void createProjectXml(IProject project, boolean buildOrmXml) { - this.createPersistenceXml(project); - - if (buildOrmXml) { - this.createOrmXml(project); - } - } - - private void createPersistenceXml(IProject project) { - IDataModel config = - DataModelFactory.createDataModel(new PersistenceFileCreationDataModelProvider()); - config.setProperty(PersistenceFileCreationDataModelProperties.PROJECT_NAME, project.getName()); - // default values for all other properties should suffice - try { - config.getDefaultOperation().execute(null, null); - } - catch (ExecutionException e) { - JptCorePlugin.log(e); - } - } - - private void createOrmXml(IProject project) { - IDataModel config = - DataModelFactory.createDataModel(new OrmFileCreationDataModelProvider()); - config.setProperty(OrmFileCreationDataModelProperties.PROJECT_NAME, project.getName()); - // default values for all other properties should suffice - try { - config.getDefaultOperation().execute(null, null); - } - catch (ExecutionException e) { - JptCorePlugin.log(e); - } - } - - // TODO remove classpath items? persistence.xml? orm.xml? - synchronized void jpaFacetedProjectPreUninstall(IProjectFacetActionEvent event) { - // assume(?) this is the first event to indicate we need to remove the JPA project to the JPA model - this.removeJpaProject(event.getProject().getProject()); - } - - - // ********** Java events ********** - - /** - * Forward the Java element changed event to all the JPA projects - * because the event could affect multiple projects. - */ - synchronized void javaElementChanged(ElementChangedEvent event) { - for (JpaProjectHolder jpaProjectHolder : this.jpaProjectHolders) { - jpaProjectHolder.javaElementChanged(event); - } - } - - - // ********** miscellaneous ********** - - /** - * The JPA settings associated with the specified Eclipse project - * have changed in such a way as to require the associated - * JPA project to be completely rebuilt - * (e.g. when the user changes a project's JPA platform). - */ - synchronized void rebuildJpaProject(IProject project) { - this.removeJpaProject(project); - this.addJpaProject(project); - } - - /** - * Dispose the JPA model by disposing and removing all its JPA projects. - * The JPA model can only be disposed by the JPA model manager. - */ - synchronized void dispose() { - // clone the list to prevent concurrent modification exceptions - JpaProjectHolder[] holders = this.jpaProjectHolders.toArray(new JpaProjectHolder[this.jpaProjectHolders.size()]); - for (JpaProjectHolder holder : holders) { - holder.remove(); - } - } - - @Override - public void toString(StringBuilder sb) { - sb.append("JPA projects size: " + this.jpaProjectsSize()); //$NON-NLS-1$ - } - - - // ********** holder callbacks ********** - - /** - * called by the JPA project holder when the JPA project is actually - * instantiated - */ - /* private */ void jpaProjectBuilt(JpaProject jpaProject) { - this.fireItemAdded(JPA_PROJECTS_COLLECTION, jpaProject); - } - - /** - * called by the JPA project holder if the JPA project has been - * instantiated and we need to remove it - */ - /* private */ void jpaProjectRemoved(JpaProject jpaProject) { - this.fireItemRemoved(JPA_PROJECTS_COLLECTION, jpaProject); - } - - /** - * called by the JPA project holder - */ - /* private */ void removeJpaProjectHolder(JpaProjectHolder jpaProjectHolder) { - this.jpaProjectHolders.remove(jpaProjectHolder); - } - - - // ********** JPA project holders ********** - - private interface JpaProjectHolder { - - boolean holdsJpaProjectFor(IProject project); - - JpaProject jpaProject() throws CoreException; - - void projectChanged(IResourceDelta delta) throws CoreException; - - void javaElementChanged(ElementChangedEvent event); - - JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jpaModel, JpaProject.Config config); - - void remove(); - - } - - private static class NullJpaProjectHolder implements JpaProjectHolder { - private static final JpaProjectHolder INSTANCE = new NullJpaProjectHolder(); - - static JpaProjectHolder instance() { - return INSTANCE; - } - - // ensure single instance - private NullJpaProjectHolder() { - super(); - } - - public boolean holdsJpaProjectFor(IProject project) { - return false; - } - - public JpaProject jpaProject() throws CoreException { - return null; - } - - public void projectChanged(IResourceDelta delta) throws CoreException { - // do nothing - } - - public void javaElementChanged(ElementChangedEvent event) { - // do nothing - } - - public JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jpaModel, Config config) { - return new DefaultJpaProjectHolder(jpaModel, config); - } - - public void remove() { - // do nothing - } - - @Override - public String toString() { - return ClassTools.shortClassNameForObject(this); - } - } - - /** - * Pair a JPA project config with its lazily-initialized JPA project. - */ - private static class DefaultJpaProjectHolder implements JpaProjectHolder { - private final GenericJpaModel jpaModel; - private final JpaProject.Config config; - private JpaProject jpaProject; - - DefaultJpaProjectHolder(GenericJpaModel jpaModel, JpaProject.Config config) { - super(); - this.jpaModel = jpaModel; - this.config = config; - } - - public boolean holdsJpaProjectFor(IProject project) { - return this.config.getProject().equals(project); - } - - public JpaProject jpaProject() throws CoreException { - if (this.jpaProject == null) { - this.jpaProject = this.buildJpaProject(); - // notify listeners of the JPA model - this.jpaModel.jpaProjectBuilt(this.jpaProject); - } - return this.jpaProject; - } - - private JpaProject buildJpaProject() throws CoreException { - JpaPlatform jpaPlatform = this.config.getJpaPlatform(); - if (jpaPlatform == null) { - return null; - } - JpaProject result = jpaPlatform.getJpaFactory().buildJpaProject(this.config); - result.setUpdater(new AsynchronousJpaProjectUpdater(result)); - return result; - } - - public void projectChanged(IResourceDelta delta) throws CoreException { - if (this.jpaProject != null) { - this.jpaProject.projectChanged(delta); - } - } - - public void javaElementChanged(ElementChangedEvent event) { - if (this.jpaProject != null) { - this.jpaProject.javaElementChanged(event); - } - } - - public JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jm, Config c) { - throw new IllegalArgumentException(c.getProject().getName()); - } - - public void remove() { - this.jpaModel.removeJpaProjectHolder(this); - if (this.jpaProject != null) { - this.jpaModel.jpaProjectRemoved(this.jpaProject); - this.jpaProject.dispose(); - } - } - - @Override - public String toString() { - return StringTools.buildToStringFor(this, this.config.getProject().getName()); - } - - } - - - // ********** resource proxy visitor ********** - - /** - * Visit the workspace resource tree, adding a JPA project to the - * JPA model for each open Eclipse project that has a JPA facet. - */ - private class ResourceProxyVisitor implements IResourceProxyVisitor { - - ResourceProxyVisitor() { - super(); - } - - public boolean visit(IResourceProxy resourceProxy) throws CoreException { - switch (resourceProxy.getType()) { - case IResource.ROOT : - return true; // all projects are in the "root" - case IResource.PROJECT : - this.checkProject(resourceProxy); - return false; // no nested projects - default : - return false; - } - } - - private void checkProject(IResourceProxy resourceProxy) { - if (resourceProxy.isAccessible()) { // the project exists and is open - IProject project = (IProject) resourceProxy.requestResource(); - if (JptCorePlugin.projectHasJpaFacet(project)) { - GenericJpaModel.this.addJpaProject(project); - } - } - } - - @Override - public String toString() { - return StringTools.buildToStringFor(this); - } - - } - - - // ********** DEBUG ********** - - // @see JpaModelTests#testDEBUG() - private static final boolean DEBUG = false; - - private static void dumpStackTrace() { - if (DEBUG) { - // lock System.out so the stack elements are printed out contiguously - synchronized (System.out) { - StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - // skip the first 3 elements - those are this method and 2 methods in Thread - for (int i = 3; i < stackTrace.length; i++) { - StackTraceElement element = stackTrace[i]; - if (element.getMethodName().equals("invoke0")) { //$NON-NLS-1$ - break; // skip all elements outside of the JUnit test - } - System.out.println("\t" + element); //$NON-NLS-1$ - } - } - } - } - -} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JpaModelManager.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JpaModelManager.java deleted file mode 100644 index b4ec22bef0..0000000000 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JpaModelManager.java +++ /dev/null @@ -1,590 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2008 Oracle. All rights reserved. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0, which accompanies this distribution - * and is available at http://www.eclipse.org/legal/epl-v10.html. - * - * Contributors: - * Oracle - initial API and implementation - ******************************************************************************/ -package org.eclipse.jpt.core.internal; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.IncrementalProjectBuilder; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jdt.core.ElementChangedEvent; -import org.eclipse.jdt.core.IElementChangedListener; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaElementDelta; -import org.eclipse.jdt.core.IOpenable; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jpt.core.JpaFile; -import org.eclipse.jpt.core.JpaModel; -import org.eclipse.jpt.core.JpaProject; -import org.eclipse.jpt.core.JptCorePlugin; -import org.eclipse.jpt.utility.internal.BitTools; -import org.eclipse.jpt.utility.internal.StringTools; -import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework; -import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent; -import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener; -import org.eclipse.wst.common.project.facet.core.events.IProjectFacetActionEvent; - -/** - * "Internal" global stuff. - * Provide access via a singleton. - * Hold and manage the JPA model (which holds all the JPA projects) - * and the various global listeners. We attempt to determine whether events - * are relevant before forwarding them to the JPA model. - * - * Various things that cause us to add or remove a JPA project: - * - Startup of the Dali plug-in will trigger all the JPA projects to be added - * - * - Project created and facet installed - * facet POST_INSTALL - * - Project facet uninstalled - * facet PRE_UNINSTALL - * - * - Project opened - * facet PROJECT_MODIFIED - * - Project closed - * facet PROJECT_MODIFIED - * - * - Pre-existing project imported from directory or archive (created and opened) - * resource POST_CHANGE -> PROJECT -> ADDED -> OPEN - * - Project deleted - * resource PRE_DELETE - * - * - Project facet installed by editing the facets settings file directly - * facet PROJECT_MODIFIED - * - Project facet uninstalled by editing the facets settings file directly - * facet PROJECT_MODIFIED - */ -public class JpaModelManager { - - /** - * The JPA model - null until the plug-in is started. - */ - private GenericJpaModel jpaModel; - - /** - * Listen for changes to projects and files. - */ - private final IResourceChangeListener resourceChangeListener; - - /** - * Listen for the JPA facet being added or removed from a project. - */ - private final IFacetedProjectListener facetedProjectListener; - - /** - * Listen for Java changes and forward them to the JPA model, - * which will forward them to the JPA projects. - */ - private final IElementChangedListener javaElementChangeListener; - private boolean javaElementChangeListenerIsActive; - - - // ********** singleton ********** - - private static final JpaModelManager INSTANCE = new JpaModelManager(); - - /** - * Return the singleton JPA model manager. - */ - public static JpaModelManager instance() { - return INSTANCE; - } - - - // ********** constructor ********** - - /** - * Private - ensure single instance. - */ - private JpaModelManager() { - super(); - this.resourceChangeListener = new ResourceChangeListener(); - this.facetedProjectListener = new FacetedProjectListener(); - this.javaElementChangeListener = new JavaElementChangeListener(); - this.javaElementChangeListenerIsActive = true; - } - - - // ********** plug-in controlled life-cycle ********** - - /** - * internal - called by JptCorePlugin - */ - public synchronized void start() throws Exception { - debug("*** START JPA model manager ***"); //$NON-NLS-1$ - try { - this.jpaModel = new GenericJpaModel(); - ResourcesPlugin.getWorkspace().addResourceChangeListener( - this.resourceChangeListener, - IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.POST_BUILD); - FacetedProjectFramework.addListener(this.facetedProjectListener, IFacetedProjectEvent.Type.values()); - JavaCore.addElementChangedListener(this.javaElementChangeListener); - } catch (RuntimeException ex) { - this.log(ex); - this.stop(); - } - } - - /** - * internal - called by JptCorePlugin - */ - public synchronized void stop() throws Exception { - debug("*** STOP JPA model manager ***"); //$NON-NLS-1$ - JavaCore.removeElementChangedListener(this.javaElementChangeListener); - FacetedProjectFramework.removeListener(this.facetedProjectListener); - ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener); - this.jpaModel.dispose(); - this.jpaModel = null; - } - - - // ********** API ********** - - /** - * Return the workspace-wide JPA model. - */ - public JpaModel getJpaModel() { - return this.jpaModel; - } - - /** - * Return the JPA project corresponding to the specified Eclipse project, - * or null if unable to associate the specified project with a - * JPA project. - */ - public JpaProject getJpaProject(IProject project) throws CoreException { - return this.jpaModel.getJpaProject(project); - } - - /** - * Return the JPA file corresponding to the specified Eclipse file, - * or null if unable to associate the specified file with a JPA file. - */ - public JpaFile getJpaFile(IFile file) throws CoreException { - return this.jpaModel.getJpaFile(file); - } - - /** - * The JPA settings associated with the specified Eclipse project - * have changed in such a way as to require the associated - * JPA project to be completely rebuilt - * (e.g. when the user changes a project's JPA platform). - */ - public void rebuildJpaProject(IProject project) { - this.jpaModel.rebuildJpaProject(project); - } - - /** - * Return whether the model manager's Java change listener is active. - */ - public boolean javaElementChangeListenerIsActive() { - return this.javaElementChangeListenerIsActive; - } - - /** - * Set whether the model manager's Java change listener is active. - */ - public void setJavaElementChangeListenerIsActive(boolean javaElementChangeListenerIsActive) { - this.javaElementChangeListenerIsActive = javaElementChangeListenerIsActive; - } - - /** - * Log the specified status. - */ - public void log(IStatus status) { - JptCorePlugin.log(status); - } - - /** - * Log the specified message. - */ - public void log(String msg) { - JptCorePlugin.log(msg); - } - - /** - * Log the specified exception or error. - */ - public void log(Throwable throwable) { - JptCorePlugin.log(throwable); - } - - - // ********** resource changed ********** - - /** - * Check for: - * - project close/delete - * - file add/remove - * - project open/rename - */ - /* private */ void resourceChanged(IResourceChangeEvent event) { - // build events can have the workspace or project as source - if (event.getType() == IResourceChangeEvent.POST_BUILD) { - this.resourcePostBuild(event); - return; - } - - if (! (event.getSource() instanceof IWorkspace)) { - return; // this probably shouldn't happen... - } - - switch (event.getType()) { - case IResourceChangeEvent.PRE_DELETE : // project-only event - this.resourcePreDelete(event); - break; - case IResourceChangeEvent.POST_CHANGE : - this.resourcePostChange(event); - break; - default : - break; - } - } - - /** - * A project is being deleted. Remove its corresponding - * JPA project if appropriate. - */ - private void resourcePreDelete(IResourceChangeEvent event) { - IProject project = (IProject) event.getResource(); - debug("Resource (Project) PRE_DELETE: ", project); //$NON-NLS-1$ - this.jpaModel.projectPreDelete(project); - } - - /** - * A resource has changed somehow. - * Check for files being added or removed. - * (The JPA project only handles added and removed files here - * Changed files are handlded via the Java Element Changed event.) - * Also check for opened projects. - */ - private void resourcePostChange(IResourceChangeEvent event) { - debug("Resource POST_CHANGE"); //$NON-NLS-1$ - this.resourceChanged(event.getDelta()); - } - - private void resourceChanged(IResourceDelta delta) { - IResource resource = delta.getResource(); - switch (resource.getType()) { - case IResource.ROOT : - this.resourceChangedChildren(delta); - break; - case IResource.PROJECT : - this.projectChanged((IProject) resource, delta); - break; - case IResource.FILE : - case IResource.FOLDER : - default : - break; - } - } - - private void resourceChangedChildren(IResourceDelta delta) { - for (IResourceDelta child : delta.getAffectedChildren()) { - this.resourceChanged(child); // recurse - } - } - - private void projectChanged(IProject project, IResourceDelta delta) { - this.projectChanged_(delta); - this.checkForOpenedProject(project, delta); - } - - - /** - * Checked exceptions bite. - */ - private void projectChanged_(IResourceDelta delta) { - try { - this.jpaModel.projectChanged(delta); - } catch (CoreException ex) { - this.log(ex); // problem traversing the project's resources - not much we can do - } - } - - /** - * Crawl the specified delta, looking for projects being opened. - * Projects being deleted are handled in IResourceChangeEvent.PRE_DELETE. - * Projects being closed are handled in IFacetedProjectEvent.Type.PROJECT_MODIFIED. - */ - private void checkForOpenedProject(IProject project, IResourceDelta delta) { - switch (delta.getKind()) { - case IResourceDelta.ADDED : // all but project import and rename handled with the facet POST_INSTALL event - this.checkDeltaFlagsForOpenedProject(project, delta); - this.checkDeltaFlagsForRenamedProject(project, delta); - break; - case IResourceDelta.REMOVED : // already handled with the PRE_DELETE event - break; - case IResourceDelta.CHANGED : - this.checkDeltaFlagsForOpenedProject(project, delta); - break; - case IResourceDelta.ADDED_PHANTOM : // ignore - break; - case IResourceDelta.REMOVED_PHANTOM : // ignore - break; - default : - break; - } - } - - /** - * We don't get any events from the Facets Framework when a pre-existing - * project is imported, so we need to check for the newly imported project here. - * - * This event also occurs when a project is simply opened. Project opening - * also triggers a Facet PROJECT_MODIFIED event and that is where we add - * the JPA project, not here - */ - private void checkDeltaFlagsForOpenedProject(IProject project, IResourceDelta delta) { - if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.OPEN) && project.isOpen()) { - debug("\tProject CHANGED - OPEN: ", project.getName()); //$NON-NLS-1$ - this.jpaModel.checkForTransition(project); - } - } - - /** - * We don't get any events from the Facets Framework when a project is renamed, - * so we need to check for the renamed projects here. - */ - private void checkDeltaFlagsForRenamedProject(IProject project, IResourceDelta delta) { - if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.MOVED_FROM) && project.isOpen()) { - debug("\tProject ADDED - MOVED_FROM: ", delta.getMovedFromPath()); //$NON-NLS-1$ - this.jpaModel.checkForTransition(project); - } - } - - /** - * A post build event has occurred. - * Check for whether the build was a "clean" build and trigger project update. - */ - private void resourcePostBuild(IResourceChangeEvent event) { - debug("Resource POST_BUILD"); //$NON-NLS-1$ - if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) { - this.resourcePostClean(event.getDelta()); - } - } - - private void resourcePostClean(IResourceDelta delta) { - IResource resource = delta.getResource(); - switch (resource.getType()) { - case IResource.ROOT : - this.resourcePostCleanChildren(delta); - break; - case IResource.PROJECT : - this.projectPostClean((IProject) resource); - break; - case IResource.FILE : - case IResource.FOLDER : - default : - break; - } - } - - private void resourcePostCleanChildren(IResourceDelta delta) { - for (IResourceDelta child : delta.getAffectedChildren()) { - this.resourcePostClean(child); // recurse - } - } - - private void projectPostClean(IProject project) { - JpaProject jpaProject = null; - try { - jpaProject = getJpaProject(project); - } - catch (CoreException ce) { - // nothing to do, we won't be able to update anyway - } - if (jpaProject != null) { - rebuildJpaProject(project); - } - } - - - // ********** faceted project changed ********** - - /** - * Check for: - * - install of JPA facet - * - un-install of JPA facet - * - any other appearance or disappearance of the JPA facet - */ - /* private */ void facetedProjectChanged(IFacetedProjectEvent event) { - switch (event.getType()) { - case POST_INSTALL : - this.facetedProjectPostInstall((IProjectFacetActionEvent) event); - break; - case PRE_UNINSTALL : - this.facetedProjectPreUninstall((IProjectFacetActionEvent) event); - break; - case PROJECT_MODIFIED : - this.facetedProjectModified(event.getProject().getProject()); - break; - default : - break; - } - } - - private void facetedProjectPostInstall(IProjectFacetActionEvent event) { - debug("Facet POST_INSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ - if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) { - this.jpaModel.jpaFacetedProjectPostInstall(event); - } - } - - private void facetedProjectPreUninstall(IProjectFacetActionEvent event) { - debug("Facet PRE_UNINSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ - if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) { - this.jpaModel.jpaFacetedProjectPreUninstall(event); - } - } - - /** - * This event is triggered for any change to a faceted project. - * We use the event to watch for the following: - * - an open project is closed - * - a closed project is opened - * - one of a project's (facet) metadata files is edited directly - */ - private void facetedProjectModified(IProject project) { - debug("Facet PROJECT_MODIFIED: ", project.getName()); //$NON-NLS-1$ - this.jpaModel.checkForTransition(project); - } - - - // ********** Java element changed ********** - - /** - * Forward the event to the JPA model. - */ - /* private */ void javaElementChanged(ElementChangedEvent event) { - if ( ! this.javaElementChangeListenerIsActive) { - return; // ignore Java events - } - if (this.eventIndicatesProjectAddedButNotOpen(event)) { - return; - } - this.jpaModel.javaElementChanged(event); - } - - //209275 - This particular event only causes problems in a clean workspace the first time a JPA project - //is created through the JPA wizard. The second time a JPA project is created, this event occurs, but - //it occurs as the wizard is closing so it does not cause a deadlock. - private boolean eventIndicatesProjectAddedButNotOpen(ElementChangedEvent event) { - IJavaElementDelta delta = event.getDelta(); - if (delta.getKind() == IJavaElementDelta.CHANGED) { - if (delta.getElement().getElementType() == IJavaElement.JAVA_MODEL) { - IJavaElementDelta[] children = delta.getAffectedChildren(); - if (children.length == 1) { - IJavaElementDelta childDelta = children[0]; - if (childDelta.getKind() == IJavaElementDelta.ADDED) { - IJavaElement childElement = childDelta.getElement(); - if (childElement.getElementType() == IJavaElement.JAVA_PROJECT) { - if (childDelta.getAffectedChildren().length == 0) { - if (!((IOpenable) childElement).isOpen()) { - return true; - } - } - } - } - } - } - } - return false; - } - - - // ********** resource change listener ********** - - /** - * Forward the Resource change event back to the JPA model manager. - */ - private class ResourceChangeListener implements IResourceChangeListener { - ResourceChangeListener() { - super(); - } - public void resourceChanged(IResourceChangeEvent event) { - JpaModelManager.this.resourceChanged(event); - } - @Override - public String toString() { - return StringTools.buildToStringFor(this); - } - } - - - // ********** faceted project listener ********** - - /** - * Forward the Faceted project change event back to the JPA model manager. - */ - private class FacetedProjectListener implements IFacetedProjectListener { - FacetedProjectListener() { - super(); - } - public void handleEvent(IFacetedProjectEvent event) { - JpaModelManager.this.facetedProjectChanged(event); - } - @Override - public String toString() { - return StringTools.buildToStringFor(this); - } - } - - - // ********** Java element change listener ********** - - /** - * Forward the Java element change event back to the JPA model manager. - */ - private class JavaElementChangeListener implements IElementChangedListener { - JavaElementChangeListener() { - super(); - } - public void elementChanged(ElementChangedEvent event) { - JpaModelManager.this.javaElementChanged(event); - } - @Override - public String toString() { - return StringTools.buildToStringFor(this); - } - } - - - // ********** debug ********** - - // @see JpaModelTests#testDEBUG() - private static final boolean DEBUG = false; - - /** - * trigger #toString() call and string concatenation only if DEBUG is true - */ - private static void debug(String message, Object object) { - if (DEBUG) { - debug_(message + object); - } - } - - private static void debug(String message) { - if (DEBUG) { - debug_(message); - } - } - - private static void debug_(String message) { - System.out.println(Thread.currentThread().getName() + ": " + message); //$NON-NLS-1$ - } - -} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JptCoreMessages.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JptCoreMessages.java index 9e64af61ba..df58bc8729 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JptCoreMessages.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/JptCoreMessages.java @@ -53,6 +53,7 @@ public class JptCoreMessages { public static String UPDATE_JOB_NAME; public static String SYNCHRONIZE_METAMODEL_JOB_NAME; public static String PLATFORM_ID_DOES_NOT_EXIST; + public static String DALI_EVENT_HANDLER_THREAD_NAME; private static final String BUNDLE_NAME = "jpa_core"; //$NON-NLS-1$ private static final Class BUNDLE_CLASS = JptCoreMessages.class; diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/jpa1/GenericJpaProject.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/jpa1/GenericJpaProject.java index afa4628b0b..5fdd73fc23 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/jpa1/GenericJpaProject.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/jpa1/GenericJpaProject.java @@ -9,7 +9,6 @@ ******************************************************************************/ package org.eclipse.jpt.core.internal.jpa1; -import org.eclipse.core.runtime.CoreException; import org.eclipse.jpt.core.internal.AbstractJpaProject; import org.eclipse.jpt.core.jpa2.JpaProject2_0; @@ -22,7 +21,7 @@ public class GenericJpaProject // ********** constructor/initialization ********** - public GenericJpaProject(JpaProject2_0.Config config) throws CoreException { + public GenericJpaProject(JpaProject2_0.Config config) { super(config); } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationDataModelProvider.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationDataModelProvider.java index bfb0b96642..db52d48cf4 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationDataModelProvider.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationDataModelProvider.java @@ -232,10 +232,7 @@ public abstract class AbstractJpaFileCreationDataModelProvider protected JpaProject getJpaProject() { IProject project = getProject(); - if (project == null) { - return null; - } - return JptCorePlugin.getJpaProject(project); + return (project == null) ? null : JptCorePlugin.getJpaProject(project); } /** diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationOperation.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationOperation.java index 1510d8ed9c..806bc48a52 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationOperation.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/operations/AbstractJpaFileCreationOperation.java @@ -1,12 +1,11 @@ /******************************************************************************* - * Copyright (c) 2009 Oracle. - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v1.0 which - * accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Oracle - initial API and implementation + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation *******************************************************************************/ package org.eclipse.jpt.core.internal.operations; @@ -19,6 +18,7 @@ import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jem.util.emf.workbench.ProjectUtilities; import org.eclipse.jpt.core.JpaProject; import org.eclipse.jpt.core.JptCorePlugin; @@ -111,5 +111,14 @@ public abstract class AbstractJpaFileCreationOperation this.createdFile = newFile; } + @Override + public ISchedulingRule getSchedulingRule() { + try { + return this.getProject(); + } catch (ExecutionException ex) { + throw new RuntimeException(ex); + } + } + protected abstract AbstractXmlResourceProvider getXmlResourceProvider(IFile file); } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/SimpleSchedulingRule.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/SimpleSchedulingRule.java index a77b0c03aa..b51b33c87e 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/SimpleSchedulingRule.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/SimpleSchedulingRule.java @@ -10,6 +10,7 @@ package org.eclipse.jpt.core.internal.utility; import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.jpt.utility.internal.StringTools; /** * A job scheduling rule that conflicts only with itself. @@ -17,21 +18,7 @@ import org.eclipse.core.runtime.jobs.ISchedulingRule; public final class SimpleSchedulingRule implements ISchedulingRule { - - // singleton - private static final SimpleSchedulingRule INSTANCE = new SimpleSchedulingRule(); - - /** - * Return the singleton. - */ - public static ISchedulingRule instance() { - return INSTANCE; - } - - /** - * Ensure single instance. - */ - private SimpleSchedulingRule() { + public SimpleSchedulingRule() { super(); } @@ -43,4 +30,9 @@ public final class SimpleSchedulingRule return rule == this; } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/validation/JpaValidator.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/validation/JpaValidator.java index 68f3fac1c4..f5d7ac64e8 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/validation/JpaValidator.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/validation/JpaValidator.java @@ -37,6 +37,10 @@ import org.eclipse.wst.validation.internal.provisional.core.IValidator; */ public class JpaValidator extends AbstractValidator implements IValidator { + public JpaValidator() { + super(); + } + // ********** IValidator implementation ********** diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/AbstractXmlResourceProvider.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/AbstractXmlResourceProvider.java index 9b532bdc7c..48be5bd41c 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/AbstractXmlResourceProvider.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/AbstractXmlResourceProvider.java @@ -173,7 +173,7 @@ public abstract class AbstractXmlResourceProvider createResourceAndUnderlyingFile(config); } }; - workspace.run(runnable, workspace.getRoot(), IWorkspace.AVOID_UPDATE, new NullProgressMonitor()); + workspace.run(runnable, this.project, IWorkspace.AVOID_UPDATE, null); return this.resource; } diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/xml/JpaXmlResource.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/xml/JpaXmlResource.java index 232f8f80ff..8f4a2c9a2f 100644 --- a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/xml/JpaXmlResource.java +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/resource/xml/JpaXmlResource.java @@ -21,6 +21,7 @@ import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.jem.util.emf.workbench.WorkbenchResourceHelperBase; import org.eclipse.jem.util.plugin.JEMUtilPlugin; import org.eclipse.jpt.core.JpaResourceModel; @@ -75,16 +76,30 @@ public class JpaXmlResource // ********** BasicNotifierImpl override ********** /** - * override to prevent notification when the resource's state is unchanged - * or the resource is not loaded + * override to fire notification only when + * - the resource's state has actually changed; and + * - the resource is loaded; and + * - the resource's resource set is still present (EMF will fire an + * notification when the resource set is set to 'null', just before + * the resource is "unloaded" - we want to swallow this notification) */ @Override public void eNotify(Notification notification) { - if ( ! notification.isTouch() && this.isLoaded()) { + if ( ! notification.isTouch() && this.isLoaded() && (this.resourceSet != null)) { super.eNotify(notification); this.resourceModelChanged(); } } + + /** + * we could use this method to suppress some notifications; or we could just + * check whether 'resourceSet' is 'null' (which is what we do) + */ + protected boolean resultSetCleared(Notification notification) { + return (notification.getNotifier() == this) && + (notification.getFeatureID(Resource.class) == RESOURCE__RESOURCE_SET) && + (notification.getNewValue() == null); + } // ********** TranslatorResource implementation ********** diff --git a/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaFactory.java b/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaFactory.java index 5f886a6fad..20ebe2df93 100644 --- a/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaFactory.java +++ b/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaFactory.java @@ -9,7 +9,6 @@ *******************************************************************************/ package org.eclipse.jpt.eclipselink.core.internal; -import org.eclipse.core.runtime.CoreException; import org.eclipse.jpt.core.JpaProject; import org.eclipse.jpt.core.context.PersistentType; import org.eclipse.jpt.core.context.java.JavaBasicMapping; @@ -54,7 +53,7 @@ public class EclipseLinkJpaFactory // ********** Core Model ********** @Override - public EclipseLinkJpaProject buildJpaProject(JpaProject.Config config) throws CoreException { + public EclipseLinkJpaProject buildJpaProject(JpaProject.Config config) { return new EclipseLinkJpaProjectImpl((JpaProject2_0.Config) config); } diff --git a/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaProjectImpl.java b/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaProjectImpl.java index 9f5647f316..edac78a5e1 100644 --- a/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaProjectImpl.java +++ b/jpa/plugins/org.eclipse.jpt.eclipselink.core/src/org/eclipse/jpt/eclipselink/core/internal/EclipseLinkJpaProjectImpl.java @@ -9,7 +9,6 @@ ******************************************************************************/ package org.eclipse.jpt.eclipselink.core.internal; -import org.eclipse.core.runtime.CoreException; import org.eclipse.jpt.core.internal.AbstractJpaProject; import org.eclipse.jpt.core.jpa2.JpaProject2_0; import org.eclipse.jpt.core.resource.xml.JpaXmlResource; @@ -22,7 +21,7 @@ public class EclipseLinkJpaProjectImpl extends AbstractJpaProject implements EclipseLinkJpaProject { - public EclipseLinkJpaProjectImpl(JpaProject2_0.Config config) throws CoreException { + public EclipseLinkJpaProjectImpl(JpaProject2_0.Config config) { super(config); } diff --git a/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/JptUiPlugin.java b/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/JptUiPlugin.java index 2b07f4a8a1..c7225e72ec 100644 --- a/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/JptUiPlugin.java +++ b/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/JptUiPlugin.java @@ -13,7 +13,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jpt.core.JpaPlatform; -import org.eclipse.jpt.core.internal.JpaModelManager; +import org.eclipse.jpt.core.JptCorePlugin; import org.eclipse.jpt.ui.internal.platform.JpaPlatformUiRegistry; import org.eclipse.jpt.ui.navigator.JpaNavigatorProvider; import org.eclipse.swt.SWT; @@ -156,7 +156,7 @@ public class JptUiPlugin * @see #focusOut() */ private void focusIn() { - JpaModelManager.instance().setJavaElementChangeListenerIsActive(false); + JptCorePlugin.setJavaElementChangeListenerIsActive(false); } /** @@ -166,7 +166,7 @@ public class JptUiPlugin * @see #focusIn() */ private void focusOut() { - JpaModelManager.instance().setJavaElementChangeListenerIsActive(true); + JptCorePlugin.setJavaElementChangeListenerIsActive(true); } /** diff --git a/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/internal/properties/JpaProjectPropertiesPage.java b/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/internal/properties/JpaProjectPropertiesPage.java index b2ee041467..5183b59dbd 100644 --- a/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/internal/properties/JpaProjectPropertiesPage.java +++ b/jpa/plugins/org.eclipse.jpt.ui/src/org/eclipse/jpt/ui/internal/properties/JpaProjectPropertiesPage.java @@ -25,6 +25,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; @@ -35,7 +36,6 @@ import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jpt.core.JpaDataSource; import org.eclipse.jpt.core.JpaProject; import org.eclipse.jpt.core.JptCorePlugin; -import org.eclipse.jpt.core.internal.JpaModelManager; import org.eclipse.jpt.core.internal.JpaPlatformRegistry; import org.eclipse.jpt.core.internal.JptCoreMessages; import org.eclipse.jpt.core.internal.facet.JpaLibraryProviderConstants; @@ -666,9 +666,12 @@ public class JpaProjectPropertiesPage return new IRunnableWithProgress() { public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { - IWorkspace ws = ResourcesPlugin.getWorkspace(); - IWorkspaceRunnable wr = JpaProjectPropertiesPage.this.buildOkWorkspaceRunnable(); - ws.run(wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, monitor); + ResourcesPlugin.getWorkspace().run( + JpaProjectPropertiesPage.this.buildOkWorkspaceRunnable(), + JpaProjectPropertiesPage.this.getOkSchedulingRule(), + IWorkspace.AVOID_UPDATE, + monitor + ); } catch (CoreException ex) { throw new InvocationTargetException(ex); @@ -685,13 +688,17 @@ public class JpaProjectPropertiesPage }; } + ISchedulingRule getOkSchedulingRule() { + return this.getProject(); + } + void performOk_(IProgressMonitor monitor) throws CoreException { if (this.isBuffering()) { boolean platformChanged = this.platformIdModel.isBuffering(); this.trigger.accept(); if (platformChanged) { // if the JPA platform is changed, we need to completely rebuild the JPA project - JpaModelManager.instance().rebuildJpaProject(this.getProject()); + JptCorePlugin.rebuildJpaProject(this.getProject()); } this.getProject().build(IncrementalProjectBuilder.FULL_BUILD, monitor); } diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/AsynchronousCommandExecutor.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/AsynchronousCommandExecutor.java new file mode 100644 index 0000000000..00baa2efd0 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/AsynchronousCommandExecutor.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import org.eclipse.jpt.utility.Command; + +/** + * This command executor will dispatch commands to be executed in a separate + * thread, allowing calls to {@link CommandExecutor#execute(Command)} to return + * immediately. + *

+ * NB: The client-supplied commands should handle any + * exception appropriately (e.g. log the exception and return gracefully) so + * the command execution thread can continue executing. + */ +public class AsynchronousCommandExecutor + implements CallbackStatefulCommandExecutor +{ + /** + * This command queue is shared with the command execution/consumer thread. + * Adding a command to it will trigger the command to be executed by the + * command execution thread or, if another command is currently executing, + * to execute the new command once the currently executing command has + * finished executing. + */ + final SynchronizedQueue commands = new SynchronizedQueue(); + + /** + * Most of the thread-related behavior is delegated to this coordinator. + */ + private final ConsumerThreadCoordinator consumerThreadCoordinator; + + private final ListenerList listenerList = new ListenerList(Listener.class); + + + // ********** construction ********** + + /** + * Construct an asynchronous command executor. + * Allow the command execution thread(s) to be assigned JDK-generated names. + */ + public AsynchronousCommandExecutor() { + this(null); + } + + /** + * Construct an asynchronous command executor. + * Assign the command execution thread(s) the specified name. + */ + public AsynchronousCommandExecutor(String threadName) { + super(); + this.consumerThreadCoordinator = new ConsumerThreadCoordinator(this.buildConsumer(), threadName); + } + + private ConsumerThreadCoordinator.Consumer buildConsumer() { + return new Consumer(); + } + + + // ********** CallbackStatefulCommandExecutor implementation ********** + + /** + * Build and start the command execution/consumer thread. + *

+ * Note: We don't clear the command queue here; so if a command has been + * added to the queue before getting here, the first command will + * be executed promptly (albeit, asynchronously). + * The command queue will be non-empty if:

    + *
  • {@link #execute(Command)} was called after the command executor was + * constructed but before {@link #start()} was called; or + *
  • {@link #execute(Command)} was called after {@link #stop()} was called + * but before {@link #start()} was called (to restart the command executor); or + *
  • {@link #stop()} was called when there were still outstanding commands + * remaining in the command queue + *
+ */ + public void start() { + this.consumerThreadCoordinator.start(); + } + + /** + * Put the specified command on the command queue, to be consumed by the + * command execution thread. + */ + public void execute(Command command) { + this.commands.enqueue(command); + } + + /** + * Interrupt the command execution thread so that it stops executing at the + * end of the current command. Suspend the current thread until + * the command execution thread is finished executing. If any uncaught + * exceptions were thrown while the execution thread was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public void stop() { + this.consumerThreadCoordinator.stop(); + } + + public void addListener(Listener listener) { + this.listenerList.add(listener); + } + + public void removeListener(Listener listener) { + this.listenerList.remove(listener); + } + + /** + * Notify our listeners. + */ + /* private */ void commandExecuted(Command command) { + for (Listener listener : this.listenerList.getListeners()) { + listener.commandExecuted(command); + } + } + + + // ********** consumer ********** + + /** + * This implementation of {@link ConsumerThreadCoordinator.Consumer} + * will execute the commands enqueued by the asynchronous command executor. + * It will wait until the shared command queue is non-empty to begin executing the + * commands in the queue. Once a comand is executed, the thread will quiesce until + * another command is placed in the command queue. If a new command is + * enqueued during the execution of another command (either recursively by + * the command itself or by another thread), + * the new command will be executed immediately after the currently + * executing command is finished. + * Stop the thread by calling {@link Thread#interrupt()}. + */ + class Consumer + implements ConsumerThreadCoordinator.Consumer + { + Consumer() { + super(); + } + + /** + * Wait until a command has been placed in the queue. + */ + public void waitForProducer() throws InterruptedException { + AsynchronousCommandExecutor.this.commands.waitUntilNotEmpty(); + } + + /** + * Execute the first command in the queue and notify our listeners. + */ + public void execute() { + Command command = AsynchronousCommandExecutor.this.commands.dequeue(); + command.execute(); + AsynchronousCommandExecutor.this.commandExecuted(command); + } + + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CallbackStatefulCommandExecutor.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CallbackStatefulCommandExecutor.java new file mode 100644 index 0000000000..d9fb9a1c6b --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CallbackStatefulCommandExecutor.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import java.util.EventListener; + +import org.eclipse.jpt.utility.Command; + +/** + * This command executor notifies clients commands are executed. + */ +public interface CallbackStatefulCommandExecutor + extends StatefulCommandExecutor +{ + /** + * Add the specified listener to be notified whenever a command has been + * executed. + * @see #removeListener(Listener) + */ + void addListener(Listener listener); + + /** + * Remove the specified listener. + * @see #addListener(Listener) + */ + void removeListener(Listener listener); + + + // ********** listener ********** + + /** + * Interface implemented by listeners to be notified whenever a + * command is executed. + */ + public interface Listener + extends EventListener + { + /** + * The specified command was executed. + */ + void commandExecuted(Command command); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CompositeCommand.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CompositeCommand.java new file mode 100644 index 0000000000..ff603eb0b7 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/CompositeCommand.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import org.eclipse.jpt.utility.Command; + +/** + * CompositeCommand provides support for treating a collection of + * {@link Command}s as a single command. + */ +public class CompositeCommand + implements Command +{ + private final Iterable commands; + + public CompositeCommand(Iterable commands) { + super(); + this.commands = commands; + } + + public void execute() { + for (Command command : this.commands) { + command.execute(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.commands); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/ConsumerThreadCoordinator.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/ConsumerThreadCoordinator.java new file mode 100644 index 0000000000..abc9e71456 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/ConsumerThreadCoordinator.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import java.util.Vector; + +/** + * A ConsumerThreadCoordinator controls the creation, + * starting, and stopping of a general purpose "consumer" thread. Construct + * the coordinator with a {@link Consumer} that both waits for the producer + * to "produce" something to "consume" and, once the wait is over, + * "consumes" whatever is available. + *

+ * NB: The client-supplied consumer should handle any + * exception appropriately (e.g. log the exception and return gracefully) so + * the thread can continue executing. + */ +public class ConsumerThreadCoordinator { + /** + * The runnable passed to the consumer thread each time it is built. + */ + private final Runnable runnable; + + /** + * Optional, client-supplied name for the consumer thread. + * If null, the JDK assigns a name. + */ + private final String threadName; + + /** + * The consumer is executed on this thread. A new thread is built + * for every start/stop cycle (since a thread cannot be started more than + * once). + */ + private volatile Thread thread; + + /** + * A list of the uncaught exceptions thrown by the consumer + * during the current start/stop cycle. + */ + final Vector exceptions = new Vector(); + + + // ********** construction ********** + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Allow the consumer thread(s) to be assigned JDK-generated names. + */ + public ConsumerThreadCoordinator(Consumer consumer) { + this(consumer, null); + } + + /** + * Construct a consumer thread coordinator for the specified consumer. + * Assign the consumer thread(s) the specified name. + */ + public ConsumerThreadCoordinator(Consumer consumer, String threadName) { + super(); + this.runnable = this.buildRunnable(consumer); + this.threadName = threadName; + } + + private Runnable buildRunnable(Consumer consumer) { + return new RunnableConsumer(consumer); + } + + + // ********** Lifecycle support ********** + + /** + * Build and start the consumer thread. + */ + public synchronized void start() { + if (this.thread != null) { + throw new IllegalStateException("Not stopped."); //$NON-NLS-1$ + } + this.thread = this.buildThread(); + this.thread.start(); + } + + private Thread buildThread() { + Thread t = new Thread(this.runnable); + if (this.threadName != null) { + t.setName(this.threadName); + } + return t; + } + + /** + * Interrupt the consumer thread so that it stops executing at the + * end of its current iteration. Suspend the current thread until + * the consumer thread is finished executing. If any uncaught + * exceptions were thrown while the consumer thread was executing, + * wrap them in a composite exception and throw the composite exception. + */ + public synchronized void stop() { + if (this.thread == null) { + throw new IllegalStateException("Not started."); //$NON-NLS-1$ + } + this.thread.interrupt(); + try { + this.thread.join(); + } catch (InterruptedException ex) { + // the thread that called #stop() was interrupted while waiting to + // join the consumer thread - ignore; + // 'thread' is still "interrupted", so its #run() loop will still stop + // after its current execution - we just won't wait around for it... + } + this.thread = null; + + if (this.exceptions.size() > 0) { + Throwable[] temp = this.exceptions.toArray(new Throwable[this.exceptions.size()]); + this.exceptions.clear(); + throw new CompositeException(temp); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.thread); + } + + + // ********** consumer thread runnable ********** + + /** + * This implementation of {@link Runnable} is a long-running consumer that + * will repeatedly execute the consumer {@link Consumer#execute()} method. + * With each iteration, the consumer thread will wait + * until the other consumer method, {@link Consumer#waitForProducer()}, allows the + * consumer thread to proceed (i.e. there is something for the consumer to + * consume). Once {@link Consumer#execute()} is finished, the thread will quiesce + * until {@link Consumer#waitForProducer()} returns again. + * Stop the thread by calling {@link Thread#interrupt()}. + */ + private class RunnableConsumer + implements Runnable + { + /** + * Client-supplied consumer that controls waiting for something to consume + * and the consuming itself. + */ + private final Consumer consumer; + + RunnableConsumer(Consumer consumer) { + super(); + this.consumer = consumer; + } + + /** + * Loop while this thread has not been interrupted by another thread. + * In each loop: Pause execution until {@link Consumer#waitForProducer()} + * allows us to proceed. + *

+ * If this thread is interrupted during {@link Consumer#execute()}, + * the call to {@link Thread#interrupted()} will stop the loop. If this thread is + * interrupted during the call to {@link Consumer#waitForProducer()}, + * we will catch the {@link InterruptedException} and stop the loop also. + */ + public void run() { + while ( ! Thread.interrupted()) { + try { + this.consumer.waitForProducer(); + } catch (InterruptedException ex) { + // we were interrupted while waiting, must be Quittin' Time + return; + } + this.execute(); + } + } + + /** + * Execute the consumer {@link Consumer#execute()} method. + * Do not allow any unhandled exceptions to kill the thread. + * Store them up for later pain. + * @see ConsumerThreadCoordinator#stop() + */ + private void execute() { + try { + this.execute_(); + } catch (Throwable ex) { + ConsumerThreadCoordinator.this.exceptions.add(ex); + } + } + + /** + * Subclass-implemented behavior: consume stuff. + */ + private void execute_() { + this.consumer.execute(); + } + + } + + + // ********** consumer interface ********** + + /** + * Interface implemented by clients that controls:

    + *
  • when the consumer thread suspends, waiting for something to consume + *
  • the consuming of whatever is being produced + *
+ */ + public interface Consumer { + /** + * Wait for something to consume. + * Throw an {@link InterruptedException} if the thread is interrupted. + */ + void waitForProducer() throws InterruptedException; + + /** + * Consume whatever is currently available. + */ + void execute(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Queue.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Queue.java new file mode 100644 index 0000000000..cf9b1f8b25 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Queue.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +/** + * Interface defining the classic queue behavior, + * without the backdoors allowed by {@link java.util.Queue}. + * + * @param the type of elements contained by the queue + */ +public interface Queue { + + /** + * "Enqueue" the specified item to the tail of the queue. + */ + void enqueue(E o); + + /** + * "Dequeue" an item from the head of the queue. + */ + E dequeue(); + + /** + * Return the item on the head of the queue + * without removing it from the queue. + */ + E peek(); + + /** + * Return whether the queue is empty. + */ + boolean isEmpty(); + + + final class Empty implements Queue, Serializable { + @SuppressWarnings("unchecked") + public static final Queue INSTANCE = new Empty(); + @SuppressWarnings("unchecked") + public static Queue instance() { + return INSTANCE; + } + // ensure single instance + private Empty() { + super(); + } + public void enqueue(E o) { + throw new UnsupportedOperationException(); + } + public E dequeue() { + throw new NoSuchElementException(); + } + public E peek() { + throw new NoSuchElementException(); + } + public boolean isEmpty() { + return true; + } + private static final long serialVersionUID = 1L; + private Object readResolve() { + // replace this object with the singleton + return INSTANCE; + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleCommandExecutor.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleCommandExecutor.java new file mode 100644 index 0000000000..de883b3e8b --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleCommandExecutor.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import org.eclipse.jpt.utility.Command; + +/** + * Straightforward implementation of {@link CallbackStatefulCommandExecutor}. + */ +public class SimpleCommandExecutor + implements CallbackStatefulCommandExecutor +{ + private boolean active = false; + private final ListenerList listenerList = new ListenerList(Listener.class); + + public SimpleCommandExecutor() { + super(); + } + + public void start() { + if (this.active) { + throw new IllegalStateException("Not stopped."); //$NON-NLS-1$ + } + this.active = true; + } + + public void execute(Command command) { + if (this.active) { + command.execute(); + this.commandExecuted(command); + } + } + + public void stop() { + if ( ! this.active) { + throw new IllegalStateException("Not started."); //$NON-NLS-1$ + } + this.active = false; + } + + public void addListener(Listener listener) { + this.listenerList.add(listener); + } + + public void removeListener(Listener listener) { + this.listenerList.remove(listener); + } + + /** + * Notify our listeners. + */ + private void commandExecuted(Command command) { + for (Listener listener : this.listenerList.getListeners()) { + listener.commandExecuted(command); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleQueue.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleQueue.java new file mode 100644 index 0000000000..89a73e1454 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleQueue.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; + +/** + * Straightforward implementation of the {@link Queue} interface. + */ +public class SimpleQueue + implements Queue, Cloneable, Serializable +{ + private LinkedList elements; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct an empty queue. + */ + public SimpleQueue() { + super(); + this.elements = new LinkedList(); + } + + /** + * Construct a queue containing the elements of the specified + * collection. The queue will dequeue its elements in the same + * order they are returned by the collection's iterator (i.e. the + * first element returned by the collection's iterator will be the + * first element returned by {@link #dequeue()}). + */ + public SimpleQueue(Collection c) { + super(); + this.elements = new LinkedList(c); + } + + + // ********** Queue implementation ********** + + public void enqueue(E o) { + this.elements.addLast(o); + } + + public E dequeue() { + return this.elements.removeFirst(); + } + + public E peek() { + return this.elements.getFirst(); + } + + public boolean isEmpty() { + return this.elements.isEmpty(); + } + + + // ********** Cloneable implementation ********** + + @Override + public SimpleQueue clone() { + try { + @SuppressWarnings("unchecked") + SimpleQueue clone = (SimpleQueue) super.clone(); + @SuppressWarnings("unchecked") + LinkedList ll = (LinkedList) this.elements.clone(); + clone.elements = ll; + return clone; + } catch (CloneNotSupportedException ex) { + throw new InternalError(); + } + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.peek()); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStack.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStack.java index 5df2f99e40..ffdea0c519 100644 --- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStack.java +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SimpleStack.java @@ -43,16 +43,16 @@ public class SimpleStack * last element returned by the collection's iterator will be the * first element returned by {@link #pop()}). */ - public SimpleStack(Collection c) { + public SimpleStack(Collection collection) { super(); - this.elements = new LinkedList(c); + this.elements = new LinkedList(collection); } // ********** Stack implementation ********** - public void push(E o) { - this.elements.addLast(o); + public void push(E element) { + this.elements.addLast(element); } public E pop() { @@ -76,7 +76,7 @@ public class SimpleStack } - // ********** Cloneable implementation ********** + // ********** standard methods ********** @Override public SimpleStack clone() { @@ -92,4 +92,9 @@ public class SimpleStack } } + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.peek()); + } + } diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Stack.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Stack.java index 4c3ac1bca6..50ba19877a 100644 --- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Stack.java +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/Stack.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2008 Oracle. All rights reserved. + * Copyright (c) 2007, 2009 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. @@ -14,15 +14,16 @@ import java.util.EmptyStackException; /** * Interface defining the classic stack behavior, - * without the backdoors allowed by java.util.Stack. - * E is the type of elements contained by the Stack. + * without the backdoors allowed by {@link java.util.Stack}. + * + * @param the type of elements contained by the stack */ public interface Stack { /** * "Push" the specified item on to the top of the stack. */ - void push(E o); + void push(E element); /** * "Pop" an item from the top of the stack. @@ -52,7 +53,7 @@ public interface Stack { private Empty() { super(); } - public void push(E o) { + public void push(E element) { throw new UnsupportedOperationException(); } public E pop() { diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StatefulCommandExecutor.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StatefulCommandExecutor.java new file mode 100644 index 0000000000..d4fd37a110 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/StatefulCommandExecutor.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import org.eclipse.jpt.utility.CommandExecutor; + +/** + * This interface allows clients to control how a command is executed. + * This is useful when the server provides the command but the client provides + * the context (e.g. the client would like to dispatch the command to the UI + * thread). + */ +public interface StatefulCommandExecutor + extends CommandExecutor +{ + /** + * Start the command executor. + */ + void start(); + + /** + * Stop the command executor. + */ + void stop(); + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedQueue.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedQueue.java new file mode 100644 index 0000000000..c35a4aa133 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedQueue.java @@ -0,0 +1,348 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal; + +import java.io.Serializable; +import java.util.NoSuchElementException; + +import org.eclipse.jpt.utility.Command; + +/** + * Thread-safe implementation of the {@link Queue} interface. + * This also provides protocol for suspending a thread until the + * queue is empty or not empty, with optional time-outs. + */ +public class SynchronizedQueue + implements Queue, Serializable +{ + /** Backing queue. */ + private final Queue queue; + + /** Object to synchronize on. */ + private final Object mutex; + + private static final long serialVersionUID = 1L; + + + // ********** constructors ********** + + /** + * Construct a synchronized queue that wraps the + * specified queue and locks on the specified mutex. + */ + public SynchronizedQueue(Queue queue, Object mutex) { + super(); + if (queue == null) { + throw new NullPointerException(); + } + this.queue = queue; + this.mutex = mutex; + } + + /** + * Construct a synchronized queue that wraps the + * specified queue and locks on itself. + */ + public SynchronizedQueue(Queue queue) { + super(); + if (queue == null) { + throw new NullPointerException(); + } + this.queue = queue; + this.mutex = this; + } + + /** + * Construct an empty synchronized queue that locks on the specified mutex. + */ + public SynchronizedQueue(Object mutex) { + this(new SimpleQueue(), mutex); + } + + /** + * Construct an empty synchronized queue that locks on itself. + */ + public SynchronizedQueue() { + this(new SimpleQueue()); + } + + + // ********** Queue implementation ********** + + public void enqueue(E element) { + synchronized (this.mutex) { + this.enqueue_(element); + } + } + + /** + * Pre-condition: synchronized + */ + private void enqueue_(E element) { + this.queue.enqueue(element); + this.mutex.notifyAll(); + } + + public E dequeue() { + synchronized (this.mutex) { + return this.dequeue_(); + } + } + + /** + * Pre-condition: synchronized + */ + private E dequeue_() { + E element = this.queue.dequeue(); + this.mutex.notifyAll(); + return element; + } + + public E peek() { + synchronized (this.mutex) { + return this.queue.peek(); + } + } + + public boolean isEmpty() { + synchronized (this.mutex) { + return this.queue.isEmpty(); + } + } + + + // ********** indefinite waits ********** + + /** + * Suspend the current thread until the queue's empty status changes + * to the specified value. + */ + public void waitUntilEmptyIs(boolean empty) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(empty); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilEmptyIs_(boolean empty) throws InterruptedException { + while (this.queue.isEmpty() != empty) { + this.mutex.wait(); + } + } + + /** + * Suspend the current thread until the queue is empty. + */ + public void waitUntilEmpty() throws InterruptedException { + this.waitUntilEmptyIs(true); + } + + /** + * Suspend the current thread until the queue has something on it. + */ + public void waitUntilNotEmpty() throws InterruptedException { + this.waitUntilEmptyIs(false); + } + + /** + * Suspend the current thread until the queue is empty, + * then "enqueue" the specified item to the tail of the queue + * and continue executing. + */ + public void waitToEnqueue(E element) throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(true); + this.enqueue_(element); + } + } + + /** + * Suspend the current thread until the queue has something on it, + * then "dequeue" an item from the head of the queue and return it. + */ + public Object waitToDequeue() throws InterruptedException { + synchronized (this.mutex) { + this.waitUntilEmptyIs_(false); + return this.dequeue_(); + } + } + + + // ********** timed waits ********** + + /** + * Suspend the current thread until the queue's empty status changes + * to the specified value or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if the specified + * empty status was achieved; return false if a time-out occurred. + * If the queue's empty status is already the specified value, + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmptyIs(boolean empty, long timeout) throws InterruptedException { + synchronized (this.mutex) { + return this.waitUntilEmptyIs_(empty, timeout); + } + } + + /** + * Pre-condition: synchronized + */ + private boolean waitUntilEmptyIs_(boolean empty, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilEmptyIs_(empty); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.queue.isEmpty() != empty) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); + } + return (this.queue.isEmpty() == empty); + } + + /** + * Suspend the current thread until the queue is empty + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the queue is empty; return false if a time-out occurred. + * If the queue is already empty, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(true, timeout); + } + + /** + * Suspend the current thread until the queue has something on it. + * or the specified time-out occurs. + * The time-out is specified in milliseconds. Return true if + * the queue is not empty; return false if a time-out occurred. + * If the queue already has something on it, return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitUntilNotEmpty(long timeout) throws InterruptedException { + return this.waitUntilEmptyIs(false, timeout); + } + + /** + * Suspend the current thread until the queue is empty, + * then "enqueue" the specified item to the tail of the queue + * and continue executing. If the queue is not emptied out + * before the time-out, simply continue executing without + * "enqueueing" the item. + * The time-out is specified in milliseconds. Return true if the + * item was enqueued; return false if a time-out occurred. + * If the queue is already empty, "enqueue" the specified item and + * return true immediately. + * If the time-out is zero, wait indefinitely. + */ + public boolean waitToEnqueue(E element, long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(true, timeout); + if (success) { + this.enqueue_(element); + } + return success; + } + } + + /** + * Suspend the current thread until the queue has something on it, + * then "dequeue" an item from the head of the queue and return it. + * If the queue is empty and nothing is "enqueued" on to it before the + * time-out, throw a no such element exception. + * The time-out is specified in milliseconds. + * If the queue is not empty, "dequeue" an item and + * return it immediately. + * If the time-out is zero, wait indefinitely. + */ + public Object waitToDequeue(long timeout) throws InterruptedException { + synchronized (this.mutex) { + boolean success = this.waitUntilEmptyIs_(false, timeout); + if (success) { + return this.dequeue_(); + } + throw new NoSuchElementException(); + } + } + + + // ********** synchronized behavior ********** + + /** + * If the current thread is not interrupted, execute the specified command + * with the mutex locked. This is useful for initializing the queue in another + * thread. + */ + public void execute(Command command) throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + synchronized (this.mutex) { + command.execute(); + } + } + + + // ********** additional public protocol ********** + + /** + * "Drain" all the current items from the queue into specified queue. + */ + public void drainTo(Queue q) { + synchronized (this.mutex) { + this.drainTo_(q); + } + } + + /** + * Pre-condition: synchronized + */ + private void drainTo_(Queue q) { + boolean changed = false; + while ( ! this.queue.isEmpty()) { + q.enqueue(this.queue.dequeue()); + changed = true; + } + if (changed) { + this.mutex.notifyAll(); + } + } + + /** + * Return the object the queue locks on while performing + * its operations. + */ + public Object getMutex() { + return this.mutex; + } + + + // ********** standard methods ********** + + @Override + public String toString() { + synchronized (this.mutex) { + return '[' + this.queue.toString() + ']'; + } + } + + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + synchronized (this.mutex) { + s.defaultWriteObject(); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedStack.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedStack.java index 1b0b9f33d9..dc0a4dce1b 100644 --- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedStack.java +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/SynchronizedStack.java @@ -11,6 +11,7 @@ package org.eclipse.jpt.utility.internal; import java.io.Serializable; import java.util.EmptyStackException; + import org.eclipse.jpt.utility.Command; /** @@ -59,14 +60,14 @@ public class SynchronizedStack } /** - * Construct a synchronized stack that locks on the specified mutex. + * Construct an empty synchronized stack that locks on the specified mutex. */ public SynchronizedStack(Object mutex) { this(new SimpleStack(), mutex); } /** - * Construct a synchronized stack that locks on itself. + * Construct an empty synchronized stack that locks on itself. */ public SynchronizedStack() { this(new SimpleStack()); @@ -75,21 +76,35 @@ public class SynchronizedStack // ********** Stack implementation ********** - public void push(E o) { + public void push(E element) { synchronized (this.mutex) { - this.stack.push(o); - this.mutex.notifyAll(); + this.push_(element); } } + /** + * Pre-condition: synchronized + */ + private void push_(E element) { + this.stack.push(element); + this.mutex.notifyAll(); + } + public E pop() { synchronized (this.mutex) { - E o = this.stack.pop(); - this.mutex.notifyAll(); - return o; + return this.pop_(); } } + /** + * Pre-condition: synchronized + */ + private E pop_() { + E o = this.stack.pop(); + this.mutex.notifyAll(); + return o; + } + public E peek() { synchronized (this.mutex) { return this.stack.peek(); @@ -111,9 +126,16 @@ public class SynchronizedStack */ public void waitUntilEmptyIs(boolean empty) throws InterruptedException { synchronized (this.mutex) { - while (this.isEmpty() != empty) { - this.mutex.wait(); - } + this.waitUntilEmptyIs_(empty); + } + } + + /** + * Pre-condition: synchronized + */ + private void waitUntilEmptyIs_(boolean empty) throws InterruptedException { + while (this.stack.isEmpty() != empty) { + this.mutex.wait(); } } @@ -121,18 +143,14 @@ public class SynchronizedStack * Suspend the current thread until the stack is empty. */ public void waitUntilEmpty() throws InterruptedException { - synchronized (this.mutex) { - this.waitUntilEmptyIs(true); - } + this.waitUntilEmptyIs(true); } /** * Suspend the current thread until the stack has something on it. */ public void waitUntilNotEmpty() throws InterruptedException { - synchronized (this.mutex) { - this.waitUntilEmptyIs(false); - } + this.waitUntilEmptyIs(false); } /** @@ -140,10 +158,10 @@ public class SynchronizedStack * then "push" the specified item on to the top of the stack * and continue executing. */ - public void waitToPush(E o) throws InterruptedException { + public void waitToPush(E element) throws InterruptedException { synchronized (this.mutex) { - this.waitUntilEmpty(); - this.push(o); + this.waitUntilEmptyIs_(true); + this.push_(element); } } @@ -153,8 +171,8 @@ public class SynchronizedStack */ public Object waitToPop() throws InterruptedException { synchronized (this.mutex) { - this.waitUntilNotEmpty(); - return this.pop(); + this.waitUntilEmptyIs_(false); + return this.pop_(); } } @@ -164,48 +182,58 @@ public class SynchronizedStack /** * Suspend the current thread until the stack's empty status changes * to the specified value or the specified time-out occurs. - * The time-out is specified in milliseconds. Return true if the specified - * value was achieved; return false if a time-out occurred. + * The time-out is specified in milliseconds. Return true if the specified + * empty status was achieved; return false if a time-out occurred. + * If the stack's empty status is already the specified value, + * return true immediately. + * If the time-out is zero, wait indefinitely. */ public boolean waitUntilEmptyIs(boolean empty, long timeout) throws InterruptedException { synchronized (this.mutex) { - if (timeout == 0L) { - this.waitUntilEmptyIs(empty); // wait indefinitely until notified - return true; // if it ever comes back, the condition was met - } + return this.waitUntilEmptyIs_(empty, timeout); + } + } - long stop = System.currentTimeMillis() + timeout; - long remaining = timeout; - while ((this.isEmpty() != empty) && (remaining > 0L)) { - this.mutex.wait(remaining); - remaining = stop - System.currentTimeMillis(); - } - return (this.isEmpty() == empty); + /** + * Pre-condition: synchronized + */ + private boolean waitUntilEmptyIs_(boolean empty, long timeout) throws InterruptedException { + if (timeout == 0L) { + this.waitUntilEmptyIs_(empty); // wait indefinitely until notified + return true; // if it ever comes back, the condition was met + } + + long stop = System.currentTimeMillis() + timeout; + long remaining = timeout; + while ((this.stack.isEmpty() != empty) && (remaining > 0L)) { + this.mutex.wait(remaining); + remaining = stop - System.currentTimeMillis(); } + return (this.stack.isEmpty() == empty); } /** * Suspend the current thread until the stack is empty * or the specified time-out occurs. - * The time-out is specified in milliseconds. Return true if - * the stack is empty; return false if a time-out occurred. + * The time-out is specified in milliseconds. Return true if + * the stack is empty; return false if a time-out occurred. + * If the stack is already empty, return true immediately. + * If the time-out is zero, wait indefinitely. */ public boolean waitUntilEmpty(long timeout) throws InterruptedException { - synchronized (this.mutex) { - return this.waitUntilEmptyIs(true, timeout); - } + return this.waitUntilEmptyIs(true, timeout); } /** * Suspend the current thread until the stack has something on it. * or the specified time-out occurs. - * The time-out is specified in milliseconds. Return true if - * the stack has something on it; return false if a time-out occurred. + * The time-out is specified in milliseconds. Return true if + * the stack is not empty; return false if a time-out occurred. + * If the stack already has something on it, return true immediately. + * If the time-out is zero, wait indefinitely. */ public boolean waitUntilNotEmpty(long timeout) throws InterruptedException { - synchronized (this.mutex) { - return this.waitUntilEmptyIs(false, timeout); - } + return this.waitUntilEmptyIs(false, timeout); } /** @@ -214,14 +242,17 @@ public class SynchronizedStack * and continue executing. If the stack is not emptied out * before the time-out, simply continue executing without * "pushing" the item. - * The time-out is specified in milliseconds. Return true if the - * item was pushed; return false if a time-out occurred. + * The time-out is specified in milliseconds. Return true if the + * item was pushed; return false if a time-out occurred. + * If the stack is already empty, "push" the specified item and + * return true immediately. + * If the time-out is zero, wait indefinitely. */ - public boolean waitToPush(E o, long timeout) throws InterruptedException { + public boolean waitToPush(E element, long timeout) throws InterruptedException { synchronized (this.mutex) { - boolean success = this.waitUntilEmpty(timeout); + boolean success = this.waitUntilEmptyIs_(true, timeout); if (success) { - this.push(o); + this.push_(element); } return success; } @@ -233,12 +264,15 @@ public class SynchronizedStack * If the stack is empty and nothing is "pushed" on to it before the * time-out, throw an empty stack exception. * The time-out is specified in milliseconds. + * If the stack is not empty, "pop" an item and + * return it immediately. + * If the time-out is zero, wait indefinitely. */ public Object waitToPop(long timeout) throws InterruptedException { synchronized (this.mutex) { - boolean success = this.waitUntilNotEmpty(timeout); + boolean success = this.waitUntilEmptyIs_(false, timeout); if (success) { - return this.pop(); + return this.pop_(); } throw new EmptyStackException(); } @@ -273,12 +307,12 @@ public class SynchronizedStack } - // ********** Object overrides ********** + // ********** standard methods ********** @Override public String toString() { synchronized (this.mutex) { - return this.stack.toString(); + return '[' + this.stack.toString() + ']'; } } diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/QueueIterable.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/QueueIterable.java new file mode 100644 index 0000000000..50819f3f9c --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/QueueIterable.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.utility.internal.Queue; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.iterators.QueueIterator; + +/** + * A QueueIterable provides an {@link Iterable} + * for a {@link Queue} of objects of type E. The queue's elements + * are {@link Queue#dequeue() dequeue}d" as the iterable's iterator returns + * them with calls to {@link Iterator#next()}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see Queue + * @see QueueIterator + */ +public class QueueIterable + implements Iterable +{ + private final Queue queue; + + /** + * Construct an iterable for the specified queue. + */ + public QueueIterable(Queue queue) { + super(); + this.queue = queue; + } + + public Iterator iterator() { + return new QueueIterator(this.queue); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.queue); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/StackIterable.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/StackIterable.java new file mode 100644 index 0000000000..a1e18318ed --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterables/StackIterable.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal.iterables; + +import java.util.Iterator; + +import org.eclipse.jpt.utility.internal.Stack; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.iterators.StackIterator; + +/** + * A StackIterable provides an {@link Iterable} + * for a {@link Stack} of objects of type E. The stack's elements + * are {@link Stack#pop() pop}ped" as the iterable's iterator returns + * them with calls to {@link Iterator#next()}. + * + * @param the type of elements returned by the iterable's iterator + * + * @see Stack + * @see StackIterator + */ +public class StackIterable + implements Iterable +{ + private final Stack stack; + + /** + * Construct an iterable for the specified stack. + */ + public StackIterable(Stack stack) { + super(); + this.stack = stack; + } + + public Iterator iterator() { + return new StackIterator(this.stack); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.stack); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/QueueIterator.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/QueueIterator.java new file mode 100644 index 0000000000..d7a1ff5cec --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/QueueIterator.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.utility.internal.Queue; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * A QueueIterator provides an {@link Iterator} + * for a {@link Queue} of objects of type E. The queue's elements + * are {@link Queue#dequeue() dequeue}d" as the iterator returns them with + * calls to {@link #next()}. + * + * @param the type of elements returned by the iterator + * + * @see Queue + */ +public class QueueIterator + implements Iterator +{ + private final Queue queue; + + + /** + * Construct an iterator for the specified queue. + */ + public QueueIterator(Queue queue) { + super(); + this.queue = queue; + } + + public boolean hasNext() { + return ! this.queue.isEmpty(); + } + + public E next() { + return this.queue.dequeue(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.queue); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/StackIterator.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/StackIterator.java new file mode 100644 index 0000000000..af510c8232 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/iterators/StackIterator.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.internal.iterators; + +import java.util.Iterator; + +import org.eclipse.jpt.utility.internal.Stack; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * A StackIterator provides an {@link Iterator} + * for a {@link Stack} of objects of type E. The stack's elements + * are {@link Stack#pop() pop}ped" as the iterator returns them with + * calls to {@link #next()}. + * + * @param the type of elements returned by the iterator + * + * @see Stack + */ +public class StackIterator + implements Iterator +{ + private final Stack stack; + + + /** + * Construct an iterator for the specified stack. + */ + public StackIterator(Stack stack) { + super(); + this.stack = stack; + } + + public boolean hasNext() { + return ! this.stack.isEmpty(); + } + + public E next() { + return this.stack.pop(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.stack); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/AsynchronousSynchronizer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/AsynchronousSynchronizer.java index 75d6fea7fa..970e94c738 100644 --- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/AsynchronousSynchronizer.java +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/AsynchronousSynchronizer.java @@ -9,11 +9,8 @@ ******************************************************************************/ package org.eclipse.jpt.utility.internal.synchronizers; -import java.util.Vector; - import org.eclipse.jpt.utility.Command; -import org.eclipse.jpt.utility.internal.CompositeException; -import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.jpt.utility.internal.ConsumerThreadCoordinator; import org.eclipse.jpt.utility.internal.SynchronizedBoolean; /** @@ -28,7 +25,7 @@ public class AsynchronousSynchronizer implements Synchronizer { /** - * This flag is shared with the synchronization thread. Setting it to true + * This flag is shared with the synchronization/consumer thread. Setting it to true * will trigger the synchronization to begin or, if the synchronization is * currently executing, to execute again, once the current execution is * complete. @@ -36,34 +33,16 @@ public class AsynchronousSynchronizer final SynchronizedBoolean synchronizeFlag = new SynchronizedBoolean(false); /** - * The runnable passed to the synchronization thread each time it is built. - */ - private final Runnable runnable; - - /** - * Optional, client-supplied name for the synchronization thread. - * If null, allow the JDK to assign a name. - */ - private final String threadName; - - /** - * The synchronization is performed on this thread. A new thread is built - * for every start/stop cycle (since a thread cannot be started more than - * once). + * Most of the thread-related behavior is delegated to this coordinator. */ - private Thread thread; - - /** - * A list of the uncaught exceptions thrown by the command. - */ - final Vector exceptions = new Vector(); + private final ConsumerThreadCoordinator consumerThreadCoordinator; // ********** construction ********** /** * Construct an asynchronous synchronizer that uses the specified command to - * perform the synchronization. Allow the generated thread(s) to be assigned + * perform the synchronization. Allow the synchronization thread(s) to be assigned * JDK-generated names. */ public AsynchronousSynchronizer(Command command) { @@ -72,17 +51,19 @@ public class AsynchronousSynchronizer /** * Construct an asynchronous synchronizer that uses the specified command to - * perform the synchronization. Assign the generated thread(s) the specified + * perform the synchronization. Assign the synchronization thread(s) the specified * name. */ public AsynchronousSynchronizer(Command command, String threadName) { super(); - this.runnable = this.buildRunnable(command); - this.threadName = threadName; + if (command == null) { + throw new NullPointerException(); + } + this.consumerThreadCoordinator = new ConsumerThreadCoordinator(this.buildConsumer(command), threadName); } - Runnable buildRunnable(Command command) { - return new RunnableSynchronization(command); + ConsumerThreadCoordinator.Consumer buildConsumer(Command command) { + return new Consumer(command); } @@ -106,20 +87,8 @@ public class AsynchronousSynchronizer * the time {@link #stop()} was called) * */ - public synchronized void start() { - if (this.thread != null) { - throw new IllegalStateException("The Synchronizer was not stopped."); //$NON-NLS-1$ - } - this.thread = this.buildThread(); - this.thread.start(); - } - - private Thread buildThread() { - Thread t = new Thread(this.runnable); - if (this.threadName != null) { - t.setName(this.threadName); - } - return t; + public void start() { + this.consumerThreadCoordinator.start(); } /** @@ -140,101 +109,49 @@ public class AsynchronousSynchronizer * exceptions were thrown while the synchronization thread was executing, * wrap them in a composite exception and throw the composite exception. */ - public synchronized void stop() { - if (this.thread == null) { - throw new IllegalStateException("The Synchronizer was not started."); //$NON-NLS-1$ - } - this.thread.interrupt(); - try { - this.thread.join(); - } catch (InterruptedException ex) { - // the thread that called #stop() was interrupted while waiting to - // join the synchronization thread - ignore; - // 'thread' is still "interrupted", so its #run() loop will still stop - // after its current execution - we just won't wait around for it... - } - this.thread = null; - - if (this.exceptions.size() > 0) { - Throwable[] temp = this.exceptions.toArray(new Throwable[this.exceptions.size()]); - this.exceptions.clear(); - throw new CompositeException(temp); - } - } - - @Override - public String toString() { - return StringTools.buildToStringFor(this, this.thread); + public void stop() { + this.consumerThreadCoordinator.stop(); } - // ********** synchronization thread runnable ********** + // ********** consumer ********** /** - * This implementation of {@link Runnable} will execute a client-supplied command. - * It will wait until a shared "synchronize" flag is set to execute the - * command. Once the the comand is executed, the thread will quiesce until + * This implementation of {@link ConsumerThreadCoordinator.Consumer} + * will execute the client-supplied "synchronize" command. + * It will wait until the shared "synchronize" flag is set to execute the + * command. Once the comand is executed, the thread will quiesce until * the flag is set again. If the flag was set during the execution of the * command (either recursively by the command itself or by another thread), * the command will be re-executed immediately. Stop the thread by calling * {@link Thread#interrupt()}. */ - class RunnableSynchronization - implements Runnable + class Consumer + implements ConsumerThreadCoordinator.Consumer { - /** The client-supplied command that executes on this thread. */ + /** + * The client-supplied command that executes on the + * synchronization/consumer thread. + */ private final Command command; - - RunnableSynchronization(Command command) { + Consumer(Command command) { super(); - if (command == null) { - throw new NullPointerException(); - } this.command = command; } /** - * Loop while this thread has not been interrupted by another thread. - * In each loop: Wait until the "synchronize" flag is set, - * then clear it and execute the command. If the - * "synchronize" flag was set during the synchronization, - * there will be no "wait" before beginning the next synchronization - * (thus the call to {@link Thread#isInterrupted()} before each cycle). - *

- * If this thread is interrupted during the synchronization, the - * call to {@link Thread#interrupted()} will stop the loop. If this thread is - * interrupted during the call to {@link SynchronizedBoolean#waitToSetFalse()}, - * we will catch the {@link InterruptedException} and stop the loop. - */ - public void run() { - while ( ! Thread.interrupted()) { - try { - AsynchronousSynchronizer.this.synchronizeFlag.waitToSetFalse(); - } catch (InterruptedException ex) { - // we were interrupted while waiting, must be Quittin' Time - return; - } - this.execute(); - } - } - - /** - * Execute the client-supplied command. Do not allow any unhandled - * exceptions to kill the thread. Store them up for later pain. + * Wait until the "synchronize" flag is set, + * then clear it and allow the "synchronize" command to execute. */ - private void execute() { - try { - this.execute_(); - } catch (Throwable ex) { - AsynchronousSynchronizer.this.exceptions.add(ex); - } + public void waitForProducer() throws InterruptedException { + AsynchronousSynchronizer.this.synchronizeFlag.waitToSetFalse(); } /** * Execute the client-supplied command. */ - void execute_() { + public void execute() { this.command.execute(); } diff --git a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java index 28c610d642..2c30a3241f 100644 --- a/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java +++ b/jpa/plugins/org.eclipse.jpt.utility/src/org/eclipse/jpt/utility/internal/synchronizers/CallbackAsynchronousSynchronizer.java @@ -10,15 +10,16 @@ package org.eclipse.jpt.utility.internal.synchronizers; import org.eclipse.jpt.utility.Command; +import org.eclipse.jpt.utility.internal.ConsumerThreadCoordinator; import org.eclipse.jpt.utility.internal.ListenerList; /** * Extend the asynchronous synchronizer to notify listeners * when a synchronization "cycle" is complete; i.e. the synchronization has, - * for the moment, handled every "synchronize" request and quiesced. + * for the moment, handled every outstanding "synchronize" request and quiesced. * This notification is not guaranteed to occur with every - * synchronization "cycle"; - * since other, unrelated, synchronizations can be triggered concurrently. + * synchronization "cycle"; since other, unrelated, synchronizations can be + * triggered concurrently. *

* NB: Listeners should handle any exceptions * appropriately (e.g. log the exception and return gracefully so the thread @@ -35,7 +36,7 @@ public class CallbackAsynchronousSynchronizer /** * Construct a callback asynchronous synchronizer that uses the specified - * command to perform the synchronization. Allow the generated thread(s) + * command to perform the synchronization. Allow the synchronization thread(s) * to be assigned JDK-generated names. */ public CallbackAsynchronousSynchronizer(Command command) { @@ -44,7 +45,7 @@ public class CallbackAsynchronousSynchronizer /** * Construct a callback asynchronous synchronizer that uses the specified - * command to perform the synchronization. Assign the generated thread(s) + * command to perform the synchronization. Assign the synchronization thread(s) * the specified name. */ public CallbackAsynchronousSynchronizer(Command command, String threadName) { @@ -52,12 +53,12 @@ public class CallbackAsynchronousSynchronizer } /** - * Build a runnable that will let us know when the synchronization has + * Build a consumer that will let us know when the synchronization has * quiesced. */ @Override - Runnable buildRunnable(Command command) { - return new RunnableCallbackSynchronization(command); + ConsumerThreadCoordinator.Consumer buildConsumer(Command command) { + return new CallbackConsumer(command); } @@ -84,7 +85,7 @@ public class CallbackAsynchronousSynchronizer // ********** synchronization thread runnable ********** /** - * Extend {@link AsynchronousSynchronizer.RunnableSynchronization} + * Extend {@link AsynchronousSynchronizer.Consumer} * to notify the synchronizer when the synchronization has quiesced * (i.e. the command has finished executing and there are no further * requests for synchronization). @@ -96,16 +97,16 @@ public class CallbackAsynchronousSynchronizer * but this synchronization will not occur until after all the * listeners have been notified. */ - class RunnableCallbackSynchronization - extends RunnableSynchronization + class CallbackConsumer + extends Consumer { - RunnableCallbackSynchronization(Command command) { + CallbackConsumer(Command command) { super(command); } @Override - void execute_() { - super.execute_(); + public void execute() { + super.execute(); // hmmm - we will notify listeners even when we our thread is "interrupted"; // that seems ok... ~bjv if (CallbackAsynchronousSynchronizer.this.synchronizeFlag.isFalse()) { diff --git a/jpa/tests/org.eclipse.jpt.core.tests/META-INF/MANIFEST.MF b/jpa/tests/org.eclipse.jpt.core.tests/META-INF/MANIFEST.MF index 3a5123b641..6696ad1f40 100644 --- a/jpa/tests/org.eclipse.jpt.core.tests/META-INF/MANIFEST.MF +++ b/jpa/tests/org.eclipse.jpt.core.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-Vendor: %providerName -Bundle-SymbolicName: org.eclipse.jpt.core.tests +Bundle-SymbolicName: org.eclipse.jpt.core.tests;singleton:=true Bundle-Version: 2.3.0.qualifier Bundle-Localization: plugin Require-Bundle: org.eclipse.core.commands;bundle-version="[3.4.0,4.0.0)", @@ -23,7 +23,8 @@ Require-Bundle: org.eclipse.core.commands;bundle-version="[3.4.0,4.0.0)", org.eclipse.wst.common.project.facet.core;bundle-version="[1.3.0,2.0.0)", org.junit;bundle-version="[3.8.2,4.0.0)" Bundle-RequiredExecutionEnvironment: J2SE-1.5 -Export-Package: org.eclipse.jpt.core.tests.internal;x-friends:="org.eclipse.jpt.ui.tests", +Export-Package: org.eclipse.jpt.core.tests, + org.eclipse.jpt.core.tests.internal;x-friends:="org.eclipse.jpt.ui.tests", org.eclipse.jpt.core.tests.internal.context;x-internal:=true, org.eclipse.jpt.core.tests.internal.context.java;x-internal:=true, org.eclipse.jpt.core.tests.internal.context.orm;x-internal:=true, @@ -38,3 +39,5 @@ Export-Package: org.eclipse.jpt.core.tests.internal;x-friends:="org.eclipse.jpt. org.eclipse.jpt.core.tests.internal.resource;x-internal:=true, org.eclipse.jpt.core.tests.internal.resource.java;x-internal:=true, org.eclipse.jpt.core.tests.internal.utility.jdt;x-internal:=true +Bundle-Activator: org.eclipse.jpt.core.tests.JptCoreTestsPlugin +Bundle-ActivationPolicy: lazy diff --git a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/JptCoreTestsPlugin.java b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/JptCoreTestsPlugin.java new file mode 100644 index 0000000000..2e3978e5bb --- /dev/null +++ b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/JptCoreTestsPlugin.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.core.tests; + +import org.eclipse.core.runtime.Plugin; +import org.eclipse.jpt.core.JpaModel; +import org.eclipse.jpt.core.JptCorePlugin; +import org.eclipse.jpt.utility.internal.ClassTools; +import org.osgi.framework.BundleContext; + +/** + * configure the core to handle events synchronously when we are + * running tests + */ +@SuppressWarnings("nls") +public class JptCoreTestsPlugin extends Plugin { + + private static JptCoreTestsPlugin INSTANCE; + + public static JptCoreTestsPlugin instance() { + return INSTANCE; + } + + + // ********** plug-in implementation ********** + + public JptCoreTestsPlugin() { + super(); + if (INSTANCE != null) { + throw new IllegalStateException(); + } + // this convention is *wack*... ~bjv + INSTANCE = this; + } + + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + JpaModel jpaModel = JptCorePlugin.getJpaModel(); + ClassTools.executeMethod(jpaModel, "handleEventsSynchronously"); + } + + @Override + public void stop(BundleContext context) throws Exception { + super.stop(context); + } + +} diff --git a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/MiscTests.java b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/MiscTests.java new file mode 100644 index 0000000000..72c9ad75ac --- /dev/null +++ b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/MiscTests.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.core.tests; + +import junit.framework.TestCase; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.Job; + +@SuppressWarnings("nls") +public class MiscTests extends TestCase { + + public MiscTests(String name) { + super(name); + } + + /* + * + */ + public void testJobsAndLocks() throws Exception { + ILock lock = Job.getJobManager().newLock(); + Job testJob = new TestJob(lock); + testJob.schedule(); + } + + class TestJob extends Job { + private final ILock lock; + TestJob(ILock lock) { + super("test job"); + this.lock = lock; + } + @Override + protected IStatus run(IProgressMonitor monitor) { + this.run(); + return Status.OK_STATUS; + } + private void run() { + try { + this.lock.acquire(); + MiscTests.sleep(100); + } finally { + this.lock.release(); + } + } + } + + static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/context/ContextModelTestCase.java b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/context/ContextModelTestCase.java index 1f08b71499..0c72eb0ff3 100644 --- a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/context/ContextModelTestCase.java +++ b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/context/ContextModelTestCase.java @@ -63,7 +63,7 @@ public abstract class ContextModelTestCase extends AnnotationTestCase super.setUp(); this.persistenceXmlResource = getJpaProject().getPersistenceXmlResource(); this.ormXmlResource = getJpaProject().getDefaultOrmXmlResource(); - waitForWorkspaceJobs(); + this.waitForWorkspaceJobsToFinish(); } @Override @@ -71,6 +71,7 @@ public abstract class ContextModelTestCase extends AnnotationTestCase this.persistenceXmlResource = null; this.ormXmlResource = null; JptCorePlugin.getWorkspacePreferences().clear(); + this.waitForWorkspaceJobsToFinish(); super.tearDown(); } @@ -99,8 +100,8 @@ public abstract class ContextModelTestCase extends AnnotationTestCase return getJavaProject().getJpaProject(); } - protected void waitForWorkspaceJobs() { - // This job will not finish running until the workspace jobs are done + protected void waitForWorkspaceJobsToFinish() throws InterruptedException { + // This job will not start running until all the other workspace jobs are done Job waitJob = new Job("Wait job") { @Override protected IStatus run(IProgressMonitor monitor) { @@ -109,11 +110,7 @@ public abstract class ContextModelTestCase extends AnnotationTestCase }; waitJob.setRule(ResourcesPlugin.getWorkspace().getRoot()); waitJob.schedule(); - try { - waitJob.join(); - } catch (InterruptedException ex) { - // the job thread was interrupted during a wait - ignore - } + waitJob.join(); } protected JpaXmlResource getPersistenceXmlResource() { diff --git a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/model/JpaModelTests.java b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/model/JpaModelTests.java index b47fb0b549..8145ff2f7a 100644 --- a/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/model/JpaModelTests.java +++ b/jpa/tests/org.eclipse.jpt.core.tests/src/org/eclipse/jpt/core/tests/internal/model/JpaModelTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2008 Oracle. All rights reserved. + * Copyright (c) 2006, 2009 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. @@ -9,18 +9,14 @@ ******************************************************************************/ package org.eclipse.jpt.core.tests.internal.model; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import junit.framework.TestCase; + import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Path; import org.eclipse.jpt.core.JpaProject; import org.eclipse.jpt.core.JptCorePlugin; -import org.eclipse.jpt.core.internal.GenericJpaModel; -import org.eclipse.jpt.core.internal.JpaModelManager; import org.eclipse.jpt.core.tests.internal.projects.TestFacetedProject; import org.eclipse.jpt.core.tests.internal.projects.TestJavaProject; import org.eclipse.jpt.core.tests.internal.projects.TestPlatformProject; @@ -47,10 +43,15 @@ public class JpaModelTests extends TestCase { } private boolean debug() { - Boolean debug = (Boolean) ClassTools.staticFieldValue(JpaModelManager.class, "DEBUG"); + Boolean debug = (Boolean) ClassTools.staticFieldValue(this.getGenericJpaModelClass(), "DEBUG"); return debug.booleanValue(); } + // GenericJpaModel is package-private + private Class getGenericJpaModelClass() { + return JptCorePlugin.getJpaModel().getClass(); + } + private void printName() { String name = this.getName(); System.out.println(); @@ -94,8 +95,7 @@ public class JpaModelTests extends TestCase { * make sure the DEBUG constants are 'false' before checking in the code */ public void testDEBUG() { - this.verifyDEBUG(JpaModelManager.class); - this.verifyDEBUG(GenericJpaModel.class); + this.verifyDEBUG(this.getGenericJpaModelClass()); } private void verifyDEBUG(Class clazz) { @@ -131,12 +131,12 @@ public class JpaModelTests extends TestCase { this.testProject.installFacet(JptCorePlugin.FACET_ID, "1.0"); JpaProject jpaProject = JptCorePlugin.getJpaProject(this.testProject.getProject()); assertNotNull(jpaProject); - assertEquals(1, JptCorePlugin.getJpaModel().jpaProjectsSize()); + assertEquals(1, JptCorePlugin.getJpaModel().getJpaProjectsSize()); this.testProject.getProject().delete(false, true, null); jpaProject = JptCorePlugin.getJpaProject(this.testProject.getProject()); assertNull(jpaProject); - assertEquals(0, JptCorePlugin.getJpaModel().jpaProjectsSize()); + assertEquals(0, JptCorePlugin.getJpaModel().getJpaProjectsSize()); assertEquals(0, ResourcesPlugin.getWorkspace().getRoot().getProjects().length); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(this.testProject.getProject().getName()); @@ -159,7 +159,7 @@ public class JpaModelTests extends TestCase { assertNull(JptCorePlugin.getJpaProject(this.testProject.getProject())); this.testProject.installFacet(JptCorePlugin.FACET_ID, "1.0"); - assertEquals(1, JptCorePlugin.getJpaModel().jpaProjectsSize()); + assertEquals(1, JptCorePlugin.getJpaModel().getJpaProjectsSize()); JpaProject jpaProject = JptCorePlugin.getJpaProject(this.testProject.getProject()); assertNotNull(jpaProject); assertEquals(4, jpaProject.jpaFilesSize()); @@ -170,7 +170,7 @@ public class JpaModelTests extends TestCase { assertNotNull(jpaProject.getJpaFile(this.getFile(this.testProject, "src/META-INF/orm.xml"))); this.testProject.uninstallFacet(JptCorePlugin.FACET_ID, "1.0"); - assertEquals(0, JptCorePlugin.getJpaModel().jpaProjectsSize()); + assertEquals(0, JptCorePlugin.getJpaModel().getJpaProjectsSize()); jpaProject = JptCorePlugin.getJpaProject(this.testProject.getProject()); assertNull(jpaProject); } diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleQueueTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleQueueTests.java new file mode 100644 index 0000000000..8ac603921f --- /dev/null +++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleQueueTests.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.tests.internal; + +import java.util.NoSuchElementException; + +import junit.framework.TestCase; + +import org.eclipse.jpt.utility.internal.Queue; +import org.eclipse.jpt.utility.internal.SimpleQueue; + +@SuppressWarnings("nls") +public class SimpleQueueTests extends TestCase { + + public SimpleQueueTests(String name) { + super(name); + } + + Queue buildQueue() { + return new SimpleQueue(); + } + + public void testIsEmpty() { + Queue queue = this.buildQueue(); + assertTrue(queue.isEmpty()); + queue.enqueue("first"); + assertFalse(queue.isEmpty()); + queue.enqueue("second"); + assertFalse(queue.isEmpty()); + queue.dequeue(); + assertFalse(queue.isEmpty()); + queue.dequeue(); + assertTrue(queue.isEmpty()); + } + + public void testEnqueueAndDequeue() { + Queue queue = this.buildQueue(); + String first = "first"; + String second = "second"; + + queue.enqueue(first); + queue.enqueue(second); + assertEquals(first, queue.dequeue()); + assertEquals(second, queue.dequeue()); + } + + public void testEnqueueAndPeek() { + Queue queue = this.buildQueue(); + String first = "first"; + String second = "second"; + + queue.enqueue(first); + queue.enqueue(second); + assertEquals(first, queue.peek()); + assertEquals(first, queue.peek()); + assertEquals(first, queue.dequeue()); + assertEquals(second, queue.peek()); + assertEquals(second, queue.peek()); + assertEquals(second, queue.dequeue()); + } + + public void testEmptyQueueExceptionPeek() { + Queue queue = this.buildQueue(); + String first = "first"; + String second = "second"; + + queue.enqueue(first); + queue.enqueue(second); + assertEquals(first, queue.peek()); + assertEquals(first, queue.dequeue()); + assertEquals(second, queue.peek()); + assertEquals(second, queue.dequeue()); + + boolean exCaught = false; + try { + queue.peek(); + fail(); + } catch (NoSuchElementException ex) { + exCaught = true; + } + assertTrue(exCaught); + } + + public void testEmptyQueueExceptionDequeue() { + Queue queue = this.buildQueue(); + String first = "first"; + String second = "second"; + + queue.enqueue(first); + queue.enqueue(second); + assertEquals(first, queue.peek()); + assertEquals(first, queue.dequeue()); + assertEquals(second, queue.peek()); + assertEquals(second, queue.dequeue()); + + boolean exCaught = false; + try { + queue.dequeue(); + fail(); + } catch (NoSuchElementException ex) { + exCaught = true; + } + assertTrue(exCaught); + } + + public void testClone() { + SimpleQueue queue = (SimpleQueue) this.buildQueue(); + queue.enqueue("first"); + queue.enqueue("second"); + queue.enqueue("third"); + + this.verifyClone(queue, queue.clone()); + } + + public void testSerialization() throws Exception { + Queue queue = this.buildQueue(); + queue.enqueue("first"); + queue.enqueue("second"); + queue.enqueue("third"); + + this.verifyClone(queue, TestTools.serialize(queue)); + } + + private void verifyClone(Queue original, Queue clone) { + assertNotSame(original, clone); + assertEquals(original.peek(), clone.peek()); + assertEquals(original.dequeue(), clone.dequeue()); + assertEquals(original.peek(), clone.peek()); + assertEquals(original.dequeue(), clone.dequeue()); + assertEquals(original.isEmpty(), clone.isEmpty()); + assertEquals(original.peek(), clone.peek()); + assertEquals(original.dequeue(), clone.dequeue()); + assertTrue(original.isEmpty()); + assertEquals(original.isEmpty(), clone.isEmpty()); + + original.enqueue("fourth"); + assertFalse(original.isEmpty()); + // clone should still be empty + assertTrue(clone.isEmpty()); + } + +} diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleStackTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleStackTests.java index f1e97895c4..4d3de7b5bf 100644 --- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleStackTests.java +++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SimpleStackTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007 Oracle. All rights reserved. + * Copyright (c) 2007, 2009 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. @@ -14,14 +14,19 @@ import junit.framework.TestCase; import org.eclipse.jpt.utility.internal.SimpleStack; import org.eclipse.jpt.utility.internal.Stack; +@SuppressWarnings("nls") public class SimpleStackTests extends TestCase { public SimpleStackTests(String name) { super(name); } + Stack buildStack() { + return new SimpleStack(); + } + public void testIsEmpty() { - Stack stack = new SimpleStack(); + Stack stack = this.buildStack(); assertTrue(stack.isEmpty()); stack.push("first"); assertFalse(stack.isEmpty()); @@ -34,7 +39,7 @@ public class SimpleStackTests extends TestCase { } public void testPushAndPop() { - Stack stack = new SimpleStack(); + Stack stack = this.buildStack(); String first = "first"; String second = "second"; @@ -45,7 +50,7 @@ public class SimpleStackTests extends TestCase { } public void testPushAndPeek() { - Stack stack = new SimpleStack(); + Stack stack = this.buildStack(); String first = "first"; String second = "second"; @@ -60,7 +65,7 @@ public class SimpleStackTests extends TestCase { } public void testEmptyStackExceptionPeek() { - Stack stack = new SimpleStack(); + Stack stack = this.buildStack(); String first = "first"; String second = "second"; @@ -74,6 +79,7 @@ public class SimpleStackTests extends TestCase { boolean exCaught = false; try { stack.peek(); + fail(); } catch (EmptyStackException ex) { exCaught = true; } @@ -81,7 +87,7 @@ public class SimpleStackTests extends TestCase { } public void testEmptyStackExceptionPop() { - Stack stack = new SimpleStack(); + Stack stack = this.buildStack(); String first = "first"; String second = "second"; @@ -95,6 +101,7 @@ public class SimpleStackTests extends TestCase { boolean exCaught = false; try { stack.pop(); + fail(); } catch (EmptyStackException ex) { exCaught = true; } @@ -102,7 +109,7 @@ public class SimpleStackTests extends TestCase { } public void testClone() { - SimpleStack stack = new SimpleStack(); + SimpleStack stack = (SimpleStack) this.buildStack(); stack.push("first"); stack.push("second"); stack.push("third"); @@ -111,7 +118,7 @@ public class SimpleStackTests extends TestCase { } public void testSerialization() throws Exception { - SimpleStack stack = new SimpleStack(); + Stack stack = this.buildStack(); stack.push("first"); stack.push("second"); stack.push("third"); diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedQueueTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedQueueTests.java new file mode 100644 index 0000000000..c95262682d --- /dev/null +++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedQueueTests.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * Copyright (c) 2009 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.utility.tests.internal; + +import java.util.NoSuchElementException; + +import org.eclipse.jpt.utility.internal.Queue; +import org.eclipse.jpt.utility.internal.SimpleQueue; +import org.eclipse.jpt.utility.internal.SynchronizedQueue; + +@SuppressWarnings("nls") +public class SynchronizedQueueTests extends SimpleQueueTests { + private volatile SynchronizedQueue sq; + private volatile boolean exCaught; + private volatile boolean timeoutOccurred; + private volatile long startTime; + private volatile long endTime; + private volatile Object dequeuedObject; + + static final String ITEM_1 = new String(); + static final String ITEM_2 = new String(); + + public SynchronizedQueueTests(String name) { + super(name); + } + + @Override + Queue buildQueue() { + return new SynchronizedQueue(); + } + + @Override + public void testClone() { + // synchronized queue is not cloneable + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + this.sq = new SynchronizedQueue(); + this.exCaught = false; + this.timeoutOccurred = false; + this.startTime = 0; + this.endTime = 0; + this.dequeuedObject = null; + } + + /** + * test first with an unsynchronized queue, + * then with a synchronized queue + */ + public void testConcurrentAccess() throws Exception { + this.verifyConcurrentAccess(new SlowSimpleQueue(), "first"); + this.verifyConcurrentAccess(new SlowSynchronizedQueue(), "second"); + } + + private void verifyConcurrentAccess(SlowQueue slowQueue, String expected) throws Exception { + slowQueue.enqueue("first"); + slowQueue.enqueue("second"); + + Thread thread = new Thread(this.buildRunnable(slowQueue)); + thread.start(); + Thread.sleep(200); + + assertEquals(expected, slowQueue.dequeue()); + thread.join(); + assertTrue(slowQueue.isEmpty()); + } + + private Runnable buildRunnable(final SlowQueue slowQueue) { + return new Runnable() { + public void run() { + slowQueue.slowDequeue(); + } + }; + } + + + private interface SlowQueue extends Queue { + Object slowDequeue(); + } + + private class SlowSimpleQueue extends SimpleQueue implements SlowQueue { + SlowSimpleQueue() { + super(); + } + public Object slowDequeue() { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + return this.dequeue(); + } + + } + + private class SlowSynchronizedQueue extends SynchronizedQueue implements SlowQueue { + SlowSynchronizedQueue() { + super(); + } + public synchronized Object slowDequeue() { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + return this.dequeue(); + } + + } + + + // ********** waits ********** + + public void testWaitToDequeue() throws Exception { + this.verifyWaitToDequeue(0); + // no timeout occurs... + assertFalse(this.timeoutOccurred); + // ...and an item should have been dequeued by t2... + assertSame(ITEM_1, this.dequeuedObject); + // ...and the queue should be empty + assertTrue(this.sq.isEmpty()); + // make a reasonable guess about how long t2 took + assertTrue(this.elapsedTime() > 150); + } + + public void testWaitToDequeueTimeout() throws Exception { + this.verifyWaitToDequeue(20); + // timeout occurs... + assertTrue(this.timeoutOccurred); + // ...and the queue was never dequeued... + assertNull(this.dequeuedObject); + // ...and it still holds the item + assertSame(ITEM_1, this.sq.peek()); + // make a reasonable guess about how long t2 took + assertTrue(this.elapsedTime() < 150); + } + + private void verifyWaitToDequeue(long timeout) throws Exception { + Runnable r1 = this.buildRunnable(this.buildEnqueueCommand(), this.sq, 200); + Runnable r2 = this.buildRunnable(this.buildWaitToDequeueCommand(timeout), this.sq, 0); + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + t1.start(); + t2.start(); + while (t1.isAlive() || t2.isAlive()) { + Thread.sleep(50); + } + assertFalse(this.exCaught); + } + + public void testWaitToEnqueue() throws Exception { + this.verifyWaitToEnqueue(0); + // no timeout occurs... + assertFalse(this.timeoutOccurred); + // ...and the queue gets dequeued by t1... + assertSame(ITEM_1, this.dequeuedObject); + // ...and an item is enqueued on to the queue by t2 + assertFalse(this.sq.isEmpty()); + assertSame(ITEM_2, this.sq.peek()); + // make a reasonable guess about how long t2 took + assertTrue(this.elapsedTime() > 150); + } + + public void testWaitToEnqueueTimeout() throws Exception { + this.verifyWaitToEnqueue(20); + // timeout occurs... + assertTrue(this.timeoutOccurred); + // ...and the queue is eventually dequeued by t1... + assertSame(ITEM_1, this.dequeuedObject); + // ...but nothing is enqueued on to the queue by t2 + assertTrue(this.sq.isEmpty()); + // make a reasonable guess about how long t2 took + assertTrue(this.elapsedTime() < 150); + } + + private void verifyWaitToEnqueue(long timeout) throws Exception { + this.sq.enqueue(ITEM_1); + Runnable r1 = this.buildRunnable(this.buildDequeueCommand(), this.sq, 200); + Runnable r2 = this.buildRunnable(this.buildWaitToEnqueueCommand(timeout), this.sq, 0); + Thread t1 = new Thread(r1); + Thread t2 = new Thread(r2); + t1.start(); + t2.start(); + while (t1.isAlive() || t2.isAlive()) { + Thread.sleep(50); + } + assertFalse(this.exCaught); + } + + private Command buildEnqueueCommand() { + return new Command() { + public void execute(SynchronizedQueue synchronizedQueue) { + synchronizedQueue.enqueue(ITEM_1); + } + }; + } + + private Command buildWaitToDequeueCommand(final long timeout) { + return new Command() { + public void execute(SynchronizedQueue synchronizedQueue) throws Exception { + SynchronizedQueueTests.this.setStartTime(System.currentTimeMillis()); + try { + SynchronizedQueueTests.this.setDequeuedObject(synchronizedQueue.waitToDequeue(timeout)); + } catch (NoSuchElementException ex) { + SynchronizedQueueTests.this.setTimeoutOccurred(true); + } + SynchronizedQueueTests.this.setEndTime(System.currentTimeMillis()); + } + }; + } + + private Command buildDequeueCommand() { + return new Command() { + public void execute(SynchronizedQueue synchronizedQueue) { + SynchronizedQueueTests.this.setDequeuedObject(synchronizedQueue.dequeue()); + } + }; + } + + private Command buildWaitToEnqueueCommand(final long timeout) { + return new Command() { + public void execute(SynchronizedQueue synchronizedQueue) throws Exception { + SynchronizedQueueTests.this.setStartTime(System.currentTimeMillis()); + SynchronizedQueueTests.this.setTimeoutOccurred( ! synchronizedQueue.waitToEnqueue(ITEM_2, timeout)); + SynchronizedQueueTests.this.setEndTime(System.currentTimeMillis()); + } + }; + } + + private Runnable buildRunnable(final Command command, final SynchronizedQueue synchronizedQueue, final long sleep) { + return new Runnable() { + public void run() { + try { + if (sleep != 0) { + Thread.sleep(sleep); + } + command.execute(synchronizedQueue); + } catch (Exception ex) { + SynchronizedQueueTests.this.setExCaught(true); + } + } + }; + } + + void setExCaught(boolean exCaught) { + this.exCaught = exCaught; + } + + void setTimeoutOccurred(boolean timeoutOccurred) { + this.timeoutOccurred = timeoutOccurred; + } + + void setStartTime(long startTime) { + this.startTime = startTime; + } + + void setEndTime(long endTime) { + this.endTime = endTime; + } + + void setDequeuedObject(Object dequeuedObject) { + this.dequeuedObject = dequeuedObject; + } + + long elapsedTime() { + return this.endTime - this.startTime; + } + + + // ********** Command interface ********** + + private interface Command { + void execute(SynchronizedQueue synchronizedQueue) throws Exception; + } + +} diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedStackTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedStackTests.java index 50c353c2de..ca187b5cb3 100644 --- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedStackTests.java +++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/SynchronizedStackTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007 Oracle. All rights reserved. + * Copyright (c) 2007, 2009 Oracle. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0, which accompanies this distribution * and is available at http://www.eclipse.org/legal/epl-v10.html. @@ -14,6 +14,7 @@ import org.eclipse.jpt.utility.internal.SimpleStack; import org.eclipse.jpt.utility.internal.Stack; import org.eclipse.jpt.utility.internal.SynchronizedStack; +@SuppressWarnings("nls") public class SynchronizedStackTests extends SimpleStackTests { private volatile SynchronizedStack ss; private volatile boolean exCaught; @@ -29,6 +30,16 @@ public class SynchronizedStackTests extends SimpleStackTests { super(name); } + @Override + Stack buildStack() { + return new SynchronizedStack(); + } + + @Override + public void testClone() { + // synchronized stack is not cloneable + } + @Override protected void setUp() throws Exception { super.setUp(); @@ -53,10 +64,13 @@ public class SynchronizedStackTests extends SimpleStackTests { slowStack.push("first"); slowStack.push("second"); - new Thread(this.buildRunnable(slowStack)).start(); + Thread thread = new Thread(this.buildRunnable(slowStack)); + thread.start(); Thread.sleep(200); assertEquals(expected, slowStack.pop()); + thread.join(); + assertTrue(slowStack.isEmpty()); } private Runnable buildRunnable(final SlowStack slowStack) { @@ -93,7 +107,7 @@ public class SynchronizedStackTests extends SimpleStackTests { } public synchronized Object slowPop() { try { - Thread.sleep(100); + Thread.sleep(500); } catch (InterruptedException ex) { throw new RuntimeException(ex); } diff --git a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/synchronizers/AsynchronousSynchronizerTests.java b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/synchronizers/AsynchronousSynchronizerTests.java index 9c16348a64..7a6a82f517 100644 --- a/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/synchronizers/AsynchronousSynchronizerTests.java +++ b/jpa/tests/org.eclipse.jpt.utility.tests/src/org/eclipse/jpt/utility/tests/internal/synchronizers/AsynchronousSynchronizerTests.java @@ -14,6 +14,7 @@ import junit.framework.TestCase; import org.eclipse.jpt.utility.Command; import org.eclipse.jpt.utility.internal.ClassTools; import org.eclipse.jpt.utility.internal.CompositeException; +import org.eclipse.jpt.utility.internal.ConsumerThreadCoordinator; import org.eclipse.jpt.utility.internal.synchronizers.AsynchronousSynchronizer; import org.eclipse.jpt.utility.internal.synchronizers.Synchronizer; @@ -57,7 +58,8 @@ public class AsynchronousSynchronizerTests extends TestCase { } protected static void stop(Synchronizer synchronizer) { - if (ClassTools.fieldValue(synchronizer, "thread") != null) { + ConsumerThreadCoordinator ctc = (ConsumerThreadCoordinator) ClassTools.fieldValue(synchronizer, "consumerThreadCoordinator"); + if (ClassTools.fieldValue(ctc, "thread") != null) { synchronizer.stop(); } } @@ -166,7 +168,8 @@ public class AsynchronousSynchronizerTests extends TestCase { public void testThreadName() { Synchronizer s = new AsynchronousSynchronizer(this.command1, "sync"); s.start(); - Thread t = (Thread) ClassTools.fieldValue(s, "thread"); + ConsumerThreadCoordinator ctc = (ConsumerThreadCoordinator) ClassTools.fieldValue(s, "consumerThreadCoordinator"); + Thread t = (Thread) ClassTools.fieldValue(ctc, "thread"); assertEquals("sync", t.getName()); s.stop(); } -- cgit v1.2.3