| /******************************************************************************* |
| * Copyright (c) 2001, 2008 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Jens Lukowski/Innoopract - initial renaming/restructuring |
| * |
| *******************************************************************************/ |
| package org.eclipse.wst.sse.core.internal.model; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocumentExtension3; |
| import org.eclipse.wst.sse.core.internal.Logger; |
| import org.eclipse.wst.sse.core.internal.document.IDocumentLoader; |
| 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.ltk.modelhandler.IModelHandler; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.BlockTagParser; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandler; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandlerExtension; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionParser; |
| import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionParserExtension; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelLoader; |
| 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.text.IStructuredDocument; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning; |
| import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument; |
| import org.eclipse.wst.sse.core.internal.text.rules.StructuredTextPartitioner; |
| import org.eclipse.wst.sse.core.internal.util.Assert; |
| |
| |
| /** |
| * This class reads a file and creates an Structured Model. |
| */ |
| public abstract class AbstractModelLoader implements IModelLoader { |
| protected static final int encodingNameSearchLimit = 1000; |
| |
| private static long computeMem() { |
| for (int i = 0; i < 5; i++) { |
| System.gc(); |
| System.runFinalization(); |
| } |
| return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); |
| } |
| |
| private boolean DEBUG = false; |
| protected IDocumentLoader documentLoaderInstance; |
| |
| /** |
| * AbstractLoader constructor also initializes encoding converter/mapper |
| */ |
| public AbstractModelLoader() { |
| super(); |
| } |
| |
| protected void addFactories(IStructuredModel model, List factoryList) { |
| Assert.isNotNull(model); |
| FactoryRegistry registry = model.getFactoryRegistry(); |
| Assert.isNotNull(registry, "IStructuredModel " + model.getId() + " has a null FactoryRegistry"); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (factoryList != null) { |
| Iterator iterator = factoryList.iterator(); |
| while (iterator.hasNext()) { |
| INodeAdapterFactory factory = (INodeAdapterFactory) iterator.next(); |
| registry.addFactory(factory); |
| } |
| } |
| } |
| |
| /** |
| * This method should perform all the model initialization required before |
| * it contains content, namely, it should call newModel, the |
| * createNewStructuredDocument(), then add those adapter factories which |
| * must be set before content is applied. This method should be called by |
| * "load" method. (this is tentative API) |
| */ |
| public IStructuredModel createModel() { |
| documentLoaderInstance = null; |
| IStructuredModel model = newModel(); |
| IEncodedDocument structuredDocument = getDocumentLoader().createNewStructuredDocument(); |
| if (structuredDocument instanceof IStructuredDocument) { |
| model.setStructuredDocument((IStructuredDocument) structuredDocument); |
| addFactories(model, getAdapterFactories()); |
| // |
| initEmbeddedTypePre(model, (IStructuredDocument) structuredDocument); |
| initEmbeddedTypePost(model); |
| // For types with propagating adapters, its important |
| // that the propagating adapter be in place before the contents |
| // are set. |
| preLoadAdapt(model); |
| } |
| return model; |
| } |
| |
| public IStructuredModel createModel(IStructuredDocument structuredDocument, String baseLocation, IModelHandler handler) { |
| documentLoaderInstance = null; |
| IStructuredModel model = newModel(); |
| model.setBaseLocation(baseLocation); |
| |
| // handler must be set early, incase a re-init is |
| // required during creation. |
| model.setModelHandler(handler); |
| |
| addFactories(model, getAdapterFactories()); |
| // For types with propagating adapters, it's important |
| // that the propagating adapter be in place before the contents |
| // are set. |
| preLoadAdapt(model); |
| initEmbeddedTypePre(model, structuredDocument); |
| |
| model.setStructuredDocument(structuredDocument); |
| // |
| initEmbeddedTypePost(model); |
| |
| return model; |
| } |
| |
| /** |
| * This method is used for cloning models. |
| */ |
| public IStructuredModel createModel(IStructuredModel oldModel) { |
| documentLoaderInstance = null; |
| IStructuredModel newModel = newModel(); |
| IStructuredDocument oldStructuredDocument = oldModel.getStructuredDocument(); |
| IStructuredDocument newStructuredDocument = oldStructuredDocument.newInstance(); |
| newModel.setStructuredDocument(newStructuredDocument); |
| // NOTE: we DO NOT simply add the standard ones to the new model |
| // addFactories(newModel, getAdapterFactories()); |
| // Now, we take the opportunity to add Factories from the oldModel's |
| // registry to the new model's registry .. if they do not already |
| // exist there. |
| duplicateFactoryRegistry(newModel, oldModel); |
| if (newModel instanceof AbstractStructuredModel) { |
| ((AbstractStructuredModel) newModel).setContentTypeIdentifier(oldModel.getContentTypeIdentifier()); |
| } |
| // addFactories(newModel, oldModel); |
| initEmbeddedType(oldModel, newModel); |
| // For types with propagating adapters, its important |
| // that the propagating adapter be in place before the contents |
| // are set. |
| preLoadAdapt(newModel); |
| return newModel; |
| } |
| |
| private void duplicateFactoryRegistry(IStructuredModel newModel, IStructuredModel oldModel) { |
| List oldAdapterFactories = oldModel.getFactoryRegistry().getFactories(); |
| List newAdapterFactories = new ArrayList(); |
| Iterator oldListIterator = oldAdapterFactories.iterator(); |
| while (oldListIterator.hasNext()) { |
| INodeAdapterFactory oldAdapterFactory = (INodeAdapterFactory) oldListIterator.next(); |
| // now "clone" the adapterfactory |
| newAdapterFactories.add(oldAdapterFactory.copy()); |
| } |
| // now that we have the "cloned" list, add to new model |
| addFactories(newModel, newAdapterFactories); |
| } |
| |
| /** |
| * This method must return those factories which must be attached to the |
| * structuredModel before content is applied. |
| */ |
| public List getAdapterFactories() { |
| // abstract method returns none |
| return new ArrayList(0); |
| } |
| |
| abstract public IDocumentLoader getDocumentLoader(); |
| |
| /** |
| * Method initEmbeddedType, "pre"-stage. Nothing to do here in super class. |
| * |
| * @param model |
| */ |
| protected void initEmbeddedTypePre(IStructuredModel model) { |
| } |
| |
| /** |
| * Method initEmbeddedType, "pre"-stage. By default simply calls the |
| * version of this method that uses only the structured model. |
| * |
| * @param model |
| * the model for which to initialize |
| * @param structuredDocument |
| * The structured document containing the text content for the |
| * model, which may be a different instance than what is in the |
| * model at this stage. |
| */ |
| protected void initEmbeddedTypePre(IStructuredModel model, IStructuredDocument structuredDocument) { |
| initEmbeddedTypePre(model); |
| } |
| |
| protected void initEmbeddedTypePost(IStructuredModel model) { |
| } |
| |
| /** |
| * Method initEmbeddedType. Nothing to do here in super class. |
| * |
| * @param oldModel |
| * @param newModel |
| */ |
| protected void initEmbeddedType(IStructuredModel oldModel, IStructuredModel newModel) { |
| } |
| |
| public void load(IFile file, IStructuredModel model) throws IOException, CoreException { |
| IEncodedDocument structuredDocument = model.getStructuredDocument(); |
| if (file == null) |
| structuredDocument = getDocumentLoader().createNewStructuredDocument(); |
| else |
| structuredDocument = getDocumentLoader().createNewStructuredDocument(file); |
| |
| // TODO: need to straighten out IEncodedDocument mess |
| if (structuredDocument instanceof IStructuredDocument) |
| transformInstance(model.getStructuredDocument(), (IStructuredDocument) structuredDocument); |
| else |
| model.getStructuredDocument().set(structuredDocument.get()); |
| |
| // original hack |
| // model.setStructuredDocument((IStructuredDocument) |
| // structuredDocument); |
| // ((IStructuredDocument) structuredDocument).fireNewDocument(this); |
| documentLoaderInstance = null; |
| // technicq of future |
| // model.setStructuredDocument((IStructuredDocument) |
| // structuredDocument); |
| // documentLoaderInstance = null; |
| } |
| |
| public void load(InputStream inputStream, IStructuredModel model, EncodingRule encodingRule) throws UnsupportedEncodingException, java.io.IOException { |
| // note we don't open the stream, so we don't close it |
| IEncodedDocument structuredDocument = model.getStructuredDocument(); |
| if (inputStream == null) { |
| structuredDocument = getDocumentLoader().createNewStructuredDocument(); |
| } |
| else { |
| // assume's model has been initialized already with base location |
| structuredDocument = getDocumentLoader().createNewStructuredDocument(model.getBaseLocation(), inputStream, encodingRule); |
| // TODO: model's not designed for this! |
| // we want to move to this "set" method, but the 'fire' was needed |
| // as |
| // a work around for strucutredModel not handling 'set' right, but |
| // that 'fireNewDocument' method was causing unbalance |
| // "aboutToChange" and "changed" |
| // events. |
| // model.setStructuredDocument((IStructuredDocument) |
| // structuredDocument); |
| // ((IStructuredDocument) |
| // structuredDocument).fireNewDocument(this); |
| model.getStructuredDocument().set(structuredDocument.get()); |
| |
| } |
| documentLoaderInstance = null; |
| |
| } |
| |
| /** |
| * deprecated -- use EncodingRule form |
| */ |
| synchronized public void load(InputStream inputStream, IStructuredModel model, String encodingName, String lineDelimiter) throws UnsupportedEncodingException, java.io.IOException { |
| // note we don't open the stream, so we don't close it |
| // TEMP work around to maintain previous function, |
| // until everyone can change to EncodingRule.FORCE_DEFAULT |
| if (encodingName != null && encodingName.trim().length() == 0) { |
| // redirect to new method |
| load(inputStream, model, EncodingRule.FORCE_DEFAULT); |
| } |
| else { |
| load(inputStream, model, EncodingRule.CONTENT_BASED); |
| } |
| } |
| |
| public void load(String filename, InputStream inputStream, IStructuredModel model, String junk, String dummy) throws UnsupportedEncodingException, java.io.IOException { |
| |
| long memoryUsed = 0; |
| if (DEBUG) { |
| memoryUsed = computeMem(); |
| System.out.println("measuring heap memory for " + filename); //$NON-NLS-1$ |
| // System.out.println("heap memory used before load: " + |
| // memoryUsed); |
| } |
| |
| // during an initial load, we expect the olddocument to be empty |
| // during re-load, however, it would be full. |
| IEncodedDocument newstructuredDocument = null; |
| IEncodedDocument oldStructuredDocument = model.getStructuredDocument(); |
| |
| // get new document |
| if (inputStream == null) { |
| newstructuredDocument = getDocumentLoader().createNewStructuredDocument(); |
| } |
| else { |
| newstructuredDocument = getDocumentLoader().createNewStructuredDocument(filename, inputStream); |
| } |
| if (DEBUG) { |
| long memoryAtEnd = computeMem(); |
| // System.out.println("heap memory used after loading new |
| // document: " + memoryAtEnd); |
| System.out.println(" heap memory implied used by document: " + (memoryAtEnd - memoryUsed)); //$NON-NLS-1$ |
| } |
| |
| |
| // TODO: need to straighten out IEncodedDocument mess |
| if (newstructuredDocument instanceof IStructuredDocument) { |
| transformInstance((IStructuredDocument) oldStructuredDocument, (IStructuredDocument) newstructuredDocument); |
| } |
| else { |
| // we don't really expect this case, just included for safety |
| oldStructuredDocument.set(newstructuredDocument.get()); |
| } |
| // original hack |
| // model.setStructuredDocument((IStructuredDocument) |
| // structuredDocument); |
| // ((IStructuredDocument) structuredDocument).fireNewDocument(this); |
| documentLoaderInstance = null; |
| // technicq of future |
| // model.setStructuredDocument((IStructuredDocument) |
| // structuredDocument); |
| // documentLoaderInstance = null; |
| if (DEBUG) { |
| long memoryAtEnd = computeMem(); |
| // System.out.println("heap memory used after setting to model: " |
| // + memoryAtEnd); |
| System.out.println(" heap memory implied used by document and model: " + (memoryAtEnd - memoryUsed)); //$NON-NLS-1$ |
| } |
| |
| } |
| |
| /** |
| * required by interface, being declared here abstractly just as another |
| * reminder. |
| */ |
| abstract public IStructuredModel newModel(); |
| |
| /** |
| * There's nothing to do here in abstract class for initializing adapters. |
| * Subclasses can and should override this method and provide proper |
| * intialization (For example, to get DOM document and 'getAdapter' on it, |
| * so that the first node/notifier has the adapter on it.) |
| */ |
| protected void preLoadAdapt(IStructuredModel structuredModel) { |
| |
| |
| } |
| |
| /** |
| * Normally, here in the abstact class, there's nothing to do, but we will |
| * reset text, since this MIGHT end up being called to recover from error |
| * conditions (e.g. IStructuredDocument exceptions) And, can be called by |
| * subclasses. |
| */ |
| public IStructuredModel reinitialize(IStructuredModel model) { |
| // Note: the "minimumization" routines |
| // of 'replaceText' allow many old nodes to pass through, when |
| // really its assumed they are created anew. |
| // so we need to use 'setText' (I think "setText' ends up |
| // throwing a 'newModel' event though, that may have some |
| // implications. |
| model.getStructuredDocument().setText(this, model.getStructuredDocument().get()); |
| return model; |
| } |
| |
| /** |
| * This method gets a fresh copy of the data, and repopulates the models |
| * ... by a call to setText on the structuredDocument. This method is |
| * needed in some cases where clients are sharing a model and then changes |
| * canceled. Say for example, one editor and several "displays" are |
| * sharing a model, if the editor is closed without saving changes, then |
| * the displays still need a model, but they should revert to the original |
| * unsaved version. |
| */ |
| synchronized public void reload(InputStream inputStream, IStructuredModel structuredModel) { |
| documentLoaderInstance = null; |
| try { |
| // temp solution ... we should be able to do better (more |
| // efficient) in future. |
| // Adapters will (probably) need to be sensitive to the fact that |
| // the document instance changed |
| // (by being life cycle listeners) |
| load(inputStream, structuredModel, EncodingRule.CONTENT_BASED); |
| |
| // // Note: we apparently read the data (and encoding) correctly |
| // // before, we just need to make sure we followed the same rule |
| // as |
| // // before. |
| // EncodingMemento previousMemento = |
| // structuredModel.getStructuredDocument().getEncodingMemento(); |
| // EncodingRule previousRule = previousMemento.getEncodingRule(); |
| // //IFile file = ResourceUtil.getFileFor(structuredModel); |
| // // Note: there's opportunity here for some odd behavior, if the |
| // // settings have changed from the first load to the reload. |
| // But, |
| // // hopefully, |
| // // will result in the intended behavior. |
| // Reader allTextReader = |
| // getDocumentLoader().readInputStream(inputStream, previousRule); |
| // |
| // // TODO: avoid use of String instance |
| // getDocumentLoader().reload(structuredModel.getStructuredDocument(), |
| // allTextReader); |
| // // and now "reset" encoding memento to keep it current with the |
| // // one |
| // // that was just determined. |
| // structuredModel.getStructuredDocument().setEncodingMemento(getDocumentLoader().getEncodingMemento()); |
| // structuredModel.setDirtyState(false); |
| // StructuredTextUndoManager undoMgr = |
| // structuredModel.getUndoManager(); |
| // if (undoMgr != null) { |
| // undoMgr.reset(); |
| // } |
| } |
| catch (UnsupportedEncodingException e) { |
| // couldn't happen. The program has apparently |
| // read the model once, and there'd be no reason the encoding |
| // could not be used again. |
| Logger.logException("Warning: XMLLoader::reload. This exception really should not have happened!! But will attemp to continue after dumping stack trace", e); //$NON-NLS-1$ |
| throw new Error("Program Error", e); //$NON-NLS-1$ |
| } |
| catch (IOException e) { |
| // couldn't happen. The program has apparently |
| // read the model once, and there'd be no (common) reason it |
| // couldn't be loaded again. |
| Logger.logException("Warning: XMLLoader::reload. This exception really should not have happened!! But will attemp to continue after dumping stack trace", e); //$NON-NLS-1$ |
| throw new Error("Program Error", e); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * this work is done better elsewhere, but done here for this version to |
| * reduce changes. especially since the need for it should go away once we |
| * no longer need to re-use old document instance. |
| */ |
| private void transformInstance(IStructuredDocument oldInstance, IStructuredDocument newInstance) { |
| /** |
| * https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4920 |
| * |
| * JSP taglib support broken, correct by duplicating extended setup |
| * information (BlockTagParser extension, |
| * StructuredDocumentRegionParser extensions) |
| */ |
| RegionParser oldParser = oldInstance.getParser(); |
| RegionParser newParser = newInstance.getParser().newInstance(); |
| // Register all of the old StructuredDocumentRegionHandlers on the new |
| // parser |
| if (oldParser instanceof StructuredDocumentRegionParserExtension && newParser instanceof StructuredDocumentRegionParserExtension) { |
| List oldHandlers = ((StructuredDocumentRegionParserExtension) oldParser).getStructuredDocumentRegionHandlers(); |
| for (int i = 0; i < oldHandlers.size(); i++) { |
| StructuredDocumentRegionHandler handler = ((StructuredDocumentRegionHandler) oldHandlers.get(i)); |
| if (handler instanceof StructuredDocumentRegionHandlerExtension) { |
| /** |
| * Skip the transferring here, the handler will do this |
| * after everything else but the source is transferred. |
| */ |
| } |
| else { |
| ((StructuredDocumentRegionParser) oldParser).removeStructuredDocumentRegionHandler(handler); |
| ((StructuredDocumentRegionParser) newParser).addStructuredDocumentRegionHandler(handler); |
| handler.resetNodes(); |
| } |
| } |
| } |
| // Add any global BlockMarkers to the new parser |
| if (oldParser instanceof BlockTagParser && newParser instanceof BlockTagParser) { |
| List oldBlockMarkers = ((BlockTagParser) oldParser).getBlockMarkers(); |
| for (int i = 0; i < oldBlockMarkers.size(); i++) { |
| BlockMarker blockMarker = ((BlockMarker) oldBlockMarkers.get(i)); |
| if (blockMarker.isGlobal()) { |
| ((BlockTagParser) newParser).addBlockMarker(blockMarker); |
| } |
| } |
| } |
| |
| ((BasicStructuredDocument) oldInstance).setParser(newParser); |
| |
| ((BasicStructuredDocument) oldInstance).setReParser(newInstance.getReParser().newInstance()); |
| |
| if (newInstance.getDocumentPartitioner() instanceof StructuredTextPartitioner) { |
| StructuredTextPartitioner partitioner = null; |
| if (oldInstance instanceof IDocumentExtension3 && newInstance instanceof IDocumentExtension3) { |
| partitioner = ((StructuredTextPartitioner) ((IDocumentExtension3) newInstance).getDocumentPartitioner(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING)); |
| if (partitioner != null) { |
| partitioner = (StructuredTextPartitioner) partitioner.newInstance(); |
| } |
| ((IDocumentExtension3) oldInstance).setDocumentPartitioner(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, partitioner); |
| } |
| if (partitioner == null) { |
| partitioner = (StructuredTextPartitioner) ((StructuredTextPartitioner) newInstance.getDocumentPartitioner()).newInstance(); |
| oldInstance.setDocumentPartitioner(partitioner); |
| } |
| if (partitioner != null) { |
| partitioner.connect(oldInstance); |
| } |
| } |
| |
| String existingLineDelimiter = null; |
| try { |
| existingLineDelimiter = newInstance.getLineDelimiter(0); |
| } |
| catch (BadLocationException e) { |
| // if empty file, assume platform default |
| // TODO: should be using user set preference, per content type? |
| existingLineDelimiter = System.getProperty("line.separator"); //$NON-NLS-1$ |
| } |
| |
| oldInstance.setLineDelimiter(existingLineDelimiter); //$NON-NLS-1$); |
| if (newInstance.getEncodingMemento() != null) { |
| oldInstance.setEncodingMemento((EncodingMemento) newInstance.getEncodingMemento().clone()); |
| } |
| |
| /** |
| * https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4920 |
| * |
| * JSP taglib support broken, correct by duplicating extended setup |
| * information (BlockTagParser extension, |
| * StructuredDocumentRegionParser extensions) |
| */ |
| if (oldParser instanceof StructuredDocumentRegionParserExtension && newParser instanceof StructuredDocumentRegionParserExtension) { |
| List oldHandlers = ((StructuredDocumentRegionParserExtension) oldParser).getStructuredDocumentRegionHandlers(); |
| for (int i = 0; i < oldHandlers.size(); i++) { |
| StructuredDocumentRegionHandler handler = ((StructuredDocumentRegionHandler) oldHandlers.get(i)); |
| if (handler instanceof StructuredDocumentRegionHandlerExtension) { |
| StructuredDocumentRegionHandlerExtension handlerExtension = (StructuredDocumentRegionHandlerExtension) handler; |
| handlerExtension.setStructuredDocument(oldInstance); |
| } |
| } |
| } |
| String holdString = newInstance.get(); |
| newInstance = null; |
| oldInstance.set(holdString); |
| } |
| } |