| /******************************************************************************* |
| * Copyright (c) 2001, 2006 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.wst.xml.core.internal.emf2xml; |
| |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.jem.util.emf.workbench.ProjectResourceSet; |
| import org.eclipse.wst.common.internal.emf.resource.EMF2DOMAdapter; |
| import org.eclipse.wst.common.internal.emf.resource.EMF2DOMRenderer; |
| import org.eclipse.wst.common.internal.emf.resource.TranslatorResource; |
| import org.eclipse.wst.common.internal.emf.utilities.DOMUtilities; |
| import org.eclipse.wst.common.internal.emfworkbench.WorkbenchResourceHelper; |
| import org.eclipse.wst.common.internal.emfworkbench.integration.ResourceSetWorkbenchEditSynchronizer; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.xml.core.internal.Logger; |
| import org.eclipse.wst.xml.core.internal.document.DocumentTypeImpl; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.w3c.dom.Node; |
| |
| |
| public class EMF2DOMSSERenderer extends EMF2DOMRenderer implements IModelStateListener, IModelLifecycleListener { |
| |
| protected Object aboutToChangeNode = null; |
| |
| protected boolean isBatchChanges = false; |
| |
| private boolean isSaving = false; |
| |
| private IModelManager modelManager; |
| |
| /** The XML DOM model */ |
| protected IDOMModel xmlModel; |
| |
| /** Used internally; the unique id for the xml model */ |
| protected String xmlModelId; |
| |
| protected boolean xmlModelReverted = false; |
| |
| public EMF2DOMSSERenderer() { |
| super(); |
| } |
| |
| public void accessForRead() { |
| if (!resource.isNew()) { |
| String id = getModelManagerId(); |
| getModelManager().getExistingModelForRead(id); |
| } |
| } |
| |
| public void accessForWrite() { |
| modelAccessForWrite(); |
| } |
| |
| /** |
| * see batchModelEnd |
| * |
| */ |
| |
| public void batchModeEnd() { |
| getXMLModel().changedModel(); |
| setRootNodeAdapterNotificationEnabled(true); |
| isBatchChanges = false; |
| } |
| |
| /** |
| * batchModeStart and batchModeEnd is a pair that controls notifications, |
| * and tread access. They should always be called in a try/finally block. |
| * |
| * setBatchModel begins the processing where notifications are not sent |
| * out on each change, but saved up until the endBatchMode called. |
| * |
| * This pair of calls can also, indirectly, "lock" the DOM Model to access |
| * from only one thread, so it should not be locked for long periods of |
| * time. That's also why it is important to have the endBatchMode in a |
| * finally block to be sure it is always called, or the DOM will be left |
| * in a locked, unusable, state and only shortly away from severere |
| * program error. |
| * |
| * <pre><code> |
| * Example |
| * </code> |
| * |
| * try { |
| * batchModelStart(); |
| * ...do a some work ... |
| * } |
| * finally { |
| * endBatchMode(); |
| * } |
| * |
| * |
| * </pre> |
| */ |
| public void batchModeStart() { |
| isBatchChanges = true; |
| getXMLModel().aboutToChangeModel(); |
| setRootNodeAdapterNotificationEnabled(false); |
| } |
| |
| private void cacheSynchronizationStamp() { |
| IFile file = WorkbenchResourceHelper.getFile(resource); |
| if (file != null) { |
| if (xmlModel != null) |
| xmlModel.resetSynchronizationStamp(file); |
| } |
| } |
| |
| /** |
| * Create a new Document given |
| * |
| * @aResource. |
| */ |
| protected void createDocument() { |
| TranslatorResource res = getResource(); |
| res.setDefaults(); |
| IFile file = WorkbenchResourceHelper.getFile(resource); |
| InputStream is = DOMUtilities.createHeaderInputStream(res.getDoctype(), res.getPublicId(), res.getSystemId()); |
| if (is == null) |
| return; |
| try { |
| try { |
| List folders = new ArrayList(); |
| IContainer container = file.getParent(); |
| while (null != container && !container.exists() && container instanceof IFolder) { |
| folders.add(container); |
| container = container.getParent(); |
| } |
| IFolder folder = null; |
| for (int i = 0; i < folders.size(); i++) { |
| folder = (IFolder) folders.get(i); |
| folder.create(true, true, null); |
| } |
| file.create(is, true, null); |
| file.setLocal(true, 1, null); |
| } |
| catch (CoreException e1) { |
| Logger.logException(e1); |
| } |
| finally { |
| if (null != is) { |
| is.close(); |
| } |
| } |
| initializeXMLModel(file, true); |
| } |
| catch (IOException ex) { |
| Logger.log(Logger.ERROR, "IWAE0017E Unexpected IO exception occurred creating xml document");//$NON-NLS-1$ |
| } |
| } |
| |
| protected void createDOMTreeIfNecessary() { |
| if (needsToCreateDOM) |
| createDOMTree(); |
| } |
| |
| protected EMF2DOMAdapter createRootDOMAdapter() { |
| return new EMF2DOMSSEAdapter(getResource(), document, this, getResource().getRootTranslator()); |
| } |
| |
| public void deRegisterAsModelLifecycleListener() { |
| if (xmlModel != null) |
| xmlModel.removeModelLifecycleListener(this); |
| } |
| |
| public void deRegisterAsModelStateListener() { |
| if (xmlModel != null) |
| xmlModel.removeModelStateListener(this); |
| } |
| |
| private void deregisterFromXMLModel() { |
| deRegisterAsModelStateListener(); |
| deRegisterAsModelLifecycleListener(); |
| // This try/catch block is a hack to fix defect 204114. This occurs |
| // because |
| // the model manager plugin is shut down and unloaded before the j2ee |
| // plugin. |
| // Calling getModelManager() can result in a class cast exception that |
| // should |
| // be ignored. |
| // ModelManager mgr = null; |
| try { |
| getModelManager(); |
| } |
| catch (ClassCastException exc) { |
| return; |
| } |
| if (xmlModel != null) { |
| int writeCount = resource.getWriteCount(); |
| int readCount = resource.getReadCount(); |
| for (int i = 0; i < writeCount; i++) |
| xmlModel.releaseFromEdit(); |
| for (int ii = 0; ii < readCount; ii++) |
| xmlModel.releaseFromRead(); |
| } |
| EMF2DOMAdapter adapter = (EMF2DOMAdapter) EcoreUtil.getAdapter(resource.eAdapters(), EMF2DOMAdapter.ADAPTER_CLASS); |
| if (adapter != null) { |
| adapter.removeAdapters(adapter.getNode()); |
| } |
| xmlModel = null; |
| xmlModelId = null; |
| } |
| |
| public void doSave(OutputStream outputStream, Map options) throws IOException { |
| |
| try { |
| isSaving = true; |
| if (null != outputStream) { |
| throw new RuntimeException(this.getClass().getName() + " should not use an OutputStream for saving");} //$NON-NLS-1$ |
| createDOMTreeIfNecessary(); |
| ResourceSetWorkbenchEditSynchronizer synchronizer = (ResourceSetWorkbenchEditSynchronizer) ((ProjectResourceSet) resource.getResourceSet()).getSynchronizer(); |
| IFile aFile = WorkbenchResourceHelper.getFile(resource); |
| try { |
| synchronizer.preSave(aFile); |
| xmlModel.save(aFile); |
| } |
| catch (CoreException ex) { |
| synchronizer.removeFromRecentlySavedList(aFile); |
| Logger.logException(ex); |
| } |
| cacheSynchronizationStamp(); |
| } |
| finally { |
| isSaving = false; |
| } |
| } |
| |
| public EMF2DOMAdapter getExistingDOMAdapter(Node node) { |
| IDOMNode xNode = (IDOMNode) node; |
| return (EMF2DOMSSEAdapter) xNode.getAdapterFor(EMF2DOMAdapter.ADAPTER_CLASS); |
| } |
| |
| protected IModelManager getModelManager() { |
| if (modelManager == null) |
| modelManager = StructuredModelManager.getModelManager(); |
| return modelManager; |
| } |
| |
| /** |
| * Return id used to key the XML resource in the XML ModelManager. |
| */ |
| protected String getModelManagerId() { |
| if (xmlModelId == null) { |
| IFile file = WorkbenchResourceHelper.getFile(getResource()); |
| if (file != null) { |
| xmlModelId = getModelManager().calculateId(file); |
| } |
| else { |
| xmlModelId = resource.getURI() + Long.toString(System.currentTimeMillis()); |
| } |
| } |
| return xmlModelId; |
| } |
| |
| protected ResourceSet getResourceSet() { |
| return resource == null ? null : resource.getResourceSet(); |
| } |
| |
| /** |
| * Return the DOM model for this resource. |
| */ |
| public IDOMModel getXMLModel() { |
| return xmlModel; |
| } |
| |
| public String getXMLModelId() { |
| return xmlModelId; |
| } |
| |
| private IDOMModel initializeXMLModel(IFile file, boolean forWrite) throws UnsupportedEncodingException, IOException { |
| if (file == null || !file.exists()) |
| throw new FileNotFoundException((file == null) ? "null" : file.getFullPath().toOSString()); //$NON-NLS-1$ |
| try { |
| if (forWrite) { |
| setXMLModel((IDOMModel) getModelManager().getModelForEdit(file)); |
| } |
| else { |
| setXMLModel((IDOMModel) getModelManager().getModelForRead(file)); |
| } |
| setXMLModelId(getXMLModel().getId()); |
| needsToCreateDOM = false; |
| } |
| catch (CoreException e) { |
| Logger.logException(e); |
| return null; |
| } |
| String id = getModelManager().calculateId(file); |
| syncReferenceCounts(id, forWrite); |
| if (xmlModel != null) |
| document = xmlModel.getDocument(); |
| return xmlModel; |
| } |
| |
| public boolean isBatchMode() { |
| return isBatchChanges; |
| } |
| |
| public boolean isModified() { |
| return (getXMLModel() != null && getXMLModel().isDirty()); |
| } |
| |
| public boolean isShared() { |
| if (getResourceSet() == null || xmlModel == null) |
| return false; |
| return xmlModel.isShared(); |
| } |
| |
| public boolean isSharedForWrite() { |
| if (getResourceSet() == null || xmlModel == null) |
| return false; |
| return xmlModel.isSharedForEdit(); |
| } |
| |
| protected void loadDocument(InputStream in, Map options) throws IOException { |
| if (null != in) { |
| throw new RuntimeException(this.getClass().getName() + " should not use an InputStream for loading");} //$NON-NLS-1$ |
| IFile file = WorkbenchResourceHelper.getFile(resource); |
| initializeXMLModel(file, (resource.getWriteCount() != 0)); |
| cacheSynchronizationStamp(); |
| } |
| |
| protected boolean managesDOMAdapters() { |
| return false; |
| } |
| |
| public void modelAboutToBeChanged(IStructuredModel model) { |
| if (model.getStructuredDocument() != null) |
| aboutToChangeNode = model.getStructuredDocument().getFirstStructuredDocumentRegion(); |
| } |
| |
| public void modelAboutToBeReinitialized(IStructuredModel structuredModel) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| private void modelAccessForWrite() { |
| String id = getModelManagerId(); |
| getModelManager().getExistingModelForEdit(id); |
| } |
| |
| public void modelChanged(IStructuredModel model) { |
| if (isBatchChanges) |
| return; |
| try { |
| if (aboutToChangeNode != null && model.getStructuredDocument() != null && model.getStructuredDocument().getFirstStructuredDocumentRegion() != aboutToChangeNode) { |
| modelAccessForWrite(); |
| try { |
| xmlModelReverted = true; |
| resource.unload(); |
| } |
| finally { |
| if (getXMLModel() != null) |
| getXMLModel().releaseFromEdit(); |
| } |
| } |
| } |
| finally { |
| aboutToChangeNode = null; |
| } |
| } |
| |
| public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) { |
| if (!isDirty && resource.isModified()) { // The XMLModel was saved |
| resource.setModified(false); |
| long stamp = WorkbenchResourceHelper.computeModificationStamp(resource); |
| WorkbenchResourceHelper.setSynhronizationStamp(resource, stamp); |
| ResourceSetWorkbenchEditSynchronizer synchronizer = (ResourceSetWorkbenchEditSynchronizer) ((ProjectResourceSet) resource.getResourceSet()).getSynchronizer(); |
| IFile aFile = WorkbenchResourceHelper.getFile(resource); |
| synchronizer.preSave(aFile); |
| } |
| if (isDirty) |
| resource.setModified(true); |
| } |
| |
| public void modelReinitialized(IStructuredModel structuredModel) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void modelResourceDeleted(IStructuredModel model) { |
| // Do nothing |
| } |
| |
| public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) { |
| // Do nothing |
| } |
| |
| /** |
| * This method is called just prior to being removed from the ResourceSet. |
| * Ensure that all reference counts to the XMLModel are removed. |
| */ |
| public void preDelete() { |
| if (resource.isLoaded()) |
| deregisterFromXMLModel(); |
| } |
| |
| public void preUnload() { |
| deregisterFromXMLModel(); |
| } |
| |
| public void processPostModelEvent(ModelLifecycleEvent event) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| public void processPreModelEvent(ModelLifecycleEvent event) { |
| if (!isSaving) { |
| if (event.getType() == ModelLifecycleEvent.MODEL_SAVED) { |
| ResourceSetWorkbenchEditSynchronizer synchronizer = (ResourceSetWorkbenchEditSynchronizer) ((ProjectResourceSet) resource.getResourceSet()).getSynchronizer(); |
| IFile aFile = WorkbenchResourceHelper.getFile(resource); |
| synchronizer.preSave(aFile); |
| } |
| } |
| } |
| |
| |
| public void registerAsModelLifecycleListener() { |
| this.xmlModel.addModelLifecycleListener(this); |
| } |
| |
| /** |
| * Insert the method's description here. Creation date: (9/7/2001 10:49:53 |
| * AM) |
| */ |
| public void registerAsModelStateListener() { |
| this.xmlModel.addModelStateListener(this); |
| } |
| |
| public void releaseFromRead() { |
| if (xmlModel != null) |
| xmlModel.releaseFromRead(); |
| } |
| |
| public void releaseFromWrite() { |
| if (xmlModel != null) |
| xmlModel.releaseFromEdit(); |
| } |
| |
| public void removeDOMAdapter(Node aNode, EMF2DOMAdapter anAdapter) { |
| ((IDOMNode) aNode).removeAdapter((EMF2DOMSSEAdapter) anAdapter); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jem.internal.util.emf.xml.EMF2DOMRenderer#replaceDocumentType(java.lang.String, |
| * java.lang.String, java.lang.String) |
| */ |
| public void replaceDocumentType(String docTypeName, String publicId, String systemId) { |
| if (document == null) |
| return; |
| DocumentTypeImpl docType = (DocumentTypeImpl) document.getDoctype(); |
| if (docType == null) |
| return; |
| if (publicId == null && systemId == null) |
| document.removeChild(docType); |
| else { |
| docType.setPublicId(publicId); |
| docType.setSystemId(systemId); |
| } |
| } |
| |
| /** |
| * @deprecated use batchModeStart and BatchModeEnd instead even if you do |
| * not use batchModelStart/End, you still need to use the |
| * try/finally pattern documented there. |
| */ |
| |
| public void setBatchMode(boolean isBatch) { |
| |
| // This is some extra processing for clients to know they may be using |
| // incorrectly |
| if (isBatch) { |
| if (isBatchChanges) { |
| Logger.log(Logger.INFO_DEBUG, "setBatch was set to true when it was already true. This can be an indication of invalid calling order"); //$NON-NLS-1$ |
| } |
| } |
| |
| |
| if (isBatch) { |
| batchModeStart(); |
| } |
| else { |
| batchModeEnd(); |
| } |
| } |
| |
| private void setRootNodeAdapterNotificationEnabled(boolean b) { |
| EObject root = resource.getRootObject(); |
| if (root != null) { |
| EMF2DOMAdapter adapter = (EMF2DOMAdapter) EcoreUtil.getExistingAdapter(root, EMF2DOMAdapter.ADAPTER_CLASS); |
| if (adapter != null) { |
| adapter.setNotificationEnabled(b); |
| if (b) |
| adapter.updateDOM(); |
| } |
| } |
| } |
| |
| /** |
| * Return the DOM model for this resource. |
| */ |
| public void setXMLModel(IDOMModel xmlModel) { |
| deRegisterAsModelStateListener(); |
| deRegisterAsModelLifecycleListener(); |
| this.xmlModel = xmlModel; |
| registerAsModelStateListener(); |
| registerAsModelLifecycleListener(); |
| } |
| |
| public void setXMLModelId(String id) { |
| xmlModelId = id; |
| } |
| |
| private void syncReferenceCounts(String id, boolean forWrite) { |
| int editIndex = 0, readIndex = 0; |
| if (forWrite) |
| editIndex++; |
| else |
| readIndex++; |
| int writeCount = resource.getWriteCount(); |
| int readCount = resource.getReadCount(); |
| for (int i = writeCount; i > editIndex; i--) |
| modelManager.getExistingModelForEdit(id); |
| for (int i = readCount; i > readIndex; i--) |
| modelManager.getExistingModelForRead(id); |
| } |
| |
| public boolean useStreamsForIO() { |
| return false; |
| } |
| |
| public boolean wasReverted() { |
| return xmlModelReverted; |
| } |
| } |