| /*********************************************************************** |
| * 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.ArrayList; |
| import java.util.Collection; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.SafeRunner; |
| 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; |
| import org.eclipse.jst.jee.JEEPlugin; |
| |
| /** |
| * 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 to 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 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 needed. |
| * </p> |
| * |
| * @author Kiril Mitov k.mitov@sap.com |
| * |
| */ |
| public abstract class AbstractMergedModelProvider<T> implements IModelProvider { |
| |
| protected IModelProvider ddProvider; |
| |
| protected IModelProvider annotationModelProvider; |
| |
| private class AnnotationModelListener implements IModelProviderListener { |
| public void modelsChanged(IModelProviderEvent event) { |
| if (isDisposed() || mergedModel == null) |
| return; |
| if (shouldDispose(event)) { |
| dispose(); |
| notifyListeners(event); |
| return; |
| } |
| AbstractMergedModelProvider.this.annotationModelChanged(event); |
| } |
| } |
| |
| private class XmlModelListener implements IModelProviderListener { |
| public void modelsChanged(IModelProviderEvent event) { |
| if (isDisposed() || mergedModel == null) |
| return; |
| if (shouldDispose(event)) { |
| mergedModel = null; |
| notifyListeners(event); |
| return; |
| } |
| 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 ArrayList<IModelProviderListener>(); |
| return listeners; |
| } |
| |
| protected T getMergedModel() { |
| try { |
| if (mergedModel == null) |
| mergedModel = loadModel(); |
| } catch (CoreException e) { |
| JEEPlugin.getDefault().getLog().log(e.getStatus()); |
| 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$ |
| if (ddProvider == null) |
| ddProvider = loadDeploymentDescriptorModel(); |
| if (ddProvider == null || ddProvider.getModelObject() == null) |
| return null; |
| if (annotationModelProvider == null) |
| annotationModelProvider = loadAnnotationModel((T) ddProvider.getModelObject()); |
| if (annotationModelProvider == null || annotationModelProvider.getModelObject() == null) |
| return null; |
| T ddModel = (T) ddProvider.getModelObject(); |
| T annotationModel = (T) annotationModelProvider.getModelObject(); |
| mergedModel = createNewModelInstance(); |
| initMergedModelResource((EObject) ddModel); |
| |
| enableInternalNotifications(); |
| return merge(ddModel, annotationModel); |
| } |
| |
| /** |
| * Creates a new instance of the model that will be used for mergedModel |
| * |
| * @return |
| */ |
| protected abstract T createNewModelInstance(); |
| |
| 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(final IModelProviderEvent event) { |
| event.setModel(this); |
| event.setProject(project); |
| final Collection<IModelProviderListener> listeners = getListeners(); |
| IModelProviderListener[] backup = listeners.toArray(new IModelProviderListener[listeners.size()]); |
| for (final IModelProviderListener listener : backup) { |
| SafeRunner.run(new ISafeRunnable() { |
| public void handleException(Throwable exception) { |
| } |
| |
| public void run() throws Exception { |
| listener.modelsChanged(event); |
| } |
| }); |
| } |
| backup = null; |
| } |
| |
| 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 (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; |
| } |
| |
| } |