blob: bc157f5847b39ceb9bddf4a845eabc8bdffc3e40 [file] [log] [blame]
/*******************************************************************************
* 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.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
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.jdt.core.JavaModelException;
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.core.internal.prefs.JpaPreferenceConstants;
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 (created and opened)
* resource POST_CHANGE -> PROJECT -> CHANGED -> 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;
/**
* Listen for changes to the "Default JPA Lib" preference
* so we can update the corresponding classpath variable.
*/
private final IPropertyChangeListener preferencesListener;
// ********** 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.preferencesListener = new PreferencesListener();
}
// ********** 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);
FacetedProjectFramework.addListener(this.facetedProjectListener, IFacetedProjectEvent.Type.values());
JavaCore.addElementChangedListener(this.javaElementChangeListener);
JptCorePlugin.instance().getPluginPreferences().addPropertyChangeListener(this.preferencesListener);
} 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$
JptCorePlugin.instance().getPluginPreferences().removePropertyChangeListener(this.preferencesListener);
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);
}
/**
* 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) {
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;
case IResourceChangeEvent.PRE_CLOSE : // project-only event
case IResourceChangeEvent.PRE_BUILD :
case IResourceChangeEvent.POST_BUILD :
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_(project, delta);
this.checkForOpenedProject(project, delta);
}
/**
* Checked exceptions bite.
*/
private void projectChanged_(IProject project, IResourceDelta delta) {
try {
this.jpaModel.projectChanged(project, 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.CHANGED :
this.checkDeltaFlagsForOpenedProject(project, delta);
break;
case IResourceDelta.REMOVED : // already handled with the PRE_DELETE event
case IResourceDelta.ADDED : // all but project rename handled with the facet POST_INSTALL event
this.checkDeltaFlagsForRenamedProject(project, delta);
break;
case IResourceDelta.ADDED_PHANTOM : // ignore
case IResourceDelta.REMOVED_PHANTOM : // 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 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);
}
}
// ********** 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.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;
}
// ********** preference changed **********
/**
* When the "Default JPA Lib" preference changes,
* update the appropriate JDT Core classpath variable.
*/
/* private */ void preferenceChanged(PropertyChangeEvent event) {
if (event.getProperty() == JpaPreferenceConstants.PREF_DEFAULT_JPA_LIB) {
try {
JavaCore.setClasspathVariable("DEFAULT_JPA_LIB", new Path((String) event.getNewValue()), null); //$NON-NLS-1$
} catch (JavaModelException ex) {
this.log(ex); // not sure what would cause this...
}
}
}
// ********** 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);
}
}
// ********** preferences listener **********
/**
* Forward the Preferences change event back to the JPA model manager.
*/
private class PreferencesListener implements IPropertyChangeListener {
PreferencesListener() {
super();
}
public void propertyChange(PropertyChangeEvent event) {
JpaModelManager.this.preferenceChanged(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$
}
}