blob: beed63dd8dd28e1b948fe6f7b8b3a739c0fdb33e [file] [log] [blame]
/***********************************************************************
* Copyright (c) 2008 by SAP AG, Walldorf.
* 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:
* SAP AG - initial API and implementation
***********************************************************************/
package org.eclipse.jst.jee.model.internal.common;
import java.util.Collection;
import java.util.HashSet;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.jst.j2ee.model.IModelProvider;
import org.eclipse.jst.j2ee.model.IModelProviderEvent;
import org.eclipse.jst.j2ee.model.IModelProviderListener;
/**
* A base class for model providers providing a merged view between two
* different model providers. The instance will be called "mergedModelProvider"
* where the two composed providers will be called "internalProviders"
*
* This class introduces the notation of a disposed state. {@link #dispose()} is
* used to dispose the model provider. {@link #isDisposed()} is used get the
* state of the provider. If the method {@link #getModelObject()} is called for
* a model provider in a disposed state, the provider should try to move to a
* non disposed state and return a correct model object.
* {@link #getModelObject()} is loading the model. Specific implementations may
* throw exceptions so calling {@link #getModelObject()} on a disposed provider
* does not guarantee that calling {@link #isDisposed()} after that will return
* <code>false</code>.
*
* <p>
* Subclasses may enable/disable notifications from internalProviders with the
* methods {@link #enableInternalNotifications()} and
* {@link #disableInternalNotifications()}.
* </p>
*
* <p>
* internalProviders are loaded with {@link #loadDeploymentDescriptorModel()}
* and {@link #loadAnnotationModel(Object)}. This methods should be override to
* provide create the specific model providers.
* </p>
*
* <p>
* The mergedModelProvider is a listener to the internalProviders. After
* disposing the instance of a mergedModelProvider it should no longer accept
* notifications from the internalProviders. It should also properly "dispose"
* the internalProviders if need.
* </p>
*
* @author Kiril Mitov k.mitov@sap.com
*
*/
public abstract class AbstractMergedModelProvider<T> implements IModelProvider {
protected IModelProvider ddProvider;
protected IModelProvider annotationModelProvider;
protected boolean isOnceDisposed = false;
private class AnnotationModelListener implements IModelProviderListener {
public void modelsChanged(IModelProviderEvent event) {
AbstractMergedModelProvider.this.annotationModelChanged(event);
}
}
private class XmlModelListener implements IModelProviderListener {
public void modelsChanged(IModelProviderEvent event) {
AbstractMergedModelProvider.this.xmlModelChanged(event);
}
}
private Collection<IModelProviderListener> listeners;
protected IProject project;
private AnnotationModelListener annotationModelListener;
private XmlModelListener xmlModelListener;
protected T mergedModel;
public AbstractMergedModelProvider(IProject project) {
this.project = project;
}
public void addListener(IModelProviderListener listener) {
getListeners().add(listener);
}
/**
* Returns the model merged from annotation and xml model. If the project is
* closed or does not exist the returns <code>null</code>
*
* @see org.eclipse.jst.j2ee.model.IModelProvider#getModelObject()
*/
public Object getModelObject() {
return getMergedModel();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.j2ee.model.IModelProvider#modify(java.lang.Runnable,
* org.eclipse.core.runtime.IPath)
*/
public void modify(Runnable runnable, IPath modelPath) {
}
public void removeListener(IModelProviderListener listener) {
getListeners().remove(listener);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.j2ee.model.IModelProvider#validateEdit(org.eclipse.core.runtime.IPath,
* java.lang.Object)
*/
public IStatus validateEdit(IPath modelPath, Object context) {
if (ddProvider == null)
getModelObject();
return ddProvider.validateEdit(modelPath, context);
}
/**
* Called when the annotationModel has changed. See also
* {@link #enableInternalNotifications()} and
* {@link #disableInternalNotifications()}
*
* @param event
*/
protected abstract void annotationModelChanged(IModelProviderEvent event);
/**
* Called when the xmlModel has changed. See also
* {@link #enableInternalNotifications()} and
* {@link #disableInternalNotifications()}
*
* @param event
*/
protected abstract void xmlModelChanged(IModelProviderEvent event);
/**
* Return a merged view of the two passed models.
*
* @param ddModel
* @param annotationsModel
* @return
*/
protected abstract T merge(T ddModel, T annotationsModel);
/**
* Load the annotation model in the context of the ddModel.
*
* @param ddModel
* @return
* @throws CoreException
*/
protected abstract IModelProvider loadAnnotationModel(T ddModel) throws CoreException;
/**
* @return
* @throws CoreException
*/
protected abstract IModelProvider loadDeploymentDescriptorModel() throws CoreException;
protected Collection<IModelProviderListener> getListeners() {
if (listeners == null)
listeners = new HashSet<IModelProviderListener>();
return listeners;
}
protected T getMergedModel() {
try {
if (mergedModel == null)
mergedModel = loadModel();
} catch (CoreException e) {
e.printStackTrace();
return null;
}
return mergedModel;
}
/**
* @return a merged view of the models from the internalProviders. This may
* include loading the internalProviders.
* @throws CoreException
*/
protected T loadModel() throws CoreException {
if (project.isAccessible() == false)
throw new IllegalStateException("The project <" + project + "> is not accessible."); //$NON-NLS-1$//$NON-NLS-2$
ddProvider = loadDeploymentDescriptorModel();
if (ddProvider == null || ddProvider.getModelObject() == null)
return null;
annotationModelProvider = loadAnnotationModel((T) ddProvider.getModelObject());
if (annotationModelProvider == null || annotationModelProvider.getModelObject() == null)
return null;
T ddModel = (T) ddProvider.getModelObject();
T annotationModel = (T) annotationModelProvider.getModelObject();
enableInternalNotifications();
isOnceDisposed = false;
return merge(ddModel, annotationModel);
}
protected void initMergedModelResource(EObject ddModel) {
Resource resourceDD = ddModel.eResource();
Resource resourceMM = ((EObject)mergedModel).eResource();
if (resourceDD != null && resourceMM == null){
ResourceImpl resRes = new ResourceImpl(resourceDD.getURI());
resRes.getContents().add((EObject)mergedModel);
}
}
/**
* The method is used for enabling notifications from the internalProviders.
* This will add the appropriate listener to the internalProviders so that
* {@link #annotationModelChanged(IModelProviderEvent)} and
* {@link #xmlModelChanged(IModelProviderEvent)} are called when needed.
*/
protected final void enableInternalNotifications() {
xmlModelListener = new XmlModelListener();
ddProvider.addListener(xmlModelListener);
annotationModelListener = new AnnotationModelListener();
annotationModelProvider.addListener(annotationModelListener);
}
/**
* Disable notifications from internalProviders. See also
* {@link #enableInternalNotifications()}
*/
protected final void disableInternalNotifications() {
ddProvider.removeListener(xmlModelListener);
annotationModelProvider.removeListener(annotationModelListener);
}
protected void notifyListeners(IModelProviderEvent event) {
event.setModel(this);
event.setProject(project);
for (IModelProviderListener listener : getListeners()) {
listener.modelsChanged(event);
}
}
protected boolean shouldDispose(IModelProviderEvent event) {
return (event.getEventCode() == IModelProviderEvent.UNLOADED_RESOURCE);
}
/**
* Returns the dispose state of this model provider. When the provider is
* disposed it can not be used until getModelObject is called again.
*
* Subclasses may override this method.
*
* @return true if the model provider is to be treated as disposed
*/
public boolean isDisposed() {
return isOnceDisposed || (ddProvider == null && annotationModelProvider == null);
}
/**
* Dispose the model provider. If the provider is already disposed the
* method has no effect.
*
* Subclasses may override this method.
*
* @see #isDisposed()
*/
public void dispose() {
if (isDisposed())
return;
disableInternalNotifications();
ddProvider = null;
annotationModelProvider = null;
mergedModel = null;
isOnceDisposed = true;
}
}