/******************************************************************************* * 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.ui.internal; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.HashMap; import java.util.Map; import org.eclipse.core.resources.IEncodedStorage; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.AnnotationModel; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPathEditorInput; import org.eclipse.ui.IStorageEditorInput; import org.eclipse.ui.editors.text.FileDocumentProvider; import org.eclipse.ui.editors.text.ILocationProvider; import org.eclipse.ui.editors.text.StorageDocumentProvider; import org.eclipse.ui.texteditor.DocumentProviderRegistry; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.IElementStateListener; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.encoding.CodedReaderCreator; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.util.Utilities; import org.eclipse.wst.sse.ui.internal.debug.BreakpointRulerAction; import org.eclipse.wst.sse.ui.internal.editor.EditorModelUtil; import org.eclipse.wst.sse.ui.internal.extension.BreakpointProviderBuilder; import org.eclipse.wst.sse.ui.internal.provisional.extensions.breakpoint.IExtendedStorageEditorInput; /** * A StorageDocumentProvider that is IStructuredModel aware */ public class StorageModelProvider extends StorageDocumentProvider implements IModelProvider { private class InternalElementStateListener implements IElementStateListener { public void elementContentAboutToBeReplaced(Object element) { if (debugElementStatelistener) { System.out.println("StorageModelProvider: elementContentAboutToBeReplaced: " + ((IEditorInput) element).getName()); //$NON-NLS-1$ } // we just forward the event StorageModelProvider.this.fireElementContentAboutToBeReplaced(element); } public void elementContentReplaced(Object element) { if (debugElementStatelistener) { System.out.println("StorageModelProvider: elementContentReplaced: " + ((IEditorInput) element).getName()); //$NON-NLS-1$ } StorageInfo info = (StorageInfo) getElementInfo(element); if (info == null) return; /** * Force a reload of the markers into annotations since their * previous Positions have been deleted. Disconnecting and * reconnecting forces a call to the private catchupWithMarkers * method. */ if (info.fModel != null) { info.fModel.disconnect(info.fDocument); } Reader reader = null; IStructuredDocument innerdocument = null; try { // update document from input's contents CodedReaderCreator codedReaderCreator = new CodedReaderCreator(calculateID((IStorageEditorInput) element), Utilities.getMarkSupportedStream(((IStorageEditorInput) element).getStorage().getContents())); reader = codedReaderCreator.getCodedReader(); innerdocument = (IStructuredDocument) info.fDocument; int originalLengthToReplace = innerdocument.getLength(); StringBuffer stringBuffer = new StringBuffer(); int bufferSize = 2048; char[] buffer = new char[bufferSize]; int nRead = 0; boolean eof = false; while (!eof) { nRead = reader.read(buffer, 0, bufferSize); if (nRead == -1) { eof = true; } else { stringBuffer.append(buffer, 0, nRead); } } innerdocument.replaceText(this, 0, originalLengthToReplace, stringBuffer.toString(), true); } catch (CoreException e) { Logger.logException(e); } catch (IOException e) { Logger.logException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { // would be highly unusual Logger.logException(e1); } } } // forward the event if (info.fCanBeSaved) { info.fCanBeSaved = false; addUnchangedElementListeners(element, info); } fireElementContentReplaced(element); fireElementDirtyStateChanged(element, false); if (info != null && info.fModel != null) { info.fModel.connect(info.fDocument); } } public void elementDeleted(Object element) { if (debugElementStatelistener) { System.out.println("StorageModelProvider: elementDeleted: " + ((IEditorInput) element).getName()); //$NON-NLS-1$ } // we just forward the event StorageModelProvider.this.fireElementDeleted(element); } public void elementDirtyStateChanged(Object element, boolean isDirty) { if (debugElementStatelistener) { System.out.println("StorageModelProvider: elementDirtyStateChanged: " + ((IEditorInput) element).getName() + " (" + isDirty + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } // we just forward the event StorageModelProvider.this.fireElementDirtyStateChanged(element, isDirty); } public void elementMoved(Object originalElement, Object movedElement) { if (debugElementStatelistener) { System.out.println("StorageModelProvider: elementMoved " + originalElement + " --> " + movedElement); //$NON-NLS-1$ //$NON-NLS-2$ } // we just forward the event StorageModelProvider.this.fireElementMoved(originalElement, movedElement); } } /** * Collection of info that goes with a model. */ private class ModelInfo { public IEditorInput fElement; public boolean fShouldReleaseOnInfoDispose; public IStructuredModel fStructuredModel; public ModelInfo(IStructuredModel structuredModel, IEditorInput element, boolean selfCreated) { fElement = element; fStructuredModel = structuredModel; fShouldReleaseOnInfoDispose = selfCreated; } } static final boolean debugElementStatelistener = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/storagemodelprovider/elementstatelistener")); //$NON-NLS-1$ //$NON-NLS-2$ static final boolean debugOperations = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/storagemodelprovider/operations")); //$NON-NLS-1$ //$NON-NLS-2$ private static StorageModelProvider fInstance = null; public synchronized static StorageModelProvider getInstance() { if (fInstance == null) fInstance = new StorageModelProvider(); return fInstance; } private IElementStateListener fInternalListener; /** IStructuredModel information of all connected elements */ private Map fModelInfoMap = new HashMap(); private boolean fReuseModelDocument = true; private StorageModelProvider() { super(); fInternalListener = new InternalElementStateListener(); } String calculateBaseLocation(IStorageEditorInput input) { String location = null; if (input instanceof IPathEditorInput) { IPath path = ((IPathEditorInput) input).getPath(); if (path != null) { location = path.toString(); } } if (location == null && input instanceof ILocationProvider) { IPath path = ((ILocationProvider) input).getPath(input); if (path != null) { location = path.toString(); } } if (location == null) { try { IStorage storage = input.getStorage(); if (storage != null) { IPath storagePath = storage.getFullPath(); String name = storage.getName(); if (storagePath != null) { /* * If the path's last segment and the name are * different, the IStorage contract is not being * honored * (https://bugs.eclipse.org/bugs/show_bug.cgi? * id=73098). Favor the name */ if (!storagePath.lastSegment().equals(name)) { IPath workingPath = storagePath.addTrailingSeparator(); location = workingPath.append(name).toString(); } else { location = storagePath.makeAbsolute().toString(); } } if (location == null) location = name; } } catch (CoreException e) { Logger.logException(e); } finally { if (location == null) location = input.getName(); } } return location; } String calculateID(IStorageEditorInput input) { /** * Typically CVS will return a path of "filename.ext" and the input's * name will be "filename.ext version". The path must be used to load * the model so that the suffix will be available to compute the * contentType properly. The editor input name can then be set as the * base location for display on the editor title bar. * */ String path = null; if (input instanceof ILocationProvider) { IPath ipath = ((ILocationProvider) input).getPath(input); if (ipath != null) { path = ipath.toString(); } } if (path == null) { try { IStorage storage = input.getStorage(); if (storage != null) { IPath storagePath = storage.getFullPath(); String name = storage.getName(); if (storagePath != null) { // If they are different, the IStorage contract is not // being honored // (https://bugs.eclipse.org/bugs/show_bug.cgi?id=73098). // Favor the name. if (!storagePath.lastSegment().equals(name)) { IPath workingPath = storagePath.addTrailingSeparator(); path = workingPath.append(name).toString(); } else { path = storagePath.makeAbsolute().toString(); } } if (path == null) path = name; } } catch (CoreException e) { Logger.logException(e); } finally { if (path == null) path = input.getName(); //$NON-NLS-1$ } } /* * Prepend the hash to the path value so that we have a 1:1:1 match * between editor inputs, element info, and models. The editor manager * should help prevent needlessly duplicated models as long as two * editor inputs from the same storage indicate they're equals(). */ path = input.hashCode() + "#" + path; //$NON-NLS-1$ return path; } // public boolean canSaveDocument(Object element) { // return false; // } protected IAnnotationModel createAnnotationModel(Object element) throws CoreException { IAnnotationModel model = null; if (debugOperations) { if (element instanceof IStorageEditorInput) System.out.println("StorageModelProvider: createAnnotationModel for " + ((IStorageEditorInput) element).getStorage().getFullPath()); //$NON-NLS-1$ else System.out.println("StorageModelProvider: createAnnotationModel for " + element); //$NON-NLS-1$ } if (element instanceof IStorageEditorInput) { IStorageEditorInput input = (IStorageEditorInput) element; String contentType = (getModel(input) != null ? getModel(input).getContentTypeIdentifier() : null); String ext = BreakpointRulerAction.getFileExtension((IEditorInput) element); IResource res = BreakpointProviderBuilder.getInstance().getResource(input, contentType, ext); String id = input.getName(); if (input.getStorage() != null && input.getStorage().getFullPath() != null) { id = input.getStorage().getFullPath().toString(); } // we can only create a resource marker annotationmodel off of a // valid resource if (res != null) model = new StructuredResourceMarkerAnnotationModel(res, id); else model = new AnnotationModel(); } if (model == null) { model = super.createAnnotationModel(element); } return model; } protected IDocument createDocument(Object element) { if (debugOperations) { if (element instanceof IStorageEditorInput) try { System.out.println("StorageModelProvider: createDocument for " + ((IStorageEditorInput) element).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: createDocument for " + element + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } else { System.out.println("StorageModelProvider: createDocument for " + element); //$NON-NLS-1$ } } // The following is largely copied from FileModelProvider IDocument document = null; if (element instanceof IEditorInput) { // create a new IDocument for the element; should always reflect // the contents of the resource ModelInfo info = getModelInfoFor((IEditorInput) element); if (info == null) { throw new IllegalArgumentException("no corresponding model info found"); //$NON-NLS-1$ } IStructuredModel model = info.fStructuredModel; if (model != null) { if (!fReuseModelDocument && element instanceof IStorageEditorInput) { Reader reader = null; IStructuredDocument innerdocument = null; try { // update document from input's contents CodedReaderCreator codedReaderCreator = new CodedReaderCreator(calculateID((IStorageEditorInput) element), Utilities.getMarkSupportedStream(((IStorageEditorInput) element).getStorage().getContents())); reader = codedReaderCreator.getCodedReader(); innerdocument = model.getStructuredDocument(); int originalLengthToReplace = innerdocument.getLength(); /* * TODO_future: we could implement with sequential * rewrite, if we don't pickup automatically from * FileBuffer support, so not so much has to be pulled * into memory (as an extra big string), but we need * to carry that API through so that StructuredModel * is not notified until done. */ // innerdocument.startSequentialRewrite(true); // innerdocument.replaceText(this, 0, // innerdocument.getLength(), ""); StringBuffer stringBuffer = new StringBuffer(); int bufferSize = 2048; char[] buffer = new char[bufferSize]; int nRead = 0; boolean eof = false; while (!eof) { nRead = reader.read(buffer, 0, bufferSize); if (nRead == -1) { eof = true; } else { stringBuffer.append(buffer, 0, nRead); // innerdocument.replaceText(this, // innerdocument.getLength(), 0, new // String(buffer, 0, nRead)); } } // ignore read-only settings if reverting whole // document innerdocument.replaceText(this, 0, originalLengthToReplace, stringBuffer.toString(), true); model.setDirtyState(false); } catch (CoreException e) { Logger.logException(e); } catch (IOException e) { Logger.logException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { // would be highly unusual Logger.logException(e1); } } // if (innerdocument != null) { // innerdocument.stopSequentialRewrite(); // } } } document = model.getStructuredDocument(); } } return document; } /** * Also create ModelInfo - extra resource synchronization classes should * be stored within the ModelInfo */ protected ElementInfo createElementInfo(Object element) throws CoreException { // create the corresponding ModelInfo if necessary if (debugOperations) { if (element instanceof IStorageEditorInput) try { System.out.println("StorageModelProvider: createElementInfo for " + ((IStorageEditorInput) element).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: createElementInfo for " + element + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } else System.out.println("storageModelProvider: createElementInfo for " + element); //$NON-NLS-1$ } if (getModelInfoFor((IEditorInput) element) == null) { createModelInfo((IEditorInput) element); } ElementInfo info = super.createElementInfo(element); return info; } public void createModelInfo(IEditorInput input) { if (getModelInfoFor(input) == null) { IStructuredModel structuredModel = selfCreateModel(input); if (structuredModel != null) { createModelInfo(input, structuredModel, true); } } } /** * To be used when model is provided to us, ensures that when setInput is * used on this input, the given model will be used. */ public void createModelInfo(IEditorInput input, IStructuredModel structuredModel, boolean releaseModelOnDisconnect) { // we have to make sure factories are added, whether we created or // not. if (getModelInfoFor(input) != null || getModelInfoFor(structuredModel) != null) { if (debugOperations) { if (input instanceof IStorageEditorInput) { try { System.out.println("StorageModelProvider: DUPLICATE createModelInfo for " + ((IStorageEditorInput) input).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: DUPLICATE createModelInfo for " + input + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } } else { System.out.println("storageModelProvider: DUPLICATE createModelInfo for " + input); //$NON-NLS-1$ } } return; } if (debugOperations) { if (input instanceof IStorageEditorInput) { try { System.out.println("StorageModelProvider: createModelInfo for " + ((IStorageEditorInput) input).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: createModelInfo for " + input + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } } else { System.out.println("StorageModelProvider: createModelInfo for " + input); //$NON-NLS-1$ } } if (input instanceof IExtendedStorageEditorInput) { ((IExtendedStorageEditorInput) input).addElementStateListener(fInternalListener); } EditorModelUtil.addFactoriesTo(structuredModel); ModelInfo modelInfo = new ModelInfo(structuredModel, input, releaseModelOnDisconnect); fModelInfoMap.put(input, modelInfo); } protected void disposeElementInfo(Object element, ElementInfo info) { if (debugOperations) { if (element instanceof IStorageEditorInput) { try { System.out.println("StorageModelProvider: disposeElementInfo for " + ((IStorageEditorInput) element).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: disposeElementInfo for " + element + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } } else { System.out.println("StorageModelProvider: disposeElementInfo for " + element); //$NON-NLS-1$ } } if (element instanceof IEditorInput) { IEditorInput input = (IEditorInput) element; ModelInfo modelInfo = getModelInfoFor(input); disposeModelInfo(modelInfo); } super.disposeElementInfo(element, info); } /** * disconnect from this model info * * @param info */ public void disposeModelInfo(ModelInfo info) { if (debugOperations) { if (info.fElement instanceof IStorageEditorInput) { try { System.out.println("StorageModelProvider: disposeModelInfo for " + ((IStorageEditorInput) info.fElement).getStorage().getFullPath()); //$NON-NLS-1$ } catch (CoreException e) { System.out.println("StorageModelProvider: disposeModelInfo for " + info.fElement + "(exception caught)"); //$NON-NLS-1$ //$NON-NLS-2$ } } else { System.out.println("StorageModelProvider: disposeModelInfo for " + info.fElement); //$NON-NLS-1$ } } if (info.fElement instanceof IStorageEditorInput) { if (info.fElement instanceof IExtendedStorageEditorInput) { ((IExtendedStorageEditorInput) info.fElement).removeElementStateListener(fInternalListener); } if (info.fShouldReleaseOnInfoDispose) { info.fStructuredModel.releaseFromEdit(); } } fModelInfoMap.remove(info.fElement); } /* * (non-Javadoc) * * @see org.eclipse.ui.texteditor.AbstractDocumentProvider#doResetDocument(java.lang.Object, * org.eclipse.core.runtime.IProgressMonitor) */ protected void doResetDocument(Object element, IProgressMonitor monitor) throws CoreException { fReuseModelDocument = false; super.doResetDocument(element, monitor); fReuseModelDocument = true; } /** * @see org.eclipse.ui.texteditor.AbstractDocumentProvider#doSaveDocument(org.eclipse.core.runtime.IProgressMonitor, * java.lang.Object, org.eclipse.jface.text.IDocument, boolean) */ protected void doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException { IDocumentProvider provider = null; // BUG119211 - try to use registered document provider if possible if (element instanceof IEditorInput) { provider = DocumentProviderRegistry.getDefault().getDocumentProvider((IEditorInput) element); } if (provider == null) provider = new FileDocumentProvider(); provider.saveDocument(monitor, element, document, overwrite); } /* (non-Javadoc) * @see org.eclipse.ui.editors.text.StorageDocumentProvider#getPersistedEncoding(java.lang.Object) */ protected String getPersistedEncoding(Object element) { String charset = super.getPersistedEncoding(element); if (charset == null && element instanceof IStorageEditorInput) { IStorage storage; try { storage = ((IStorageEditorInput) element).getStorage(); if (storage != null && !(storage instanceof IEncodedStorage)) { InputStream contents = null; try { contents = storage.getContents(); if (contents != null) { QualifiedName[] detectionOptions = new QualifiedName[]{IContentDescription.BYTE_ORDER_MARK, IContentDescription.CHARSET}; IContentDescription description = Platform.getContentTypeManager().getDescriptionFor(contents, storage.getName(), detectionOptions); if (description != null) { charset = description.getCharset(); } } } catch (IOException e) { } finally { if (contents != null) try { contents.close(); } catch (IOException e) { // do nothing } } } } catch (CoreException e) { Logger.logException(e); } } return charset; } public IStructuredModel getModel(IEditorInput element) { IStructuredModel result = null; ModelInfo info = getModelInfoFor(element); if (info != null) { result = info.fStructuredModel; } return result; } /* * (non-Javadoc) * * @see org.eclipse.wst.sse.ui.IModelProvider#getModel(java.lang.Object) */ public IStructuredModel getModel(Object element) { if (element instanceof IEditorInput) return getModel((IEditorInput) element); return null; } private ModelInfo getModelInfoFor(IEditorInput element) { ModelInfo result = (ModelInfo) fModelInfoMap.get(element); return result; } private ModelInfo getModelInfoFor(IStructuredModel structuredModel) { ModelInfo result = null; if (structuredModel != null) { ModelInfo[] modelInfos = (ModelInfo[]) fModelInfoMap.values().toArray(new ModelInfo[0]); for (int i = 0; i < modelInfos.length; i++) { ModelInfo info = modelInfos[i]; if (structuredModel.equals(info.fStructuredModel)) { result = info; break; } } } return result; } /** * Method loadModel. * * @param input * @return IStructuredModel */ public IStructuredModel loadModel(IStorageEditorInput input) { return loadModel(input, false); } /** * Method loadModel. * * @param input * @param logExceptions * @return IStructuredModel */ public IStructuredModel loadModel(IStorageEditorInput input, boolean logExceptions) { String id = calculateID(input); if (id == null) { return null; } InputStream contents = null; try { contents = input.getStorage().getContents(); } catch (CoreException noStorageExc) { if (logExceptions) Logger.logException(NLS.bind(SSEUIMessages._32concat_EXC_, new Object[]{input.getName()}), noStorageExc); } IStructuredModel model = null; try { // first parameter must be unique model = StructuredModelManager.getModelManager().getModelForEdit(id, contents, null); model.setBaseLocation(calculateBaseLocation(input)); } catch (IOException e) { if (logExceptions) Logger.logException(NLS.bind(SSEUIMessages._32concat_EXC_, new Object[]{input}), e); } finally { if (contents != null) { try { contents.close(); } catch (IOException e) { // nothing } catch (Exception e) { Logger.logException(e); } } } return model; } /** * @param input * @return */ private IStructuredModel selfCreateModel(IEditorInput input) { return loadModel((IStorageEditorInput) input); } }