blob: 26a4f6b519146feb1feefa64425d8fbf439dd5e7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
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.jdt.core.JavaModelException;
import org.eclipse.jpt.core.internal.prefs.JpaPreferenceConstants;
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;
public class JpaModelManager
{
private static JpaModelManager INSTANCE;
/**
* Returns the singleton JpaModelManager
*/
public final static JpaModelManager instance() {
if (INSTANCE == null) {
INSTANCE = new JpaModelManager();
}
return INSTANCE;
}
/**
* Unique handle onto the JpaModel
*/
JpaModel model;
/**
* Processes resource changes
*/
private IResourceChangeListener resourceChangeListener;
/**
* Processes changes to project facets
*/
private final IFacetedProjectListener facetedProjectListener;
/**
* Process element changes
*/
private IElementChangedListener elementChangeListener;
/**
* Process changes to preferences
*/
private IPropertyChangeListener preferencesListener;
private JpaModelManager() {
super();
model = JpaCoreFactory.eINSTANCE.createJpaModel();
resourceChangeListener = new ResourceChangeListener();
facetedProjectListener = new FacetedProjectListener();
elementChangeListener = new ElementChangeListener();
preferencesListener = new PreferencesListener();
}
public void startup() {
try {
buildWorkspace();
ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener);
FacetedProjectFramework.addListener(facetedProjectListener, IFacetedProjectEvent.Type.values());
JavaCore.addElementChangedListener(elementChangeListener);
JptCorePlugin.getPlugin().getPluginPreferences().addPropertyChangeListener(preferencesListener);
}
catch (RuntimeException re) {
JptCorePlugin.log(re);
shutdown();
}
}
public void shutdown() {
JptCorePlugin.getPlugin().getPluginPreferences().removePropertyChangeListener(preferencesListener);
JavaCore.removeElementChangedListener(elementChangeListener);
ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener);
model.dispose();
}
private void buildWorkspace() {
Job workspaceBuildJob = new WorkspaceBuildJob();
workspaceBuildJob.schedule(5000L); //temporary delay for bundle init problem
}
/**
* Return the workspace-wide IJpaModel
*
* This IJpaProject may not be fully filled (it may not have all the correct
* projects added) if retrieved soon after it is created (e.g. workspace opening,
* project opening, facet installation ...) To ensure it is fully filled in
* those cases, you may instead use getFilledJpaModel().
* @see getFilledJpaModel()
*/
public IJpaModel getJpaModel() {
return model;
}
/**
* Return the workspace-wide IJpaModel
*
* This IJpaModel will be fully filled (it will have all the correct projects added).
* @see getJpaProject(IProject)
*/
public IJpaModel getFilledJpaModel()
throws CoreException {
model.fill();
return model;
}
/**
* Returns the IJpaProject corresponding to the given IProject.
* Returns <code>null</code> if unable to associate the given project
* with an IJpaProject.
*
* This IJpaProject may not be fully filled (it may not have all the correct
* files added) if retrieved soon after it is created (e.g. workspace opening,
* project opening, facet installation ...) To ensure it is fully filled in
* those cases, you may instead use getFilledJpaProject(IProject).
* @see getFilledJpaProject(IProject)
*/
public synchronized IJpaProject getJpaProject(IProject project) {
if (project == null) {
return null;
}
return model.getJpaProject(project);
}
/**
* Returns the IJpaProject corresponding to the given IProject.
* Returns <code>null</code> if unable to associate the given project
* with an IJpaProject.
*
* This IJpaProject will be fully filled (it will have all the correct files added).
* @see getJpaProject(IProject)
*/
public synchronized IJpaProject getFilledJpaProject(IProject project)
throws CoreException {
JpaProject jpaProject = (JpaProject) getJpaProject(project);
if (jpaProject != null) {
jpaProject.fill();
}
return jpaProject;
}
/**
* INTERNAL ONLY
*
* Fills the IJpaProject associated with the IProject, if it exists
*/
public synchronized void fillJpaProject(IProject project)
throws CoreException {
JpaProject jpaProject = (JpaProject) getJpaProject(project);
if (jpaProject != null) {
jpaProject.fill();
}
}
/*
* determine whether project should be created, then create it if necessary
*/
private synchronized void processProject(IProject project) {
JpaProject jpaProject = (JpaProject) model.getJpaProject(project);
boolean jpaFacetExists = false;
try {
jpaFacetExists = FacetedProjectFramework.hasProjectFacet(project, JptCorePlugin.FACET_ID);
}
catch (CoreException ce) {
// nothing to do, assume facet doesn't exist
JptCorePlugin.log(ce);
}
if (jpaProject == null && jpaFacetExists) {
try {
JpaModelManager.instance().createFilledJpaProject(project);
}
catch (CoreException ce) {
JptCorePlugin.log(ce);
}
}
else if (jpaProject != null && ! jpaFacetExists) {
jpaProject.dispose();
}
}
/**
* INTERNAL ONLY
* Create an IJpaProject without files filled in
*/
public synchronized IJpaProject createJpaProject(IProject project)
throws CoreException {
if (FacetedProjectFramework.hasProjectFacet(project, JptCorePlugin.FACET_ID)) {
JpaProject jpaProject = JpaCoreFactory.eINSTANCE.createJpaProject();
jpaProject.setProject(project);
model.getProjects().add(jpaProject);
return jpaProject;
}
return null;
}
/**
* INTERNAL ONLY
* Create an IJpaProject with files filled in
*/
public synchronized IJpaProject createFilledJpaProject(IProject project)
throws CoreException {
JpaProject jpaProject = (JpaProject) createJpaProject(project);
if (jpaProject != null) {
jpaProject.fill();
}
return jpaProject;
}
/**
* INTERNAL ONLY
* Dispose the IJpaProject
*/
public void disposeJpaProject(IJpaProject jpaProject) {
((JpaProject) jpaProject).dispose();
}
/**
* Returns the IJpaFile corresponding to the given IFile.
* Returns <code>null</code> if unable to associate the given file
* with an IJpaFile.
*/
public synchronized IJpaFile getJpaFile(IFile file) {
if (file == null) {
return null;
}
IProject project = file.getProject();
JpaProject jpaProject = (JpaProject) getJpaProject(project);
if (jpaProject == null) {
return null;
}
return jpaProject.getJpaFile(file);
}
private class WorkspaceBuildJob extends Job
{
WorkspaceBuildJob() {
// TODO - Internationalize (? It *is* a system job ...)
super("Initializing JPA Model ...");
setSystem(true);
setPriority(SHORT);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
try {
workspace.run(
new IWorkspaceRunnable() {
public void run(IProgressMonitor progress) throws CoreException {
model.fill();
}
},
monitor);
}
catch (CoreException ce) {
return ce.getStatus();
}
return Status.OK_STATUS;
}
}
private static class ResourceChangeListener
implements IResourceChangeListener
{
ThreadLocal<ResourceChangeProcessor> resourceChangeProcessors = new ThreadLocal<ResourceChangeProcessor>();
ResourceChangeListener() {
super();
}
public void resourceChanged(IResourceChangeEvent event) {
getResourceChangeProcessor().resourceChanged(event);
}
public ResourceChangeProcessor getResourceChangeProcessor() {
ResourceChangeProcessor processor = this.resourceChangeProcessors.get();
if (processor == null) {
processor = new ResourceChangeProcessor();
this.resourceChangeProcessors.set(processor);
}
return processor;
}
}
private static class ResourceChangeProcessor
{
private JpaModel model;
ResourceChangeProcessor() {
model = JpaModelManager.instance().model;
}
public void resourceChanged(IResourceChangeEvent event) {
if (event.getSource() instanceof IWorkspace) {
IResource resource = event.getResource();
IResourceDelta delta = event.getDelta();
switch (event.getType()){
case IResourceChangeEvent.PRE_DELETE :
case IResourceChangeEvent.PRE_CLOSE :
try {
if ((resource.getType() == IResource.PROJECT)
&& (FacetedProjectFramework.hasProjectFacet(
(IProject) resource, JptCorePlugin.FACET_ID))) {
projectBeingDeleted((IProject) resource);
}
}
catch (CoreException e) {
// project doesn't exist or is not open: ignore
}
return;
case IResourceChangeEvent.POST_CHANGE :
if (isApplicable(delta)) { // avoid changing due to SYNC or MARKER deltas
checkForProjectsBeingAddedOrRemoved(delta);
checkForFilesBeingAddedOrRemoved(delta);
}
return;
}
}
}
/**
* Process the given delta and look for files being added, removed, or changed
*/
private void checkForFilesBeingAddedOrRemoved(IResourceDelta delta) {
IResource resource = delta.getResource();
boolean processChildren = false;
switch (resource.getType()) {
case IResource.ROOT :
processChildren = true;
break;
case IResource.PROJECT :
IProject project = (IProject) resource;
try {
if (FacetedProjectFramework.hasProjectFacet(project, JptCorePlugin.FACET_ID)) {
JpaProject jpaProject = (JpaProject) model.getJpaProject(project);
if (jpaProject != null) {
// sometimes we receive events before the project
// has been fully initialized
jpaProject.synchInternalResources(delta);
}
}
}
catch (CoreException ex) {
// we can't do anything anyway
}
break;
}
if (processChildren) {
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
checkForFilesBeingAddedOrRemoved(children[i]);
}
}
}
/**
* Process the given delta and look for projects being added, opened, or closed.
* Note that projects being deleted are checked in deletingProject(IProject).
*/
private void checkForProjectsBeingAddedOrRemoved(IResourceDelta delta) {
IResource resource = delta.getResource();
boolean processChildren = false;
switch (resource.getType()) {
case IResource.ROOT :
processChildren = true;
break;
case IResource.PROJECT :
// NB: No need to check project's facet as if the project is not a jpa project:
// - if the project is added or changed this is a noop for projectsBeingDeleted
// - if the project is closed, it has already lost its jpa facet
IProject project = (IProject) resource;
switch (delta.getKind()) {
case IResourceDelta.REMOVED :
// we should have already handled this in the PRE_DELETE event
break;
case IResourceDelta.ADDED :
// if project is renamed (for instance, we should act as though it's been opened)
// fall through
case IResourceDelta.CHANGED :
if ((delta.getFlags() & IResourceDelta.OPEN) != 0) {
if (project.isOpen()) {
JpaModelManager.instance().processProject(project);
}
}
break;
}
break;
}
if (processChildren) {
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
checkForProjectsBeingAddedOrRemoved(children[i]);
}
}
}
/**
* The platform project is being deleted. Remove jpa info.
*/
private void projectBeingDeleted(IProject project) {
// could be problems here ...
JpaProject jpaProject = (JpaProject) model.getJpaProject(project);
jpaProject.dispose();
}
/**
* Returns whether a given delta contains some information relevant to
* the JPA model,
* in particular it will not consider SYNC or MARKER only deltas.
*/
private boolean isApplicable(IResourceDelta rootDelta) {
if (rootDelta != null) {
// use local exception to quickly escape from delta traversal
class FoundRelevantDeltaException extends RuntimeException {
private static final long serialVersionUID = 7137113252936111022L; // backward compatible
// only the class name is used (to differentiate from other RuntimeExceptions)
}
try {
rootDelta.accept(
new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) {
switch (delta.getKind()) {
case IResourceDelta.ADDED :
case IResourceDelta.REMOVED :
throw new FoundRelevantDeltaException();
case IResourceDelta.CHANGED :
// if any flag is set but SYNC or MARKER, this delta should be considered
if (delta.getAffectedChildren().length == 0 // only check leaf delta nodes
&& (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0) {
throw new FoundRelevantDeltaException();
}
}
return true;
}
}
);
}
catch(FoundRelevantDeltaException e) {
return true;
}
catch(CoreException e) { // ignore delta if not able to traverse
}
}
return false;
}
}
private static class FacetedProjectListener
implements IFacetedProjectListener
{
ThreadLocal<FacetedProjectChangeProcessor> processors =
new ThreadLocal<FacetedProjectChangeProcessor>();
FacetedProjectListener() {
super();
}
public void handleEvent(IFacetedProjectEvent event) {
getProcessor().handleEvent(event);
}
public FacetedProjectChangeProcessor getProcessor() {
FacetedProjectChangeProcessor processor = processors.get();
if (processor == null) {
processor = new FacetedProjectChangeProcessor();
processors.set(processor);
}
return processor;
}
}
private static class FacetedProjectChangeProcessor
{
private JpaModel model;
FacetedProjectChangeProcessor() {
model = JpaModelManager.instance().model;
}
protected void handleEvent(IFacetedProjectEvent event) {
JpaModelManager.instance().processProject(event.getProject().getProject());
}
}
private static class ElementChangeListener
implements IElementChangedListener
{
ThreadLocal<ElementChangeProcessor> elementChangeProcessors = new ThreadLocal<ElementChangeProcessor>();
ElementChangeListener() {
super();
}
public void elementChanged(ElementChangedEvent event) {
getElementChangeProcessor().elementChanged(event);
}
public ElementChangeProcessor getElementChangeProcessor() {
ElementChangeProcessor processor = this.elementChangeProcessors.get();
if (processor == null) {
processor = new ElementChangeProcessor();
this.elementChangeProcessors.set(processor);
}
return processor;
}
}
private static class ElementChangeProcessor
{
ElementChangeProcessor() {
super();
}
public void elementChanged(ElementChangedEvent event) {
JpaModelManager.instance().model.handleEvent(event);
}
}
private static class PreferencesListener
implements IPropertyChangeListener
{
PreferencesListener() {
super();
}
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty() == JpaPreferenceConstants.PREF_DEFAULT_JPA_LIB) {
try {
JavaCore.setClasspathVariable("DEFAULT_JPA_LIB", new Path((String) event.getNewValue()), null);
}
catch (JavaModelException jme) {
JptCorePlugin.log(jme);
}
}
}
}
}