/******************************************************************************* * Copyright (c) 2001, 2015 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 * * Frank Zigler/Web Performance, Inc. - 288196 - Deadlock in ModelManagerImpl after IOException * *******************************************************************************/ package org.eclipse.wst.sse.core.internal.model; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.sse.core.internal.FileBufferModelManager; import org.eclipse.wst.sse.core.internal.Logger; import org.eclipse.wst.sse.core.internal.NullMemento; import org.eclipse.wst.sse.core.internal.SSECoreMessages; import org.eclipse.wst.sse.core.internal.SSECorePlugin; import org.eclipse.wst.sse.core.internal.document.DocumentReader; import org.eclipse.wst.sse.core.internal.document.IDocumentLoader; import org.eclipse.wst.sse.core.internal.encoding.CodedIO; import org.eclipse.wst.sse.core.internal.encoding.CodedStreamCreator; import org.eclipse.wst.sse.core.internal.encoding.CommonEncodingPreferenceNames; import org.eclipse.wst.sse.core.internal.encoding.ContentBasedPreferenceGateway; import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento; import org.eclipse.wst.sse.core.internal.encoding.EncodingRule; import org.eclipse.wst.sse.core.internal.exceptions.MalformedOutputExceptionWithDetail; import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler; import org.eclipse.wst.sse.core.internal.modelhandler.ModelHandlerRegistry; import org.eclipse.wst.sse.core.internal.provisional.IModelLoader; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument; import org.eclipse.wst.sse.core.internal.provisional.exceptions.ResourceAlreadyExists; 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.util.Assert; import org.eclipse.wst.sse.core.internal.util.ProjectResolver; import org.eclipse.wst.sse.core.internal.util.URIResolver; import org.eclipse.wst.sse.core.internal.util.URIResolverExtension; import org.eclipse.wst.sse.core.internal.util.Utilities; /** * <p>Not intended to be subclassed, referenced or instantiated by clients. * Clients should obtain an instance of the IModelManager interface through * {@link StructuredModelManager#getModelManager()}.</p> * * <p>This class is responsible for creating, retrieving, and caching * StructuredModels It retrieves the cached objects by an id which is * typically a String representing the resources URI. Note: Its important that * all clients that share a resource do so using <b>identical </b> * identifiers, or else different instances will be created and retrieved, * even if they all technically point to the same resource on the file system. * This class also provides a convenient place to register Model Loaders and * Dumpers based on 'type'.</p> */ public class ModelManagerImpl implements IModelManager { static class ReadEditType { ReadEditType(String type) { } } class SharedObject { int referenceCountForEdit; int referenceCountForRead; volatile IStructuredModel theSharedModel; final ILock LOAD_LOCK = Job.getJobManager().newLock(); volatile boolean initializing = true; volatile boolean doWait = true; // The field 'id' is only meant for debug final String id; SharedObject(String id) { this.id=id; // be aware, this lock will leak and cause the deadlock detector to be horrible if we never release it LOAD_LOCK.acquire(); } /** * Waits until this shared object has been attempted to be loaded. The * load is "attempted" because not all loads result in a model. However, * upon leaving this method, theShareModel variable is up-to-date. */ public void waitForLoadAttempt() { final boolean allowInterrupt = PrefUtil.ALLOW_INTERRUPT_WAITING_THREAD; final long timeLimit = (PrefUtil.WAIT_DELAY==0) ? Long.MAX_VALUE : PrefUtil.now() + PrefUtil.WAIT_DELAY; final Job current = Job.getJobManager().currentJob(); boolean interrupted = false; try { while (initializing) { if (current!=null) { current.yieldRule(null); } try { loop(); } catch (InterruptedException e) { if (allowInterrupt) { throw new OperationCanceledException("Waiting thread interrupted while waiting for model id: "+id + " to load"); } else { interrupted=true; } } if (PrefUtil.now() >= timeLimit ) throw new OperationCanceledException("Waiting thread timeout exceeded while waiting for model id: "+id + " to load"); } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } private void loop() throws InterruptedException { if (initializing) { if (LOAD_LOCK.acquire(PrefUtil.WAIT_INTERVAL_MS)) { // if we got the lock, but initializing is still not true the deadlock detector gave us // the lock and caused reentrancy into this critical section. This is invalid and the // sign of a cyclical load attempt. In this case, we through an // OperationCanceledException in lew of entering a spin-loop. if (initializing) { LOAD_LOCK.release(); throw new OperationCanceledException("Aborted cyclic load attempt for model with id: "+ id ); } else { LOAD_LOCK.release(); } } } } /** * Flags this model as loaded. All waiting methods on * {@link #waitForLoadAttempt()} will proceed after this method returns. */ public void setLoaded() { initializing = false; LOAD_LOCK.release(); } } private Exception debugException = null; /** * Our singleton instance */ private static ModelManagerImpl instance; private final static int READ_BUFFER_SIZE = 4096; /** * Not to be called by clients, will be made restricted access. * * @return */ public synchronized static IModelManager getInstance() { if (instance == null) { instance = new ModelManagerImpl(); } return instance; } /** * Our cache of managed objects */ private Map fManagedObjects; private ModelHandlerRegistry fModelHandlerRegistry; private final ReadEditType READ = new ReadEditType("read"); //$NON-NLS-1$ private final ReadEditType EDIT = new ReadEditType("edit"); //$NON-NLS-1$ private final ILock SYNC = Job.getJobManager().newLock(); /** * Intentionally default access only. * */ ModelManagerImpl() { super(); fManagedObjects = new HashMap(); // To prevent deadlocks: always acquire multiple locks in this order: SYNC, sharedObject. // DO NOT acquire a SYNC within a sharedObject lock, unless you already own the SYNC lock // Tip: Try to hold the smallest number of locks you can } private IStructuredModel _commonCreateModel(IFile file, String id, IModelHandler handler, URIResolver resolver, ReadEditType rwType, EncodingRule encodingRule) throws IOException,CoreException { SharedObject sharedObject = null; SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); while(true) { if (sharedObject!=null) { sharedObject.waitForLoadAttempt(); } SYNC.acquire(); // we know this object's model has passed the load, however, we don't know // it's reference count status. It might have already been disposed. Or it could have // been disposed and a concurrent thread has already begun loading it, in which case // we should use the sharedobject they are loading. // NOTE: This pattern is applied 3 times in this class, but only doc'd once. The logic is // exactly the same. SharedObject testObject = (SharedObject) fManagedObjects.get(id); if (testObject==null) { // null means it's been disposed, we need to do the work to reload it. sharedObject = new SharedObject(id); fManagedObjects.put(id, sharedObject); SYNC.release(); _doCommonCreateModel(file, id, handler, resolver, rwType, encodingRule, sharedObject); break; } else if (sharedObject == testObject) { // if nothing happened, just increment the could and return the shared model synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, rwType); } } SYNC.release(); break; } else { // sharedObject != testObject which means the object we were waiting on has been disposed // a replacement has already been placed in the managedObjects table. Through away our // stale sharedObject and continue on with the one we got from the queue. Note: We don't know its // state, so continue the waitForLoad-check loop. SYNC.release(); sharedObject = testObject; } } // we expect to always return something if (sharedObject == null) { debugException = new Exception("instance only for stack trace"); //$NON-NLS-1$ Logger.logException("Program Error: no model recorded for id " + id, debugException); //$NON-NLS-1$ } // note: clients must call release for each time they call get. return sharedObject==null ? null : sharedObject.theSharedModel; } private void _decrCount(SharedObject sharedObject, ReadEditType type) { if (type == READ) { sharedObject.referenceCountForRead--; if (Logger.DEBUG_MODELMANAGER) { trace("decrementing Read count for model", sharedObject.theSharedModel.getId(), sharedObject.referenceCountForRead); } FileBufferModelManager.getInstance().disconnect(sharedObject.theSharedModel.getStructuredDocument()); } else if (type == EDIT) { sharedObject.referenceCountForEdit--; if (Logger.DEBUG_MODELMANAGER) { trace("decrementing Edit count for model", sharedObject.theSharedModel.getId(), sharedObject.referenceCountForEdit); } FileBufferModelManager.getInstance().disconnect(sharedObject.theSharedModel.getStructuredDocument()); } else throw new IllegalArgumentException(); } private void _doCommonCreateModel(IFile file, String id, IModelHandler handler, URIResolver resolver, ReadEditType rwType, EncodingRule encodingRule, SharedObject sharedObject) throws CoreException, IOException { // XXX: Does not integrate with FileBuffers boolean doRemove = true; try { synchronized(sharedObject) { InputStream inputStream = null; IStructuredModel model = null; try { model = _commonCreateModel(id, handler, resolver); IModelLoader loader = handler.getModelLoader(); inputStream = Utilities.getMarkSupportedStream(file.getContents(true)); loader.load(Utilities.getMarkSupportedStream(inputStream), model, encodingRule); } catch (ResourceInUse e) { // impossible, since we've already found handleProgramError(e); } finally { if (inputStream!=null) { try { inputStream.close(); } catch(IOException e) { } } } if (model != null) { // add to our cache sharedObject.theSharedModel=model; _initCount(sharedObject, rwType); doRemove = false; } } } finally{ if (doRemove) { SYNC.acquire(); fManagedObjects.remove(id); SYNC.release(); } sharedObject.setLoaded(); } } private IStructuredModel _commonCreateModel(InputStream inputStream, String id, IModelHandler handler, URIResolver resolver, ReadEditType rwType, String encoding, String lineDelimiter) throws IOException { if (id == null) { throw new IllegalArgumentException("Program Error: id may not be null"); //$NON-NLS-1$ } SharedObject sharedObject = null; SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); while(true) { if (sharedObject!=null) { sharedObject.waitForLoadAttempt(); } SYNC.acquire(); SharedObject testObject = (SharedObject) fManagedObjects.get(id); if (testObject==null) { // it was removed ,so lets create it sharedObject = new SharedObject(id); fManagedObjects.put(id, sharedObject); SYNC.release(); _doCommonCreateModel(inputStream, id, handler, resolver, rwType, encoding, lineDelimiter, sharedObject); break; } else if (sharedObject == testObject) { synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, rwType); } } SYNC.release(); break; } else { SYNC.release(); sharedObject = testObject; } } // we expect to always return something Assert.isNotNull(sharedObject, "Program Error: no model recorded for id " + id); //$NON-NLS-1$ // note: clients must call release for each time they call get. return sharedObject.theSharedModel; } private void _doCommonCreateModel(InputStream inputStream, String id, IModelHandler handler, URIResolver resolver, ReadEditType rwType, String encoding, String lineDelimiter, SharedObject sharedObject) throws IOException { boolean doRemove = true; try { synchronized(sharedObject) { IStructuredModel model = null; try { model = _commonCreateModel(id, handler, resolver); IModelLoader loader = handler.getModelLoader(); if (inputStream == null) { Logger.log(Logger.WARNING, "model was requested for id " + id + " without a content InputStream"); //$NON-NLS-1$ //$NON-NLS-2$ } loader.load(id, Utilities.getMarkSupportedStream(inputStream), model, encoding, lineDelimiter); } catch (ResourceInUse e) { // impossible, since we've already found handleProgramError(e); } if (model != null) { /** * https://bugs.eclipse.org/bugs/show_bug.cgi?id=264228 * * Ensure that the content type identifier field of the model * is properly set. This is normally handled by the * FileBufferModelManager when working with files as it knows * the content type in advance; here is where we handle it for * streams. */ if (model instanceof AbstractStructuredModel) { DocumentReader reader = new DocumentReader(model.getStructuredDocument()); IContentDescription description = Platform.getContentTypeManager().getDescriptionFor(reader, id, new QualifiedName[0]); reader.close(); if (description != null && description.getContentType() != null) { ((AbstractStructuredModel) model).setContentTypeIdentifier(description.getContentType().getId()); } } sharedObject.theSharedModel = model; _initCount(sharedObject, rwType); doRemove = false; } } } finally { if (doRemove) { SYNC.acquire(); // remove it if we didn't get one back fManagedObjects.remove(id); SYNC.release(); } sharedObject.setLoaded(); } } private IStructuredModel _commonCreateModel(String id, IModelHandler handler, URIResolver resolver) throws ResourceInUse { IModelLoader loader = handler.getModelLoader(); IStructuredModel result = loader.createModel(); // in the past, id was null for "unmanaged" case, so we won't // try and set it if (id != null) { result.setId(id); } result.setModelHandler(handler); result.setResolver(resolver); // some obvious redunancy here that maybe could be improved // in future, but is necessary for now result.setBaseLocation(id); if (resolver != null) { resolver.setFileBaseLocation(id); } addFactories(result, handler); return result; } private IStructuredModel _commonGetModel(IFile iFile, ReadEditType rwType, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { IStructuredModel model = null; if (iFile != null && iFile.exists()) { String id = calculateId(iFile); IModelHandler handler = calculateType(iFile); URIResolver resolver = calculateURIResolver(iFile); model = _commonCreateModel(iFile, id, handler, resolver, rwType, encodingRule); } return model; } private IStructuredModel _commonGetModel(IFile iFile, ReadEditType rwType, String encoding, String lineDelimiter) throws UnsupportedEncodingException, IOException, CoreException { String id = calculateId(iFile); IStructuredModel model = _commonGetModel(iFile, id, rwType, encoding, lineDelimiter); return model; } private IStructuredModel _commonGetModel(IFile file, String id, ReadEditType rwType, String encoding, String lineDelimiter) throws IOException, CoreException { if (id == null) throw new IllegalArgumentException("Program Error: id may not be null"); //$NON-NLS-1$ SharedObject sharedObject = null; if (file != null && file.exists()) { SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); while(true) { if (sharedObject!=null) { sharedObject.waitForLoadAttempt(); } SYNC.acquire(); SharedObject testObject = (SharedObject) fManagedObjects.get(id); if (testObject==null) { // it was removed ,so lets create it sharedObject = new SharedObject(id); fManagedObjects.put(id, sharedObject); SYNC.release(); _doCommonGetModel(file, id, sharedObject,rwType); break; } else if (sharedObject == testObject) { synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, rwType); } } SYNC.release(); break; } else { // we got a different object than what we were expecting SYNC.release(); // two threads were interested in models for the same id. // The other thread one, so lets back off and try again. sharedObject = testObject; } } } // if we don't know how to create a model // for this type of file, return null // note: clients must call release for each time they call // get. return sharedObject==null ? null : sharedObject.theSharedModel; } private void _doCommonGetModel(IFile file, String id, SharedObject sharedObject,ReadEditType rwType) { boolean doRemove = true; try { synchronized(sharedObject) { sharedObject.doWait=false; IStructuredModel model = null; try { model = FileBufferModelManager.getInstance().getModel(file); } finally { sharedObject.doWait=true; } if (model != null) { sharedObject.theSharedModel=model; _initCount(sharedObject, rwType); doRemove = false; } } } finally { if (doRemove) { SYNC.acquire(); fManagedObjects.remove(id); SYNC.release(); } sharedObject.setLoaded(); } } private SharedObject _commonNewModel(IFile iFile, boolean force) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException { IStructuredModel aSharedModel = null; // First, check if resource already exists on file system. // if is does, then throw Resource in Use iff force==false if (iFile.exists() && !force) { throw new ResourceAlreadyExists(); } SharedObject sharedObject = null; String id = calculateId(iFile); try { SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null && !force) { // if in cache already, and force is not true, then this is an // error // in call throw new ResourceInUse(); } sharedObject = new SharedObject(id); fManagedObjects.put(id, sharedObject); } finally { SYNC.release(); } // if we get to here without above exceptions, then all is ok // to get model like normal, but set 'new' attribute (where the // 'new' attribute means this is a model without a corresponding // underlying resource. aSharedModel = FileBufferModelManager.getInstance().getModel(iFile); aSharedModel.setNewState(true); sharedObject.theSharedModel=aSharedModel; // when resource is provided, we can set // synchronization stamp ... otherwise client should // Note: one client which does this is FileModelProvider. aSharedModel.resetSynchronizationStamp(iFile); return sharedObject; } public IStructuredModel _getModelFor(IStructuredDocument document, ReadEditType accessType) { String id = FileBufferModelManager.getInstance().calculateId(document); if (id == null) { if (READ == accessType) return getExistingModelForRead(document); if (EDIT == accessType) return getExistingModelForEdit(document); Assert.isNotNull(id, "unknown IStructuredDocument " + document); //$NON-NLS-1$ } SharedObject sharedObject = null; SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); while(true) { if (sharedObject!=null) { sharedObject.waitForLoadAttempt(); } SYNC.acquire(); SharedObject testObject = (SharedObject) fManagedObjects.get(id); if (testObject==null) { sharedObject = new SharedObject(id); fManagedObjects.put(id, sharedObject); SYNC.release(); synchronized(sharedObject) { sharedObject.theSharedModel = FileBufferModelManager.getInstance().getModel(document); _initCount(sharedObject, accessType); sharedObject.setLoaded(); } break; } else if (sharedObject == testObject) { synchronized(sharedObject) { Assert.isTrue(sharedObject.referenceCountForEdit + sharedObject.referenceCountForRead > 0, "reference count was less than zero"); if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, accessType); } } SYNC.release(); break; } else { SYNC.release(); sharedObject = testObject; } } return sharedObject==null ? null : sharedObject.theSharedModel; } private void _incrCount(SharedObject sharedObject, ReadEditType type) { synchronized(sharedObject) { if (type == READ) { sharedObject.referenceCountForRead++; if (Logger.DEBUG_MODELMANAGER) { trace("incrementing Read count for model", sharedObject.theSharedModel.getId(), sharedObject.referenceCountForRead); } FileBufferModelManager.getInstance().connect(sharedObject.theSharedModel.getStructuredDocument()); } else if (type == EDIT) { sharedObject.referenceCountForEdit++; if (Logger.DEBUG_MODELMANAGER) { trace("incrementing Edit count for model", sharedObject.theSharedModel.getId(), sharedObject.referenceCountForEdit); } FileBufferModelManager.getInstance().connect(sharedObject.theSharedModel.getStructuredDocument()); } else throw new IllegalArgumentException(); } } private void _initCount(SharedObject sharedObject, ReadEditType type) { synchronized(sharedObject) { if (type == READ) { if (Logger.DEBUG_MODELMANAGER) { trace("Creating model for Read", sharedObject.theSharedModel.getId(), 1); } FileBufferModelManager.getInstance().connect(sharedObject.theSharedModel.getStructuredDocument()); sharedObject.referenceCountForRead = 1; } else if (type == EDIT) { if (Logger.DEBUG_MODELMANAGER) { trace("Creating model for Edit", sharedObject.theSharedModel.getId(), 1); } FileBufferModelManager.getInstance().connect(sharedObject.theSharedModel.getStructuredDocument()); sharedObject.referenceCountForEdit = 1; } else throw new IllegalArgumentException(); } } private void addFactories(IStructuredModel model, IModelHandler handler) { Assert.isNotNull(model, "model can not be null"); //$NON-NLS-1$ FactoryRegistry registry = model.getFactoryRegistry(); Assert.isNotNull(registry, "model's Factory Registry can not be null"); //$NON-NLS-1$ List factoryList = handler.getAdapterFactories(); addFactories(model, factoryList); } private void addFactories(IStructuredModel model, List factoryList) { Assert.isNotNull(model, "model can not be null"); //$NON-NLS-1$ FactoryRegistry registry = model.getFactoryRegistry(); Assert.isNotNull(registry, "model's Factory Registry can not be null"); //$NON-NLS-1$ // Note: we add all of them from handler, even if // already exists. May need to reconsider this. if (factoryList != null) { Iterator iterator = factoryList.iterator(); while (iterator.hasNext()) { INodeAdapterFactory factory = (INodeAdapterFactory) iterator.next(); registry.addFactory(factory); } } } /** * Calculate id provides a common way to determine the id from the input * ... needed to get and save the model. It is a simple class utility, but * is an instance method so can be accessed via interface. */ public String calculateId(IFile file) { return FileBufferModelManager.getInstance().calculateId(file); } private IModelHandler calculateType(IFile iFile) throws CoreException { // IModelManager mm = ((ModelManagerPlugin) // Platform.getPlugin(ModelManagerPlugin.ID)).getModelManager(); ModelHandlerRegistry cr = getModelHandlerRegistry(); IModelHandler cd = cr.getHandlerFor(iFile); return cd; } private IModelHandler calculateType(String filename, InputStream inputStream) throws IOException { ModelHandlerRegistry cr = getModelHandlerRegistry(); IModelHandler cd = cr.getHandlerFor(filename, inputStream); return cd; } /** * */ private URIResolver calculateURIResolver(IFile file) { // Note: see comment in plugin.xml for potentially // breaking change in behavior. IProject project = file.getProject(); URIResolver resolver = (URIResolver) project.getAdapter(URIResolver.class); if (resolver == null) resolver = new ProjectResolver(project); Object location = file.getLocation(); if (location == null) location = file.getLocationURI(); if (location != null) resolver.setFileBaseLocation(location.toString()); return resolver; } /* * Note: This method appears in both ModelManagerImpl and JSEditor (with * just a minor difference). They should be kept the same. * * @deprecated - handled by platform */ private void convertLineDelimiters(IDocument document, IFile iFile) throws CoreException { // Note: calculateType(iFile) returns a default xml model handler if // content type is null. String contentTypeId = calculateType(iFile).getAssociatedContentTypeId(); String endOfLineCode = ContentBasedPreferenceGateway.getPreferencesString(contentTypeId, CommonEncodingPreferenceNames.END_OF_LINE_CODE); // endOfLineCode == null means the content type does not support this // function (e.g. DTD) // endOfLineCode == "" means no translation if (endOfLineCode != null && endOfLineCode.length() > 0) { String lineDelimiterToUse = System.getProperty("line.separator"); //$NON-NLS-1$ if (endOfLineCode.equals(CommonEncodingPreferenceNames.CR)) lineDelimiterToUse = CommonEncodingPreferenceNames.STRING_CR; else if (endOfLineCode.equals(CommonEncodingPreferenceNames.LF)) lineDelimiterToUse = CommonEncodingPreferenceNames.STRING_LF; else if (endOfLineCode.equals(CommonEncodingPreferenceNames.CRLF)) lineDelimiterToUse = CommonEncodingPreferenceNames.STRING_CRLF; TextEdit multiTextEdit = new MultiTextEdit(); int lineCount = document.getNumberOfLines(); try { for (int i = 0; i < lineCount; i++) { IRegion lineInfo = document.getLineInformation(i); int lineStartOffset = lineInfo.getOffset(); int lineLength = lineInfo.getLength(); int lineEndOffset = lineStartOffset + lineLength; if (i < lineCount - 1) { String currentLineDelimiter = document.getLineDelimiter(i); if (currentLineDelimiter != null && currentLineDelimiter.compareTo(lineDelimiterToUse) != 0) multiTextEdit.addChild(new ReplaceEdit(lineEndOffset, currentLineDelimiter.length(), lineDelimiterToUse)); } } if (multiTextEdit.getChildrenSize() > 0) multiTextEdit.apply(document); } catch (BadLocationException exception) { // just adding generic runtime here, until whole method // deleted. throw new RuntimeException(exception.getMessage()); } } } /** * this used to be in loader, but has been moved here */ private IStructuredModel copy(IStructuredModel model, String newId) throws ResourceInUse { IStructuredModel newModel = null; IStructuredModel oldModel = model; IModelHandler modelHandler = oldModel.getModelHandler(); IModelLoader loader = modelHandler.getModelLoader(); // newModel = loader.newModel(); newModel = loader.createModel(oldModel); // newId, oldModel.getResolver(), oldModel.getModelManager()); newModel.setModelHandler(modelHandler); // IStructuredDocument oldStructuredDocument = // oldModel.getStructuredDocument(); // IStructuredDocument newStructuredDocument = // oldStructuredDocument.newInstance(); // newModel.setStructuredDocument(newStructuredDocument); newModel.setResolver(oldModel.getResolver()); newModel.setModelManager(oldModel.getModelManager()); // duplicateFactoryRegistry(newModel, oldModel); newModel.setId(newId); // set text of new one after all initialization is done String contents = oldModel.getStructuredDocument().getText(); newModel.getStructuredDocument().setText(this, contents); return newModel; } /** */ public IStructuredModel copyModelForEdit(String oldId, String newId) throws ResourceInUse { IStructuredModel newModel = null; // get the existing model associated with this id IStructuredModel model = getExistingModel(oldId); // if it doesn't exist, ignore request (though this would normally // be a programming error. if (model == null) return null; SharedObject sharedObject = null; try { SYNC.acquire(); // now be sure newModel does not exist sharedObject = (SharedObject) fManagedObjects.get(newId); if (sharedObject != null) { throw new ResourceInUse(); } sharedObject = new SharedObject(newId); fManagedObjects.put(newId,sharedObject); } finally { SYNC.release(); } // get loader based on existing type (note the type assumption) // Object type = ((IStructuredModel) model).getType(); // IModelHandler type = model.getModelHandler(); // IModelLoader loader = (IModelLoader) getModelLoaders().get(type); // IModelLoader loader = (IModelLoader) getModelLoaders().get(type); // ask the loader to copy synchronized(sharedObject) { sharedObject.doWait = false; newModel = copy(model, newId); sharedObject.doWait = true; } if (newModel != null) { // add to our cache synchronized(sharedObject) { sharedObject.theSharedModel=newModel; sharedObject.referenceCountForEdit = 1; trace("copied model", newId, sharedObject.referenceCountForEdit); //$NON-NLS-1$ } } else { SYNC.acquire(); fManagedObjects.remove(newId); SYNC.release(); } sharedObject.setLoaded(); return newModel; } /** * Similar to clone, except the new instance has no content. Note: this * produces an unmanaged model, for temporary use. If a true shared model * is desired, use "copy". */ public IStructuredModel createNewInstance(IStructuredModel oldModel) throws IOException { IModelHandler handler = oldModel.getModelHandler(); IModelLoader loader = handler.getModelLoader(); IStructuredModel newModel = loader.createModel(oldModel); newModel.setModelHandler(handler); if (newModel instanceof AbstractStructuredModel) { ((AbstractStructuredModel) newModel).setContentTypeIdentifier(oldModel.getContentTypeIdentifier()); } URIResolver oldResolver = oldModel.getResolver(); if (oldResolver instanceof URIResolverExtension) { oldResolver = ((URIResolverExtension) oldResolver).newInstance(); } newModel.setResolver(oldResolver); try { newModel.setId(DUPLICATED_MODEL); } catch (ResourceInUse e) { // impossible, since this is an unmanaged model } // base location should be null, but we'll set to // null to be sure. newModel.setBaseLocation(null); return newModel; } /** * Factory method, since a proper IStructuredDocument must have a proper * parser assigned. Note: its assume that IFile does not actually exist as * a resource yet. If it does, ResourceAlreadyExists exception is thrown. * If the resource does already exist, then createStructuredDocumentFor is * the right API to use. * * @throws ResourceInUse * */ public IStructuredDocument createNewStructuredDocumentFor(IFile iFile) throws ResourceAlreadyExists, IOException, CoreException { if (iFile.exists()) { throw new ResourceAlreadyExists(iFile.getFullPath().toOSString()); } // Will reconsider in future version // String id = calculateId(iFile); // if (isResourceInUse(id)) { // throw new ResourceInUse(iFile.getFullPath().toOSString()); // } IDocumentLoader loader = null; IModelHandler handler = calculateType(iFile); loader = handler.getDocumentLoader(); // for this API, "createNew" we assume the IFile does not exist yet // as checked above, so just create empty document. IStructuredDocument result = (IStructuredDocument) loader.createNewStructuredDocument(); return result; } /** * Factory method, since a proper IStructuredDocument must have a proper * parser assigned. Note: clients should verify IFile exists before using * this method. If this IFile does not exist, then * createNewStructuredDocument is the correct API to use. * * @throws ResourceInUse */ public IStructuredDocument createStructuredDocumentFor(IFile iFile) throws IOException, CoreException { if (!iFile.exists()) { throw new FileNotFoundException(iFile.getFullPath().toOSString()); } // Will reconsider in future version // String id = calculateId(iFile); // if (isResourceInUse(id)) { // throw new ResourceInUse(iFile.getFullPath().toOSString()); // } IDocumentLoader loader = null; IModelHandler handler = calculateType(iFile); loader = handler.getDocumentLoader(); IStructuredDocument result = (IStructuredDocument) loader.createNewStructuredDocument(iFile); return result; } /** * Conveience method, since a proper IStructuredDocument must have a * proper parser assigned. It should only be used when an empty * structuredDocument is needed. Otherwise, use IFile form. * * @deprecated - TODO: to be removed by C4 do we really need this? I * recommend to - use createStructuredDocumentFor(filename, * null, null) - the filename does not need to represent a * real - file, but can take for form of dummy.jsp, test.xml, * etc. - That way we don't hard code the handler, but specify * we - want the handler that "goes with" a certain type of - * file. */ public IStructuredDocument createStructuredDocumentFor(String contentTypeId) { IDocumentLoader loader = null; ModelHandlerRegistry cr = getModelHandlerRegistry(); IModelHandler handler = cr.getHandlerForContentTypeId(contentTypeId); if (handler == null) Logger.log(Logger.ERROR, "Program error: no model handler found for " + contentTypeId); //$NON-NLS-1$ loader = handler.getDocumentLoader(); IStructuredDocument result = (IStructuredDocument) loader.createNewStructuredDocument(); return result; } /** * Conveience method, since a proper IStructuredDocument must have a * proper parser assigned. * * @deprecated -- - TODO: to be removed by C4 I marked as deprecated to * discouage use of this method. It does not really work for * JSP fragments, since JSP Fragments need an IFile to * correctly look up the content settings. Use IFile form * instead. */ public IStructuredDocument createStructuredDocumentFor(String filename, InputStream inputStream, URIResolver resolver) throws IOException { IDocumentLoader loader = null; InputStream istream = Utilities.getMarkSupportedStream(inputStream); if (istream != null) { istream.reset(); } IModelHandler handler = calculateType(filename, istream); loader = handler.getDocumentLoader(); IStructuredDocument result = null; if (inputStream == null) { result = (IStructuredDocument) loader.createNewStructuredDocument(); } else { result = (IStructuredDocument) loader.createNewStructuredDocument(filename, istream); } return result; } /** * Special case method. This method was created for the special case where * there is an encoding for input stream that should override all the * normal rules for encoding. For example, if there is an encoding * (charset) specified in HTTP response header, then that encoding is used * to translate the input stream to a string, but then the normal encoding * rules are ignored, so that the string is not translated twice (for * example, if its an HTML "file", then even if it contains a charset in * meta tag, its ignored since its assumed its all correctly decoded by * the HTTP charset. */ public IStructuredDocument createStructuredDocumentFor(String filename, InputStream inputStream, URIResolver resolver, String encoding) throws IOException { String content = readInputStream(inputStream, encoding); IStructuredDocument result = createStructuredDocumentFor(filename, content, resolver); return result; } /** * Convenience method. This method can be used when the resource does not * really exist (e.g. when content is being created, but hasn't been * written to disk yet). Note that since the content is being provided as * a String, it is assumed to already be decoded correctly so no * transformation is done. */ public IStructuredDocument createStructuredDocumentFor(String filename, String content, URIResolver resolver) throws IOException { IDocumentLoader loader = null; IModelHandler handler = calculateType(filename, null); loader = handler.getDocumentLoader(); IStructuredDocument result = (IStructuredDocument) loader.createNewStructuredDocument(); result.setEncodingMemento(new NullMemento()); result.setText(this, content); return result; } /** * @param iFile * @param result * @return * @throws CoreException */ private IStructuredModel createUnManagedEmptyModelFor(IFile iFile) throws CoreException { IStructuredModel result = null; IModelHandler handler = calculateType(iFile); String id = calculateId(iFile); URIResolver resolver = calculateURIResolver(iFile); try { result = _commonCreateModel(id, handler, resolver); } catch (ResourceInUse e) { // impossible, since we're not sharing // (even if it really is in use ... we don't care) // this may need to be re-examined. if (Logger.DEBUG_MODELMANAGER) Logger.log(Logger.INFO, "ModelMangerImpl::createUnManagedStructuredModelFor. Model unexpectedly in use."); //$NON-NLS-1$ //$NON-NLS-2$ } return result; } /** * Conveience method. It depends on the loaders newModel method to return * an appropriate StrucuturedModel appropriately initialized. */ public IStructuredModel createUnManagedStructuredModelFor(IFile iFile) throws IOException, CoreException { IStructuredModel result = null; result = createUnManagedEmptyModelFor(iFile); IDocumentLoader loader = result.getModelHandler().getDocumentLoader(); IEncodedDocument document = loader.createNewStructuredDocument(iFile); result.getStructuredDocument().setText(this, document.get()); return result; } /** * Conveience method. It depends on the loaders newModel method to return * an appropriate StrucuturedModel appropriately initialized. */ public IStructuredModel createUnManagedStructuredModelFor(String contentTypeId) { return createUnManagedStructuredModelFor(contentTypeId, null); } /** * Conveience method. It depends on the loaders newModel method to return * an appropriate StrucuturedModel appropriately initialized. */ public IStructuredModel createUnManagedStructuredModelFor(String contentTypeId, URIResolver resolver) { IStructuredModel result = null; ModelHandlerRegistry cr = getModelHandlerRegistry(); IModelHandler handler = cr.getHandlerForContentTypeId(contentTypeId); try { result = _commonCreateModel(UNMANAGED_MODEL, handler, resolver); //$NON-NLS-1$ } catch (ResourceInUse e) { // impossible, since we're not sharing // (even if it really is in use ... we don't care) // this may need to be re-examined. if (Logger.DEBUG_MODELMANAGER) Logger.log(Logger.INFO, "ModelMangerImpl::createUnManagedStructuredModelFor. Model unexpectedly in use."); //$NON-NLS-1$ //$NON-NLS-2$ } return result; } private IStructuredModel getExistingModel(Object id) { IStructuredModel result = null; SYNC.acquire(); /** * While a good check in theory, it's possible for an event fired to * cause a listener to access a method that calls this one. */ //Assert.isTrue(SYNC.getDepth()==1, "depth not equal to 1"); // let's see if we already have it in our cache SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); // if not, then we'll simply return null if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); result = sharedObject.theSharedModel; } else { SYNC.release(); } return result; } /** * Note: users of this 'model' must still release it when finished. * Returns null if there's not a model corresponding to document. */ public IStructuredModel getExistingModelForEdit(IDocument document) { IStructuredModel result = null; SYNC.acquire(); // create a snapshot Set ids = new HashSet(fManagedObjects.keySet()); SYNC.release(); for (Iterator iterator = ids.iterator(); iterator.hasNext();) { Object potentialId = iterator.next(); SYNC.acquire(); if (fManagedObjects.containsKey(potentialId)) { // check to see if still valid SYNC.release(); IStructuredModel tempResult = getExistingModel(potentialId); if (tempResult!=null && document == tempResult.getStructuredDocument()) { result = getExistingModelForEdit(potentialId); break; } } else { SYNC.release(); } } return result; } /** * This is similar to the getModel method, except this method does not * create a model. This method does increment the reference count (if it * exists). If the model does not already exist in the cache of models, * null is returned. */ public IStructuredModel getExistingModelForEdit(IFile iFile) { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ Object id = calculateId(iFile); IStructuredModel result = getExistingModelForEdit(id); return result; } /** * This is similar to the getModel method, except this method does not * create a model. This method does increment the reference count (if it * exists). If the model does not already exist in the cache of models, * null is returned. * * @deprecated use IFile form - this one will become protected or private */ public IStructuredModel getExistingModelForEdit(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ IStructuredModel result = null; // let's see if we already have it in our cache SharedObject sharedObject = null; SYNC.acquire(); try { sharedObject = (SharedObject) fManagedObjects.get(id); } finally { SYNC.release(); } // if not, then we'll simply return null if (sharedObject != null) { // if shared object is in our cache, then simply increment its ref // count, and return the object. synchronized(sharedObject) { if (sharedObject.doWait) { sharedObject.waitForLoadAttempt(); } } SYNC.acquire(); try { synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, EDIT); } } } finally { SYNC.release(); } result = sharedObject.theSharedModel; trace("got existing model for Edit: ", id); //$NON-NLS-1$ trace(" incremented referenceCountForEdit ", id, sharedObject.referenceCountForEdit); //$NON-NLS-1$ } return result; } /** * Note: users of this 'model' must still release it when finished. * Returns null if there's not a model corresponding to document. */ public IStructuredModel getExistingModelForRead(IDocument document) { IStructuredModel result = null; SYNC.acquire(); // create a snapshot Set ids = new HashSet(fManagedObjects.keySet()); SYNC.release(); for (Iterator iterator = ids.iterator(); iterator.hasNext();) { Object potentialId = iterator.next(); SYNC.acquire(); if (fManagedObjects.containsKey(potentialId)) { // check to see if still valid SYNC.release(); IStructuredModel tempResult = getExistingModel(potentialId); if (tempResult!=null && document == tempResult.getStructuredDocument()) { result = getExistingModelForRead(potentialId); break; } } else { SYNC.release(); } } return result; } public IStructuredModel getExistingModelForRead(IFile iFile) { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ Object id = calculateId(iFile); IStructuredModel result = getExistingModelForRead(id); return result; } /** * This is similar to the getModel method, except this method does not * create a model. This method does increment the reference count (if it * exists). If the model does not already exist in the cache of models, * null is returned. * * @deprecated use IFile form - this one will become protected or private */ public IStructuredModel getExistingModelForRead(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ IStructuredModel result = null; SharedObject sharedObject = null; // let's see if we already have it in our cache SYNC.acquire(); try { sharedObject = (SharedObject) fManagedObjects.get(id); } finally { SYNC.release(); } // if not, then we'll simply return null if (sharedObject != null) { // if shared object is in our cache, then simply increment its ref // count, and return the object. synchronized(sharedObject) { if (sharedObject.doWait) { sharedObject.waitForLoadAttempt(); } } SYNC.acquire(); try { synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { _incrCount(sharedObject, READ); } } } finally { SYNC.release(); } result = sharedObject.theSharedModel; } return result; } /** * @deprecated DMW: Tom, this is "special" for links builder Assuming its * still needed, wouldn't it be better to change to * getExistingModels()? -- will be removed. Its not thread * safe for one thread to get the Enumeration, when underlying * data could be changed in another thread. */ public Enumeration getExistingModelIds() { try { SYNC.acquire(); // create a copy Vector keys = new Vector( fManagedObjects.keySet() ); return keys.elements(); } finally { SYNC.release(); } } // TODO: replace (or supplement) this is a "model info" association to the // IFile that created the model private IFile getFileFor(IStructuredModel model) { if (model == null) return null; String path = model.getBaseLocation(); if (path == null || path.length() == 0) { Object id = model.getId(); if (id == null) return null; path = id.toString(); } // TOODO needs rework for linked resources IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IFile file = root.getFileForLocation(new Path(path)); return file; } /** * One of the primary forms to get a managed model */ public IStructuredModel getModelForEdit(IFile iFile) throws IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, EDIT, null, null); } public IStructuredModel getModelForEdit(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, EDIT, encodingRule); } public IStructuredModel getModelForEdit(IFile iFile, String encoding, String lineDelimiter) throws java.io.UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, EDIT, encoding, lineDelimiter); } public IStructuredModel getModelForEdit(IStructuredDocument document) { return _getModelFor(document, EDIT); } /** * @see IModelManager * @deprecated use IFile or String form */ public IStructuredModel getModelForEdit(Object id, InputStream inputStream, URIResolver resolver) throws java.io.UnsupportedEncodingException, IOException { Assert.isNotNull(id, "requested model id can not be null"); //$NON-NLS-1$ String stringId = id.toString(); return getModelForEdit(stringId, Utilities.getMarkSupportedStream(inputStream), resolver); } /** * @see IModelManager * @deprecated - use IFile or String form */ public IStructuredModel getModelForEdit(Object id, Object modelType, String encodingName, String lineDelimiter, InputStream inputStream, URIResolver resolver) throws java.io.UnsupportedEncodingException, IOException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ String stringId = id.toString(); return getModelForEdit(stringId, Utilities.getMarkSupportedStream(inputStream), resolver); } public IStructuredModel getModelForEdit(String id, InputStream inputStream, URIResolver resolver) throws IOException { if (id == null) { throw new IllegalArgumentException("Program Error: id may not be null"); //$NON-NLS-1$ } IStructuredModel result = null; InputStream istream = Utilities.getMarkSupportedStream(inputStream); IModelHandler handler = calculateType(id, istream); if (handler != null) { result = _commonCreateModel(istream, id, handler, resolver, EDIT, null, null); } else { Logger.log(Logger.INFO, "no model handler found for id"); //$NON-NLS-1$ } return result; } /** * One of the primary forms to get a managed model */ public IStructuredModel getModelForRead(IFile iFile) throws IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, READ, null, null); } public IStructuredModel getModelForRead(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, READ, encodingRule); } public IStructuredModel getModelForRead(IFile iFile, String encodingName, String lineDelimiter) throws java.io.UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ return _commonGetModel(iFile, READ, encodingName, lineDelimiter); } public IStructuredModel getModelForRead(IStructuredDocument document) { return _getModelFor(document, READ); } /** * @see IModelManager * @deprecated use IFile or String form */ public IStructuredModel getModelForRead(Object id, InputStream inputStream, URIResolver resolver) throws java.io.UnsupportedEncodingException, IOException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ String stringId = id.toString(); return getModelForRead(stringId, Utilities.getMarkSupportedStream(inputStream), resolver); } /** * @see IModelManager * @deprecated use IFile form */ public IStructuredModel getModelForRead(Object id, Object modelType, String encodingName, String lineDelimiter, InputStream inputStream, URIResolver resolver) throws java.io.UnsupportedEncodingException, IOException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ String stringId = id.toString(); return getModelForRead(stringId, Utilities.getMarkSupportedStream(inputStream), resolver); } public IStructuredModel getModelForRead(String id, InputStream inputStream, URIResolver resolver) throws IOException { InputStream istream = Utilities.getMarkSupportedStream(inputStream); IModelHandler handler = calculateType(id, istream); IStructuredModel result = null; result = _commonCreateModel(istream, id, handler, resolver, READ, null, null); return result; } /** * @deprecated - only temporarily visible */ public ModelHandlerRegistry getModelHandlerRegistry() { if (fModelHandlerRegistry == null) { fModelHandlerRegistry = ModelHandlerRegistry.getInstance(); } return fModelHandlerRegistry; } /** * @see IModelManager#getNewModelForEdit(IFile, boolean) */ public IStructuredModel getNewModelForEdit(IFile iFile, boolean force) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ SharedObject sharedObject = _commonNewModel(iFile, force); synchronized(sharedObject) { sharedObject.referenceCountForEdit = 1; } sharedObject.setLoaded(); return sharedObject.theSharedModel; } /** * @see IModelManager#getNewModelForRead(IFile, boolean) */ public IStructuredModel getNewModelForRead(IFile iFile, boolean force) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException { Assert.isNotNull(iFile, "IFile parameter can not be null"); //$NON-NLS-1$ SharedObject sharedObject = _commonNewModel(iFile, force); SYNC.acquire(); synchronized(sharedObject) { if (sharedObject.theSharedModel!=null) { sharedObject.referenceCountForRead = 1; } } SYNC.release(); sharedObject.setLoaded(); return sharedObject.theSharedModel; } /** * This function returns the reference count of underlying model. * * @param id * Object The id of the model TODO: try to refine the design * not to use this function */ public int getReferenceCount(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized (sharedObject) { count = sharedObject.referenceCountForRead + sharedObject.referenceCountForEdit; } } else { SYNC.release(); } return count; } /** * This function returns the reference count of underlying model. * * @param id * Object The id of the model TODO: try to refine the design * not to use this function */ public int getReferenceCountForEdit(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { count = sharedObject.referenceCountForEdit; } } else { SYNC.release(); } return count; } /** * This function returns the reference count of underlying model. * * @param id * Object The id of the model TODO: try to refine the design * not to use this function */ public int getReferenceCountForRead(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { count = sharedObject.referenceCountForRead; } } else { SYNC.release(); } return count; } private void handleConvertLineDelimiters(IStructuredDocument structuredDocument, IFile iFile, EncodingRule encodingRule, EncodingMemento encodingMemento) throws CoreException, MalformedOutputExceptionWithDetail, UnsupportedEncodingException { if (structuredDocument.getNumberOfLines() > 1) { convertLineDelimiters(structuredDocument, iFile); } } private void handleProgramError(Throwable t) { Logger.logException("Impossible Program Error", t); //$NON-NLS-1$ } /** * This function returns true if there are other references to the * underlying model. */ public boolean isShared(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; boolean result = false; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { count = sharedObject.referenceCountForRead + sharedObject.referenceCountForEdit; } } else { SYNC.release(); } result = count > 1; return result; } /** * This function returns true if there are other references to the * underlying model. * * @param id * Object The id of the model */ public boolean isSharedForEdit(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; boolean result = false; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { count = sharedObject.referenceCountForEdit; } } else { SYNC.release(); } result = count > 1; return result; } /** * This function returns true if there are other references to the * underlying model. * * @param id * Object The id of the model */ public boolean isSharedForRead(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ int count = 0; boolean result = false; SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject != null) { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { count = sharedObject.referenceCountForRead; } } else { SYNC.release(); } result = count > 1; return result; } /** * This method can be called to determine if the model manager is within a * "aboutToChange" and "changed" sequence. * * @deprecated the manager does not otherwise interact with these states * @return false */ public boolean isStateChanging() { // doesn't seem to be used anymore return false; } /** * This method changes the id of the model. TODO: try to refine the design * not to use this function */ public void moveModel(Object oldId, Object newId) { Assert.isNotNull(oldId, "old id parameter can not be null"); //$NON-NLS-1$ Assert.isNotNull(newId, "new id parameter can not be null"); //$NON-NLS-1$ SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(oldId); // if not found in cache, ignore request. // this would normally be a program error if (sharedObject != null) { fManagedObjects.remove(oldId); fManagedObjects.put(newId, sharedObject); } SYNC.release(); } private String readInputStream(InputStream inputStream, String ianaEncodingName) throws UnsupportedEncodingException, IOException { String allText = null; if ((ianaEncodingName != null) && (ianaEncodingName.length() != 0)) { String enc = CodedIO.getAppropriateJavaCharset(ianaEncodingName); if (enc == null) { // if no conversion was possible, let's assume that // the encoding is already a java encoding name, so we'll // proceed with that assumption. This is the case, for // example, // for the reload() procedure. // If in fact it is not a valid java encoding, then // the "allText=" line will cause an // UnsupportedEncodingException enc = ianaEncodingName; } allText = readInputStream(new InputStreamReader(inputStream, enc)); } else { // we normally assume encoding is provided for this method, but if // not, // we'll use platform default allText = readInputStream(new InputStreamReader(inputStream)); } return allText; } private String readInputStream(InputStreamReader inputStream) throws IOException { int numRead = 0; StringBuffer buffer = new StringBuffer(); char tBuff[] = new char[READ_BUFFER_SIZE]; while ((numRead = inputStream.read(tBuff, 0, tBuff.length)) != -1) { buffer.append(tBuff, 0, numRead); } // remember -- we didn't open stream ... so we don't close it return buffer.toString(); } /* * @see IModelManager#reinitialize(IStructuredModel) */ public IStructuredModel reinitialize(IStructuredModel model) { // getHandler (assume its the "new one") IModelHandler handler = model.getModelHandler(); // getLoader for that new one IModelLoader loader = handler.getModelLoader(); // ask it to reinitialize model = loader.reinitialize(model); // the loader should check to see if the one it received // is the same type it would normally create. // if not, it must "start from scratch" and create a whole // new one. // if it is of the same type, it should just 'replace text' // replacing all the existing text with the new text. // the important one is the JSP loader ... it should go through // its embedded content checking and initialization return model; } void releaseFromEdit(IStructuredModel structuredModel) { Object id = structuredModel.getId(); if (id.equals(UNMANAGED_MODEL) || id.equals(DUPLICATED_MODEL)) { cleanupDiscardedModel(structuredModel); } else { releaseFromEdit(id); } } void releaseFromRead(IStructuredModel structuredModel) { Object id = structuredModel.getId(); if (id.equals(UNMANAGED_MODEL) || id.equals(DUPLICATED_MODEL)) { cleanupDiscardedModel(structuredModel); } else { releaseFromRead(id); } } /** * default for use in same package, not subclasses * */ private void releaseFromEdit(Object id) { // ISSUE: many of these asserts should be changed to "logs" // and continue to limp along? Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ SharedObject sharedObject = null; // ISSUE: here we need better "spec" what to do with // unmanaged or duplicated models. Release still needs // to be called on them, for now, but the model manager // doesn't need to do anything. if (id.equals(UNMANAGED_MODEL) || id.equals(DUPLICATED_MODEL)) { throw new IllegalArgumentException("Ids of UNMANAGED_MODEL or DUPLICATED_MODEL are illegal here"); } else { SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); Assert.isNotNull(sharedObject, "release was requested on a model that was not being managed"); //$NON-NLS-1$ sharedObject.waitForLoadAttempt(); SYNC.acquire(); synchronized(sharedObject) { _decrCount(sharedObject, EDIT); if ((sharedObject.referenceCountForRead == 0) && (sharedObject.referenceCountForEdit == 0)) { discardModel(id, sharedObject); } } SYNC.release(); // if edit goes to zero, but still open for read, // then we should reload here, so we are in synch with // contents on disk. // ISSUE: should we check isDirty here? // ANSWER: here, for now now. model still has its own dirty // flag for some reason. // we need to address * that * too. synchronized(sharedObject) { if ((sharedObject.referenceCountForRead > 0) && (sharedObject.referenceCountForEdit == 0) && sharedObject.theSharedModel.isDirty()) { signalPreLifeCycleListenerRevert(sharedObject.theSharedModel); revertModel(id, sharedObject); /* * Because model events are fired to notify about the * revert's changes, and listeners can still get/release * the model from this thread (locking prevents it being * done from other threads), the reference counts could * have changed since we entered this if block, and the * model could have been discarded. Check the counts again. */ if (sharedObject.referenceCountForRead > 0 && sharedObject.referenceCountForEdit == 0) { sharedObject.theSharedModel.setDirtyState(false); } signalPostLifeCycleListenerRevert(sharedObject.theSharedModel); } } } } // private for now, though public forms have been requested, in past. private void revertModel(Object id, SharedObject sharedObject) { IStructuredDocument structuredDocument = sharedObject.theSharedModel.getStructuredDocument(); FileBufferModelManager.getInstance().revert(structuredDocument); } private void signalPreLifeCycleListenerRevert(IStructuredModel structuredModel) { int type = ModelLifecycleEvent.MODEL_REVERT | 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); } private void signalPostLifeCycleListenerRevert(IStructuredModel structuredModel) { int type = ModelLifecycleEvent.MODEL_REVERT | 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 discardModel(Object id, SharedObject sharedObject) { SYNC.acquire(); fManagedObjects.remove(id); SYNC.release(); IStructuredDocument structuredDocument = sharedObject.theSharedModel.getStructuredDocument(); if (structuredDocument == null) { Platform.getLog(SSECorePlugin.getDefault().getBundle()).log(new Status(IStatus.ERROR, SSECorePlugin.ID, IStatus.ERROR, "Attempted to discard a structured model but the underlying document has already been set to null: " + sharedObject.theSharedModel.getBaseLocation(), null)); } cleanupDiscardedModel(sharedObject.theSharedModel); if (Logger.DEBUG_MODELMANAGER) { trace("Remaining models in the model manager", fManagedObjects.entrySet(), fManagedObjects.size()); } } private void cleanupDiscardedModel(IStructuredModel structuredModel) { IStructuredDocument structuredDocument = structuredModel.getStructuredDocument(); /* * This call (and setting the StructuredDocument to null) were * previously done within the model itself, but for concurrency it * must be done here during a synchronized release. */ structuredModel.getFactoryRegistry().release(); /* * For structured documents originating from file buffers, disconnect * us from the file buffer, now. */ FileBufferModelManager.getInstance().releaseModel(structuredDocument); /* * Setting the document to null is required since some subclasses of * model might have "cleanup" of listeners, etc., to remove, which * were initialized during the initial setStructuredDocument. * * The model itself in particular may have internal listeners used to * coordinate the document with its own "structure". */ structuredModel.setStructuredDocument(null); } /** * default for use in same package, not subclasses * */ private void releaseFromRead(Object id) { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ SharedObject sharedObject = null; if (id.equals(UNMANAGED_MODEL) || id.equals(DUPLICATED_MODEL)) { throw new IllegalArgumentException("Ids of UNMANAGED_MODEL or DUPLICATED_MODEL are illegal here"); } else { SYNC.acquire(); sharedObject = (SharedObject) fManagedObjects.get(id); SYNC.release(); Assert.isNotNull(sharedObject, "release was requested on a model that was not being managed"); //$NON-NLS-1$ sharedObject.waitForLoadAttempt(); } SYNC.acquire(); synchronized(sharedObject) { _decrCount(sharedObject, READ); if ((sharedObject.referenceCountForRead == 0) && (sharedObject.referenceCountForEdit == 0)) { discardModel(id, sharedObject); } } SYNC.release(); } /** * This is similar to the getModel method, except this method does not use * the cached version, but forces the cached version to be replaced with a * fresh, unchanged version. Note: this method does not change any * reference counts. Also, if there is not already a cached version of the * model, then this call is essentially ignored (that is, it does not put * a model in the cache) and returns null. * * @deprecated - will become protected, use reload directly on model */ public IStructuredModel reloadModel(Object id, java.io.InputStream inputStream) throws java.io.UnsupportedEncodingException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ // get the existing model associated with this id IStructuredModel structuredModel = getExistingModel(id); // for the model to be null is probably an error (that is, // reload should not have been called, but we'll guard against // a null pointer example and return null if we are no longer managing // that model. if (structuredModel != null) { // get loader based on existing type // dmwTODO evaluate when reload should occur // with potentially new type (e.g. html 'save as' jsp). IModelHandler handler = structuredModel.getModelHandler(); IModelLoader loader = handler.getModelLoader(); // ask the loader to re-load loader.reload(Utilities.getMarkSupportedStream(inputStream), structuredModel); trace("re-loading model", id); //$NON-NLS-1$ } return structuredModel; } public void saveModel(IFile iFile, String id, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(iFile, "file parameter can not be null"); //$NON-NLS-1$ Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ // let's see if we already have it in our cache SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject == null || sharedObject.theSharedModel == null) { SYNC.release(); throw new IllegalStateException(SSECoreMessages.Program_Error__ModelManage_EXC_); //$NON-NLS-1$ = "Program Error: ModelManagerImpl::saveModel. Model should be in the cache" } else { SYNC.release(); sharedObject.waitForLoadAttempt(); /** * https://bugs.eclipse.org/bugs/show_bug.cgi?id=221610 * * Sync removed from here to prevent deadlock. Although the model * instance may disappear or be made invalid while the save is * happening, the document itself still has the contents we're * trying to save. Simultaneous saves should be throttled by * resource locking without our intervention. */ boolean saved = false; // if this model was based on a File Buffer and we're writing back // to the same location, use the buffer to do the writing if (FileBufferModelManager.getInstance().isExistingBuffer(sharedObject.theSharedModel.getStructuredDocument())) { ITextFileBuffer buffer = FileBufferModelManager.getInstance().getBuffer(sharedObject.theSharedModel.getStructuredDocument()); IPath fileLocation = FileBuffers.normalizeLocation(iFile.getFullPath()); if (fileLocation.equals(buffer.getLocation())) { buffer.commit(new NullProgressMonitor(), true); saved = true; } } if (!saved) { IStructuredModel model = sharedObject.theSharedModel; IStructuredDocument document = model.getStructuredDocument(); saveStructuredDocument(document, iFile, encodingRule); trace("saving model", id); //$NON-NLS-1$ } sharedObject.theSharedModel.setDirtyState(false); sharedObject.theSharedModel.setNewState(false); } } /** * Saving the model really just means to save it's structured document. * * @param id * @param outputStream * @param encodingRule * @throws UnsupportedEncodingException * @throws IOException * @throws CoreException */ public void saveModel(String id, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ // let's see if we already have it in our cache SYNC.acquire(); SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject == null) { SYNC.release(); throw new IllegalStateException(SSECoreMessages.Program_Error__ModelManage_EXC_); //$NON-NLS-1$ = "Program Error: ModelManagerImpl::saveModel. Model should be in the cache" } else { SYNC.release(); sharedObject.waitForLoadAttempt(); /** * https://bugs.eclipse.org/bugs/show_bug.cgi?id=221610 * * Sync removed from here to prevent deadlock. Although the model * instance may disappear or be made invalid while the save is * happening, the document itself still has the contents we're * trying to save. Simultaneous saves should be throttled by * resource locking without our intervention. */ /* * if this model was based on a File Buffer and we're writing back * to the same location, use the buffer to do the writing */ if (FileBufferModelManager.getInstance().isExistingBuffer(sharedObject.theSharedModel.getStructuredDocument())) { ITextFileBuffer buffer = FileBufferModelManager.getInstance().getBuffer(sharedObject.theSharedModel.getStructuredDocument()); buffer.commit(new NullProgressMonitor(), true); } else { IFile iFile = getFileFor(sharedObject.theSharedModel); IStructuredModel model = sharedObject.theSharedModel; IStructuredDocument document = model.getStructuredDocument(); saveStructuredDocument(document, iFile); trace("saving model", id); //$NON-NLS-1$ } sharedObject.theSharedModel.setDirtyState(false); sharedObject.theSharedModel.setNewState(false); } } /** * @deprecated - this method is less efficient than IFile form, since it * requires an extra "copy" of byte array, and should be avoid * in favor of the IFile form. */ public void saveModel(String id, OutputStream outputStream, EncodingRule encodingRule) throws UnsupportedEncodingException, CoreException, IOException { Assert.isNotNull(id, "id parameter can not be null"); //$NON-NLS-1$ SYNC.acquire(); // let's see if we already have it in our cache SharedObject sharedObject = (SharedObject) fManagedObjects.get(id); if (sharedObject == null) { SYNC.release(); throw new IllegalStateException(SSECoreMessages.Program_Error__ModelManage_EXC_); //$NON-NLS-1$ = "Program Error: ModelManagerImpl::saveModel. Model should be in the cache" } else { SYNC.release(); sharedObject.waitForLoadAttempt(); synchronized(sharedObject) { CodedStreamCreator codedStreamCreator = new CodedStreamCreator(); codedStreamCreator.set(sharedObject.theSharedModel.getId(), new DocumentReader(sharedObject.theSharedModel.getStructuredDocument())); codedStreamCreator.setPreviousEncodingMemento(sharedObject.theSharedModel.getStructuredDocument().getEncodingMemento()); ByteArrayOutputStream byteArrayOutputStream = codedStreamCreator.getCodedByteArrayOutputStream(encodingRule); byte[] outputBytes = byteArrayOutputStream.toByteArray(); outputStream.write(outputBytes); trace("saving model", id); //$NON-NLS-1$ sharedObject.theSharedModel.setDirtyState(false); sharedObject.theSharedModel.setNewState(false); } } } public void saveStructuredDocument(IStructuredDocument structuredDocument, IFile iFile) throws UnsupportedEncodingException, CoreException, IOException { saveStructuredDocument(structuredDocument, iFile, EncodingRule.CONTENT_BASED); } public void saveStructuredDocument(IStructuredDocument structuredDocument, IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, CoreException, IOException { Assert.isNotNull(iFile, "file parameter can not be null"); //$NON-NLS-1$ if (FileBufferModelManager.getInstance().isExistingBuffer(structuredDocument)) { ITextFileBuffer buffer = FileBufferModelManager.getInstance().getBuffer(structuredDocument); if (iFile.getFullPath().equals(buffer.getLocation()) || (iFile.getLocation() != null && iFile.getLocation().equals(buffer.getLocation())) || (iFile.getLocationURI() != null && buffer.getFileStore() != null && iFile.getLocationURI().equals(buffer.getFileStore().toURI()))) { buffer.commit(new NullProgressMonitor(), true); } } else { // IModelHandler handler = calculateType(iFile); // IDocumentDumper dumper = handler.getDocumentDumper(); CodedStreamCreator codedStreamCreator = new CodedStreamCreator(); Reader reader = new DocumentReader(structuredDocument); codedStreamCreator.set(iFile, reader); codedStreamCreator.setPreviousEncodingMemento(structuredDocument.getEncodingMemento()); EncodingMemento encodingMemento = codedStreamCreator.getCurrentEncodingMemento(); // be sure document's is updated, in case exception is thrown in // getCodedByteArrayOutputStream structuredDocument.setEncodingMemento(encodingMemento); // Convert line delimiters after encoding memento is figured out, // but // before writing to output stream. handleConvertLineDelimiters(structuredDocument, iFile, encodingRule, encodingMemento); ByteArrayOutputStream codedByteStream = codedStreamCreator.getCodedByteArrayOutputStream(encodingRule); InputStream codedStream = new ByteArrayInputStream(codedByteStream.toByteArray()); if (iFile.exists()) iFile.setContents(codedStream, true, true, null); else iFile.create(codedStream, false, null); codedByteStream.close(); codedStream.close(); } } /** * Common trace method */ private void trace(String msg, Object id) { if (Logger.DEBUG_MODELMANAGER) { Logger.log(Logger.INFO, msg + " " + Utilities.makeShortId(id)); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Common trace method */ private void trace(String msg, Object id, int value) { if (Logger.DEBUG_MODELMANAGER) { Logger.log(Logger.INFO, msg + Utilities.makeShortId(id) + " (" + value + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } boolean isIdInUse(String newId) { boolean inUse = false; SYNC.acquire(); SharedObject object =(SharedObject) fManagedObjects.get(newId); if (object!=null) { inUse = object.theSharedModel!=null; } SYNC.release(); return inUse; } }