| /******************************************************************************* |
| * Copyright (c) 2010, 2012 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.jaxb.core; |
| |
| import java.util.Vector; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| 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.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| 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.common.core.internal.JptCommonCoreMessages; |
| import org.eclipse.jpt.common.core.internal.utility.CallbackJobSynchronizer; |
| import org.eclipse.jpt.common.core.internal.utility.JobSynchronizer; |
| import org.eclipse.jpt.common.core.utility.command.JobCommand; |
| import org.eclipse.jpt.common.utility.ExceptionHandler; |
| import org.eclipse.jpt.common.utility.command.Command; |
| import org.eclipse.jpt.common.utility.command.StatefulCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean; |
| import org.eclipse.jpt.common.utility.internal.command.AsynchronousExtendedCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.command.SimpleStatefulExtendedCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.iterables.LiveCloneIterable; |
| import org.eclipse.jpt.common.utility.internal.model.AbstractModel; |
| import org.eclipse.jpt.common.utility.synchronizers.CallbackSynchronizer; |
| import org.eclipse.jpt.common.utility.synchronizers.Synchronizer; |
| import org.eclipse.jpt.jaxb.core.internal.JptJaxbCoreMessages; |
| import org.eclipse.jpt.jaxb.core.internal.SimpleJaxbProjectConfig; |
| import org.eclipse.jpt.jaxb.core.platform.JaxbPlatformDefinition; |
| import org.eclipse.osgi.util.NLS; |
| 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; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * The JAXB project manager maintains a list of all JAXB projects in the workspace. |
| * It keeps the list (and the state of the JAXB projects themselves) |
| * synchronized with the workspace by listening for various |
| * changes:<ul> |
| * <li>Resource |
| * <li>Java |
| * <li>Faceted Project |
| * </ul> |
| * We use an Eclipse {@link ILock lock} to synchronize access to the JAXB |
| * 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. |
| * <p> |
| * Events that trigger either the adding or removing of a JAXB project (e.g. |
| * {@link IResourceChangeEvent#POST_CHANGE}) are handled "synchronously" |
| * by allowing the background thread to handle any outstanding events before |
| * updating the list of JAXB projects and returning execution to the event |
| * source. |
| * <p> |
| * Various things that cause us to add or remove a JAXB project:<ul> |
| * <li>The {@link JptJaxbCorePlugin} will "lazily" instantiate and {@link #start() start} |
| * a JAXB project manager as appropriate. This will trigger the manager |
| * to find and add all pre-existing JAXB projects. |
| * |
| * <li>Project created and facet installed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * <li>Project facet uninstalled<p> |
| * {@link IFacetedProjectEvent.Type#PRE_UNINSTALL} |
| * |
| * <li>Project opened<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#ADDED} facet settings file |
| * (<code>/.settings/org.eclipse.wst.common.project.facet.core.xml</code>) |
| * <li>Project closed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file |
| * |
| * <li>Pre-existing project imported from directory or archive (created and opened)<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#ADDED} facet settings file |
| * <li>Project renamed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file of old project |
| * -> {@link IResourceDelta#ADDED} facet settings file of new project |
| * <li>Project deleted<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file |
| * |
| * <li>Project facet installed by editing the facets settings file directly<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#CHANGED} facet settings file |
| * <li>Project facet uninstalled by editing the facets settings file directly<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#CHANGED} facet settings file |
| * </ul> |
| */ |
| //TODO Still need to look at faceted project listener for facet uninstall |
| class GenericJaxbProjectManager |
| extends AbstractModel |
| implements JaxbProjectManager { |
| |
| /** |
| * All the JAXB projects in the workspace. |
| */ |
| private final Vector<JaxbProject> jaxbProjects = new Vector<JaxbProject>(); |
| |
| /** |
| * Synchronize access to the JAXB projects. |
| */ |
| /* private */ final ILock lock = this.getJobManager().newLock(); |
| |
| /** |
| * Determine how Resource and Java change events are |
| * handled (i.e. synchronously or asynchronously). |
| */ |
| private volatile StatefulCommandExecutor eventHandler = |
| new AsynchronousExtendedCommandExecutor( |
| JptCommonCoreMessages.DALI_EVENT_HANDLER_THREAD_NAME, |
| new LocalExceptionHandler() |
| ); |
| |
| /* CU private */ class LocalExceptionHandler |
| implements ExceptionHandler |
| { |
| public void handleException(Throwable t) { |
| JptJaxbCorePlugin.log(t); |
| } |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| } |
| |
| /** |
| * Listen for<ul> |
| * <li>changes to projects and files |
| * <li>clean builds |
| * </ul> |
| */ |
| 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.POST_CHANGE | |
| IResourceChangeEvent.POST_BUILD; |
| |
| /** |
| * Listen for changes to this file to determine when the JAXB facet is |
| * added to or removed from a "faceted" project. |
| */ |
| private static final String FACETED_PROJECT_FRAMEWORK_SETTINGS_FILE_NAME = FacetedProjectFramework.PLUGIN_ID + ".xml"; //$NON-NLS-1$ |
| |
| /** |
| * Listen for the JAXB facet being added to or removed from a "faceted" project. |
| */ |
| private final IFacetedProjectListener facetedProjectListener = new FacetedProjectListener(); |
| |
| /** |
| * The types of faceted project events that interest |
| * {@link #facetedProjectListener}. |
| */ |
| private static final IFacetedProjectEvent.Type[] FACETED_PROJECT_EVENT_TYPES = new IFacetedProjectEvent.Type[] { |
| IFacetedProjectEvent.Type.PRE_UNINSTALL |
| }; |
| |
| /** |
| * Listen for Java changes (unless the Dali UI is active). |
| * @see #javaElementChangeListenerIsActive() |
| */ |
| private final JavaElementChangeListener javaElementChangeListener = new JavaElementChangeListener(); |
| |
| /** |
| * The types of resource change events that interest |
| * {@link #javaElementChangeListener}. |
| */ |
| private static final int JAVA_CHANGE_EVENT_TYPES = |
| ElementChangedEvent.POST_CHANGE | |
| ElementChangedEvent.POST_RECONCILE; |
| |
| |
| // ********** constructor ********** |
| |
| /** |
| * Internal: called by {@link JptJUaxbCorePlugin Dali plug-in}. |
| */ |
| GenericJaxbProjectManager() { |
| super(); |
| } |
| |
| |
| // ********** plug-in controlled life-cycle ********** |
| |
| /** |
| * Internal: called by {@link JptJaxbCorePlugin Dali plug-in}. |
| */ |
| void start() { |
| try { |
| this.lock.acquire(); |
| this.start_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private void start_() { |
| debug("*** JAXB project manager START ***"); //$NON-NLS-1$ |
| try { |
| this.buildJaxbProjects(); |
| this.eventHandler.start(); |
| this.getWorkspace().addResourceChangeListener(this.resourceChangeListener, RESOURCE_CHANGE_EVENT_TYPES); |
| FacetedProjectFramework.addListener(this.facetedProjectListener, FACETED_PROJECT_EVENT_TYPES); |
| JavaCore.addElementChangedListener(this.javaElementChangeListener, JAVA_CHANGE_EVENT_TYPES); |
| } catch (RuntimeException ex) { |
| JptJaxbCorePlugin.log(ex); |
| this.stop_(); |
| } |
| } |
| |
| /** |
| * Side-effect: {@link #jaxbProjects} populated. |
| */ |
| private void buildJaxbProjects() { |
| try { |
| this.buildJaxbProjects_(); |
| } catch (CoreException ex) { |
| // if we have a problem, leave the currently built JAXB projects in |
| // place and keep executing (should be OK...) |
| JptJaxbCorePlugin.log(ex); |
| } |
| } |
| |
| private void buildJaxbProjects_() throws CoreException { |
| this.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE); |
| } |
| |
| /** |
| * Internal: called by {@link JptJaxbCorePlugin Dali plug-in}. |
| */ |
| void stop() throws Exception { |
| try { |
| this.lock.acquire(); |
| this.stop_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private void stop_() { |
| debug("*** JAXB project manager STOP ***"); //$NON-NLS-1$ |
| JavaCore.removeElementChangedListener(this.javaElementChangeListener); |
| FacetedProjectFramework.removeListener(this.facetedProjectListener); |
| this.getWorkspace().removeResourceChangeListener(this.resourceChangeListener); |
| try { |
| this.eventHandler.stop(); |
| } catch (InterruptedException ex) { |
| Thread.currentThread().interrupt(); |
| } |
| this.clearJaxbProjects(); |
| } |
| |
| private void clearJaxbProjects() { |
| // clone to prevent concurrent modification exceptions |
| for (JaxbProject jaxbProject : this.getJaxbProjects_()) { |
| this.removeJaxbProject(jaxbProject); |
| } |
| } |
| |
| |
| // ********** JaxbProjectManager implementation ********** |
| |
| public Iterable<JaxbProject> getJaxbProjects() { |
| try { |
| this.lock.acquire(); |
| return this.getJaxbProjects_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private Iterable<JaxbProject> getJaxbProjects_() { |
| return new LiveCloneIterable<JaxbProject>(this.jaxbProjects); |
| } |
| |
| public int getJaxbProjectsSize() { |
| return this.jaxbProjects.size(); |
| } |
| |
| public JaxbProject getJaxbProject(IProject project) { |
| try { |
| this.lock.acquire(); |
| return this.getJaxbProject_(project); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private JaxbProject getJaxbProject_(IProject project) { |
| for (JaxbProject jaxbProject : this.jaxbProjects) { |
| if (jaxbProject.getProject().equals(project)) { |
| return jaxbProject; |
| } |
| } |
| return null; |
| } |
| |
| public JaxbFile getJaxbFile(IFile file) { |
| JaxbProject jaxbProject = this.getJaxbProject(file.getProject()); |
| return (jaxbProject == null) ? null : jaxbProject.getJaxbFile(file); |
| } |
| |
| public void rebuildJaxbProject(IProject project) { |
| try { |
| this.lock.acquire(); |
| this.rebuildJaxbProject_(project); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| /** |
| * assumption: the JAXB project holder exists |
| */ |
| private void rebuildJaxbProject_(IProject project) { |
| this.removeJaxbProject(this.getJaxbProject_(project)); |
| this.addJaxbProject(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 JAXB projects ********** |
| |
| /* private */ void addJaxbProject(IProject project) { |
| this.addJaxbProject(this.buildJaxbProject(project)); |
| } |
| |
| private void addJaxbProject(JaxbProject jaxbProject) { |
| // figure out exactly when JAXB projects are added |
| dumpStackTrace("add: ", jaxbProject); //$NON-NLS-1$ |
| // the JAXB project will be null if we have any problems building it... |
| // (e.g. if we have problems getting the JAXB platform) |
| if (jaxbProject != null) { |
| this.addItemToCollection(jaxbProject, this.jaxbProjects, JAXB_PROJECTS_COLLECTION); |
| } |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JaxbProject buildJaxbProject(IProject project) { |
| return this.buildJaxbProject(this.buildJaxbProjectConfig(project)); |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JaxbProject buildJaxbProject(JaxbProject.Config config) { |
| JaxbPlatformDefinition platformDefinition = config.getPlatformDefinition(); |
| if (platformDefinition == null) { |
| return null; |
| } |
| JaxbProject jaxbProject = this.buildJaxbProject(platformDefinition, config); |
| if (jaxbProject == null) { |
| return null; |
| } |
| jaxbProject.setContextModelSynchronizer(buildJobContextModelSynchronizer(jaxbProject)); |
| jaxbProject.setUpdateSynchronizer(buildJobUpdateSynchronizer(jaxbProject)); |
| return jaxbProject; |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JaxbProject buildJaxbProject(JaxbPlatformDefinition platformDefinition, JaxbProject.Config config) { |
| try { |
| return platformDefinition.getFactory().buildJaxbProject(config); |
| } |
| catch (RuntimeException ex) { |
| JptJaxbCorePlugin.log(ex); |
| return null; |
| } |
| } |
| |
| private JaxbProject.Config buildJaxbProjectConfig(IProject project) { |
| SimpleJaxbProjectConfig config = new SimpleJaxbProjectConfig(); |
| config.setProject(project); |
| config.setPlatformDefinition(JptJaxbCorePlugin.getJaxbPlatformManager().buildJaxbPlatformDefinition(project)); |
| return config; |
| } |
| |
| private Synchronizer buildJobContextModelSynchronizer(JaxbProject jaxbProject) { |
| return new JobSynchronizer( |
| buildContextModelJobName(jaxbProject), |
| buildContextModelJobCommand(jaxbProject), |
| jaxbProject.getProject()); |
| } |
| |
| private String buildContextModelJobName(JaxbProject jaxbProject) { |
| return NLS.bind(JptJaxbCoreMessages.CONTEXT_MODEL_SYNC_JOB_NAME, jaxbProject.getName()); |
| } |
| |
| private JobCommand buildContextModelJobCommand(final JaxbProject jaxbProject) { |
| return new JobCommand() { |
| public IStatus execute(IProgressMonitor monitor) { |
| return jaxbProject.synchronizeContextModel(monitor); |
| } |
| }; |
| } |
| |
| private CallbackSynchronizer buildJobUpdateSynchronizer(JaxbProject jaxbProject) { |
| return new CallbackJobSynchronizer( |
| buildUpdateJobName(jaxbProject), |
| buildUpdateJobCommand(jaxbProject), |
| jaxbProject.getProject()); |
| } |
| |
| private String buildUpdateJobName(JaxbProject jaxbProject) { |
| return NLS.bind(JptJaxbCoreMessages.UPDATE_JOB_NAME, jaxbProject.getName()); |
| } |
| |
| private JobCommand buildUpdateJobCommand(final JaxbProject jaxbProject) { |
| return new JobCommand() { |
| public IStatus execute(IProgressMonitor monitor) { |
| return jaxbProject.update(monitor); |
| } |
| }; |
| } |
| |
| /* private */ void removeJaxbProject(JaxbProject jaxbProject) { |
| // figure out exactly when JAXB projects are removed |
| dumpStackTrace("remove: ", jaxbProject); //$NON-NLS-1$ |
| this.removeItemFromCollection(jaxbProject, this.jaxbProjects, JAXB_PROJECTS_COLLECTION); |
| jaxbProject.dispose(); |
| } |
| |
| |
| // ********** Project POST_CHANGE ********** |
| |
| /* private */ void projectChanged(IResourceDelta delta) { |
| this.eventHandler.execute(this.buildProjectChangedCommand(delta)); |
| } |
| |
| private Command buildProjectChangedCommand(final IResourceDelta delta) { |
| return new EventHandlerCommand("Project POST_CHANGE Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.projectChanged_(delta); |
| } |
| }; |
| } |
| |
| /** |
| * Forward the specified resource delta to all our JAXB projects; |
| * they will each determine whether the event is significant. |
| */ |
| /* private */ void projectChanged_(IResourceDelta delta) { |
| for (JaxbProject jaxbProject : this.jaxbProjects) { |
| jaxbProject.projectChanged(delta); |
| } |
| } |
| |
| |
| // ********** Project POST_BUILD (CLEAN_BUILD) ********** |
| |
| /* private */ void projectPostCleanBuild(IProject project) { |
| this.executeAfterEventsHandled(this.buildProjectPostCleanBuildCommand(project)); |
| } |
| |
| private Command buildProjectPostCleanBuildCommand(final IProject project) { |
| return new EventHandlerCommand("Project POST_BUILD (CLEAN_BUILD) Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.projectPostCleanBuild_(project); |
| } |
| }; |
| } |
| |
| /* private */ void projectPostCleanBuild_(IProject project) { |
| JaxbProject jaxbProject = this.getJaxbProject_(project); |
| if (jaxbProject != null) { |
| this.removeJaxbProject(jaxbProject); |
| this.addJaxbProject(project); |
| } |
| } |
| |
| |
| // ********** File POST_CHANGE ********** |
| |
| /** |
| * The Faceted Project settings file has changed in some fashion, check |
| * whether the JAXB facet has been added to or removed from the specified |
| * project. |
| */ |
| /* private */ void checkForJaxbFacetTransition(IProject project) { |
| JaxbProject jaxbProject = this.getJaxbProject_(project); |
| |
| if (JaxbFacet.isInstalled(project)) { |
| if (jaxbProject == null) { // JAXB facet added |
| this.executeAfterEventsHandled(this.buildAddJaxbProjectCommand(project)); |
| } |
| } else { |
| if (jaxbProject != null) { // JAXB facet removed |
| this.executeAfterEventsHandled(this.buildRemoveJaxbProjectCommand(jaxbProject)); |
| } |
| } |
| } |
| |
| private Command buildAddJaxbProjectCommand(final IProject project) { |
| return new EventHandlerCommand("Add JAXB Project Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.addJaxbProject(project); |
| } |
| }; |
| } |
| |
| private Command buildRemoveJaxbProjectCommand(final JaxbProject jaxbProject) { |
| return new EventHandlerCommand("Remove JAXB Project Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.removeJaxbProject(jaxbProject); |
| } |
| }; |
| } |
| |
| // ********** FacetedProject PRE_UNINSTALL ********** |
| |
| /* private */ void jaxbFacetedProjectPreUninstall(IProjectFacetActionEvent event) { |
| IProject project = event.getProject().getProject(); |
| this.executeAfterEventsHandled(this.buildJaxbFacetedProjectPreUninstallCommand(project)); |
| } |
| |
| private Command buildJaxbFacetedProjectPreUninstallCommand(final IProject project) { |
| return new EventHandlerCommand("Faceted Project PRE_UNINSTALL Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.jaxbFacetedProjectPreUninstall_(project); |
| } |
| }; |
| } |
| |
| /* private */ void jaxbFacetedProjectPreUninstall_(IProject project) { |
| // assume(?) this is the first event to indicate we need to remove the JAXB project from the JAXB project manager |
| this.removeJaxbProject(this.getJaxbProject_(project)); |
| } |
| |
| |
| // ********** Java element changed ********** |
| |
| /* private */ void javaElementChanged(ElementChangedEvent event) { |
| this.eventHandler.execute(this.buildJavaElementChangedCommand(event)); |
| } |
| |
| private Command buildJavaElementChangedCommand(final ElementChangedEvent event) { |
| return new EventHandlerCommand("Java element changed Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJaxbProjectManager.this.javaElementChanged_(event); |
| } |
| }; |
| } |
| |
| /** |
| * Forward the Java element changed event to all the JAXB projects |
| * because the event could affect multiple projects. |
| */ |
| /* private */ void javaElementChanged_(ElementChangedEvent event) { |
| for (JaxbProject jaxbProject : this.jaxbProjects) { |
| jaxbProject.javaElementChanged(event); |
| } |
| } |
| |
| |
| // ********** miscellaneous ********** |
| |
| @Override |
| public void toString(StringBuilder sb) { |
| sb.append(this.jaxbProjects); |
| } |
| |
| |
| // ********** event handler ********** |
| |
| /** |
| * If the event handler is executing asynchronously:<br> |
| * Allow all the commands currently on the command executor's queue to execute. |
| * Once they have executed, suspend the command executor and process the |
| * specified command (on <em>this</em> thread, <em>not</em> the command |
| * executor thread). Once the specified command is finished, allow the |
| * command executor to resume processing its command queue. |
| * <p> |
| * If the event handler is executing synchronously:<br> |
| * All the events have already been handled synchronously, so we simply |
| * execute the specified command [sorta] directly. |
| */ |
| private void executeAfterEventsHandled(Command command) { |
| SynchronizedBoolean flag = new SynchronizedBoolean(false); |
| this.eventHandler.execute(new PauseCommand(flag)); |
| try { |
| flag.waitUntilTrue(); |
| } catch (InterruptedException ex) { |
| // ignore - not sure why this thread would be interrupted |
| } |
| try { |
| command.execute(); |
| } finally { |
| flag.setFalse(); |
| } |
| } |
| |
| /** |
| * If this "pause" command is executing (asynchronously) on a different |
| * thread than the JAXB project manager:<ol> |
| * <li>it will set the flag to <code>true</code>, allowing the JAXB project |
| * manager to resume executing on its own thread |
| * <li>then it will suspend its command executor until the JAXB project |
| * manager sets the flag back to <code>false</code>. |
| * </ol> |
| * If this "pause" command is executing (synchronously) on the same thread |
| * as the JAXB project manager, it will simply set the flag to |
| * <code>true</code> and return. |
| */ |
| private static class PauseCommand |
| implements Command |
| { |
| private final Thread producerThread; |
| private final SynchronizedBoolean flag; |
| |
| PauseCommand(SynchronizedBoolean flag) { |
| this(Thread.currentThread(), flag); |
| } |
| |
| PauseCommand(Thread producerThread, SynchronizedBoolean flag) { |
| super(); |
| this.producerThread = producerThread; |
| this.flag = flag; |
| } |
| |
| public void execute() { |
| this.flag.setTrue(); |
| if (Thread.currentThread() != this.producerThread) { |
| try { |
| this.flag.waitUntilFalse(); |
| } catch (InterruptedException ex) { |
| // ignore - the command executor will check for interruptions |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method is called (via reflection) when the test plug-in is loaded. |
| * @see JptJaxbCoreTestsPlugin#start(BundleContext) |
| */ |
| public void handleEventsSynchronously() throws InterruptedException { |
| try { |
| this.lock.acquire(); |
| this.handleEventsSynchronously_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private void handleEventsSynchronously_() throws InterruptedException { |
| this.eventHandler.stop(); |
| this.eventHandler = new SimpleStatefulExtendedCommandExecutor(); |
| this.eventHandler.start(); |
| } |
| |
| |
| // ********** resource proxy visitor ********** |
| |
| /** |
| * Visit the workspace resource tree, adding a JAXB project to the |
| * JAXB project manager for each open Eclipse project that has a JAXB 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.processProject(resourceProxy); |
| return false; // no nested projects |
| case IResource.FOLDER : |
| return false; // ignore |
| case IResource.FILE : |
| return false; // ignore |
| default : |
| return false; |
| } |
| } |
| |
| private void processProject(IResourceProxy resourceProxy) { |
| if (resourceProxy.isAccessible()) { // the project exists and is open |
| IProject project = (IProject) resourceProxy.requestResource(); |
| if (JaxbFacet.isInstalled(project)) { |
| GenericJaxbProjectManager.this.addJaxbProject(project); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** event handler command ********** |
| |
| /** |
| * Command that holds the JAXB project manager lock while |
| * executing. |
| */ |
| private abstract class EventHandlerCommand |
| implements Command |
| { |
| private final String name; |
| |
| EventHandlerCommand(String name) { |
| super(); |
| this.name = name; |
| } |
| |
| public final void execute() { |
| try { |
| GenericJaxbProjectManager.this.lock.acquire(); |
| this.execute_(); |
| } catch (RuntimeException ex) { |
| JptJaxbCorePlugin.log(ex); |
| } finally { |
| GenericJaxbProjectManager.this.lock.release(); |
| } |
| } |
| |
| abstract void execute_(); |
| |
| @Override |
| public String toString() { |
| return this.name; |
| } |
| } |
| |
| |
| // ********** resource change listener ********** |
| |
| private class ResourceChangeListener implements IResourceChangeListener { |
| |
| ResourceChangeListener() { |
| super(); |
| } |
| |
| /** |
| * PRE_UNINSTALL is the only facet event we use for |
| * removing JAXB projects. These are the cases where we listen for resource events. |
| * <p> |
| * Check for:<ul> |
| * <li>facet settings file added/removed/changed |
| * (<code>/.settings/org.eclipse.wst.common.project.facet.core.xml</code>) |
| * <li>file add/remove - forwarded to the individual JAXB projects |
| * <li>project clean |
| * </ul> |
| */ |
| public void resourceChanged(IResourceChangeEvent event) { |
| switch (event.getType()) { |
| case IResourceChangeEvent.POST_CHANGE : |
| this.processPostChangeEvent(event); |
| break; |
| |
| // workspace or project events |
| case IResourceChangeEvent.PRE_REFRESH : |
| break; // ignore |
| case IResourceChangeEvent.PRE_BUILD : |
| break; // ignore |
| case IResourceChangeEvent.POST_BUILD : |
| this.processPostBuildEvent(event); |
| break; |
| |
| // project-only events |
| case IResourceChangeEvent.PRE_CLOSE : |
| break; // ignore |
| case IResourceChangeEvent.PRE_DELETE : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostChangeEvent(IResourceChangeEvent event) { |
| debug("Resource POST_CHANGE"); //$NON-NLS-1$ |
| this.processPostChangeDelta(event.getDelta()); |
| } |
| |
| private void processPostChangeDelta(IResourceDelta delta) { |
| IResource resource = delta.getResource(); |
| switch (resource.getType()) { |
| case IResource.ROOT : |
| this.processPostChangeRootDelta(delta); |
| break; |
| case IResource.PROJECT : |
| this.processPostChangeProjectDelta(delta); |
| break; |
| case IResource.FOLDER : |
| this.processPostChangeFolderDelta((IFolder) resource, delta); |
| break; |
| case IResource.FILE : |
| this.processPostChangeFileDelta((IFile) resource, delta); |
| break; |
| default : |
| break; |
| } |
| } |
| |
| // ***** POST_CHANGE ROOT |
| private void processPostChangeRootDelta(IResourceDelta delta) { |
| this.processPostChangeDeltaChildren(delta); |
| } |
| |
| // ***** POST_CHANGE PROJECT |
| /** |
| * Process the project first for the Opening project case. |
| * The JAXB project will not be built until the children are processed |
| * and we see that the facet metadata file is added. |
| * Otherwise the JAXB project would be built and then we would process |
| * the ADDED deltas for all the files in the project. |
| */ |
| private void processPostChangeProjectDelta(IResourceDelta delta) { |
| GenericJaxbProjectManager.this.projectChanged(delta); |
| this.processPostChangeDeltaChildren(delta); |
| } |
| |
| // ***** POST_CHANGE FOLDER |
| private void processPostChangeFolderDelta(IFolder folder, IResourceDelta delta) { |
| if (folder.getName().equals(".settings")) { //$NON-NLS-1$ |
| this.processPostChangeDeltaChildren(delta); |
| } |
| } |
| |
| // ***** POST_CHANGE FILE |
| private void processPostChangeFileDelta(IFile file, IResourceDelta delta) { |
| if (file.getName().equals(FACETED_PROJECT_FRAMEWORK_SETTINGS_FILE_NAME)) { |
| this.checkForFacetFileChanges(file, delta); |
| } |
| } |
| |
| private void checkForFacetFileChanges(IFile file, IResourceDelta delta) { |
| switch (delta.getKind()) { |
| case IResourceDelta.ADDED : |
| case IResourceDelta.REMOVED : |
| case IResourceDelta.CHANGED : |
| GenericJaxbProjectManager.this.checkForJaxbFacetTransition(file.getProject()); |
| break; |
| case IResourceDelta.ADDED_PHANTOM : |
| break; // ignore |
| case IResourceDelta.REMOVED_PHANTOM : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostChangeDeltaChildren(IResourceDelta delta) { |
| for (IResourceDelta child : delta.getAffectedChildren()) { |
| this.processPostChangeDelta(child); // recurse |
| } |
| } |
| |
| /** |
| * A post build event has occurred. |
| * Check for whether the build was a "clean" build and trigger project update. |
| */ |
| // ***** POST_BUILD |
| private void processPostBuildEvent(IResourceChangeEvent event) { |
| debug("Resource POST_BUILD: ", event.getResource()); //$NON-NLS-1$ |
| if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) { |
| this.processPostCleanBuildDelta(event.getDelta()); |
| } |
| } |
| |
| private void processPostCleanBuildDelta(IResourceDelta delta) { |
| IResource resource = delta.getResource(); |
| switch (resource.getType()) { |
| case IResource.ROOT : |
| this.processPostCleanBuildDeltaChildren(delta); |
| break; |
| case IResource.PROJECT : |
| this.processProjectPostCleanBuild((IProject) resource); |
| break; |
| case IResource.FOLDER : |
| break; // ignore |
| case IResource.FILE : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostCleanBuildDeltaChildren(IResourceDelta delta) { |
| for (IResourceDelta child : delta.getAffectedChildren()) { |
| this.processPostCleanBuildDelta(child); // recurse |
| } |
| } |
| |
| private void processProjectPostCleanBuild(IProject project) { |
| debug("\tProject CLEAN: ", project.getName()); //$NON-NLS-1$ |
| GenericJaxbProjectManager.this.projectPostCleanBuild(project); |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** faceted project listener ********** |
| |
| /** |
| * Forward the Faceted project change event back to the JAXB project manager. |
| */ |
| private class FacetedProjectListener implements IFacetedProjectListener { |
| |
| FacetedProjectListener() { |
| super(); |
| } |
| |
| /** |
| * Check for:<ul> |
| * <li>un-install of JAXB facet |
| * </ul> |
| */ |
| public void handleEvent(IFacetedProjectEvent event) { |
| switch (event.getType()) { |
| case PRE_UNINSTALL : |
| this.processPreUninstallEvent((IProjectFacetActionEvent) event); |
| break; |
| default : |
| break; |
| } |
| } |
| |
| private void processPreUninstallEvent(IProjectFacetActionEvent event) { |
| debug("Facet PRE_UNINSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ |
| if (event.getProjectFacet().equals(JaxbFacet.FACET)) { |
| GenericJaxbProjectManager.this.jaxbFacetedProjectPreUninstall(event); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** Java element change listener ********** |
| |
| /** |
| * Forward the Java element change event back to the JAXB project manager. |
| */ |
| 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) { |
| GenericJaxbProjectManager.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 JaxbProjectManagerTests#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$ |
| } |
| } |
| } |
| |
| } |