/******************************************************************************* * Copyright (c) 2001, 2011 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 * Jens Lukowski/Innoopract - initial renaming/restructuring * *******************************************************************************/ package org.eclipse.wst.sse.core.internal.model; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.Logger; import org.eclipse.wst.sse.core.internal.SSECoreMessages; import org.eclipse.wst.sse.core.internal.encoding.EncodingRule; import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler; import org.eclipse.wst.sse.core.internal.provisional.DocumentChanged; 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.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.events.AboutToBeChangedEvent; import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener; import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener; import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent; import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent; import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent; import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent; import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent; import org.eclipse.wst.sse.core.internal.provisional.exceptions.ResourceInUse; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager; import org.eclipse.wst.sse.core.internal.util.URIResolver; import org.eclipse.wst.sse.core.internal.util.Utilities; public abstract class AbstractStructuredModel implements IStructuredModel { private static final String MODEL_MANAGER_NULL = "Warning: AbstractStructuredModel::close: model manager was null during a close of a model (which should be impossible)"; //$NON-NLS-1$ class DirtyStateWatcher implements IStructuredDocumentListener { public void newModel(NewDocumentEvent structuredDocumentEvent) { // I don't think its safe to assume a new model // is always "fresh", so we'll leave dirty state // unchanged; // but we'll tell everyone about it. setDirtyState(fDirtyState); } public void noChange(NoChangeEvent structuredDocumentEvent) { // don't change dirty state } public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) { setDirtyState(true); // no need to listen any more if (fStructuredDocument != null) { fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher); } } public void regionChanged(RegionChangedEvent structuredDocumentEvent) { setDirtyState(true); // no need to listen any more if (fStructuredDocument != null) { fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher); } } public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) { setDirtyState(true); // no need to listen any more if (fStructuredDocument != null) { fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher); } } } class DocumentToModelNotifier implements IStructuredDocumentListener, IModelAboutToBeChangedListener { /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IModelAboutToBeChangedListener#modelAboutToBeChanged(org.eclipse.wst.sse.core.events.AboutToBeChangedEvent) */ public void modelAboutToBeChanged(AboutToBeChangedEvent structuredDocumentEvent) { // If we didn't originate the change, take note we are about to // change based on our underlying document changing. // If we did originate the change, we, or client, should have // already called aboutToChangeModel. if (structuredDocumentEvent.getOriginalRequester() != this) { aboutToChangeModel(); } } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#newModel(org.eclipse.wst.sse.core.events.NewDocumentEvent) */ public void newModel(NewDocumentEvent structuredDocumentEvent) { // if we didn't originate the change, take note we have changed if (structuredDocumentEvent.getOriginalRequester() != this) { changedModel(); } } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#noChange(org.eclipse.wst.sse.core.events.NoChangeEvent) */ public void noChange(NoChangeEvent structuredDocumentEvent) { // if we didn't originate the change, take note we have changed if (structuredDocumentEvent.getOriginalRequester() != this) { changedModel(); } } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#nodesReplaced(org.eclipse.wst.sse.core.events.StructuredDocumentRegionsReplacedEvent) */ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) { // if we didn't originate the change, take note we have changed if (structuredDocumentEvent.getOriginalRequester() != this) { changedModel(); } } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#regionChanged(org.eclipse.wst.sse.core.events.RegionChangedEvent) */ public void regionChanged(RegionChangedEvent structuredDocumentEvent) { // if we didn't originate the change, take note we have changed if (structuredDocumentEvent.getOriginalRequester() != this) { changedModel(); } } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#regionsReplaced(org.eclipse.wst.sse.core.events.RegionsReplacedEvent) */ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) { // if we didn't originate the change, take note we have changed if (structuredDocumentEvent.getOriginalRequester() != this) { changedModel(); } } } private FactoryRegistry factoryRegistry; private String fBaseLocation; boolean fDirtyState; DirtyStateWatcher fDirtyStateWatcher; DocumentToModelNotifier fDocumentToModelNotifier; private String fExplicitContentTypeIdentifier; private String fId; private LifecycleNotificationManager fLifecycleNotificationManager; private final Object fListenerLock = new byte[0]; protected ILock fLockObject; // private String fLineDelimiter; // private Object fType; private IModelHandler fModelHandler; // issue: we should not "hold on" to model manager, can // easily get with StructuredModelManager.getModelManager(); // but will need to add more null checks. private IModelManager fModelManager; private int fModelStateChanging; private Object[] fModelStateListeners; private boolean fNewState = false; private URIResolver fResolver; protected IStructuredDocument fStructuredDocument; /** * The time stamp of the underlying resource's modification date, at the * time this model was created, or the last time it was saved. Note: for * this version, this variable is not set automatically, be needs to be * managed by client. The FileModelProvider does this for most cases, but * if client do not use FileModelProvider, they must set this variable */ public long fSynchronizationStamp = IResource.NULL_STAMP; private boolean reinitializationNeeded; private Object reinitializeStateData; /** * AbstractStructuredModel constructor comment. */ public AbstractStructuredModel() { super(); fDirtyStateWatcher = new DirtyStateWatcher(); fDocumentToModelNotifier = new DocumentToModelNotifier(); } /** * This method is just for getting an instance of the model manager of the * right Impl type, to be used "internally" for making protected calls * directly to the impl class. */ private ModelManagerImpl _getModelManager() { // TODO_future: redesign so we don't need this 'Impl' version if (fModelManager == null) { fModelManager = StructuredModelManager.getModelManager(); } return (ModelManagerImpl) fModelManager; } /** * This API allows clients to declare that they are about to make a * "large" change to the model. This change might be in terms of content * or it might be in terms of the model id or base location. Note that in * the case of embedded calls, notification to listeners is sent only * once. Note that the client who is making these changes has the * responsibility to restore the models state once finished with the * changes. See getMemento and restoreState. The method * isModelStateChanging can be used by a client to determine if the model * is already in a change sequence. */ public void aboutToChangeModel() { // notice this is just a public avenue to our protected method internalAboutToBeChanged(); } public void aboutToReinitializeModel() { // notice this is just a public avenue to our protected method fireModelAboutToBeReinitialized(); } public void addModelLifecycleListener(IModelLifecycleListener listener) { synchronized (fListenerLock) { if (fLifecycleNotificationManager == null) { fLifecycleNotificationManager = new LifecycleNotificationManager(); } fLifecycleNotificationManager.addListener(listener); } } public void addModelStateListener(IModelStateListener listener) { synchronized (fListenerLock) { if (!Utilities.contains(fModelStateListeners, listener)) { int oldSize = 0; if (fModelStateListeners != null) { // normally won't be null, but we need to be sure, for // first // time through oldSize = fModelStateListeners.length; } int newSize = oldSize + 1; Object[] newListeners = new Object[newSize]; if (fModelStateListeners != null) { System.arraycopy(fModelStateListeners, 0, newListeners, 0, oldSize); } // add listener to last position newListeners[newSize - 1] = listener; // // now switch new for old fModelStateListeners = newListeners; } } } /** * This lock to lock the small bits of data and operations in the models * themselves. this lock is "shared" with document, so, eventually, * changes can be made safely from either side. * * @deprecated */ protected final void beginLock() { } public void beginRecording(Object requester) { beginRecording(requester, null, null); } public void beginRecording(Object requester, int cursorPosition, int selectionLength) { beginRecording(requester, null, null, cursorPosition, selectionLength); } public void beginRecording(Object requester, String label) { beginRecording(requester, label, null); } public void beginRecording(Object requester, String label, int cursorPosition, int selectionLength) { beginRecording(requester, label, null, cursorPosition, selectionLength); } public void beginRecording(Object requester, String label, String description) { if (getUndoManager() != null) getUndoManager().beginRecording(requester, label, description); } public void beginRecording(Object requester, String label, String description, int cursorPosition, int selectionLength) { if (getUndoManager() != null) getUndoManager().beginRecording(requester, label, description, cursorPosition, selectionLength); } /** * This API allows a client controlled way of notifying all ModelEvent * listners that the model has been changed. This method is a matched pair * to aboutToChangeModel, and *must* be called after aboutToChangeModel * ... or some listeners could be left waiting indefinitely for the * changed event. So, its suggested that changedModel always be in a * finally clause. Likewise, a client should never call changedModel * without calling aboutToChangeModel first. In the case of embedded * calls, the notification is just sent once. */ public void changedModel() { // notice this is just a public avenue to our protected method internalModelChanged(); // also note! // if we've been "changed" by a client, we might still need // to be re-initialized, so we'll check and handle that here. // Note only does this provide a solution to some "missed" // re-inits, in provides a built in way for clients to // "force" the model to handle itself, by bracketing any // changes with aboutToChange and changed, the model itself // will check. But only call re-init if all other pending // modelChanged states have been handled. if (fModelStateChanging == 0 && isReinitializationNeeded()) { reinit(); } } /** * Based on similar method in FileDocumentProvider. It will provide what * the modificationStamp would be if resetSynchronzationStamp(resource) * were used, although for this 'compute' API, no changes to the instance * are made. */ public long computeModificationStamp(IResource resource) { long modificationStamp = resource.getModificationStamp(); IPath path = resource.getLocation(); if (path == null) { return modificationStamp; } // Note: checking existence of file is a little different than // impl in // the FileDocumentProvider. See defect number 223790. File file = path.toFile(); if (!file.exists()) { return modificationStamp; } modificationStamp = file.lastModified(); return modificationStamp; } /** * Provides a copy of the model, but a new ID must be provided. The * principle of this copy is not to copy fields, etc., as is typically * done in a clone method, but to return a model with the same content in * the structuredDocument. Note: It is the callers responsibility to * setBaseLocation, listners, etc., as appropriate. Type and Encoding are * the only fields set by this method. If the newId provided already exist * in the model manager, a ResourceInUse exception is thrown. */ public IStructuredModel copy(String newId) throws ResourceInUse { IStructuredModel newModel = null; // this first one should fail, if not, its treated as an error // If the caller wants to use an existing one, they can call // getExisting // after this failure newModel = getModelManager().getExistingModelForEdit(newId); if (newModel != null) { // be sure to release the reference we got "by accident" (and // no // longer need) newModel.releaseFromEdit(); throw new ResourceInUse(); } newModel = getModelManager().copyModelForEdit(getId(), newId); return newModel; } /** * Disable undo management. */ public void disableUndoManagement() { if (getUndoManager() != null) getUndoManager().disableUndoManagement(); } /** * Enable undo management. */ public void enableUndoManagement() { if (getUndoManager() != null) getUndoManager().enableUndoManagement(); } /** * endLock is protected only for a very special purpose. So subclasses can * call it to end the lock after updates have been made, but before * notifications are sent * * @deprecated */ protected final void endLock() { } public void endRecording(Object requester) { if (getUndoManager() != null) getUndoManager().endRecording(requester); } public void endRecording(Object requester, int cursorPosition, int selectionLength) { if (getUndoManager() != null) getUndoManager().endRecording(requester, cursorPosition, selectionLength); } /** * Informs all registered model state listeners that the the model is * about to under go a change. This change might be in terms of contents * or might be in terms of the model's id or base location. */ private void fireModelAboutToBeChanged() { // we must assign listeners to local variable, since the add and // remove listner // methods can change the actual instance of the listener array // from another thread if (fModelStateListeners != null) { Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { ((IModelStateListener) holdListeners[i]).modelAboutToBeChanged(this); } } } protected void fireModelAboutToBeReinitialized() { // we must assign listeners to local variable, since the add and // remove // listner // methods can change the actual instance of the listener array from // another thread if (fModelStateListeners != null) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelAboutToBeReinitialized"); //$NON-NLS-1$ //$NON-NLS-2$ } Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { // NOTE: trick for transition. We actual use the same // listeners // as modelState, but only send this to those that have // implemented ModelStateExtended. IModelStateListener listener = (IModelStateListener) holdListeners[i]; listener.modelAboutToBeReinitialized(this); } } } private void fireModelChanged() { // we must assign listeners // to local variable, since the add // and remove listner // methods can change the actual instance of the listener // array from another thread if (fModelStateListeners != null) { Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { try { ((IModelStateListener) holdListeners[i]).modelChanged(this); } // its so criticial that the begin/end arrive in // pairs, // if there happends to be an error in one of the // modelChanged, // they we want to be sure rest complete ok. catch (Exception e) { Logger.logException(e); } } } } /** * Informs all registered model state listeners about a change in the * dirty state of the model. The dirty state is entirely about changes in * the content of the model (not, for example, about changes to id, or * base location -- see modelMoved). */ protected void fireModelDirtyStateChanged(IStructuredModel element, boolean isDirty) { // we must assign listeners to local variable, since the add and // remove // listner // methods can change the actual instance of the listener array from // another thread if (fModelStateListeners != null) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelDirtyStateChanged"); //$NON-NLS-1$ //$NON-NLS-2$ } Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { ((IModelStateListener) holdListeners[i]).modelDirtyStateChanged(element, isDirty); } } } protected void fireModelReinitialized() { // we must assign listeners to local variable, since the add and // remove // listner // methods can change the actual instance of the listener array from // another thread if (fModelStateListeners != null) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelReinitialized"); //$NON-NLS-1$ //$NON-NLS-2$ } Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { IModelStateListener listener = (IModelStateListener) holdListeners[i]; listener.modelReinitialized(this); } } } /** * Informs all registered model state listeners about the deletion of a * model's underlying resource. */ protected void fireModelResourceDeleted(IStructuredModel element) { // we must assign listeners to local variable, since the add and // remove // listner // methods can change the actual instance of the listener array from // another thread if (fModelStateListeners != null) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelResourceDeleted"); //$NON-NLS-1$ //$NON-NLS-2$ } Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { ((IModelStateListener) holdListeners[i]).modelResourceDeleted(element); } } } /** * Informs all registered model state listeners that the resource * underlying a model has been moved. This is typically reflected in a * change to the id, baseLocation, or both. */ protected void fireModelResourceMoved(IStructuredModel originalElement, IStructuredModel movedElement) { // we must assign listeners to local variable, since the add and // remove // listner // methods can change the actual instance of the listener array from // another thread if (fModelStateListeners != null) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelResourceMoved"); //$NON-NLS-1$ //$NON-NLS-2$ } Object[] holdListeners = fModelStateListeners; for (int i = 0; i < holdListeners.length; i++) { ((IModelStateListener) holdListeners[i]).modelResourceMoved(originalElement, movedElement); } } } public Object getAdapter(Class adapter) { return Platform.getAdapterManager().getAdapter(this, adapter); } /** * @return java.lang.String */ public java.lang.String getBaseLocation() { return fBaseLocation; } /** * @see org.eclipse.wst.sse.core.internal.provisional.IStructuredModel#getContentTypeIdentifier() */ public String getContentTypeIdentifier() { if (fExplicitContentTypeIdentifier != null) return fExplicitContentTypeIdentifier; return fModelHandler.getAssociatedContentTypeId(); } /** * */ public FactoryRegistry getFactoryRegistry() { if (factoryRegistry == null) { factoryRegistry = new FactoryRegistry(); } return factoryRegistry; } /** * The id is the id that the model manager uses to identify this model * * @ISSUE - no one should need to know ID, so this should be default access eventually. * If clients believe they do need ID, be sure to let us know (open a bug). */ public String getId() { return fId; } public abstract IndexedRegion getIndexedRegion(int offset); /** * Gets the contentTypeDescription. * * @return Returns a ContentTypeDescription */ public IModelHandler getModelHandler() { return fModelHandler; } public IModelManager getModelManager() { return _getModelManager(); } /** * This function returns the reference count of underlying model. */ // TODO: try to refine the design not to use this function public int getReferenceCount() { if (getModelManager() == null) return 0; return getModelManager().getReferenceCount(getId()); } /** * This function returns the reference count of underlying model. */ // TODO: try to refine the design not to use this function public int getReferenceCountForEdit() { if (getModelManager() == null) return 0; return getModelManager().getReferenceCountForEdit(getId()); } /** * This function returns the reference count of underlying model. */ // TODO: try to refine the design not to use this function public int getReferenceCountForRead() { if (getModelManager() == null) return 0; return getModelManager().getReferenceCountForRead(getId()); } public Object getReinitializeStateData() { return reinitializeStateData; } public URIResolver getResolver() { return fResolver; } public IStructuredDocument getStructuredDocument() { IStructuredDocument result = null; result = fStructuredDocument; return result; } /** * Insert the method's description here. Creation date: (9/7/2001 2:30:26 * PM) * * @return long */ public long getSynchronizationStamp() { return fSynchronizationStamp; } public IStructuredTextUndoManager getUndoManager() { IStructuredTextUndoManager structuredTextUndoManager = null; IStructuredDocument structuredDocument = getStructuredDocument(); if (structuredDocument == null) { structuredTextUndoManager = null; } else { structuredTextUndoManager = structuredDocument.getUndoManager(); } return structuredTextUndoManager; } public void initId(String id) { fId = id; } private final Object STATE_LOCK = new Object(); final protected void internalAboutToBeChanged() { int state = 0; // we always increment counter, for every request (so *must* receive // corresponding number of 'changedModel' requests) synchronized (STATE_LOCK) { state = fModelStateChanging++; } // notice we only fire this event if we are not // already in a model state changing sequence if (state == 0) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelAboutToBeChanged"); //$NON-NLS-1$ //$NON-NLS-2$ } try { fireModelAboutToBeChanged(); } catch (Exception e) { Logger.logException("Exception while notifying model state listers of about to change", e); //$NON-NLS-1$ } } } /** * Informs all registered model state listeners that an impending change * is now complete. This method must only be called by 'modelChanged' * since it keeps track of counts. */ final protected void internalModelChanged() { int state = 0; // always decrement synchronized (STATE_LOCK) { state = --fModelStateChanging; } // Check integrity // to be less than zero is a programming error, // but we'll reset to zero // and try to continue if (state < 0) { state = 0; throw new IllegalStateException("Program Error: modelStateChanging was less than zero"); //$NON-NLS-1$ } // We only fire this event if all pending requests are done. // That is, if we've received the same number of modelChanged as // we have aboutToChangeModel. if (state == 0) { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelChanged"); //$NON-NLS-1$ //$NON-NLS-2$ } fireModelChanged(); } } public boolean isDirty() { return fDirtyState; } /** * This method has very special purpose, its used in subclass * 'changedModel' to know when to do "ending" sorts of things, right * before a call to super.ChangedModel would in deed put the model in * 'end' state. Put another way, at the beginning of the subclasses's * changedModel, the isModelStateChanging is true, but at end, it will be * false. So, this method allows a small "peek ahead". */ protected boolean isModelChangeStateOnVergeOfEnding() { return fModelStateChanging == 1; } /** * This method can be called to determine if the model is within a * "aboutToChange" and "changed" sequence. */ public boolean isModelStateChanging() { return fModelStateChanging > 0; } public boolean isNew() { return fNewState; } public boolean isReinitializationNeeded() { return reinitializationNeeded; } public boolean isSaveNeeded() { if (!isSharedForEdit()) return isDirty(); else return false; } /** * This function returns true if there are other references to the * underlying model. */ public boolean isShared() { if (getModelManager() == null) return false; return getModelManager().isShared(getId()); } /** * This function returns true if there are other references to the * underlying model. */ public boolean isSharedForEdit() { if (getModelManager() == null) return false; return getModelManager().isSharedForEdit(getId()); } /** * This function returns true if there are other references to the * underlying model. */ public boolean isSharedForRead() { if (getModelManager() == null) return false; return getModelManager().isSharedForRead(getId()); } public void modelReinitialized() { // notice this is just a public avenue to our protected method fireModelReinitialized(); } public IStructuredModel newInstance() throws IOException { IStructuredModel newModel = null; // we delegate to the model manager, so loader, etc., can be // used. newModel = getModelManager().createNewInstance(this); return newModel; } public IStructuredModel reinit() { IStructuredModel result = null; if (fModelStateChanging == 0) { try { aboutToChangeModel(); aboutToReinitializeModel(); result = _getModelManager().reinitialize(this); } finally { setReinitializeNeeded(false); setReinitializeStateData(null); modelReinitialized(); changedModel(); } } else { if (Logger.DEBUG_MODELSTATE) { Logger.log(Logger.INFO, "indeed!!!"); //$NON-NLS-1$ } } return result; } /** * This function allows the model to free up any resources it might be * using. In particular, itself, as stored in the IModelManager. */ public void releaseFromEdit() { if (getModelManager() == null) { throw new IllegalStateException(MODEL_MANAGER_NULL); //$NON-NLS-1$ } else { /* * Be sure to check the shared state before releasing. (Since * isShared assumes a count of 1 means not shared ... and we want * our '1' to be that one.) The problem, of course, is that * between pre-cycle notification and post-release notification, * the model could once again have become shared, rendering the * release notification incorrect. */ boolean isShared = isShared(); if (!isShared) { signalPreLifeCycleEventRelease(this); } _getModelManager().releaseFromEdit(this); if (!isShared) { signalPostLifeCycleListenerRelease(this); } } } /** * This function allows the model to free up any resources it might be * using. In particular, itself, as stored in the IModelManager. */ public void releaseFromRead() { if (getModelManager() == null) { throw new IllegalStateException(MODEL_MANAGER_NULL); //$NON-NLS-1$ } else { /* * Be sure to check the shared state before releasing. (Since * isShared assumes a count of 1 means not shared ... and we want * our '1' to be that one.) The problem, of course, is that * between pre-cycle notification and post-release notification, * the model could once again have become shared, rendering the * release notification incorrect. */ boolean isShared = isShared(); if (!isShared) { signalPreLifeCycleEventRelease(this); } _getModelManager().releaseFromRead(this); if (!isShared) { signalPostLifeCycleListenerRelease(this); } } } /** * This function replenishes the model with the resource without saving * any possible changes. It is used when one editor may be closing, and * specifially says not to save the model, but another "display" of the * model still needs to hang on to some model, so needs a fresh copy. */ public IStructuredModel reload(InputStream inputStream) throws IOException { IStructuredModel result = null; try { aboutToChangeModel(); result = _getModelManager().reloadModel(getId(), inputStream); } catch (UnsupportedEncodingException e) { // log for now, unless we find reason not to Logger.log(Logger.INFO, e.getMessage()); } finally { changedModel(); } return result; } public void removeModelLifecycleListener(IModelLifecycleListener listener) { // if manager is null, then none have been added, so // no need to remove any if (fLifecycleNotificationManager == null) return; synchronized (fListenerLock) { fLifecycleNotificationManager.removeListener(listener); } } public void removeModelStateListener(IModelStateListener listener) { if (listener == null) return; if (fModelStateListeners == null) return; // if its not in the listeners, we'll ignore the request synchronized (fListenerLock) { if (Utilities.contains(fModelStateListeners, listener)) { int oldSize = fModelStateListeners.length; int newSize = oldSize - 1; Object[] newListeners = new Object[newSize]; int index = 0; for (int i = 0; i < oldSize; i++) { if (fModelStateListeners[i] == listener) { // ignore } else { // copy old to new if its not the one we are // removing newListeners[index++] = fModelStateListeners[i]; } } // now that we have a new array, let's switch it for the // old // one fModelStateListeners = newListeners; } } } /** * A method that modifies the model's synchronization stamp to match the * resource. Turns out there's several ways of doing it, so this ensures a * common algorithm. */ public void resetSynchronizationStamp(IResource resource) { setSynchronizationStamp(computeModificationStamp(resource)); } /** * This API allows a client to initiate notification to all interested * parties that a model's underlying resource has been deleted. */ public void resourceDeleted() { // notice this is just a public avenue to our protected method fireModelResourceDeleted(this); } /** * This method allows a model client to initiate notification to all * interested parties that a model's underlying resource location has * changed. Note: we assume caller has already changed baseLocation, Id, * etc., since its really up to the client to determine what's "new" about * a moved model. Caution: 'this' and 'newModel' may be the same object. * This is the case for current working with FileModelProvider, but have * left the dual argument for future possibilities. */ public void resourceMoved(IStructuredModel newModel) { // notice this is just a public avenue to our protected method fireModelResourceMoved(this, newModel); } public void save() throws UnsupportedEncodingException, IOException, CoreException { int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); try { String stringId = getId(); _getModelManager().saveModel(stringId, EncodingRule.CONTENT_BASED); } finally { // we put end notification in finally block, so even if // error occurs during save, listeners are still notified, // since their code could depend on receiving, to clean up // some state, or coordinate other resources. type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } public void save(EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); try { String stringId = getId(); _getModelManager().saveModel(stringId, encodingRule); } finally { // we put end notification in finally block, so even if // error occurs during save, listeners are still notified, // since their code could depend on receiving, to clean up // some state, or coordinate other resources. type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } public void save(IFile iFile) throws UnsupportedEncodingException, IOException, CoreException { int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); try { String stringId = getId(); _getModelManager().saveModel(iFile, stringId, EncodingRule.CONTENT_BASED); } finally { // we put end notification in finally block, so even if // error occurs during save, listeners are still notified, // since their code could depend on receiving, to clean up // some state, or coordinate other resources. type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } public void save(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); try { String stringId = getId(); _getModelManager().saveModel(iFile, stringId, encodingRule); } finally { // we put end notificatioon in finally block, so even if // error occurs during save, listeners are still notified, // since their code could depend on receiving, to clean up // some state, or coordinate other resources. type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } public void save(OutputStream outputStream) throws UnsupportedEncodingException, CoreException, IOException { int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); try { String stringId = getId(); _getModelManager().saveModel(stringId, outputStream, EncodingRule.CONTENT_BASED); } finally { // we put end notification in finally block, so even if // error occurs during save, listeners are still notified, // since their code could depend on receiving, to clean up // some state, or coordinate other resources. type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } /** * This attribute is typically used to denote the model's underlying * resource. */ public void setBaseLocation(java.lang.String newBaseLocation) { fBaseLocation = newBaseLocation; if (fResolver != null) { fResolver.setFileBaseLocation(newBaseLocation); } } public void setContentTypeIdentifier(String contentTypeIdentifier) { fExplicitContentTypeIdentifier = contentTypeIdentifier; } /** * */ public void setDirtyState(boolean dirtyState) { // no need to process (set or fire event), if same value if (fDirtyState != dirtyState) { // pre-change notification int type = ModelLifecycleEvent.MODEL_DIRTY_STATE | ModelLifecycleEvent.PRE_EVENT; ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); // the actual change fDirtyState = dirtyState; // old notification // TODO: C3 remove old notification if (fDirtyState == false) { // if we are being set to not dirty (such as just been saved) // then we need to start listening for changes // again to know when to set state to true; getStructuredDocument().addDocumentChangedListener(fDirtyStateWatcher); } fireModelDirtyStateChanged(this, dirtyState); // post change notification type = ModelLifecycleEvent.MODEL_DIRTY_STATE | ModelLifecycleEvent.POST_EVENT; modelLifecycleEvent = new ModelLifecycleEvent(this, type); signalLifecycleEvent(modelLifecycleEvent); } } /** * @deprecated - will likely be deprecated soon, in favor of direct 'adds' * ... but takes some redesign. */ public void setFactoryRegistry(FactoryRegistry factoryRegistry) { this.factoryRegistry = factoryRegistry; } /** * The id is the id that the model manager uses to identify this model. If * it is being set here, it means the model manger is already managing the * model with another id, so we have to keep it in sync. This method calls * notifies listners, if they haven't been notified already, that a "model * state change" is about to occur. */ public void setId(String newId) throws ResourceInUse { // It makes no sense, I don't think, to have an id of null, so // we'll throw an illegal argument exception if someone trys. Note: // the IModelManager could not manage a model with an id of null, // since it uses hashtables, and you can't have a null id for a // hashtable. if (newId == null) throw new IllegalArgumentException(SSECoreMessages.A_model_s_id_can_not_be_nu_EXC_); //$NON-NLS-1$ = "A model's id can not be null" // To guard against throwing a spurious ResourceInUse exception, // which can occur when two pieces of code both want to change the id, // so the second request is spurious, we'll ignore any requests that // attempt to change the id to what it already is ... note, we use // 'equals', not identity ('==') so that things like // strings can be used. This is the same criteria that ids are // found in model manager -- well, actually, I just checked, and for // the hashtable impl, the criteria uses .equals AND the condition // that the hash values be identical (I'm assuming this is always // true, if equals is true, for now, I'm not sure // we can assume that hashtable will always be used, but in // general, should match.) // if (newId.equals(fId)) return; // we must guard against reassigning an id to one that we already // are managing. if (getModelManager() != null) { boolean inUse = ((ModelManagerImpl)getModelManager()).isIdInUse(newId); if (inUse) { throw new ResourceInUse(); } } try { // normal code path aboutToChangeModel(); String oldId = fId; fId = newId; if (getModelManager() != null) { // if managed and the id has changed, notify to // IModelManager // TODO: try to refine the design not to do that if (oldId != null && newId != null && !newId.equals(oldId)) { getModelManager().moveModel(oldId, newId); } } } finally { // make sure this finally is only executed if 'about to Change // model' has // been executed. changedModel(); } } /** * Sets the contentTypeDescription. * * @param contentTypeDescription * The contentTypeDescription to set */ public void setModelHandler(IModelHandler modelHandler) { // no need to fire events if modelHandler has been null // for this model -- // this is an attempt at initialization optimization and may need // to change in future. boolean trueChange = false; if (fModelHandler != null) trueChange = true; if (trueChange) { internalAboutToBeChanged(); } fModelHandler = modelHandler; if (trueChange) { internalModelChanged(); } } public void setModelManager(IModelManager newModelManager) { fModelManager = newModelManager; } /** * */ public void setNewState(boolean newState) { fNewState = newState; } /** * Sets a "flag" that reinitialization is needed. */ public void setReinitializeNeeded(boolean needed) { reinitializationNeeded = needed; } /** * Holds any data that the reinit procedure might find useful in * reinitializing the model. This is handy, since the reinitialization may * not take place at once, and some "old" data may be needed to properly * undo previous settings. Note: the parameter was intentionally made to * be of type 'Object' so different models can use in different ways. */ public void setReinitializeStateData(Object object) { reinitializeStateData = object; } public void setResolver(URIResolver newResolver) { fResolver = newResolver; } public void setStructuredDocument(IStructuredDocument newStructuredDocument) { boolean lifeCycleNotification = false; if (fStructuredDocument != null) { fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher); fStructuredDocument.removeDocumentAboutToChangeListener(fDocumentToModelNotifier); fStructuredDocument.removeDocumentChangedListener(fDocumentToModelNotifier); // prechange notification lifeCycleNotification = true; ModelLifecycleEvent modelLifecycleEvent = new DocumentChanged(ModelLifecycleEvent.PRE_EVENT, this, fStructuredDocument, newStructuredDocument); signalLifecycleEvent(modelLifecycleEvent); } // hold for life cycle notification IStructuredDocument previousDocument = fStructuredDocument; // the actual change fStructuredDocument = newStructuredDocument; // at the super class level, we'll listen for structuredDocument // changes // so we can set our dirty state flag if (fStructuredDocument != null) { fStructuredDocument.addDocumentChangedListener(fDirtyStateWatcher); fStructuredDocument.addDocumentAboutToChangeListener(fDocumentToModelNotifier); fStructuredDocument.addDocumentChangedListener(fDocumentToModelNotifier); } if (lifeCycleNotification) { // post change notification ModelLifecycleEvent modelLifecycleEvent = new DocumentChanged(ModelLifecycleEvent.POST_EVENT, this, previousDocument, newStructuredDocument); signalLifecycleEvent(modelLifecycleEvent); } } /** * Insert the method's description here. Creation date: (9/7/2001 2:30:26 * PM) * * @param newSynchronizationStamp * long */ protected void setSynchronizationStamp(long newSynchronizationStamp) { fSynchronizationStamp = newSynchronizationStamp; } public void setUndoManager(IStructuredTextUndoManager undoManager) { IStructuredDocument structuredDocument = getStructuredDocument(); if (structuredDocument == null) { throw new IllegalStateException("document was null when undo manager set on model"); //$NON-NLS-1$ } structuredDocument.setUndoManager(undoManager); } /** * To be called only by "friendly" classes, such as ModelManager, and * subclasses. */ void signalLifecycleEvent(ModelLifecycleEvent event) { if (fLifecycleNotificationManager == null) return; fLifecycleNotificationManager.signalLifecycleEvent(event); } private void signalPostLifeCycleListenerRelease(IStructuredModel structuredModel) { int type = ModelLifecycleEvent.MODEL_RELEASED | ModelLifecycleEvent.POST_EVENT; // what's wrong with this design that a cast is needed here!? ModelLifecycleEvent event = new ModelLifecycleEvent(structuredModel, type); ((AbstractStructuredModel) structuredModel).signalLifecycleEvent(event); } private void signalPreLifeCycleEventRelease(IStructuredModel structuredModel) { int type = ModelLifecycleEvent.MODEL_RELEASED | ModelLifecycleEvent.PRE_EVENT; // what's wrong with this design that a cast is needed here!? ModelLifecycleEvent event = new ModelLifecycleEvent(structuredModel, type); ((AbstractStructuredModel) structuredModel).signalLifecycleEvent(event); } }