Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'core/bundles/org.eclipse.wst.sse.core/src/org/eclipse')
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/StructuredModelManager.java82
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/indexing/AbstractIndexManager.java2014
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/FileBufferModelManager.java969
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/IExecutionDelegate.java27
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ILockable.java30
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/JSPAwareAdapterFactory.java22
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/Logger.java219
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ModelManagerPluginRegistryReader.java198
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NoCancelProgressMonitor.java35
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NotImplementedException.java37
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NullMemento.java37
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapter.java38
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapterFactory.java29
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECoreMessages.java50
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePlugin.java107
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePluginResources.properties32
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/AbstractStructuredCleanupProcessor.java498
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupHandler.java23
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupPreferences.java56
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupProcessor.java114
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredCleanupPreferences.java135
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandler.java20
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandlerImpl.java43
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/AbstractDocumentLoader.java438
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/DocumentReader.java131
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentCharsetDetector.java22
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentLoader.java82
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/StructuredDocumentFactory.java66
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/TextUtilities.java73
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/filebuffers/BasicStructuredDocumentFactory.java91
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/AbstractStructuredFormatProcessor.java524
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IFormattingDelegate.java29
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatContraints.java39
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatPreferences.java33
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatProcessor.java70
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatter.java36
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatContraints.java53
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatPreferences.java43
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/AbstractModelHandler.java72
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/EmbeddedTypeHandler.java80
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IDocumentTypeHandler.java42
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IModelHandler.java47
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockMarker.java103
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTagParser.java32
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTokenizer.java58
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/IBlockedStructuredDocumentRegion.java41
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/JSPCapableParser.java28
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/RegionParser.java52
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandler.java28
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandlerExtension.java20
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParser.java24
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParserExtension.java28
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/TagMarker.java80
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractModelLoader.java545
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractStructuredModel.java1517
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/FactoryRegistry.java179
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/LifecycleNotificationManager.java119
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelLifecycleEvent.java117
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelManagerImpl.java2187
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelResourceFactory.java43
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/PrefUtil.java146
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistry.java35
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryImpl.java132
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryReader.java71
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistry.java311
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistryReader.java106
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerUtility.java50
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/PluginContributedFactoryReader.java126
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ContextRegion.java197
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ForeignRegion.java81
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/CommonModelPreferenceNames.java76
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/PreferenceInitializer.java32
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/propertytester/StructuredFilePropertyTester.java85
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractAdapterFactory.java158
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractNotifier.java240
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/DocumentChanged.java61
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLifecycleListener.java27
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLoader.java87
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelManager.java564
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelStateListener.java64
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapter.java43
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapterFactory.java76
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeNotifier.java103
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IStructuredModel.java419
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IndexedRegion.java74
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/StructuredModelManager.java73
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IEncodedDocument.java58
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IStructuredDocumentProposed.java223
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/AboutToBeChangedEvent.java44
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IModelAboutToBeChangedListener.java26
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IStructuredDocumentListener.java30
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentContentEvent.java40
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentEvent.java68
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NoChangeEvent.java73
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionChangedEvent.java80
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionsReplacedEvent.java85
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentEvent.java146
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentRegionsReplacedEvent.java84
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceAlreadyExists.java44
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceInUse.java43
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelManagerProposed.java338
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelStateListenerProposed.java36
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/INodeAdapterFactoryManager.java33
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelEvent.java17
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelProposed.java152
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocument.java218
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegion.java163
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegionList.java52
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitionTypes.java24
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitioning.java22
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextPartitioner.java86
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextReParser.java72
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegion.java158
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionCollection.java178
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionContainer.java40
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionList.java131
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocument.java2979
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocumentRegion.java622
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CharSequenceReader.java127
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CoreNodeList.java130
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/DeleteEqualPositionUpdater.java66
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/GenericPositionManager.java409
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IExecutionDelegatable.java20
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IRegionComparible.java20
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/JobSafeStructuredDocument.java249
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/MinimalDocument.java445
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/ReadOnlyPosition.java37
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentReParser.java1700
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionEnumeration.java87
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionIterator.java115
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentSequentialRewriteTextStore.java132
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentTextStore.java191
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/SubSetTextStore.java127
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/TextRegionListImpl.java236
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredRegion.java27
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredTypedRegion.java24
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredRegion.java91
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredTypedRegion.java88
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/StructuredTextPartitioner.java655
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/CommandCursorPosition.java66
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IDocumentSelectionMediator.java35
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IStructuredTextUndoManager.java155
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommand.java34
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommandImpl.java137
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCompoundCommandImpl.java260
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextUndoManager.java650
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/UndoDocumentEvent.java45
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java205
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Assert.java164
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Debug.java208
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/DocumentInputStream.java108
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/JarUtilities.java394
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/PathHelper.java152
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ProjectResolver.java259
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ResourceUtil.java79
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ScriptLanguageKeys.java54
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Sorter.java79
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/TextUtilities.java63
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolver.java81
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolverExtension.java25
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Utilities.java140
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ErrorInfo.java26
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationAdapter.java31
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationMessage.java70
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationReporter.java28
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidatorGroupListener.java113
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/text/IStructuredPartitions.java24
-rw-r--r--core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/utils/StringUtils.java751
168 files changed, 31201 insertions, 0 deletions
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/StructuredModelManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/StructuredModelManager.java
new file mode 100644
index 0000000000..ecb0ece8c9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/StructuredModelManager.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.eclipse.wst.sse.core.internal.model.ModelManagerImpl;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.osgi.framework.Bundle;
+
+/**
+ * Class to allow access to properly configured implementors of IModelManager.
+ *
+ * @since 1.5 org.eclipse.wst.sse.core
+ */
+final public class StructuredModelManager {
+ /**
+ * Do not allow instances to be created.
+ */
+ private StructuredModelManager() {
+ super();
+ }
+
+ /**
+ * Provides access to the instance of IModelManager. Returns null if model
+ * manager can not be created or is not valid (such as, when workbench is
+ * shutting down).
+ *
+ * @return IModelManager - returns the one model manager for structured
+ * models or null if the owning bundle is neither active nor
+ * starting.
+ */
+ public static IModelManager getModelManager() {
+ boolean isReady = false;
+ IModelManager modelManager = null;
+ boolean interrupted = false;
+ try {
+ while (!isReady) {
+ Bundle localBundle = Platform.getBundle(SSECorePlugin.ID);
+ int state = localBundle.getState();
+ if (state == Bundle.ACTIVE) {
+ isReady = true;
+ // getInstance is a synchronized static method.
+ modelManager = ModelManagerImpl.getInstance();
+ }
+ else if (state == Bundle.STARTING) {
+ try {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e) {
+ // ignore, just loop again
+ interrupted = true;
+ }
+ }
+ else if (state == Bundle.STOPPING || state == Bundle.UNINSTALLED) {
+ isReady = true;
+ modelManager = null;
+ }
+ else {
+ // not sure about other states, 'resolved', 'installed'
+ isReady = true;
+ modelManager = null;
+ }
+ }
+ }
+ finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return modelManager;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/indexing/AbstractIndexManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/indexing/AbstractIndexManager.java
new file mode 100644
index 0000000000..f247be294d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/indexing/AbstractIndexManager.java
@@ -0,0 +1,2014 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2017 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.indexing;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IResourceProxy;
+import org.eclipse.core.resources.IResourceProxyVisitor;
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.SSECoreMessages;
+
+/**
+ * <p>
+ * A generic class for implementing a resource index manager. It is important
+ * to note that this only provides the framework for managing an index, not
+ * actually indexing. The subtle difference is that the manager is in charge
+ * of paying attention to all of the resource actions that take place in the
+ * workspace and filtering those actions down to simple actions that need to
+ * be performed on whatever index this manager is managing.
+ * </p>
+ *
+ * <p>
+ * The manager does its very best to make sure the index is always consistent,
+ * even if resource events take place when the manager is not running. In the
+ * event that the manager determines it has missed, lost, or corrupted any
+ * resource change events that have occurred before, during, or after its
+ * activation or deactivation then the manager will inspect the entire
+ * workspace to insure the index it is managing is consistent.
+ * </p>
+ *
+ */
+public abstract class AbstractIndexManager {
+
+ /** Used to encode path bytes in case they contain double byte characters */
+ private static final String ENCODING_UTF16 = "utf16"; //$NON-NLS-1$
+
+ /** Default time to wait for other tasks to finish */
+ private static final int WAIT_TIME = 300;
+
+ /**
+ * <p>
+ * Used to report progress on jobs where the total work to complete is
+ * unknown. The created effect is a progress bar that moves but that will
+ * never complete.
+ * </p>
+ */
+ private static final int UNKNOWN_WORK = 100;
+
+ /**
+ * The amount of events to batch up before sending them off to the
+ * processing job
+ */
+ private static final int BATCH_UP_AMOUNT = 100;
+
+ /** If this file exists then a full workspace re-processing is needed */
+ private static final String RE_PROCESS_FILE_NAME = ".re-process"; //$NON-NLS-1$
+
+ /** Common error message to log */
+ private static final String LOG_ERROR_INDEX_INVALID = "Index may become invalid, incomplete, or enter some other inconsistent state."; //$NON-NLS-1$
+
+ /** State: manager is stopped */
+ private static final byte STATE_DISABLED = 0;
+
+ /** State: manager is running */
+ private static final byte STATE_ENABLED = 1;
+
+ /** Action: add to index */
+ protected static final byte ACTION_ADD = 0;
+
+ /** Action: remove from index */
+ protected static final byte ACTION_REMOVE = 1;
+
+ /** Action: add to index caused by move operation */
+ protected static final byte ACTION_ADD_MOVE_FROM = 2;
+
+ /** Action: remove from index caused by move operation */
+ protected static final byte ACTION_REMOVE_MOVE_TO = 3;
+
+ /** Source: action originated from resource change event */
+ protected static final byte SOURCE_RESOURCE_CHANGE = 0;
+
+ /** Source: action originated from workspace scan */
+ protected static final byte SOURCE_WORKSPACE_SCAN = 1;
+
+ /** Source: action originated from saved state */
+ protected static final byte SOURCE_SAVED_STATE = 2;
+
+ /** Source: preserved resources to index */
+ protected static final byte SOURCE_PRESERVED_RESOURCES_TO_INDEX = 3;
+
+ /** the name of this index manager */
+ private String fName;
+
+ /** {@link IResourceChangeListener} to listen for file changes */
+ private ResourceChangeListener fResourceChangeListener;
+
+ /** The {@link Job} that does all of the indexing */
+ private ResourceEventProcessingJob fResourceEventProcessingJob;
+
+ /** A {@link Job} to search the workspace for all files */
+ private Job fWorkspaceVisitorJob;
+
+ /**
+ * <p>
+ * Current state of the manager
+ * </p>
+ *
+ * @see #STATE_DISABLED
+ * @see #STATE_ENABLED
+ */
+ private volatile byte fState;
+
+ /** used to prevent manager from starting and stopping at the same time */
+ private Object fStartStopLock = new Object();
+
+ /**
+ * <code>true</code> if the manager is currently starting,
+ * <code>false</code> otherwise
+ */
+ private boolean fStarting;
+
+ /**
+ * <p>
+ * Creates the manager with a given name.
+ * </p>
+ *
+ * @param name
+ * This will be pre-pended to progress reporting messages and
+ * thus should be translated
+ */
+ protected AbstractIndexManager(String name) {
+ this.fName = name;
+ this.fState = STATE_DISABLED;
+ this.fResourceChangeListener = new ResourceChangeListener();
+ this.fResourceEventProcessingJob = new ResourceEventProcessingJob();
+ this.fStarting = false;
+ }
+
+ /**
+ * <p>
+ * Creates the manager with a given name.
+ * </p>
+ *
+ * @param name
+ * This will be pre-pended to progress reporting messages and
+ * thus should be translated
+ *
+ * @param messageRunning
+ * ignored
+ * @param messageInitializing
+ * ignored
+ * @param messageProcessingFiles
+ * ignored
+ *
+ * @deprecated This constructor ignores the last three parameters.
+ * @see #AbstractIndexManager(String)
+ */
+
+ protected AbstractIndexManager(String name, String messageRunning, String messageInitializing, String messageProcessingFiles) {
+ this(name);
+ }
+
+ /**
+ * <p>
+ * Starts up the {@link AbstractIndexManager}. If a {@link IResourceDelta}
+ * is provided then it is assumed that all other files in the workspace
+ * have already been index and thus only those in the provided
+ * {@link IResourceDelta} will be processed. Else if the provided
+ * {@link IResourceDelta} is <code>null</code> it is assumed no files have
+ * been indexed yet so the entire workspace will be searched for files to
+ * be indexed.
+ * </p>
+ *
+ * <p>
+ * If {@link IResourceDelta} is provided this will block until that delta
+ * has finished processing. If no {@link IResourceDelta} provided then a
+ * separate job will be created to process the entire workspace and this
+ * method will return without waiting for that job to complete
+ * </p>
+ *
+ * <p>
+ * Will block until {@link #stop()} has finished running if it is
+ * currently running
+ * </p>
+ *
+ * @param savedStateDelta
+ * the delta from a saved state, if <code>null</code> then the
+ * entire workspace will be searched for files to index, else
+ * only files in this {@link IResourceDelta} will be indexed
+ * @param monitor
+ * This action can not be canceled but this monitor will be
+ * used to report progress
+ */
+ public final void start(IResourceDelta savedStateDelta, IProgressMonitor monitor) {
+ SubMonitor progress = SubMonitor.convert(monitor);
+ synchronized (this.fStartStopLock) {
+ this.fStarting = true;
+
+ try {
+ if (this.fState == STATE_DISABLED) {
+ // report status
+ progress.beginTask(NLS.bind(SSECoreMessages.IndexManager_0_starting, this.fName), 2);
+
+ // start listening for resource change events
+ this.fResourceChangeListener.start();
+
+ // check to see if a full re-index is required
+ boolean forcedFullReIndexNeeded = isForcedFullReIndexNeeded();
+
+ /*
+ * start the indexing job only loading preserved state, if
+ * not doing full index. if failed loading preserved state,
+ * then force full re-index
+ */
+ forcedFullReIndexNeeded = !this.fResourceEventProcessingJob.start(!forcedFullReIndexNeeded, progress.newChild(1));
+ progress.setWorkRemaining(1);
+
+ // don't bother processing saved delta if forced full
+ // re-index is needed
+ if (!forcedFullReIndexNeeded) {
+ /*
+ * if there is a delta attempt to process it else need
+ * to do a full workspace index
+ */
+ if (savedStateDelta != null) {
+ forcedFullReIndexNeeded = false;
+ try {
+ // deal with reporting progress
+ SubMonitor savedStateProgress = progress.newChild(1, SubMonitor.SUPPRESS_NONE);
+
+ savedStateProgress.setTaskName(NLS.bind(SSECoreMessages.IndexManager_0_starting_1, new String[]{this.fName, SSECoreMessages.IndexManager_processing_deferred_resource_changes}));
+
+ // process delta
+ ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(savedStateProgress, AbstractIndexManager.SOURCE_SAVED_STATE);
+ savedStateDelta.accept(visitor);
+
+ // process any remaining batched up resources
+ // to index
+ visitor.processBatchedResourceEvents();
+ }
+ catch (CoreException e) {
+ forcedFullReIndexNeeded = true;
+ Logger.logException(this.fName + ": Could not process saved state. " + //$NON-NLS-1$
+ "Forced to do a full workspace re-index.", e); //$NON-NLS-1$
+ }
+ }
+ else {
+ forcedFullReIndexNeeded = true;
+ }
+ }
+ progress.worked(1);
+
+ // if need to process the entire workspace do so in
+ // another job
+ if (forcedFullReIndexNeeded) {
+ this.fWorkspaceVisitorJob = new WorkspaceVisitorJob();
+ this.fWorkspaceVisitorJob.schedule();
+ }
+
+ // update state
+ this.fState = STATE_ENABLED;
+ }
+ }
+ finally {
+ this.fStarting = false;
+ progress.done();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Safely shuts down the manager.
+ * </p>
+ *
+ * <p>
+ * This will block until the
+ * {@link #start(IResourceDelta, IProgressMonitor)} has finished (if
+ * running). Also until the current resource event has finished being
+ * processed. Finally it will block until the events still to be processed
+ * by the processing job have been preserved to be processed on the next
+ * call to {@link #start(IResourceDelta, IProgressMonitor)}.
+ * </p>
+ *
+ * <p>
+ * If at any point during this shut down processes something goes wrong
+ * the manager will be sure that on the next call to
+ * {@link #start(IResourceDelta, IProgressMonitor)} the entire workspace
+ * will be re-processed.
+ * </p>
+ *
+ * @throws InterruptedException
+ */
+ public final void stop() throws InterruptedException {
+ synchronized (this.fStartStopLock) {
+ if (this.fState != STATE_DISABLED) {
+
+ // stop listening for events, and wait for the current event
+ // to finish
+ this.fResourceChangeListener.stop();
+
+ // if currently visiting entire workspace, give up and try
+ // again next load
+ boolean forceFullReIndexNextStart = false;
+ if (this.fWorkspaceVisitorJob != null) {
+ if (this.fWorkspaceVisitorJob.getState() != Job.NONE) {
+ this.fWorkspaceVisitorJob.cancel();
+
+ this.forceFullReIndexNextStart();
+ forceFullReIndexNextStart = true;
+ }
+ }
+
+ // stop the indexing job, only preserve if not already forcing
+ // a re-index
+ forceFullReIndexNextStart = !this.fResourceEventProcessingJob.stop(!forceFullReIndexNextStart);
+
+ // if preserving failed, then force re-index
+ if (forceFullReIndexNextStart) {
+ this.forceFullReIndexNextStart();
+ }
+
+ doStop();
+
+ // update status
+ this.fState = STATE_DISABLED;
+ }
+ }
+ }
+
+ protected void doStop() {
+ // any stop logic
+ }
+
+ /**
+ * @return the name of this indexer
+ */
+ protected String getName() {
+ return this.fName;
+ }
+
+ /**
+ * <p>
+ * Should be called by a client of the index this manager manages before
+ * the index is accessed, assuming the client wants an index consistent
+ * with the latest resource changes.
+ * </p>
+ *
+ * <p>
+ * The supplied monitor will be used to supply user readable progress as
+ * the manager insures the index has been given all the latest resource
+ * events. This monitor may be canceled, but if it is the state of the
+ * index is not guaranteed to be consistent with the latest resource
+ * change events.
+ * </p>
+ *
+ * @param monitor
+ * Used to report user readable progress as the manager insures
+ * the index is consistent with the latest resource events.
+ * This monitor can be canceled to stop waiting for consistency
+ * but then no guaranty is made about the consistency of the
+ * index in relation to unprocessed resource changes
+ *
+ * @return <code>true</code> if the wait finished successfully and the
+ * manager is consistent, <code>false</code> otherwise, either an
+ * error occurred while waiting for the manager or the monitor was
+ * canceled
+ *
+ * @throws InterruptedException
+ * This can happen when waiting for other jobs
+ */
+ public final boolean waitForConsistent(IProgressMonitor monitor) {
+ boolean success = true;
+ boolean interupted = false;
+ SubMonitor progress = SubMonitor.convert(monitor);
+
+ // set up the progress of waiting
+ int remainingWork = 4;
+ progress.beginTask(NLS.bind(SSECoreMessages.IndexManager_Waiting_for_0, this.fName), remainingWork);
+
+ // wait for start up
+ if (this.fStarting && !monitor.isCanceled()) {
+ SubMonitor startingProgress = progress.newChild(1);
+ startingProgress.subTask(NLS.bind(SSECoreMessages.IndexManager_0_starting, this.fName));
+ while (this.fStarting && !monitor.isCanceled()) {
+ // this creates a never ending progress that still moves
+ // forward
+ startingProgress.setWorkRemaining(UNKNOWN_WORK);
+ startingProgress.newChild(1).worked(1);
+ try {
+ Thread.sleep(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ interupted = true;
+ }
+ }
+ }
+ progress.setWorkRemaining(--remainingWork);
+
+ // wait for workspace visiting job
+ if (this.fWorkspaceVisitorJob != null && this.fWorkspaceVisitorJob.getState() != Job.NONE && !monitor.isCanceled()) {
+ SubMonitor workspaceVisitorProgress = progress.newChild(1);
+ workspaceVisitorProgress.subTask(SSECoreMessages.IndexManager_Processing_entire_workspace_for_the_first_time);
+ while (this.fWorkspaceVisitorJob.getState() != Job.NONE && !monitor.isCanceled()) {
+ // this creates a never ending progress that still moves
+ // forward
+ workspaceVisitorProgress.setWorkRemaining(UNKNOWN_WORK);
+ workspaceVisitorProgress.newChild(1).worked(1);
+ try {
+ Thread.sleep(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ interupted = true;
+ }
+ }
+ }
+ progress.setWorkRemaining(--remainingWork);
+
+ // wait for the current resource event
+ if (this.fResourceChangeListener.isProcessingEvents() && !monitor.isCanceled()) {
+ SubMonitor workspaceVisitorProgress = progress.newChild(1);
+ workspaceVisitorProgress.subTask(SSECoreMessages.IndexManager_processing_recent_resource_changes);
+ while (this.fResourceChangeListener.isProcessingEvents() && !monitor.isCanceled()) {
+ workspaceVisitorProgress.setWorkRemaining(UNKNOWN_WORK);
+ workspaceVisitorProgress.newChild(1).worked(1);
+ try {
+ this.fResourceChangeListener.waitForCurrentEvent(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ interupted = true;
+ }
+ }
+ }
+ progress.setWorkRemaining(--remainingWork);
+
+ // wait for all files to be indexed
+ if (this.fResourceEventProcessingJob.getNumResourceEventsToProcess() != 0 && !monitor.isCanceled()) {
+ SubMonitor indexingProgress = progress.newChild(1);
+ int prevNumResources;
+ int numResources = this.fResourceEventProcessingJob.getNumResourceEventsToProcess();
+ while (numResources != 0 && !monitor.isCanceled()) {
+ // update the progress indicator
+ indexingProgress.subTask(NLS.bind(SSECoreMessages.IndexManager_0_Indexing_1_Files, new Object[]{AbstractIndexManager.this.fName, "" + numResources})); //$NON-NLS-1$
+ indexingProgress.setWorkRemaining(numResources);
+ prevNumResources = numResources;
+ numResources = this.fResourceEventProcessingJob.getNumResourceEventsToProcess();
+ int numProcessed = prevNumResources - numResources;
+ indexingProgress.worked(numProcessed > 0 ? numProcessed : 0);
+
+ // give the index some time to do some indexing
+ try {
+ this.fResourceEventProcessingJob.waitForConsistant(WAIT_TIME);
+ }
+ catch (InterruptedException e) {
+ interupted = true;
+ }
+ }
+ }
+ progress.setWorkRemaining(--remainingWork);
+
+ if (monitor.isCanceled()) {
+ success = false;
+ }
+
+ // reset the interrupted flag if we were interrupted
+ if (interupted) {
+ Thread.currentThread().interrupt();
+ }
+
+ progress.done();
+ return success;
+ }
+
+ /**
+ * <p>
+ * Called for each {@link IResource} given in a resource delta. If the
+ * resource type is a file then used to determine if that file should be
+ * processed by the manager, if the resource type is a project or
+ * directory then it is used to determine if the children of the project
+ * or directory should be processed looking for file resources.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE:</b> Even if <code>true</code> is returned for a directory
+ * resource that only means the children of the directory should be
+ * inspected for possible files to index. Directories themselves can not
+ * be managed in order to add to an index.
+ * </p>
+ *
+ * @param type
+ * the {@link IResource#getType()} result of the resource to
+ * possibly index
+ * @param path
+ * the full {@link IPath} to the resource to possibly index
+ * @return <code>true</code> If the resource with the given
+ * <code>type</code> and <code>path</code> should either itself be
+ * indexed, or its children should be indexed, <code>false</code>
+ * if neither the described resource or any children should be
+ * indexed
+ */
+ protected abstract boolean isResourceToIndex(int type, IPath path);
+
+ /**
+ * <p>
+ * Called for each {@link ResourceEvent} gathered by the various sources
+ * and processed by the {@link ResourceEventProcessingJob}. The
+ * implementation of this method should use the given information to
+ * update the index this manager is managing.
+ * </p>
+ *
+ * @param source
+ * The source that reported this resource event
+ * @param action
+ * The action to be taken on the given <code>resource</code>
+ * @param resource
+ * The index should perform the given <code>action</code> on
+ * this resource
+ * @param movePath
+ * If the given <code>action</code> is
+ * {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} or
+ * {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then this
+ * field will not be null and reports the path the given
+ * <code>resource</code> was either moved from or moved to
+ * respectively.
+ *
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ * @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN
+ * @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX
+ *
+ * @see AbstractIndexManager#ACTION_ADD
+ * @see AbstractIndexManager#ACTION_REMOVE
+ * @see AbstractIndexManager#ACTION_ADD_MOVE_FROM
+ * @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO
+ */
+ protected abstract void performAction(byte source, byte action, IResource resource, IPath movePath);
+
+ /**
+ * <p>
+ * Gets the working location of the manager. This is where any relevant
+ * state can be persisted.
+ * </p>
+ *
+ * @return the working location of the manager
+ */
+ protected abstract IPath getWorkingLocation();
+
+ /**
+ * <p>
+ * Next time the manager starts up force a full workspace index
+ * </p>
+ */
+ private void forceFullReIndexNextStart() {
+ IPath reIndexPath = AbstractIndexManager.this.getWorkingLocation().append(RE_PROCESS_FILE_NAME);
+ File file = new File(reIndexPath.toOSString());
+ try {
+ file.createNewFile();
+ }
+ catch (IOException e) {
+ Logger.logException(this.fName + ": Could not create file to tell manager to" + " do a full re-index on next load. " + AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /**
+ * @return <code>true</code> if a full workspace index is needed as
+ * dictated by a previous call to
+ * {@link #forceFullReIndexNextStart()}, <code>false</code>
+ * otherwise
+ */
+ private boolean _isForcedFullReIndexNeeded() {
+ boolean forcedFullReIndexNeeded = false;
+
+ IPath reIndexPath = AbstractIndexManager.this.getWorkingLocation().append(RE_PROCESS_FILE_NAME);
+ File file = new File(reIndexPath.toOSString());
+ if (file.exists()) {
+ forcedFullReIndexNeeded = true;
+ file.delete();
+ }
+
+ return forcedFullReIndexNeeded;
+ }
+
+ /**
+ * @return <code>true</code> if a full workspace index is needed as
+ * dictated by this indexer, <code>false</code>
+ * otherwise
+ * @since 1.2.1001
+ */
+ protected boolean isForcedFullReIndexNeeded() {
+ return _isForcedFullReIndexNeeded();
+ }
+ /**
+ * <p>
+ * A system {@link Job} used to visit all of the files in the workspace
+ * looking for files to index.
+ * </p>
+ *
+ * <p>
+ * This should only have to be done once per workspace on the first load,
+ * but if it fails or a SavedState can not be retrieved on a subsequent
+ * workspace load then this will have to be done again.
+ * </p>
+ */
+ private class WorkspaceVisitorJob extends Job {
+ /**
+ * <p>
+ * Default constructor that sets up this job as a system job
+ * </p>
+ */
+ protected WorkspaceVisitorJob() {
+ super(NLS.bind(SSECoreMessages.IndexManager_0_Processing_entire_workspace_for_the_first_time, AbstractIndexManager.this.fName));
+
+ this.setUser(false);
+ this.setSystem(true);
+ this.setPriority(Job.LONG);
+ }
+
+ /**
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ // update status
+ monitor.beginTask(NLS.bind(SSECoreMessages.IndexManager_0_Processing_entire_workspace_for_the_first_time, AbstractIndexManager.this.fName), IProgressMonitor.UNKNOWN);
+
+ // visit the workspace
+ WorkspaceVisitor visitor = new WorkspaceVisitor(monitor);
+ ResourcesPlugin.getWorkspace().getRoot().accept(visitor, IResource.NONE);
+
+ // process any remaining batched up resources to index
+ visitor.processBatchedResourceEvents();
+ }
+ catch (CoreException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Failed visiting entire workspace for initial index. " + AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); //$NON-NLS-1$
+ }
+
+ IStatus status;
+ if (monitor.isCanceled()) {
+ status = Status.CANCEL_STATUS;
+ }
+ else {
+ status = Status.OK_STATUS;
+ }
+
+ monitor.done();
+ return status;
+ }
+
+ /**
+ * <p>
+ * An {@link IResourceProxyVisitor} used to visit all of the files in
+ * the workspace looking for files to add to the index.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE: </b>After this visitor is used
+ * {@link WorkspaceVisitor#processBatchedResourceEvents() must be
+ * called to flush out the last of the {@link ResourceEvent}s produced
+ * by this visitor.
+ * </p>
+ */
+ private class WorkspaceVisitor implements IResourceProxyVisitor {
+ /**
+ * {@link IProgressMonitor} used to report status and check for
+ * cancellation
+ */
+ private SubMonitor fProgress;
+
+ /**
+ * {@link Map}&lt{@link IResource}, {@link ResourceEvent}&gt
+ * <p>
+ * Map of resources events created and batched up by this visitor.
+ * These events are periodical be sent off to the
+ * {@link ResourceEventProcessingJob} but need to be sent off one
+ * final time after this visitor finishes it work.
+ * </p>
+ *
+ * @see #processBatchedResourceEvents()
+ */
+ private Map fBatchedResourceEvents;
+
+ /**
+ * <p>
+ * Default constructor
+ * </p>
+ *
+ * @param monitor
+ * used to report status and allow this visitor to be
+ * canceled
+ */
+ protected WorkspaceVisitor(IProgressMonitor monitor) {
+ this.fProgress = SubMonitor.convert(monitor);
+ this.fBatchedResourceEvents = new LinkedHashMap(BATCH_UP_AMOUNT);
+ }
+
+ /**
+ * <p>
+ * As long as the monitor is not canceled visit each file in the
+ * workspace that should be visited.
+ * </p>
+ *
+ * @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy)
+ * @see AbstractIndexManager#shouldVisit(String)
+ */
+ public boolean visit(IResourceProxy proxy) throws CoreException {
+ this.fProgress.subTask(proxy.getName());
+
+ boolean visitChildren = false;
+
+ /*
+ * if not canceled or a hidden resource then process file else
+ * don't visit children
+ */
+ if (!this.fProgress.isCanceled()) {
+ if (proxy.isDerived()) {
+ /*
+ * Do not include derived resources
+ */
+ visitChildren = false;
+ }
+ else if (proxy.requestFullPath().isRoot()) {
+ visitChildren = true;
+ }
+ else if (isResourceToIndex(proxy.getType(), proxy.requestFullPath())) {
+ if (proxy.getType() == IResource.FILE) {
+ // add the file to be indexed
+ IFile file = (IFile) proxy.requestResource();
+ if (file.exists()) {
+ this.fBatchedResourceEvents.put(file, new ResourceEvent(AbstractIndexManager.SOURCE_WORKSPACE_SCAN, AbstractIndexManager.ACTION_ADD, null));
+ }
+ }
+ visitChildren = true;
+ }
+ }
+
+ // batch up resource changes before sending them out
+ if (this.fBatchedResourceEvents.size() >= BATCH_UP_AMOUNT) {
+ this.processBatchedResourceEvents();
+ }
+
+ return visitChildren;
+ }
+
+ /**
+ * <p>
+ * Sends any batched up resource events created by this visitor to
+ * the {@link ResourceEventProcessingJob}.
+ * <p>
+ *
+ * <p>
+ * <b>NOTE:</b> This will be called every so often as the visitor
+ * is visiting resources but needs to be called a final time by
+ * the user of this visitor to be sure the final events are sent
+ * off
+ * </p>
+ */
+ protected void processBatchedResourceEvents() {
+ AbstractIndexManager.this.fResourceEventProcessingJob.addResourceEvents(this.fBatchedResourceEvents);
+ this.fBatchedResourceEvents.clear();
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Used to listen to resource change events in the workspace. These events
+ * are batched up and then passed onto the
+ * {@link ResourceEventProcessingJob}.
+ * </p>
+ */
+ private class ResourceChangeListener implements IResourceChangeListener {
+ /**
+ * <p>
+ * The number of events currently being processed by this listener.
+ * </p>
+ * <p>
+ * Use the {@link #fEventsBeingProcessedLock} when reading or writing
+ * this field
+ * </p>
+ *
+ * @see #fEventsBeingProcessedLock
+ */
+ private volatile int fEventsBeingProcessed;
+
+ /**
+ * Lock to use when reading or writing {@link #fEventsBeingProcessed}
+ *
+ * @see #fEventsBeingProcessed
+ */
+ private final Object fEventsBeingProcessedLock = new Object();
+
+ /**
+ * <p>
+ * Current state of this listener
+ * </p>
+ *
+ * @see AbstractIndexManager#STATE_DISABLED
+ * @see AbstractIndexManager#STATE_ENABLED
+ */
+ private volatile byte fState;
+
+ /**
+ * <p>
+ * Default constructor
+ * </p>
+ */
+ protected ResourceChangeListener() {
+ this.fState = STATE_DISABLED;
+ this.fEventsBeingProcessed = 0;
+ }
+
+ /**
+ * <p>
+ * Start listening for resource change events
+ * </p>
+ */
+ protected void start() {
+ this.fState = STATE_ENABLED;
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
+ }
+
+ /**
+ * <p>
+ * Stop listening for resource change events and if already processing
+ * an event then wait for that processing to finish
+ * </p>
+ *
+ * @throws InterruptedException
+ * waiting for a current event to finish processing could
+ * be interrupted
+ */
+ protected void stop() throws InterruptedException {
+ ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+
+ // wait indefinitely for current event to finish processing
+ this.waitForCurrentEvent(0);
+
+ this.fState = STATE_DISABLED;
+ }
+
+ /**
+ * <p>
+ * Blocks until either the current resource event has been processed
+ * or until the given timeout has passed.
+ * </p>
+ *
+ * @param timeout
+ * block until either this timeout elapses (0 means never
+ * to timeout) or the current resource change event
+ * finishes being processed
+ *
+ * @throws InterruptedException
+ * This can happen when waiting for a lock
+ */
+ protected void waitForCurrentEvent(int timeout) throws InterruptedException {
+ synchronized (this.fEventsBeingProcessedLock) {
+ if (this.fEventsBeingProcessed != 0) {
+ this.fEventsBeingProcessedLock.wait(timeout);
+ }
+ }
+ }
+
+ /**
+ * @return <code>true</code> if this listener is currently processing
+ * any events, <code>false</code> otherwise.
+ */
+ protected boolean isProcessingEvents() {
+ return this.fEventsBeingProcessed != 0;
+ }
+
+ /**
+ * <p>
+ * Process a resource change event. If it is a pre-close or pre-delete
+ * then the {@link ResourceEventProcessingJob} is paused so it does
+ * not try to process resources that are about to be deleted. The
+ * {@link ResourceDeltaVisitor} is used to actually process the event.
+ * </p>
+ *
+ * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
+ * @see ResourceDeltaVisitor
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ try {
+ // update the number of events being processed
+ synchronized (this.fEventsBeingProcessedLock) {
+ ++this.fEventsBeingProcessed;
+ }
+
+ if (this.fState == STATE_ENABLED) {
+ switch (event.getType()) {
+ case IResourceChangeEvent.PRE_CLOSE :
+ case IResourceChangeEvent.PRE_DELETE : {
+ /*
+ * pause the persister job so it does not
+ * interfere
+ */
+ AbstractIndexManager.this.fResourceEventProcessingJob.pause();
+ break;
+ }
+ case IResourceChangeEvent.POST_BUILD :
+ case IResourceChangeEvent.POST_CHANGE : {
+ // post change start up the indexer job and
+ // process the delta
+ AbstractIndexManager.this.fResourceEventProcessingJob.unPause();
+
+ // only analyze the full (starting at root) delta
+ // hierarchy
+ IResourceDelta delta = event.getDelta();
+ if (delta != null && delta.getFullPath().toString().equals("/")) { //$NON-NLS-1$
+ try {
+ // use visitor to visit all children
+ ResourceDeltaVisitor visitor = new ResourceDeltaVisitor(AbstractIndexManager.SOURCE_RESOURCE_CHANGE);
+ delta.accept(visitor, false);
+
+ // process any remaining batched up
+ // resources to index
+ visitor.processBatchedResourceEvents();
+ }
+ catch (CoreException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Failed visiting resource change delta. " + AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e); //$NON-NLS-1$
+ }
+ }
+ break;
+ }
+ }
+ }
+ else {
+ Logger.log(Logger.ERROR, "A resource change event came in after " + //$NON-NLS-1$
+ AbstractIndexManager.this.fName + " shut down. This should never " + //$NON-NLS-1$
+ "ever happen, but if it does the index may now be inconsistant."); //$NON-NLS-1$
+ }
+ }
+ finally {
+ // no matter how we exit be sure to update the number of
+ // events being processed
+ synchronized (this.fEventsBeingProcessedLock) {
+ --this.fEventsBeingProcessed;
+
+ // if currently not events being processed, then notify
+ if (this.fEventsBeingProcessed == 0) {
+ this.fEventsBeingProcessedLock.notifyAll();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Used to visit {@link IResourceDelta}s from both the
+ * {@link IResourceChangeListener} and from a {@link ISaveParticipant}
+ * given to
+ * {@link AbstractIndexManager#start(IResourceDelta, IProgressMonitor)}.
+ * The resource events are batched into groups of
+ * {@link AbstractIndexManager#BATCH_UP_AMOUNT} before being passed onto
+ * the {@link ResourceEventProcessingJob}.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE 1: </b> This class is intended for one time use, thus a new
+ * instance should be instantiated each time this visitor is needed to
+ * process a new {@link IResourceDelta}.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE 2: </b> Be sure to call
+ * {@link ResourceDeltaVisitor#processBatchedResourceEvents()} after using
+ * this visitor to be sure any remaining events get passed onto the
+ * {@link ResourceEventProcessingJob}.
+ * </p>
+ *
+ * @see ResourceDeltaVisitor#processBatchedResourceEvents()
+ */
+ private class ResourceDeltaVisitor implements IResourceDeltaVisitor {
+ /** {@link IProgressMonitor} used to report status */
+ private SubMonitor fProgress;
+
+ /**
+ * <p>
+ * The source that should be used when sending resource events to the
+ * {@link ResourceEventProcessingJob}.
+ * </p>
+ *
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ */
+ private byte fSource;
+
+ /**
+ * <p>
+ * Due to the nature of a visitor it has no way of knowing the total
+ * amount of work it has to do but it can start to predict it based on
+ * the number of children of each event it processes and whether it
+ * plans on visiting those children
+ * </p>
+ */
+ private int fPredictedWorkRemaining;
+
+ /**
+ * {@link Map}&lt{@link IResource}, {@link ResourceEvent}&gt
+ * <p>
+ * Map of resources events created and batched up by this visitor.
+ * These events are periodical be sent off to the
+ * {@link ResourceEventProcessingJob} but need to be sent off one
+ * final time after this visitor finishes it work.
+ * </p>
+ *
+ * @see #processBatchedResourceEvents()
+ */
+ private Map fBatchedResourceEvents;
+
+ /**
+ * <p>
+ * Creates a visitor that will create resource events based on the
+ * resources it visits and using the given source as the source of the
+ * events.
+ * </p>
+ *
+ * @param source
+ * The source of the events that should be used when
+ * creating resource events from visited resources
+ *
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ */
+ protected ResourceDeltaVisitor(byte source) {
+ this(SubMonitor.convert(null), source);
+ }
+
+ /**
+ * <p>
+ * Creates a visitor that will create resource events based on the
+ * resources it visits and using the given source as the source of the
+ * events and report its status to the given progress as best it can
+ * as it visits resources.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE:</b> While the {@link SubMonitor} is provided to report
+ * status the visitor will not honor any cancellation requests.
+ * </p>
+ *
+ * @param progress
+ * Used to report status. This visitor can <b>not</b> be
+ * canceled
+ * @param source
+ * The source of the events that should be used when
+ * creating resource events from visited resources
+ *
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ */
+ protected ResourceDeltaVisitor(SubMonitor progress, byte source) {
+ this.fProgress = progress;
+ this.fSource = source;
+ this.fBatchedResourceEvents = new LinkedHashMap(BATCH_UP_AMOUNT);
+ this.fPredictedWorkRemaining = 1;
+ }
+
+ /**
+ * <p>
+ * Transforms each {@link IResourceDelta} into a {@link ResourceEvent}
+ * . Batches up these {@link ResourceEvent}s and then passes them onto
+ * the {@link ResourceEventProcessingJob}.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE 1: </b> Be sure to call
+ * {@link ResourceDeltaVisitor#processBatchedResourceEvents()} after
+ * using this visitor to be sure any remaining events get passed onto
+ * the {@link ResourceEventProcessingJob}.
+ * </p>
+ *
+ * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
+ * @see #processBatchedResourceEvents()
+ */
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ // report status
+ this.fProgress.subTask(NLS.bind(SSECoreMessages.IndexManager_0_resources_to_go_1, new Object[]{"" + fPredictedWorkRemaining, delta.getFullPath().toString()})); //$NON-NLS-1$
+
+ // process delta if resource not hidden
+ boolean visitChildren = false;
+
+ /*
+ * if root node always visit its children else ask manager
+ * implementation if resource and its children should be visited
+ */
+ if (delta.getResource().isDerived()) { // Do not include derived
+ // resources
+ visitChildren = false;
+ }
+ else if (delta.getFullPath().isRoot()) { //$NON-NLS-1$
+ visitChildren = true;
+ }
+ else {
+ IResource resource = delta.getResource();
+
+ // check if should index resource or its children
+ if (isResourceToIndex(resource.getType(), resource.getFullPath())) {
+ if (resource.getType() == IResource.FILE) {
+
+ switch (delta.getKind()) {
+ case IResourceDelta.CHANGED : {
+ /*
+ * ignore any change that is not a CONTENT,
+ * REPLACED, TYPE, or MOVE_FROM change
+ */
+ if (!((delta.getFlags() & IResourceDelta.CONTENT) != 0) && !((delta.getFlags() & IResourceDelta.REPLACED) != 0) && !((delta.getFlags() & IResourceDelta.TYPE) != 0) && !(((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0))) {
+
+ break;
+ }
+ }
+ /*
+ * it is intended that sometimes a change will
+ * fall through to add
+ */
+ //$FALL-THROUGH$
+ case IResourceDelta.ADDED : {
+ if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0) {
+ // create add move from action
+ this.fBatchedResourceEvents.put(resource, new ResourceEvent(this.fSource, AbstractIndexManager.ACTION_ADD_MOVE_FROM, delta.getMovedFromPath()));
+
+ }
+ else {
+ // create add action
+ this.fBatchedResourceEvents.put(resource, new ResourceEvent(this.fSource, AbstractIndexManager.ACTION_ADD, null));
+ }
+
+ break;
+ }
+ case IResourceDelta.REMOVED : {
+ if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
+ // create remove move to action
+ this.fBatchedResourceEvents.put(resource, new ResourceEvent(this.fSource, AbstractIndexManager.ACTION_REMOVE_MOVE_TO, delta.getMovedToPath()));
+ }
+ else {
+ // create remove action
+ this.fBatchedResourceEvents.put(resource, new ResourceEvent(this.fSource, AbstractIndexManager.ACTION_REMOVE, null));
+ }
+ break;
+ }
+ }
+ }// end is file
+
+ visitChildren = true;
+ }
+ else {
+ visitChildren = false;
+ }
+
+
+
+ // deal with trying to report progress
+ if (visitChildren) {
+ this.fPredictedWorkRemaining += delta.getAffectedChildren().length;
+ }
+ this.fProgress.setWorkRemaining(this.fPredictedWorkRemaining);
+ this.fProgress.worked(1);
+ --this.fPredictedWorkRemaining;
+
+ // batch up resource changes before sending them out
+ if (this.fBatchedResourceEvents.size() >= BATCH_UP_AMOUNT) {
+ this.processBatchedResourceEvents();
+ }
+ }
+
+ return visitChildren;
+ }
+
+ /**
+ * <p>
+ * Sends any batched up resource events created by this visitor to the
+ * {@link ResourceEventProcessingJob}.
+ * <p>
+ *
+ * <p>
+ * <b>NOTE:</b> This will be called every so often as the visitor is
+ * visiting resources but needs to be called a final time by the user
+ * of this visitor to be sure the final events are sent off
+ * </p>
+ */
+ protected void processBatchedResourceEvents() {
+ AbstractIndexManager.this.fResourceEventProcessingJob.addResourceEvents(this.fBatchedResourceEvents);
+ this.fBatchedResourceEvents.clear();
+ }
+ }
+
+ /**
+ * <p>
+ * Collects {@link ResourceEvent}s from the different sources and then
+ * processes each one by calling
+ * {@link AbstractIndexManager#performAction(byte, byte, IResource, IPath)}
+ * for each {@link ResourceEvent}.
+ * </p>
+ *
+ * @see AbstractIndexManager#performAction(byte, byte, IResource, IPath)
+ */
+ private class ResourceEventProcessingJob extends Job {
+ /** Length to delay when scheduling job */
+ private static final int DELAY = 500;
+
+ /**
+ * <p>
+ * Name of the file where resource events still to index will be
+ * preserved for the next start up.
+ * </p>
+ */
+ private static final String PRESERVED_RESOURCE_EVENTS_TO_PROCESS_FILE_NAME = ".preservedResourceEvents"; //$NON-NLS-1$
+
+ /**
+ * <p>
+ * This needs to be updated if
+ * {@link #preserveReceivedResourceEvents()} ever changes how it
+ * persists resource events so that
+ * {@link #loadPreservedReceivedResourceEvents(SubMonitor)} knows when
+ * opening a file that the format is of the current version and if not
+ * knows it does not know how to read the older version.
+ * </p>
+ *
+ * @see #preserveReceivedResourceEvents()
+ * @see #loadPreservedReceivedResourceEvents(SubMonitor)
+ */
+ private static final long serialVersionUID = 2L;
+
+ /** Whether this job has been paused or not */
+ private volatile boolean fIsPaused;
+
+ /**
+ * {@link Map}&lt{@link IResource}, {@link ResourceEvent}&gt
+ * <p>
+ * The list of resources events to be processed
+ * </p>
+ */
+ private Map fResourceEvents;
+
+ /** Lock used when accessing {@link #fBatchedResourceEvents} */
+ private final Object fResourceEventsLock = new Object();
+
+ /**
+ * Locked used for allowing other jobs to wait on this job. This job
+ * will notify those waiting on this lock whenever it is done
+ * processing all resource events it currently knows about.
+ *
+ * @see #waitForConsistant(int)
+ */
+ private final Object fToNotifyLock = new Object();
+
+ /**
+ * <p>
+ * Sets up this job as a long running system job
+ * </p>
+ */
+ protected ResourceEventProcessingJob() {
+ super(NLS.bind(SSECoreMessages.IndexManager_0_Processing_resource_events, AbstractIndexManager.this.fName));
+
+ // set this up as a long running system job
+ this.setUser(false);
+ this.setSystem(true);
+ this.setPriority(Job.LONG);
+
+ this.fIsPaused = false;
+ this.fResourceEvents = new LinkedHashMap();
+ }
+
+ /**
+ * <p>
+ * Loads any preserved {@link ResourceEvent}s from the last time
+ * {@link #stop(boolean)} was invoked and schedules the job to be run
+ * </p>
+ *
+ * <p>
+ * <b>NOTE: </b>Should be used instead of of calling
+ * {@link Job#schedule()} because this method also takes care of
+ * loading preserved state.
+ * </p>
+ *
+ * @param loadPreservedResourceEvents
+ * <code>true</code> if should load any preserved
+ * {@link ResourceEvent}s from the last time
+ * {@link #stop(boolean)} was invoked
+ *
+ * @return <code>true</code> if either
+ * <code>loadPreservedResourceEvents</code> was false or there
+ * was success in loading the preserved {@link ResourceEvent}
+ * s. If <code>false</code> then some {@link ResourceEvent}s
+ * may have been loosed and to insure index consistency with
+ * the workspace a full workspace re-index is needed.
+ *
+ * @see #stop(boolean)
+ */
+ protected synchronized boolean start(boolean loadPreservedResourceEvents, SubMonitor progress) {
+
+ boolean successLoadingPreserved = true;
+
+ // attempt to load preserved resource events if requested
+ if (!loadPreservedResourceEvents) {
+ File preservedResourceEventsFile = this.getPreservedResourceEventsFile();
+ preservedResourceEventsFile.delete();
+ }
+ else {
+ successLoadingPreserved = this.loadPreservedReceivedResourceEvents(progress);
+ }
+
+ // start up the job
+ this.schedule();
+
+ return successLoadingPreserved;
+ }
+
+ /**
+ * <p>
+ * Immediately stops the job and preserves any {@link ResourceEvent}s
+ * in the queue to be processed by not yet processed if requested
+ * </p>
+ *
+ * @param preserveResourceEvents
+ * <code>true</code> to preserve any {@link ResourceEvent}s
+ * in the queue yet to be processed, <code>false</code>
+ * otherwise
+ *
+ * @return <code>true</code> if either
+ * <code>preserveResourceEvents</code> is <code>false</code>
+ * or if there was success in preserving the
+ * {@link ResourceEvent}s yet to be processed. If
+ * <code>false</code> then the preserving failed and a full
+ * workspace re-processing is needed the next time the manager
+ * is started
+ *
+ * @throws InterruptedException
+ * This could happen when trying to cancel or join the job
+ * in progress, but it really shouldn't
+ *
+ * @see #start(boolean, SubMonitor)
+ */
+ protected synchronized boolean stop(boolean preserveResourceEvents) throws InterruptedException {
+ // this will not block indefinitely because it is known this job
+ // can be canceled
+ this.cancel();
+ this.join();
+
+ // preserve if requested, else be sure no preserve file is left
+ // over for next start
+ boolean success = true;
+ if (preserveResourceEvents && this.hasResourceEventsToProcess()) {
+ success = this.preserveReceivedResourceEvents();
+ }
+ else {
+ this.getPreservedResourceEventsFile().delete();
+ }
+
+ return success;
+ }
+
+ /**
+ * @return <code>true</code> if job is currently running or paused
+ *
+ * @see #pause()
+ * @see #unPause()
+ */
+ protected synchronized boolean isProcessing() {
+ return this.getState() != Job.NONE || this.fIsPaused;
+ }
+
+ /**
+ * <p>
+ * Un-pauses this job. This has no effect if the job is already
+ * running.
+ * </p>
+ * <p>
+ * This should be used in place of {@link Job#schedule()} to reset
+ * state caused by calling {@link #pause()}
+ * </p>
+ *
+ * @see #pause()
+ */
+ protected synchronized void unPause() {
+ this.fIsPaused = false;
+
+ // get the job running again depending on its current state
+ if (this.getState() == Job.SLEEPING) {
+ this.wakeUp(DELAY);
+ }
+ else {
+ this.schedule(DELAY);
+ }
+ }
+
+ /**
+ * <p>
+ * Pauses this job, even if it is running
+ * </p>
+ * <p>
+ * This should be used in place of {@link Job#sleep()} because
+ * {@link Job#sleep()} will not pause a job that is already running
+ * but calling this will pause this job even if it is running.
+ * {@link #unPause()} must be used to start this job again
+ * </p>
+ *
+ * @see #unPause()
+ */
+ protected synchronized void pause() {
+ // if job is already running this will force it to pause
+ this.fIsPaused = true;
+
+ // this only works if the job is not running
+ this.sleep();
+ }
+
+ /**
+ * <p>
+ * Adds a batch of {@link ResourceEvent}s to the queue of events to be
+ * processed. Will also un-pause the job if it is not already running
+ * </p>
+ *
+ * @param resourceEvents
+ * {@link Map}&lt{@link IResource}, {@link ResourceEvent}
+ * &gt A batch of {@link ResourceEvent}s to be processed
+ *
+ * @see #addResourceEvent(ResourceEvent)
+ * @see #unPause()
+ */
+ protected void addResourceEvents(Map resourceEvents) {
+ Iterator iter = resourceEvents.keySet().iterator();
+ while (iter.hasNext()) {
+ IResource resource = (IResource) iter.next();
+ ResourceEvent resourceEvent = (ResourceEvent) resourceEvents.get(resource);
+ addResourceEvent(resource, resourceEvent);
+ }
+
+ // un-pause the processor if it is not already running
+ if (!isProcessing()) {
+ this.unPause();
+ }
+ }
+
+ /**
+ * <p>
+ * Gets the number of {@link ResourceEvent}s left to process by this
+ * job. This count is only valid for the exact moment it is returned
+ * because events are constantly being added and removed from the
+ * queue of events to process
+ * </p>
+ *
+ * @return the number of {@link ResourceEvent}s left to process
+ */
+ protected int getNumResourceEventsToProcess() {
+ return this.fResourceEvents.size();
+ }
+
+ /**
+ * <p>
+ * Blocks until either the given timeout elapses (0 means never to
+ * timeout), or there are currently no {@link ResourceEvent}s to
+ * process or being processed by this job
+ * </p>
+ *
+ * @param timeout
+ * block until either this timeout elapses (0 means never
+ * to timeout) or there are currently no
+ * {@link ResourceEvent}s to process or being processed by
+ * this job
+ *
+ * @throws InterruptedException
+ * This can happen when waiting for a lock
+ */
+ protected void waitForConsistant(int timeout) throws InterruptedException {
+ if (hasResourceEventsToProcess() || isProcessing()) {
+ synchronized (this.fToNotifyLock) {
+ this.fToNotifyLock.wait(timeout);
+ }
+ }
+ }
+
+ /**
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ // report status
+ SubMonitor progress = SubMonitor.convert(monitor);
+
+ while (!this.fIsPaused && !monitor.isCanceled() && this.hasResourceEventsToProcess()) {
+ // report status
+ progress.setTaskName(NLS.bind(SSECoreMessages.IndexManager_0_Indexing_1_Files, new Object[]{AbstractIndexManager.this.fName, "" + getNumResourceEventsToProcess()})); //$NON-NLS-1$
+ progress.setWorkRemaining(getNumResourceEventsToProcess());
+
+ // get the next event to process
+ ResourceEvent resourceEvent = null;
+ IResource resource = null;
+ synchronized (this.fResourceEventsLock) {
+ resource = (IResource) this.fResourceEvents.keySet().iterator().next();
+ resourceEvent = (ResourceEvent) this.fResourceEvents.remove(resource);
+ }
+
+ // report status
+ monitor.subTask(resource.getName());
+
+ // perform action safely
+ final byte source = resourceEvent.fSource;
+ final byte action = resourceEvent.fAction;
+ final IResource finResource = resource;
+ final IPath movePath = resourceEvent.fMovePath;
+ SafeRunner.run(new ISafeRunnable() {
+ public void run() throws Exception {
+ AbstractIndexManager.this.performAction(source, action, finResource, movePath);
+ }
+
+ public void handleException(Throwable e) {
+ Logger.logException("Error while performing an update to the index. " + //$NON-NLS-1$
+ AbstractIndexManager.LOG_ERROR_INDEX_INVALID, e);
+ }
+ });
+
+ // report progress
+ progress.worked(1);
+
+ // avoid dead locks
+ Job.getJobManager().currentJob().yieldRule(monitor);
+ }
+
+ // done work
+ monitor.done();
+ }
+ finally {
+ // want to be sure we notify no matter how we exit
+ this.notifyIfConsistant();
+ }
+
+ /*
+ * if canceled then return CANCEL, else if done or paused return
+ * OK
+ */
+ IStatus exitStatus;
+ if (monitor.isCanceled()) {
+ exitStatus = Status.CANCEL_STATUS;
+ }
+ else {
+ exitStatus = Status.OK_STATUS;
+ }
+
+ return exitStatus;
+ }
+
+ /**
+ * <p>
+ * If resource not already scheduled to be processed, schedule it else
+ * if resource already scheduled to be processed, update the action
+ * only if the new action comes from a resource change event.
+ * </p>
+ *
+ * <p>
+ * Ignore other sources for updating existing resource events because
+ * all other sources are "start-up" type sources and thus only
+ * {@link ResourceEvent} with a source of a resource change event
+ * trump existing events.
+ * </p>
+ *
+ * @param resourceEvent
+ * {@link ResourceEvent} to be processed by this job
+ */
+ private void addResourceEvent(IResource resource, ResourceEvent resourceEvent) {
+
+ synchronized (this.fResourceEventsLock) {
+ /*
+ * if resource not already scheduled to be processed, schedule
+ * it else if resource already scheduled to be processed,
+ * update the action only if the new action comes from a
+ * resource change event
+ */
+ if (!this.fResourceEvents.containsKey(resource)) {
+ this.fResourceEvents.put(resource, resourceEvent);
+ }
+ else if (resourceEvent.fSource == AbstractIndexManager.SOURCE_RESOURCE_CHANGE) {
+ ((ResourceEvent) this.fResourceEvents.get(resource)).fAction = resourceEvent.fAction;
+ }
+ else {
+ // Purposely ignoring all other resource events
+ }
+ }
+ }
+
+ /**
+ * @return <code>true</code> if there are any resources to process,
+ * <code>false</code> otherwise
+ */
+ private boolean hasResourceEventsToProcess() {
+ return !this.fResourceEvents.isEmpty();
+ }
+
+ /**
+ * <p>
+ * Preserves all of the resource events that have been received by
+ * this manager but not yet processed
+ * </p>
+ *
+ * <p>
+ * If this operation was successful then the next time the manager
+ * starts it can load these events and process them. If it was not
+ * successful then a full re-processing of the entire workspace will
+ * need to take place to be sure the index is consistent.
+ * </p>
+ *
+ * <p>
+ * <b>NOTE:</b> If this method changes how it preserves these events
+ * then {@link #serialVersionUID} will need to be incremented so that
+ * the manager does not attempt to load an old version of the file
+ * that may exist in a users workspace. Also
+ * {@link #loadPreservedReceivedResourceEvents(SubMonitor)} will have
+ * to be updated to load the new file structure.
+ * </p>
+ *
+ * @return <code>true</code> if successfully preserved the resource
+ * events that have been received by not yet processed,
+ * <code>false</code> otherwise
+ *
+ * @see #serialVersionUID
+ * @see #loadPreservedReceivedResourceEvents(SubMonitor)
+ */
+ private boolean preserveReceivedResourceEvents() {
+ File preservedResourceEventsFile = this.getPreservedResourceEventsFile();
+ boolean success = true;
+
+ synchronized (this.fResourceEventsLock) {
+ DataOutputStream dos = null;
+ try {
+ // if file already exists delete it
+ if (preservedResourceEventsFile.exists()) {
+ preservedResourceEventsFile.delete();
+ preservedResourceEventsFile.createNewFile();
+ }
+
+ // create output objects
+ FileOutputStream fos = new FileOutputStream(preservedResourceEventsFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+ dos = new DataOutputStream(bos);
+
+ // write serial version
+ dos.writeLong(serialVersionUID);
+
+ // write size
+ dos.writeInt(this.getNumResourceEventsToProcess());
+
+ // write out all the information needed to restore the
+ // resource events to process
+ Iterator iter = this.fResourceEvents.keySet().iterator();
+ while (iter.hasNext()) {
+ IResource resource = (IResource) iter.next();
+ ResourceEvent resourceEvent = (ResourceEvent) this.fResourceEvents.get(resource);
+
+ if (resourceEvent.fSource != AbstractIndexManager.SOURCE_WORKSPACE_SCAN) {
+ // write out information
+ dos.writeByte(resourceEvent.fAction);
+ dos.writeByte(resource.getType());
+ byte[] pathBytes = resource.getFullPath().toString().getBytes(ENCODING_UTF16);
+ dos.writeInt(pathBytes.length);
+ dos.write(pathBytes);
+ pathBytes = resourceEvent.fMovePath != null ? resourceEvent.fMovePath.toPortableString().getBytes(ENCODING_UTF16) : new byte[0];
+ dos.writeInt(pathBytes.length);
+ if (pathBytes.length > 0) {
+ dos.write(pathBytes);
+ }
+ }
+ }
+
+ this.fResourceEvents.clear();
+
+ dos.flush();
+ }
+ catch (FileNotFoundException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while opening file to preserve resources to index.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ catch (IOException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while writing to file to preserve resources to index.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ finally {
+ // be sure to close output
+ if (dos != null) {
+ try {
+ dos.close();
+ }
+ catch (IOException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while closing file with preserved resources to index.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ }
+ }
+
+ // if failed, for consistency must do a full re-process next
+ // workspace load
+ if (!success) {
+ preservedResourceEventsFile.delete();
+ }
+ }
+
+ return success;
+ }
+
+ /**
+ * <p>
+ * Loads the received resource events that were preserved during the
+ * manager's last shut down so they can be processed now
+ * </p>
+ *
+ * <p>
+ * If this operation is not successful then a full re-processing of
+ * the entire workspace is needed to be sure the index is consistent.
+ * </p>
+ *
+ * @param progress
+ * used to report status of loading the preserved received
+ * resource events
+ * @return <code>true</code> if the loading of the preserved received
+ * resource events was successful, <code>false</code>
+ * otherwise.
+ *
+ * @see #serialVersionUID
+ * @see #preserveReceivedResourceEvents()
+ */
+ private boolean loadPreservedReceivedResourceEvents(SubMonitor progress) {
+ progress.subTask(SSECoreMessages.IndexManager_processing_deferred_resource_changes);
+
+ boolean success = true;
+ File preservedResourceEventsFile = this.getPreservedResourceEventsFile();
+
+ if (preservedResourceEventsFile.exists()) {
+ Map preservedResourceEvents = null;
+
+ DataInputStream dis = null;
+ try {
+ FileInputStream fis = new FileInputStream(preservedResourceEventsFile);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ dis = new DataInputStream(bis);
+
+ // check serial version first
+ long preservedSerialVersionUID = dis.readLong();
+ if (preservedSerialVersionUID == serialVersionUID) {
+
+ // read each record
+ int numberOfRecords = dis.readInt();
+ preservedResourceEvents = new LinkedHashMap(numberOfRecords);
+ progress.setWorkRemaining(numberOfRecords);
+ for (int i = 0; i < numberOfRecords; ++i) {
+ // action is first byte
+ byte action = dis.readByte();
+
+ // file type is the next byte
+ byte fileType = dis.readByte();
+
+ // resource location are the next bytes
+ final String resourceLocation = readStringFromStream(dis);
+ // get the resource
+ IResource resource = null;
+ IPath resourcePath = new Path(resourceLocation);
+ if (!resourcePath.isRoot() && resourcePath.segmentCount() > 1) {
+ if (fileType == IResource.FILE) {
+ resource = ResourcesPlugin.getWorkspace().getRoot().getFile(resourcePath);
+ }
+ else {
+ resource = ResourcesPlugin.getWorkspace().getRoot().getFolder(resourcePath);
+ }
+ }
+ else {
+ Logger.log(Logger.WARNING, "The AbstractIndexManager " + AbstractIndexManager.this.fName + " attempted to load an invlaid preserved resource event:\n" + "(" + resourcePath + ")");
+ }
+
+ // move path are the next bytes
+ final String moveLocation = readStringFromStream(dis);
+ // get the move path
+ IPath movePath = null;
+ if (moveLocation.length() > 0) {
+ movePath = new Path(moveLocation);
+ }
+
+ // add the object to the list of of preserved
+ // resources
+ preservedResourceEvents.put(resource, new ResourceEvent(AbstractIndexManager.SOURCE_PRESERVED_RESOURCES_TO_INDEX, action, movePath));
+
+ progress.worked(1);
+ }
+ }
+ else {
+ success = false;
+ }
+ }
+ catch (FileNotFoundException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while opening file to read preserved resources to index. Index manager will recover by re-indexing workspace.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ catch (IOException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while reading from file of preserved resources to index. Index manager will recover by re-indexing workspace.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ catch (Exception e) {
+ // Purposely catching all exceptions here so that index
+ // manager can recover gracefully
+ Logger.logException(AbstractIndexManager.this.fName + ": Unexpected exception while reading from file of preserved resources to index. Index manager will recover by re-indexing workspace.", //$NON-NLS-1$
+ e);
+ success = false;
+ }
+ finally {
+ if (dis != null) {
+ try {
+ dis.close();
+ }
+ catch (IOException e) {
+ Logger.logException(AbstractIndexManager.this.fName + ": Exception while closing file of preserved resources" + //$NON-NLS-1$
+ " to index that was just read. This should have no" + //$NON-NLS-1$
+ " effect on the consistency of the index.", //$NON-NLS-1$
+ e);
+ }
+ }
+ }
+
+ // if success loading preserved then add to master list
+ if (success && preservedResourceEvents != null) {
+ synchronized (this.fResourceEventsLock) {
+ Iterator iter = preservedResourceEvents.keySet().iterator();
+ while (iter.hasNext()) {
+ IResource resource = (IResource) iter.next();
+ ResourceEvent event = (ResourceEvent) preservedResourceEvents.get(resource);
+ this.fResourceEvents.put(resource, event);
+ }
+ }
+ }
+ else {
+ // failed reading file, so delete it
+ preservedResourceEventsFile.delete();
+ }
+ }
+
+ progress.done();
+ return success;
+ }
+
+ /**
+ * Reads a string from the input stream. An integer length is read first
+ * followed by the bytes of the string
+ * @param dis the input stream to read from
+ * @return a String represented by the bytes
+ * @throws IOException
+ */
+ private String readStringFromStream(DataInputStream dis) throws IOException {
+ // Read the int for the string's length
+ final int length = dis.readInt();
+ // Read in length bytes for the string
+ final byte[] resourceLocation = new byte[length];
+ int read = 0;
+ int offset = 0;
+ while (offset < resourceLocation.length && (read = dis.read(resourceLocation, offset, resourceLocation.length - offset)) > 0) {
+ offset += read;
+ }
+ return new String(resourceLocation, ENCODING_UTF16);
+ }
+
+ /**
+ * @return {@link File} that contains any resource events received but
+ * not processed by this manager the last time it shutdown.
+ * This file may or may not actually exist.
+ *
+ * @see #preserveReceivedResourceEvents()
+ * @see #loadPreservedReceivedResourceEvents(SubMonitor)
+ */
+ private File getPreservedResourceEventsFile() {
+ IPath preservedResourcesToIndexPath = AbstractIndexManager.this.getWorkingLocation().append(PRESERVED_RESOURCE_EVENTS_TO_PROCESS_FILE_NAME);
+ return new File(preservedResourcesToIndexPath.toOSString());
+ }
+
+ /**
+ * <p>
+ * If all resource events have been processed
+ */
+ private void notifyIfConsistant() {
+ if (!this.hasResourceEventsToProcess()) {
+ synchronized (this.fToNotifyLock) {
+ this.fToNotifyLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Represents a resource that was discovered by this manager. Contains all
+ * the information this manager and the index needs to know about this
+ * resource. Such has how the manager was notified about this resource and
+ * the type of action occurring on the resource.
+ * </p>
+ */
+ private static class ResourceEvent {
+ /**
+ * <p>
+ * The source of this resource event
+ * </p>
+ *
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ * @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN
+ * @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX
+ */
+ protected byte fSource;
+
+ /**
+ * <p>
+ * The action that the index should take with this resource
+ * </p>
+ *
+ * @see AbstractIndexManager#ACTION_ADD
+ * @see AbstractIndexManager#ACTION_REMOVE
+ * @see AbstractIndexManager#ACTION_ADD_MOVE_FROM
+ * @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO
+ */
+ protected byte fAction;
+
+ /**
+ *
+ * <p>
+ * If the {@link #fAction} is
+ * {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} or
+ * {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then this field
+ * will have the path the resource was moved from or moved to. Else
+ * this field will be <code>null</code>
+ * </p>
+ *
+ * <p>
+ * <b>NOTE: </b>Maybe <code>null</code>.
+ * </p>
+ *
+ * @see AbstractIndexManager#ACTION_ADD_MOVE_FROM
+ * @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO
+ */
+ protected IPath fMovePath;
+
+ /**
+ * <p>
+ * Creates a resource event that the index needs to react to in some
+ * way
+ * </p>
+ *
+ * @param source
+ * source that the manager used to learn of this resource
+ * @param action
+ * action the index should take on this resource
+ * @param resource
+ * resource that the index should know about
+ * @param movePath
+ * if action is
+ * {@link AbstractIndexManager#ACTION_ADD_MOVE_FROM} or
+ * {@link AbstractIndexManager#ACTION_REMOVE_MOVE_TO} then
+ * this should be the path the resource was moved from or
+ * moved to respectively, else should be <code>null</code>
+ *
+ * @see AbstractIndexManager#SOURCE_RESOURCE_CHANGE
+ * @see AbstractIndexManager#SOURCE_SAVED_STATE
+ * @see AbstractIndexManager#SOURCE_WORKSPACE_SCAN
+ * @see AbstractIndexManager#SOURCE_PRESERVED_RESOURCES_TO_INDEX
+ *
+ * @see AbstractIndexManager#ACTION_ADD
+ * @see AbstractIndexManager#ACTION_REMOVE
+ * @see AbstractIndexManager#ACTION_ADD_MOVE_FROM
+ * @see AbstractIndexManager#ACTION_REMOVE_MOVE_TO
+ */
+ protected ResourceEvent(byte source, byte action, IPath movePath) {
+ this.fSource = source;
+ this.fAction = action;
+ this.fMovePath = movePath;
+ }
+ }
+} \ No newline at end of file
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/FileBufferModelManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/FileBufferModelManager.java
new file mode 100644
index 0000000000..93f763fa00
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/FileBufferModelManager.java
@@ -0,0 +1,969 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2013 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
+ * David Carver (Intalio) - bug 300434 - Make inner classes static where possible
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.eclipse.core.filebuffers.FileBuffers;
+import org.eclipse.core.filebuffers.IFileBuffer;
+import org.eclipse.core.filebuffers.IFileBufferListener;
+import org.eclipse.core.filebuffers.ITextFileBuffer;
+import org.eclipse.core.filebuffers.ITextFileBufferManager;
+import org.eclipse.core.filebuffers.LocationKind;
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin;
+import org.eclipse.wst.common.uriresolver.internal.util.URIHelper;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
+import org.eclipse.wst.sse.core.internal.model.AbstractStructuredModel;
+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.IStructuredModel;
+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.URIResolver;
+import org.eclipse.wst.sse.core.internal.util.URIResolverExtension;
+
+/**
+ * Not intended to be subclassed, referenced or instantiated by clients.
+ *
+ * This class is responsible for coordinating the creation and disposal of
+ * structured models built on structured documents found in FileBuffers. It
+ * allows the SSE Model Manager to act as a client to the
+ * TextFileBufferManager.
+ */
+public class FileBufferModelManager {
+
+ static class DocumentInfo {
+ /**
+ * The ITextFileBuffer
+ */
+ ITextFileBuffer buffer = null;
+
+ /**
+ * The platform content-type ID of this document
+ */
+ String contentTypeID = null;
+
+ /**
+ * The IStructureModel containing this document; might be null at
+ * points in the ITextFileBuffer's lifecycle
+ */
+ IStructuredModel model = null;
+
+ /**
+ * Whether FileBufferModelManager called connect() for this
+ * DocumentInfo's text filebuffer
+ */
+ boolean selfConnected = false;
+
+ int bufferReferenceCount = 0;
+ int modelReferenceCount = 0;
+
+ /**
+ * The default value is the "compatibility" kind from before there was
+ * a LocationKind hint object--this is expected to be overridden at
+ * runtime.
+ */
+ LocationKind locationKind = LocationKind.NORMALIZE;
+ }
+
+ /**
+ * A URIResolver instance of models built on java.io.Files
+ */
+ static class ExternalURIResolver implements URIResolver, URIResolverExtension {
+ IPath fLocation;
+
+ ExternalURIResolver(IPath location) {
+ fLocation = location;
+ }
+
+ public String getFileBaseLocation() {
+ if (fLocation == null)
+ return null;
+ else
+ return fLocation.toString();
+ }
+
+ public String getLocationByURI(String uri) {
+ return getLocationByURI(uri, getFileBaseLocation(), false);
+ }
+
+ public String getLocationByURI(String uri, boolean resolveCrossProjectLinks) {
+ return getLocationByURI(uri, getFileBaseLocation(), resolveCrossProjectLinks);
+ }
+
+ public String getLocationByURI(String uri, String baseReference) {
+ return getLocationByURI(uri, baseReference, false);
+ }
+
+ public String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks) {
+ // ignore resolveCrossProjectLinks value
+ if (uri == null)
+ return null;
+ if (uri.startsWith("file:")) { //$NON-NLS-1$
+ try {
+ URL url = new URL(uri);
+ return url.getFile();
+ }
+ catch (MalformedURLException e) {
+ }
+ }
+ return URIHelper.normalize(uri, baseReference, Path.ROOT.toString());
+ }
+
+ public IProject getProject() {
+ return null;
+ }
+
+ public IContainer getRootLocation() {
+ return ResourcesPlugin.getWorkspace().getRoot();
+ }
+
+ public InputStream getURIStream(String uri) {
+ return null;
+ }
+
+ public void setFileBaseLocation(String newLocation) {
+ if (newLocation != null)
+ fLocation = new Path(newLocation);
+ else
+ fLocation = null;
+ }
+
+ public void setProject(IProject newProject) {
+ }
+
+ public URIResolver newInstance() {
+ return new ExternalURIResolver(fLocation != null ? (IPath) fLocation.clone() : null);
+ }
+ }
+
+ static class BasicURIResolver implements URIResolver, URIResolverExtension {
+ private URI fURI;
+
+ BasicURIResolver(URI uri) {
+ fURI = uri;
+ }
+
+ public URIResolver newInstance() {
+ return new BasicURIResolver(fURI);
+ }
+
+ public String getFileBaseLocation() {
+ return fURI.toString();
+ }
+
+ public String getLocationByURI(String uri) {
+ return getLocationByURI(uri, getFileBaseLocation(), false);
+ }
+
+ public String getLocationByURI(String uri, boolean resolveCrossProjectLinks) {
+ return getLocationByURI(uri, getFileBaseLocation(), resolveCrossProjectLinks);
+ }
+
+ public String getLocationByURI(String uri, String baseReference) {
+ return getLocationByURI(uri, baseReference, false);
+ }
+
+ public String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks) {
+ return URI.create(baseReference).resolve(uri).toString();
+ }
+
+ public IProject getProject() {
+ return null;
+ }
+
+ public IContainer getRootLocation() {
+ return null;
+ }
+
+ public InputStream getURIStream(String uri) {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ public void setFileBaseLocation(String newLocation) {
+ }
+
+ public void setProject(IProject newProject) {
+ }
+ }
+
+ /**
+ * A URIResolver instance of models built on the extensible WST URI
+ * resolver
+ */
+ static class CommonURIResolver implements URIResolver, URIResolverExtension {
+ String fLocation;
+ IPath fPath;
+ private IProject fProject;
+ final static String SEPARATOR = "/"; //$NON-NLS-1$
+ final static String FILE_PREFIX = "file://"; //$NON-NLS-1$
+
+ CommonURIResolver(IFile workspaceFile) {
+ fPath = workspaceFile.getFullPath();
+ fProject = workspaceFile.getProject();
+ }
+
+ private CommonURIResolver() {
+ }
+
+ public String getFileBaseLocation() {
+ return fLocation;
+ }
+
+ public String getLocationByURI(String uri) {
+ return getLocationByURI(uri, getFileBaseLocation(), false);
+ }
+
+ public String getLocationByURI(String uri, boolean resolveCrossProjectLinks) {
+ return getLocationByURI(uri, getFileBaseLocation(), resolveCrossProjectLinks);
+ }
+
+ public String getLocationByURI(String uri, String baseReference) {
+ return getLocationByURI(uri, baseReference, false);
+ }
+
+ public String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks) {
+ boolean baseHasPrefix = baseReference != null && baseReference.startsWith(FILE_PREFIX);
+ String reference = null;
+ if (baseHasPrefix) {
+ reference = baseReference;
+ }
+ else {
+ reference = FILE_PREFIX + baseReference;
+ }
+ String result = URIResolverPlugin.createResolver().resolve(reference, null, uri);
+ // Logger.log(Logger.INFO_DEBUG,
+ // "URIResolverPlugin.createResolver().resolve("
+ // + reference + ", null, " +uri+") = " + result);
+ if (!baseHasPrefix && result.startsWith(FILE_PREFIX) && result.length() > FILE_PREFIX.length()) {
+ result = result.substring(FILE_PREFIX.length());
+ }
+ return result;
+ }
+
+ public IProject getProject() {
+ return fProject;
+ }
+
+ public IContainer getRootLocation() {
+ String root = URIResolverPlugin.createResolver().resolve(FILE_PREFIX + getFileBaseLocation(), null, SEPARATOR);
+ IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(new Path(root));
+ for (int i = 0; i < files.length; i++) {
+ if ((files[i].getType() & IResource.FOLDER) == IResource.FOLDER) {
+ if (fPath.isPrefixOf(((IFolder) files[i]).getFullPath())) {
+ return (IFolder) files[i];
+ }
+ }
+ }
+ return getProject();
+ }
+
+ public InputStream getURIStream(String uri) {
+ return null;
+ }
+
+ public void setFileBaseLocation(String newLocation) {
+ fLocation = newLocation;
+ }
+
+ public void setProject(IProject newProject) {
+ fProject = newProject;
+ }
+
+ public URIResolver newInstance() {
+ CommonURIResolver resolver = new CommonURIResolver();
+ resolver.fLocation = fLocation;
+ resolver.fPath = (IPath) fPath.clone();
+ resolver.fProject = fProject;
+ return resolver;
+ }
+ }
+
+ /**
+ * Maps interesting documents in file buffers to those file buffers.
+ * Required to allow us to go from the document instances to complete
+ * models.
+ */
+ class FileBufferMapper implements IFileBufferListener {
+ public void bufferContentAboutToBeReplaced(IFileBuffer buffer) {
+ }
+
+ public void bufferContentReplaced(IFileBuffer buffer) {
+ }
+
+ public void bufferCreated(IFileBuffer buffer) {
+ if (buffer instanceof ITextFileBuffer) {
+ ITextFileBuffer textBuffer = (ITextFileBuffer) buffer;
+ if (!(textBuffer.getDocument() instanceof IStructuredDocument))
+ return;
+
+ if (Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, "Learned new buffer: " + locationString(textBuffer) + " " + buffer + " " + ((ITextFileBuffer) buffer).getDocument()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ DocumentInfo info = new DocumentInfo();
+ info.buffer = textBuffer;
+ info.contentTypeID = detectContentType(buffer).getId();
+ info.bufferReferenceCount++;
+ fDocumentMap.put(textBuffer.getDocument(), info);
+ }
+ }
+
+ public void bufferDisposed(IFileBuffer buffer) {
+ if (buffer instanceof ITextFileBuffer) {
+ ITextFileBuffer textBuffer = (ITextFileBuffer) buffer;
+ if (!(textBuffer.getDocument() instanceof IStructuredDocument))
+ return;
+ if (Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, "Discarded buffer: " + locationString(textBuffer) + " " + buffer + " " + ((ITextFileBuffer) buffer).getDocument()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(textBuffer.getDocument());
+ if (info != null) {
+ info.bufferReferenceCount--;
+ checkReferenceCounts(info, textBuffer.getDocument());
+ }
+ }
+ }
+
+ public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) {
+ if (buffer instanceof ITextFileBuffer) {
+ ITextFileBuffer textBuffer = (ITextFileBuffer) buffer;
+ if (Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, "Buffer dirty state changed: (" + isDirty + ") " + locationString(textBuffer) + " " + buffer + " " + ((ITextFileBuffer) buffer).getDocument()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ if (!(textBuffer.getDocument() instanceof IStructuredDocument))
+ return;
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(textBuffer.getDocument());
+ if (info != null && info.model != null) {
+ String msg = "Updating model dirty state for" + locationString(textBuffer); //$NON-NLS-1$
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT || Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, msg);
+ }
+ info.model.setDirtyState(isDirty);
+
+ IPath location = info.buffer.getLocation();
+ if (location != null) {
+ IFile workspaceFile = FileBuffers.getWorkspaceFileAtLocation(location);
+ if (!isDirty && workspaceFile != null) {
+ info.model.resetSynchronizationStamp(workspaceFile);
+ }
+ }
+ }
+ }
+ }
+
+ public void stateChangeFailed(IFileBuffer buffer) {
+ }
+
+ public void stateChanging(IFileBuffer buffer) {
+ }
+
+ public void stateValidationChanged(IFileBuffer buffer, boolean isStateValidated) {
+ }
+
+ public void underlyingFileDeleted(IFileBuffer buffer) {
+ if (buffer instanceof ITextFileBuffer) {
+ if (Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, "Deleted buffer: " + locationString((ITextFileBuffer) buffer) + " " + buffer); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ }
+
+ public void underlyingFileMoved(IFileBuffer buffer, IPath path) {
+ if (buffer instanceof ITextFileBuffer) {
+ if (Logger.DEBUG_TEXTBUFFERLIFECYCLE) {
+ Logger.log(Logger.INFO, "Moved buffer from: " + locationString((ITextFileBuffer) buffer) + " " + buffer); //$NON-NLS-1$ //$NON-NLS-2$
+ Logger.log(Logger.INFO, "Moved buffer to: " + path.toString() + " " + buffer); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ }
+ }
+
+ private static FileBufferModelManager instance = new FileBufferModelManager();
+
+ private static String locationString(ITextFileBuffer textBuffer) {
+ return textBuffer.getLocation() != null ? textBuffer.getLocation().toString() : (textBuffer.getFileStore() != null ? textBuffer.getFileStore().getName() : String.valueOf(textBuffer.getDocument().hashCode()));
+ }
+
+ public static FileBufferModelManager getInstance() {
+ return instance;
+ }
+
+ static synchronized final void shutdown() {
+ FileBuffers.getTextFileBufferManager().removeFileBufferListener(instance.fFileBufferListener);
+
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT || Logger.DEBUG_FILEBUFFERMODELLEAKS) {
+ IDocument[] danglingDocuments = (IDocument[]) instance.fDocumentMap.keySet().toArray(new IDocument[0]);
+ for (int i = 0; i < danglingDocuments.length; i++) {
+ DocumentInfo info = (DocumentInfo) instance.fDocumentMap.get(danglingDocuments[i]);
+ if (info.modelReferenceCount > 0)
+ System.err.println("LEAKED MODEL: " + locationString(info.buffer) + " " + (info.model != null ? info.model.getId() : null)); //$NON-NLS-1$ //$NON-NLS-2$
+ if (info.bufferReferenceCount > 0)
+ System.err.println("LEAKED BUFFER: " + locationString(info.buffer) + " " + info.buffer.getDocument()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ }
+
+ static synchronized final void startup() {
+ FileBuffers.getTextFileBufferManager().addFileBufferListener(getInstance().fFileBufferListener);
+ }
+
+ // a map of IStructuredDocuments to DocumentInfo objects
+ Map fDocumentMap = null;
+
+ FileBufferMapper fFileBufferListener = new FileBufferMapper();
+
+ FileBufferModelManager() {
+ super();
+ fDocumentMap = new Hashtable(4);
+ }
+
+ public String calculateId(IFile file) {
+ if (file == null) {
+ Exception iae = new IllegalArgumentException("can not calculate a model ID without an IFile"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ String id = null;
+ IPath path = file.getFullPath();
+ if (path != null) {
+ /*
+ * The ID of models must be the same as the normalized paths
+ * stored in the underlying FileBuffers to retrieve them by common
+ * ID later on. We chose the FileBuffer normalized path over the
+ * previously used absolute IFile path because the buffers should
+ * already exist before we build a model and we can't retrieve a
+ * FileBuffer using the ID of a model that doesn't yet exist.
+ */
+ id = FileBuffers.normalizeLocation(path).toString();
+ }
+ return id;
+
+ }
+
+
+ public String calculateId(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not calculate a model ID without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ String id = null;
+ ITextFileBuffer buffer = getBuffer(document);
+ if (buffer != null) {
+ id = locationString(buffer);
+ }
+ return id;
+ }
+
+ /**
+ * Registers "interest" in a document, or rather the file buffer that
+ * backs it. Intentionally used to alter the reference count of the file
+ * buffer so it is not accidentally disposed of while we have a model open
+ * on top of it.
+ */
+ public boolean connect(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not connect() without a document"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return false;
+ }
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info == null)
+ return false;
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
+ boolean isOK = true;
+ try {
+ if (info.buffer.getLocation() != null) {
+ bufferManager.connect(info.buffer.getLocation(), info.locationKind, null);
+ }
+ else if (info.buffer.getFileStore() != null) {
+ bufferManager.connectFileStore(info.buffer.getFileStore(), null);
+ }
+ }
+ catch (CoreException e) {
+ Logger.logException(e);
+ isOK = false;
+ }
+ return isOK;
+ }
+
+ URIResolver createURIResolver(ITextFileBuffer buffer) {
+ URIResolver resolver = null;
+ IPath location = buffer.getLocation();
+ if (location != null) {
+ IFile workspaceFile = FileBuffers.getWorkspaceFileAtLocation(location);
+ if (workspaceFile != null) {
+ IProject project = workspaceFile.getProject();
+ resolver = (URIResolver) project.getAdapter(URIResolver.class);
+ if (resolver == null) {
+ resolver = new CommonURIResolver(workspaceFile);
+ }
+
+ String baseLocation = null;
+ if (workspaceFile.getLocation() != null) {
+ baseLocation = workspaceFile.getLocation().toString();
+ }
+ if (baseLocation == null && workspaceFile.getLocationURI() != null) {
+ baseLocation = workspaceFile.getLocationURI().toString();
+ }
+ if (baseLocation == null) {
+ baseLocation = workspaceFile.getFullPath().toString();
+ }
+ resolver.setFileBaseLocation(baseLocation);
+ }
+ else {
+ resolver = new ExternalURIResolver(location);
+ }
+ }
+ else if (buffer.getFileStore() != null) {
+ resolver = new BasicURIResolver(buffer.getFileStore().toURI());
+ }
+ return resolver;
+ }
+
+
+ IContentType detectContentType(IFileBuffer buffer) {
+ IContentType type = null;
+
+ IPath location = buffer.getLocation();
+ if (location != null) {
+ IResource resource = FileBuffers.getWorkspaceFileAtLocation(location);
+ if (resource != null) {
+ if (resource.getType() == IResource.FILE && resource.isAccessible()) {
+ IContentDescription d = null;
+ try {
+ // Optimized description lookup, might not succeed
+ d = ((IFile) resource).getContentDescription();
+ if (d != null) {
+ type = d.getContentType();
+ }
+ }
+ catch (CoreException e) {
+ /*
+ * Should not be possible given the accessible and
+ * file type check above
+ */
+ }
+ if (type == null) {
+ type = Platform.getContentTypeManager().findContentTypeFor(resource.getName());
+ }
+ }
+ }
+ else {
+ File file = FileBuffers.getSystemFileAtLocation(location);
+ if (file != null) {
+ InputStream input = null;
+ try {
+ input = new FileInputStream(file);
+ type = Platform.getContentTypeManager().findContentTypeFor(input, file.getName());
+ }
+ catch (FileNotFoundException e) {
+ }
+ catch (IOException e) {
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e1) {
+ }
+ }
+ }
+ if (type == null) {
+ type = Platform.getContentTypeManager().findContentTypeFor(file.getName());
+ }
+ }
+ }
+ }
+ else {
+ IFileStore fileStore = buffer.getFileStore();
+ if (fileStore != null) {
+ InputStream input = null;
+ try {
+ input = fileStore.openInputStream(EFS.NONE, null);
+ if (input != null) {
+ type = Platform.getContentTypeManager().findContentTypeFor(input, fileStore.getName());
+ }
+ }
+ catch (CoreException e) {
+ // failure, assume plain text
+ }
+ catch (IOException e) {
+ // failure, assume plain text
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e1) {
+ }
+ }
+ }
+ if (type == null) {
+ type = Platform.getContentTypeManager().findContentTypeFor(fileStore.getName());
+ }
+ }
+ }
+
+ if (type == null) {
+ type = Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
+ }
+ return type;
+ }
+
+ /**
+ * Deregisters "interest" in a document, or rather the file buffer that
+ * backs it. Intentionally used to alter the reference count of the file
+ * buffer so that it knows it can safely be disposed of.
+ */
+ public boolean disconnect(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not disconnect() without a document"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return false;
+ }
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if( info == null)
+ return false;
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
+ boolean isOK = true;
+ try {
+ if (info.buffer.getLocation() != null) {
+ bufferManager.disconnect(info.buffer.getLocation(), info.locationKind, null);
+ }
+ else if (info.buffer.getFileStore() != null) {
+ bufferManager.disconnectFileStore(info.buffer.getFileStore(), null);
+ }
+ }
+ catch (CoreException e) {
+ Logger.logException(e);
+ isOK = false;
+ }
+ return isOK;
+ }
+
+ public ITextFileBuffer getBuffer(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not get a buffer without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info != null)
+ return info.buffer;
+ return null;
+ }
+
+ String getContentTypeID(IDocument document) {
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info != null)
+ return info.contentTypeID;
+ return null;
+ }
+
+ IStructuredModel getModel(File file) {
+ if (file == null) {
+ Exception iae = new IllegalArgumentException("can not get/create a model without a java.io.File"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ IStructuredModel model = null;
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
+ try {
+ IPath location = new Path(file.getAbsolutePath());
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT) {
+ Logger.log(Logger.INFO, "FileBufferModelManager connecting to File " + location); //$NON-NLS-1$
+ }
+ bufferManager.connect(location, LocationKind.LOCATION, getProgressMonitor());
+ ITextFileBuffer buffer = bufferManager.getTextFileBuffer(location, LocationKind.LOCATION);
+ if (buffer != null) {
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(buffer.getDocument());
+ if (info != null) {
+ /*
+ * Note: "info" being null at this point is a slight
+ * error.
+ *
+ * The connect call from above (or at some time earlier in
+ * the session) would have notified the FileBufferMapper
+ * of the creation of the corresponding text buffer and
+ * created the DocumentInfo object for
+ * IStructuredDocuments.
+ */
+ info.locationKind = LocationKind.LOCATION;
+ info.selfConnected = true;
+ }
+ /*
+ * Check the document type. Although returning null for
+ * unknown documents would be fair, try to get a model if
+ * the document is at least a valid type.
+ */
+ IDocument bufferDocument = buffer.getDocument();
+ if (bufferDocument instanceof IStructuredDocument) {
+ model = getModel((IStructuredDocument) bufferDocument);
+ }
+ else {
+ /*
+ * 190768 - Quick diff marks do not disappear in the
+ * vertical ruler of JavaScript editor and
+ *
+ * 193805 - Changes are not thrown away when close
+ * with no save for files with no structured model
+ * associated with them (text files, javascript files,
+ * etc) in web project
+ */
+ bufferManager.disconnect(location, LocationKind.IFILE, getProgressMonitor());
+ }
+ }
+ }
+ catch (CoreException e) {
+ Logger.logException("Error getting model for " + file.getPath(), e); //$NON-NLS-1$
+ }
+ return model;
+ }
+
+ public IStructuredModel getModel(IFile file) {
+ if (file == null) {
+ Exception iae = new IllegalArgumentException("can not get/create a model without an IFile"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ IStructuredModel model = null;
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
+ try {
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT) {
+ Logger.log(Logger.INFO, "FileBufferModelManager connecting to IFile " + file.getFullPath()); //$NON-NLS-1$
+ }
+ // see TextFileDocumentProvider#createFileInfo about why we use
+ // IFile#getFullPath
+ // here, not IFile#getLocation.
+ IPath location = file.getFullPath();
+ if (location != null) {
+ bufferManager.connect(location, LocationKind.IFILE, getProgressMonitor());
+ ITextFileBuffer buffer = bufferManager.getTextFileBuffer(location, LocationKind.IFILE);
+ if (buffer != null) {
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(buffer.getDocument());
+ if (info != null) {
+ /*
+ * Note: "info" being null at this point is a slight
+ * error.
+ *
+ * The connect call from above (or at some time
+ * earlier in the session) would have notified the
+ * FileBufferMapper of the creation of the
+ * corresponding text buffer and created the
+ * DocumentInfo object for IStructuredDocuments.
+ */
+ info.selfConnected = true;
+ info.locationKind = LocationKind.IFILE;
+ }
+ /*
+ * Check the document type. Although returning null for
+ * unknown documents would be fair, try to get a model if
+ * the document is at least a valid type.
+ */
+ IDocument bufferDocument = buffer.getDocument();
+ if (bufferDocument instanceof IStructuredDocument) {
+ model = getModel((IStructuredDocument) bufferDocument);
+ }
+ else {
+ /*
+ * 190768 - Quick diff marks do not disappear in the
+ * vertical ruler of JavaScript editor and
+ *
+ * 193805 - Changes are not thrown away when close
+ * with no save for files with no structured model
+ * associated with them (text files, javascript files,
+ * etc) in web project
+ */
+ bufferManager.disconnect(location, LocationKind.IFILE, getProgressMonitor());
+ }
+ }
+ }
+ }
+ catch (CoreException e) {
+ Logger.logException("Error getting model for " + file.getFullPath(), e); //$NON-NLS-1$
+ }
+ return model;
+ }
+
+ public IStructuredModel getModel(IStructuredDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not get/create a model without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return null;
+ }
+
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info != null && info.model == null) {
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT) {
+ Logger.log(Logger.INFO, "FileBufferModelManager creating model for " + locationString(info.buffer) + " " + info.buffer.getDocument()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ info.modelReferenceCount++;
+
+ IStructuredModel model = null;
+ IModelHandler handler = ModelHandlerRegistry.getInstance().getHandlerForContentTypeId(info.contentTypeID);
+ IModelLoader loader = handler.getModelLoader();
+ String id = (info.buffer.getLocation() != null ? info.buffer.getLocation().toString() : String.valueOf(document.hashCode()));
+ model = loader.createModel(document, id, handler);
+ try {
+ info.model = model;
+ model.setId(id);
+ // handler now set by loader, for now
+ // model.setModelHandler(handler);
+ if (model instanceof AbstractStructuredModel) {
+ ((AbstractStructuredModel) model).setContentTypeIdentifier(info.contentTypeID);
+ }
+ model.setResolver(createURIResolver(info.buffer));
+ if (info.buffer.isDirty()) {
+ model.setDirtyState(true);
+ }
+ }
+ catch (ResourceInUse e) {
+ Logger.logException("attempted to create new model with existing ID", e); //$NON-NLS-1$
+ model = null;
+ }
+ }
+ if (info != null) {
+ return info.model;
+ }
+ return null;
+ }
+
+ /**
+ * @return
+ */
+ private IProgressMonitor getProgressMonitor() {
+ return new NullProgressMonitor();
+ }
+
+ /**
+ * Will remove the entry corresponding to <code>document</code> if both
+ * there are no more buffer or model reference counts for <code>info</code>
+ *
+ * @param info the document info to check for reference counts
+ * @param document the key to remove from the document map if there are no more
+ * references
+ */
+ private void checkReferenceCounts(DocumentInfo info, IDocument document) {
+ if (info.bufferReferenceCount == 0 && info.modelReferenceCount == 0)
+ fDocumentMap.remove(document);
+ }
+
+ public boolean isExistingBuffer(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not check for an existing buffer without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return false;
+ }
+
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ return info != null;
+ }
+
+ public void releaseModel(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not release a model without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return;
+ }
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info != null) {
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT) {
+ Logger.log(Logger.INFO, "FileBufferModelManager noticed full release of model for " + locationString(info.buffer) + " " + info.buffer.getDocument()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ info.model = null;
+ info.modelReferenceCount--;
+ if (info.selfConnected) {
+ if (Logger.DEBUG_FILEBUFFERMODELMANAGEMENT) {
+ Logger.log(Logger.INFO, "FileBufferModelManager disconnecting from " + locationString(info.buffer) + " " + info.buffer.getDocument()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
+ try {
+ if (info.buffer.getLocation() != null) {
+ bufferManager.disconnect(info.buffer.getLocation(), info.locationKind, null);
+ }
+ else if (info.buffer.getFileStore() != null) {
+ bufferManager.disconnectFileStore(info.buffer.getFileStore(), null);
+ }
+ }
+ catch (CoreException e) {
+ Logger.logException("Error releasing model for " + locationString(info.buffer), e); //$NON-NLS-1$
+ }
+ }
+ // [265899]
+ // In some scenarios, a model can be held onto after the editor has been disposed even if the lifecycle is
+ // maintained properly (e.g., an editor being closed before a DirtyRegionProcessor has a chance to complete). Because of this,
+ // the manager cannot be reliant upon the FileBufferMapper having the sole responsibility of the fDocumentMap cleanup
+ checkReferenceCounts(info, document);
+ }
+ }
+
+ public void revert(IDocument document) {
+ if (document == null) {
+ Exception iae = new IllegalArgumentException("can not revert a model without a document reference"); //$NON-NLS-1$
+ Logger.logException(iae);
+ return;
+ }
+ DocumentInfo info = (DocumentInfo) fDocumentMap.get(document);
+ if (info == null) {
+ Logger.log(Logger.ERROR, "FileBufferModelManager was asked to revert a document that was not being managed"); //$NON-NLS-1$
+ }
+ else {
+ // get path just for potential error message
+ try {
+ // ISSUE: in future, clients should provide progress monitor
+ info.buffer.revert(getProgressMonitor());
+ }
+ catch (CoreException e) {
+ // ISSUE: should we not be re-throwing CoreExceptions? Or
+ // not catch them at all?
+ Logger.logException("Error reverting model for " + locationString(info.buffer), e); //$NON-NLS-1$
+ }
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/IExecutionDelegate.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/IExecutionDelegate.java
new file mode 100644
index 0000000000..63c5894a52
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/IExecutionDelegate.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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;
+
+import org.eclipse.core.runtime.ISafeRunnable;
+
+/**
+ * An abstraction that allows even processing to be performed in a different
+ * context, e.g. a different Thread, if needed.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IExecutionDelegate {
+
+ void execute(ISafeRunnable runnable);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ILockable.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ILockable.java
new file mode 100644
index 0000000000..c8aa5005af
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ILockable.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+import org.eclipse.core.runtime.jobs.ILock;
+
+/**
+ *
+ * Not API: not to be used or implemented by clients. This is a special
+ * purpose interface to help guard some threading issues betweeen model and
+ * document. Will be changed soon.
+ *
+ */
+
+public interface ILockable {
+
+ ILock getLockObject();
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/JSPAwareAdapterFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/JSPAwareAdapterFactory.java
new file mode 100644
index 0000000000..a9e229254b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/JSPAwareAdapterFactory.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+
+
+public interface JSPAwareAdapterFactory extends INodeAdapterFactory {
+
+ void initializeWith(EmbeddedTypeHandler embeddedContentType);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/Logger.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/Logger.java
new file mode 100644
index 0000000000..f826867a1e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/Logger.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal;
+
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.Bundle;
+
+/**
+ * Small convenience class to log messages to plugin's log file and also, if
+ * desired, the console. This class should only be used by classes in this
+ * plugin. Other plugins should make their own copy, with appropriate ID.
+ */
+public class Logger {
+ private static final String PLUGIN_ID = "org.eclipse.wst.sse.core"; //$NON-NLS-1$
+ /**
+ * true if both platform and this plugin are in debug mode
+ */
+ public static final boolean DEBUG = Platform.inDebugMode() && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/debug")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging adapter
+ * notification time
+ */
+ public static final boolean DEBUG_ADAPTERNOTIFICATIONTIME = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/dom/adapter/notification/time")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging structured
+ * document
+ */
+ public static final boolean DEBUG_DOCUMENT = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/structureddocument")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging file buffer
+ * model management
+ */
+ public static final boolean DEBUG_FILEBUFFERMODELMANAGEMENT = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/filebuffers/modelmanagement")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging file buffer
+ * models not being released on shutdown
+ */
+ public static final boolean DEBUG_FILEBUFFERMODELLEAKS = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/filebuffers/leaks")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging formatting
+ */
+ public static final boolean DEBUG_FORMAT = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/format")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging text buffer
+ * lifecycle
+ */
+ public static final boolean DEBUG_TEXTBUFFERLIFECYCLE = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/filebuffers/lifecycle")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging model
+ * lifecycle
+ */
+ public static final boolean DEBUG_LIFECYCLE = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/structuredmodel/lifecycle")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging model state
+ */
+ public static final boolean DEBUG_MODELSTATE = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/structuredmodel/state")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging model lock
+ * state
+ */
+ public static final boolean DEBUG_MODELLOCK = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/structuredmodel/locks")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging model
+ * manager
+ */
+ public static final boolean DEBUG_MODELMANAGER = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/structuredmodel/modelmanager")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ */
+ public static final boolean DEBUG_TASKS = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * content type detection
+ */
+ public static final boolean DEBUG_TASKSCONTENTTYPE = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/detection")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * jobs
+ */
+ public static final boolean DEBUG_TASKSJOB = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/job")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * overall performance
+ */
+ public static final boolean DEBUG_TASKSOVERALLPERF = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/overalltime")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * performance
+ */
+ public static final boolean DEBUG_TASKSPERF = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/time")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * preferences
+ */
+ public static final boolean DEBUG_TASKSPREFS = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/preferences")); //$NON-NLS-1$ //$NON-NLS-2$
+ /**
+ * true if platform and plugin are in debug mode and debugging task tags
+ * registry
+ */
+ public static final boolean DEBUG_TASKSREGISTRY = DEBUG && "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.core/tasks/registry")); //$NON-NLS-1$ //$NON-NLS-2$
+
+ /*
+ * Keep our own copy in case we want to add other severity levels
+ */
+ public static final int OK = IStatus.OK;
+ public static final int INFO = IStatus.INFO;
+ public static final int WARNING = IStatus.WARNING;
+ public static final int ERROR = IStatus.ERROR;
+ public static final int OK_DEBUG = 200 + OK;
+ public static final int INFO_DEBUG = 200 + INFO;
+ public static final int WARNING_DEBUG = 200 + WARNING;
+ public static final int ERROR_DEBUG = 200 + ERROR;
+
+ /**
+ * @return true if the platform is debugging
+ */
+ private static boolean isDebugging() {
+ return Platform.inDebugMode();
+ }
+
+ /**
+ * Adds message to log.
+ *
+ * @param level
+ * severity level of the message (OK, INFO, WARNING, ERROR,
+ * @param message
+ * text to add to the log
+ * @param exception
+ * exception thrown
+ */
+ private static void _log(int level, String message, Throwable exception) {
+ if (level == OK_DEBUG || level == INFO_DEBUG || level == WARNING_DEBUG || level == ERROR_DEBUG) {
+ if (!isDebugging())
+ return;
+ }
+ int severity = IStatus.OK;
+ switch (level) {
+ case INFO_DEBUG :
+ case INFO :
+ severity = IStatus.INFO;
+ break;
+ case WARNING_DEBUG :
+ case WARNING :
+ severity = IStatus.WARNING;
+ break;
+ case ERROR_DEBUG :
+ case ERROR :
+ severity = IStatus.ERROR;
+ }
+ message = (message != null) ? message : ""; //$NON-NLS-1$
+ Status statusObj = new Status(severity, PLUGIN_ID, severity, message, exception);
+ Bundle bundle = Platform.getBundle(PLUGIN_ID);
+ if (bundle != null)
+ Platform.getLog(bundle).log(statusObj);
+ }
+
+ /**
+ * Write a message to the log with the given severity level
+ *
+ * @param level
+ * ERROR, WARNING, INFO, OK
+ * @param message
+ * message to add to the log
+ */
+ public static void log(int level, String message) {
+ _log(level, message, null);
+ }
+
+ /**
+ * Writes a message and exception to the log with the given severity level
+ *
+ * @param level
+ * ERROR, WARNING, INFO, OK
+ * @param message
+ * message to add to the log
+ * @param exception
+ * exception to add to the log
+ */
+ public static void log(int level, String message, Throwable exception) {
+ _log(level, message, exception);
+ }
+
+ /**
+ * Writes the exception as an error in the log along with an accompanying
+ * message
+ *
+ * @param message
+ * message to add to the log
+ * @param exception
+ * exception to add to the log
+ */
+ public static void logException(String message, Throwable exception) {
+ _log(IStatus.ERROR, message, exception);
+ }
+
+ /**
+ * Writes the exception as an error in the log
+ *
+ * @param exception
+ * exception to add to the log
+ */
+ public static void logException(Throwable exception) {
+ _log(IStatus.ERROR, exception.getMessage(), exception);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ModelManagerPluginRegistryReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ModelManagerPluginRegistryReader.java
new file mode 100644
index 0000000000..2b9025fdd2
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ModelManagerPluginRegistryReader.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Vector;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IDocumentTypeHandler;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+import org.osgi.framework.Bundle;
+
+
+public class ModelManagerPluginRegistryReader {
+ private static ModelManagerPluginRegistryReader reader = null;
+
+ public synchronized static ModelManagerPluginRegistryReader getInstance() {
+ if (reader == null) {
+ reader = new ModelManagerPluginRegistryReader();
+ }
+ return reader;
+ }
+
+ protected final String ATTR_ADAPTERKEY = "adapterKeyClass"; //$NON-NLS-1$
+ protected final String ATTR_CLASS = "class"; //$NON-NLS-1$
+ protected final String ATTR_CONTENTTYPE = "type"; //$NON-NLS-1$
+ protected final String ATTR_REGISTERADAPTER = "registerAdapters"; //$NON-NLS-1$
+
+ protected final String EXTENSION_POINT_ID = "adaptOnCreateFactory"; //$NON-NLS-1$
+ protected final String TAG_NAME = "AdaptOnCreateFactory"; //$NON-NLS-1$
+
+ /**
+ * XMLEditorPluginRegistryReader constructor comment.
+ */
+ protected ModelManagerPluginRegistryReader() {
+ super();
+ }
+
+ public List getFactories(IDocumentTypeHandler handler) {
+ return loadRegistry(handler.getId());
+ }
+
+ public List getFactories(String type) {
+ return loadRegistry(type);
+ }
+
+ protected INodeAdapterFactory loadFactoryFromConfigurationElement(IConfigurationElement element, Object requesterType) {
+ INodeAdapterFactory factory = null;
+ if (element.getName().equals(TAG_NAME)) {
+ String contentType = element.getAttribute(ATTR_CONTENTTYPE);
+ if (!contentType.equals(requesterType))
+ return null;
+ String className = element.getAttribute(ATTR_CLASS);
+ String adapterKeyClass = element.getAttribute(ATTR_ADAPTERKEY);
+ String registerAdapters = element.getAttribute(ATTR_REGISTERADAPTER);
+
+ // if className is null, then no one defined the extension point
+ // for adapter factories
+ if (className != null) {
+ String name = element.getDeclaringExtension().getNamespace();
+ Bundle bundle = null;
+ try {
+ bundle = Platform.getBundle(name);
+ }
+ catch (Exception e) {
+ // if an error occurs here, its probably that the plugin
+ // could not be found/loaded
+ Logger.logException("Could not find bundle: " + name, e); //$NON-NLS-1$
+
+ }
+ if (bundle != null) {
+ boolean useExtendedConstructor = false;
+ boolean doRegisterAdapters = false;
+ Object adapterKey = null;
+
+ if (registerAdapters != null && registerAdapters.length() > 0 && Boolean.valueOf(registerAdapters).booleanValue()) {
+ doRegisterAdapters = true;
+ }
+ if (adapterKeyClass != null) {
+ try {
+ Class aClass = null;
+ // aClass = classLoader != null ?
+ // classLoader.loadClass(adapterKeyClass) :
+ // Class.forName(adapterKeyClass);
+ if (bundle.getState() != Bundle.UNINSTALLED) {
+ aClass = bundle.loadClass(adapterKeyClass);
+ }
+ else {
+ aClass = Class.forName(adapterKeyClass);
+ }
+ if (aClass != null) {
+ useExtendedConstructor = true;
+ adapterKey = aClass;
+ }
+ else {
+ adapterKey = adapterKeyClass;
+ }
+ }
+ catch (Exception anyErrors) {
+ adapterKey = adapterKeyClass;
+ }
+ }
+
+ try {
+ Class theClass = null;
+ // Class theClass = classLoader != null ?
+ // classLoader.loadClass(className) :
+ // Class.forName(className);
+ if (bundle.getState() != Bundle.UNINSTALLED) {
+ theClass = bundle.loadClass(className);
+ }
+ else {
+ theClass = Class.forName(className);
+ }
+ if (useExtendedConstructor) {
+ java.lang.reflect.Constructor[] ctors = theClass.getConstructors();
+ for (int i = 0; i < ctors.length; i++) {
+ Class[] paramTypes = ctors[i].getParameterTypes();
+ if (ctors[i].isAccessible() && paramTypes.length == 2 && paramTypes[0].equals(Object.class) && paramTypes[1].equals(boolean.class)) {
+ try {
+ factory = (INodeAdapterFactory) ctors[i].newInstance(new Object[]{adapterKey, new Boolean(doRegisterAdapters)});
+ }
+ catch (IllegalAccessException e) {
+ // log for now, unless we find reason
+ // not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ catch (IllegalArgumentException e) {
+ // log for now, unless we find reason
+ // not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ catch (InstantiationException e) {
+ // log for now, unless we find reason
+ // not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ catch (InvocationTargetException e) {
+ // log for now, unless we find reason
+ // not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ catch (ExceptionInInitializerError e) {
+ // log or now, unless we find reason
+ // not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ }
+ }
+ }
+ if (factory == null) {
+ factory = (INodeAdapterFactory) element.createExecutableExtension(ATTR_CLASS);
+ }
+ }
+ catch (ClassNotFoundException e) {
+ // log or now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ catch (CoreException e) {
+ // log or now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ }
+ }
+ }
+ return factory;
+ }
+
+ protected List loadRegistry(Object contentType) {
+ List factoryList = new Vector();
+ IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
+ IExtensionPoint point = extensionRegistry.getExtensionPoint(SSECorePlugin.ID, EXTENSION_POINT_ID);
+ if (point != null) {
+ IConfigurationElement[] elements = point.getConfigurationElements();
+ for (int i = 0; i < elements.length; i++) {
+ INodeAdapterFactory factory = loadFactoryFromConfigurationElement(elements[i], contentType);
+ if (factory != null)
+ factoryList.add(factory);
+ }
+ }
+ return factoryList;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NoCancelProgressMonitor.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NoCancelProgressMonitor.java
new file mode 100644
index 0000000000..c9f129f5e6
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NoCancelProgressMonitor.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+
+public class NoCancelProgressMonitor extends NullProgressMonitor {
+
+
+ public NoCancelProgressMonitor() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.IProgressMonitor#isCanceled()
+ */
+ public boolean isCanceled() {
+
+ return false;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NotImplementedException.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NotImplementedException.java
new file mode 100644
index 0000000000..3a0b985c13
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NotImplementedException.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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;
+
+
+public class NotImplementedException extends RuntimeException {
+ /**
+ * Comment for <code>serialVersionUID</code>
+ */
+ private static final long serialVersionUID = 1L;
+
+ public NotImplementedException() {
+ super();
+ }
+
+ public NotImplementedException(String message) {
+ super(message);
+ }
+
+ public NotImplementedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NotImplementedException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NullMemento.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NullMemento.java
new file mode 100644
index 0000000000..9da4305f3d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/NullMemento.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+import org.eclipse.wst.sse.core.internal.encoding.NonContentBasedEncodingRules;
+
+
+
+/**
+ * This class can be used in place of an EncodingMemento (its super class),
+ * when there is not in fact ANY encoding information. For example, when a
+ * structuredDocument is created directly from a String
+ */
+public class NullMemento extends EncodingMemento {
+ /**
+ *
+ */
+ public NullMemento() {
+ super();
+ String defaultCharset = NonContentBasedEncodingRules.useDefaultNameRules(null);
+ setJavaCharsetName(defaultCharset);
+ setAppropriateDefault(defaultCharset);
+ setDetectedCharsetName(null);
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapter.java
new file mode 100644
index 0000000000..401c484773
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapter.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier;
+
+public interface PropagatingAdapter extends INodeAdapter {
+
+ void addAdaptOnCreateFactory(INodeAdapterFactory factory);
+
+ List getAdaptOnCreateFactories();
+
+ /**
+ * This method should be called immediately after adding a factory,
+ * typically on the document (top level) node, so all nodes can be
+ * adapted, if needed. This is needed for those occasions when a factory
+ * is addeded after some nodes may have already been created at the time
+ * the factory is added.
+ */
+ void initializeForFactory(INodeAdapterFactory factory, INodeNotifier node);
+
+ // dmw: should have getFactoryFor?
+ void release();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapterFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapterFactory.java
new file mode 100644
index 0000000000..70c84dafb9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/PropagatingAdapterFactory.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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;
+
+
+
+import java.util.ArrayList;
+
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+
+
+
+public interface PropagatingAdapterFactory extends INodeAdapterFactory {
+
+ void addContributedFactories(INodeAdapterFactory factory);
+
+ void setContributedFactories(ArrayList list);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECoreMessages.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECoreMessages.java
new file mode 100644
index 0000000000..4546839af9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECoreMessages.java
@@ -0,0 +1,50 @@
+/**********************************************************************
+ * Copyright (c) 2005, 2010 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 - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.wst.sse.core.internal;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Strings used by SSE Core
+ *
+ * @plannedfor 1.0
+ */
+public class SSECoreMessages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.wst.sse.core.internal.SSECorePluginResources";//$NON-NLS-1$
+
+ static {
+ // load message values from bundle file
+ NLS.initializeMessages(BUNDLE_NAME, SSECoreMessages.class);
+ }
+
+ private SSECoreMessages() {
+ // cannot create new instance
+ }
+
+ public static String A_model_s_id_can_not_be_nu_EXC_;
+ public static String Program_Error__ModelManage_EXC_;
+ public static String Original_Error__UI_;
+ public static String Text_Change_UI_;
+ public static String TaskScanner_0;
+ public static String TaskScanningJob_0;
+ public static String TaskScanningJob_1;
+ public static String Migrate_Charset;
+
+ public static String IndexManager_0_starting;
+ public static String IndexManager_0_starting_1;
+ public static String IndexManager_0_Indexing_1_Files;
+ public static String IndexManager_processing_deferred_resource_changes;
+ public static String IndexManager_Processing_entire_workspace_for_the_first_time;
+ public static String IndexManager_0_Processing_entire_workspace_for_the_first_time;
+ public static String IndexManager_processing_recent_resource_changes;
+ public static String IndexManager_0_resources_to_go_1;
+ public static String IndexManager_Waiting_for_0;
+ public static String IndexManager_0_Processing_resource_events;
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePlugin.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePlugin.java
new file mode 100644
index 0000000000..15e32bb35a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePlugin.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal;
+
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Preferences;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.encoding.CommonEncodingPreferenceNames;
+import org.eclipse.wst.sse.core.internal.modelhandler.ModelHandlerRegistry;
+import org.eclipse.wst.sse.core.internal.preferences.CommonModelPreferenceNames;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.tasks.TaskScanningScheduler;
+import org.osgi.framework.BundleContext;
+
+
+/**
+ * SSE Core Plugin.
+ */
+public class SSECorePlugin extends Plugin {
+ static SSECorePlugin instance = null;
+
+ public static final String ID = "org.eclipse.wst.sse.core"; //$NON-NLS-1$
+
+ public static SSECorePlugin getDefault() {
+ return instance;
+ }
+
+ public SSECorePlugin() {
+ super();
+ instance = this;
+ }
+
+ /**
+ * Set default non-UI
+ */
+ protected void initializeDefaultPluginPreferences() {
+ Preferences prefs = getDefault().getPluginPreferences();
+ // set model preference defaults
+
+ prefs.setDefault(CommonEncodingPreferenceNames.USE_3BYTE_BOM_WITH_UTF8, false);
+
+ prefs.setDefault(CommonModelPreferenceNames.TASK_TAG_ENABLE, false);
+ prefs.setDefault(CommonModelPreferenceNames.TASK_TAG_TAGS, "TODO,FIXME,XXX"); //$NON-NLS-1$
+ prefs.setDefault(CommonModelPreferenceNames.TASK_TAG_PRIORITIES, "1,2,1"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ savePluginPreferences();
+
+ TaskScanningScheduler.shutdown();
+
+ FileBufferModelManager.shutdown();
+
+ super.stop(context);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.Plugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+
+ // initialize FileBuffer handling
+ FileBufferModelManager.startup();
+
+ /**
+ * If the user starts the workbench with
+ * -Dorg.eclipse.wst.sse.core.taskscanner=off, the scanner should be
+ * disabled
+ */
+ String scan = System.getProperty("org.eclipse.wst.sse.core.taskscanner"); //$NON-NLS-1$
+ if (scan == null || !scan.equalsIgnoreCase("off")) { //$NON-NLS-1$
+ TaskScanningScheduler.startup();
+ }
+ }
+
+ /**
+ * @deprecated
+ */
+ public ModelHandlerRegistry getModelHandlerRegistry() {
+ return ModelHandlerRegistry.getInstance();
+ }
+
+ /**
+ * @deprecated - use StructuredModelManager.getModelManager();
+ */
+ public IModelManager getModelManager() {
+ return StructuredModelManager.getModelManager();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePluginResources.properties b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePluginResources.properties
new file mode 100644
index 0000000000..971c0f7509
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/SSECorePluginResources.properties
@@ -0,0 +1,32 @@
+###############################################################################
+# Copyright (c) 2001, 2010 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
+#
+###############################################################################
+A_model_s_id_can_not_be_nu_EXC_=A model's id can not be null
+Program_Error__ModelManage_EXC_=Program Error: ModelManagerImpl::saveModel. Model should be in the cache
+Original_Error__UI_=Original Error:
+Text_Change_UI_=Text Change
+TaskScanner_0=Scanning for Tasks
+TaskScanningJob_0=Scanning
+TaskScanningJob_1=Errors while detecting Tasks
+###############################################################################
+Migrate_Charset=Migrate Charset
+
+IndexManager_0_starting={0}: Starting
+IndexManager_0_starting_1={0}: Starting: {1}
+IndexManager_0_Indexing_1_Files={0}: Indexing {1} Files
+IndexManager_processing_deferred_resource_changes=Processing deferred resource changes
+IndexManager_Processing_entire_workspace_for_the_first_time=Processing entire workspace for the first time
+IndexManager_0_Processing_entire_workspace_for_the_first_time={0}: Processing entire workspace for the first time
+IndexManager_processing_recent_resource_changes=Processing recent resource changes
+IndexManager_0_resources_to_go_1={0} resources to index: {1}
+IndexManager_Waiting_for_0=Waiting for {0}
+IndexManager_0_Processing_resource_events={0}: Processing Resource Events \ No newline at end of file
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/AbstractStructuredCleanupProcessor.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/AbstractStructuredCleanupProcessor.java
new file mode 100644
index 0000000000..72a61e61a6
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/AbstractStructuredCleanupProcessor.java
@@ -0,0 +1,498 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2013 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.cleanup;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Vector;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension4;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.eclipse.wst.sse.core.internal.format.IFormattingDelegate;
+import org.eclipse.wst.sse.core.internal.format.IStructuredFormatProcessor;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Node;
+
+
+public abstract class AbstractStructuredCleanupProcessor implements IStructuredCleanupProcessor {
+ public boolean refreshCleanupPreferences = true; // special flag for JUnit
+ private static IFormattingDelegate delegate;
+
+ // tests to skip refresh
+ // of cleanup preferences
+ // when it's set to false
+
+ public String cleanupContent(String input) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ try {
+ // setup structuredModel
+ inputStream = new ByteArrayInputStream(input.getBytes("UTF8")); //$NON-NLS-1$
+ String id = inputStream.toString() + getContentType();
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(id, inputStream, null);
+
+ // cleanup
+ cleanupModel(structuredModel, 0, structuredModel.getStructuredDocument().getLength());
+
+ // return output
+ return structuredModel.getStructuredDocument().get();
+ } finally {
+ ensureClosed(null, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public String cleanupContent(String input, int start, int length) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ try {
+ // setup structuredModel
+ inputStream = new ByteArrayInputStream(input.getBytes("UTF8")); //$NON-NLS-1$
+ String id = inputStream.toString() + getContentType();
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(id, inputStream, null);
+
+ // cleanup
+ cleanupModel(structuredModel, start, length);
+
+ // return output
+ return structuredModel.getStructuredDocument().get();
+ } finally {
+ ensureClosed(null, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public void cleanupDocument(IDocument document) throws IOException, CoreException {
+ if (document == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ structuredModel = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
+
+ // cleanup
+ cleanupModel(structuredModel);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ } finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+
+ public void cleanupDocument(IDocument document, int start, int length) throws IOException, CoreException {
+ if (document == null)
+ return;
+
+ if (start >= 0 && length >= 0 && start + length <= document.getLength()) {
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if
+ // model changed.
+ structuredModel = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
+
+ // cleanup
+ cleanupModel(structuredModel, start, length);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ } finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+ }
+
+ public void cleanupFile(IFile file) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(file);
+
+ // cleanup
+ cleanupModel(structuredModel, 0, structuredModel.getStructuredDocument().getLength());
+
+ // save output to file
+ // outputStream = new
+ // FileOutputStream(file.getLocation().toString());
+ structuredModel.save(file);
+ } finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public void cleanupFile(IFile file, int start, int length) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(file);
+
+ // cleanup
+ cleanupModel(structuredModel, start, length);
+
+ // save output to file
+ // outputStream = new
+ // FileOutputStream(file.getLocation().toString());
+ structuredModel.save(file);
+ } finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public void cleanupFileName(String fileName) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ inputStream = new FileInputStream(fileName);
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(fileName, inputStream, null);
+
+ // cleanup
+ cleanupModel(structuredModel, 0, structuredModel.getStructuredDocument().getLength());
+
+ // save output to file
+ // outputStream = new FileOutputStream(fileName);
+ structuredModel.save();
+ } finally {
+ // ensureClosed(outputStream, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public void cleanupFileName(String fileName, int start, int length) throws IOException, CoreException {
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ inputStream = new FileInputStream(fileName);
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(fileName, inputStream, null);
+
+ // cleanup
+ cleanupModel(structuredModel, start, length);
+
+ // save output to file
+ // outputStream = new FileOutputStream(fileName);
+ structuredModel.save();
+ } finally {
+ // ensureClosed(outputStream, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public void cleanupModel(IStructuredModel structuredModel) {
+ cleanupModel(structuredModel, null);
+ }
+
+ public void cleanupModel(IStructuredModel structuredModel, int start, int length) {
+ cleanupModel(structuredModel, start, length, null);
+ }
+
+ public void cleanupModel(IStructuredModel structuredModel, Object context) {
+ int start = 0;
+ int length = structuredModel.getStructuredDocument().getLength();
+ cleanupModel(structuredModel, start, length, context);
+ }
+
+ public void cleanupModel(IStructuredModel structuredModel, int start, int length, Object context) {
+
+ if (structuredModel != null) {
+ if ((start >= 0) && (length <= structuredModel.getStructuredDocument().getLength())) {
+ Vector activeNodes = getActiveNodes(structuredModel, start, length);
+ if (activeNodes.size() > 0) {
+ Node firstNode = (Node) activeNodes.firstElement();
+ Node lastNode = (Node) activeNodes.lastElement();
+ boolean done = false;
+ Node eachNode = firstNode;
+ Node nextNode = null;
+
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=123621
+ // if doing any sort of cleanup, set up rewrite session/modelchanged
+ IDocumentExtension4 docExt4 = null;
+ if (structuredModel.getStructuredDocument() instanceof IDocumentExtension4) {
+ docExt4 = (IDocumentExtension4) structuredModel.getStructuredDocument();
+ }
+ DocumentRewriteSession rewriteSession = null;
+
+ try {
+ // whenever formatting model, fire
+ // abouttochange/modelchanged
+ structuredModel.aboutToChangeModel();
+ rewriteSession = (docExt4 == null || docExt4.getActiveRewriteSession() != null) ? null : docExt4.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
+
+ while (!done) {
+ // update "done"
+ done = (eachNode == lastNode);
+
+ // get next sibling before cleanup because eachNode
+ // may
+ // be deleted,
+ // for example when it's an empty text node
+ nextNode = eachNode.getNextSibling();
+
+ // cleanup selected node(s)
+ cleanupNode(eachNode);
+
+ // update each node
+ if (nextNode != null && nextNode.getParentNode() == null)
+ // nextNode is deleted during cleanup
+ eachNode = eachNode.getNextSibling();
+ else
+ eachNode = nextNode;
+
+ // This should not be needed, but just in case
+ // something went wrong with with eachNode.
+ // We don't want an infinite loop here.
+ if (eachNode == null)
+ done = true;
+ }
+
+ // format source
+ if (getFormatSourcePreference(structuredModel)) {
+ // format the document
+ IFormattingDelegate delegate = getFormattingDelegate();
+ if (context != null && delegate != null) {
+ delegate.format(context);
+ }
+ else {
+ IStructuredFormatProcessor formatProcessor = getFormatProcessor();
+ formatProcessor.formatModel(structuredModel);
+ }
+ }
+ }
+ finally {
+ // we need two finally's, just in case first fails
+ try {
+ if ((docExt4 != null) && (rewriteSession != null))
+ docExt4.stopRewriteSession(rewriteSession);
+ }
+ finally {
+ // always make sure to fire changedmodel when done
+ structuredModel.changedModel();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void cleanupNode(Node node) {
+ if (node != null) {
+ Node cleanupNode = node;
+
+ // cleanup the owner node if it's an attribute node
+ if (cleanupNode.getNodeType() == Node.ATTRIBUTE_NODE)
+ cleanupNode = ((Attr) cleanupNode).getOwnerElement();
+
+ // refresh cleanup preferences before getting cleanup handler
+ if (refreshCleanupPreferences)
+ refreshCleanupPreferences();
+
+ // get cleanup handler
+ IStructuredCleanupHandler cleanupHandler = getCleanupHandler(cleanupNode);
+ if (cleanupHandler != null) {
+ // cleanup each node
+ cleanupHandler.cleanup(cleanupNode);
+ }
+ }
+ }
+
+ protected void convertLineDelimiters(IDocument document, String newDelimiter) {
+ final int lineCount = document.getNumberOfLines();
+ Map partitioners = TextUtilities.removeDocumentPartitioners(document);
+ try {
+ for (int i = 0; i < lineCount; i++) {
+ final String delimiter = document.getLineDelimiter(i);
+ if (delimiter != null && delimiter.length() > 0 && !delimiter.equals(newDelimiter)) {
+ IRegion region = document.getLineInformation(i);
+ document.replace(region.getOffset() + region.getLength(), delimiter.length(), newDelimiter);
+ }
+ }
+ } catch (BadLocationException e) {
+ Logger.logException(e);
+ } finally {
+ TextUtilities.addDocumentPartitioners(document, partitioners);
+ }
+ }
+
+ protected void ensureClosed(OutputStream outputStream, InputStream inputStream) {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ Logger.logException(e); // hopeless
+ }
+ try {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ } catch (IOException e) {
+ Logger.logException(e); // hopeless
+ }
+ }
+
+ protected Vector getActiveNodes(IStructuredModel structuredModel, int startNodeOffset, int length) {
+ Vector activeNodes = new Vector();
+
+ if (structuredModel != null) {
+ Node startNode = (Node) structuredModel.getIndexedRegion(startNodeOffset);
+ Node endNode = (Node) structuredModel.getIndexedRegion(startNodeOffset + length);
+
+ // make sure it's an non-empty document
+ if (startNode != null) {
+ while (isSiblingOf(startNode, endNode) == false) {
+ if (endNode != null)
+ endNode = endNode.getParentNode();
+ if (endNode == null) {
+ startNode = startNode.getParentNode();
+ endNode = (Node) structuredModel.getIndexedRegion(startNodeOffset + length);
+ }
+ }
+
+ while (startNode != endNode) {
+ activeNodes.addElement(startNode);
+ startNode = startNode.getNextSibling();
+ }
+ if (startNode != null)
+ activeNodes.addElement(startNode);
+ }
+ }
+
+ return activeNodes;
+ }
+
+ abstract protected IStructuredCleanupHandler getCleanupHandler(Node node);
+
+ abstract protected String getContentType();
+
+ protected boolean getConvertEOLCodesPreference(IStructuredModel structuredModel) {
+
+ boolean convertEOLCodes = true;
+ IStructuredCleanupHandler cleanupHandler = getCleanupHandler((Node) structuredModel.getIndexedRegion(0));
+ if (cleanupHandler != null) {
+ IStructuredCleanupPreferences cleanupPreferences = cleanupHandler.getCleanupPreferences();
+ convertEOLCodes = cleanupPreferences.getConvertEOLCodes();
+ }
+ return convertEOLCodes;
+ }
+
+ protected String getEOLCodePreference(IStructuredModel structuredModel) {
+
+ IScopeContext[] scopeContext = new IScopeContext[]{new InstanceScope()};
+ String eolCode = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, scopeContext);
+
+ IStructuredCleanupHandler cleanupHandler = getCleanupHandler((Node) structuredModel.getIndexedRegion(0));
+ if (cleanupHandler != null) {
+ IStructuredCleanupPreferences cleanupPreferences = cleanupHandler.getCleanupPreferences();
+ eolCode = cleanupPreferences.getEOLCode();
+ }
+ return eolCode;
+ }
+
+ abstract protected IStructuredFormatProcessor getFormatProcessor();
+
+ protected boolean getFormatSourcePreference(IStructuredModel structuredModel) {
+
+ boolean formatSource = true;
+ IStructuredCleanupHandler cleanupHandler = getCleanupHandler((Node) structuredModel.getIndexedRegion(0));
+ if (cleanupHandler != null) {
+ IStructuredCleanupPreferences cleanupPreferences = cleanupHandler.getCleanupPreferences();
+ formatSource = cleanupPreferences.getFormatSource();
+ }
+ return formatSource;
+ }
+
+ protected boolean isSiblingOf(Node node, Node endNode) {
+ if (endNode == null) {
+ return true;
+ } else {
+ Node siblingNode = node;
+ while (siblingNode != null) {
+ if (siblingNode == endNode)
+ return true;
+ else
+ siblingNode = siblingNode.getNextSibling();
+ }
+ return false;
+ }
+ }
+
+ abstract protected void refreshCleanupPreferences();
+
+ private synchronized IFormattingDelegate getFormattingDelegate() {
+ if (delegate == null) {
+ IConfigurationElement[] element = Platform.getExtensionRegistry().getConfigurationElementsFor(SSECorePlugin.ID, "formattingDelegate"); //$NON-NLS-1$
+ if (element.length > 0) {
+ try {
+ Object d = element[0].createExecutableExtension("class");
+ if (d instanceof IFormattingDelegate) {
+ delegate = (IFormattingDelegate) d;
+ }
+ } catch (CoreException e) {
+ Logger.logException("Exception while creating the formatting delegate.", e);
+ }
+ }
+ }
+ return delegate;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupHandler.java
new file mode 100644
index 0000000000..4f98956ed3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupHandler.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+import org.w3c.dom.Node;
+
+public interface IStructuredCleanupHandler {
+ Node cleanup(Node node);
+
+ IStructuredCleanupPreferences getCleanupPreferences();
+
+ void setCleanupPreferences(IStructuredCleanupPreferences cleanupPreferences);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupPreferences.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupPreferences.java
new file mode 100644
index 0000000000..7f5058da91
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupPreferences.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+import org.eclipse.core.runtime.Preferences;
+
+public interface IStructuredCleanupPreferences {
+
+ int getAttrNameCase();
+
+ boolean getCompressEmptyElementTags();
+
+ boolean getConvertEOLCodes();
+
+ String getEOLCode();
+
+ boolean getFormatSource();
+
+ boolean getInsertMissingTags();
+
+ boolean getInsertRequiredAttrs();
+
+ boolean getQuoteAttrValues();
+
+ int getTagNameCase();
+
+ void setAttrNameCase(int attrNameCase);
+
+ void setCompressEmptyElementTags(boolean compressEmptyElementTags);
+
+ void setConvertEOLCodes(boolean convertEOLCodes);
+
+ void setEOLCode(String EOLCode);
+
+ void setFormatSource(boolean formatSource);
+
+ void setInsertMissingTags(boolean insertMissingTags);
+
+ void setInsertRequiredAttrs(boolean insertRequiredAttrs);
+
+ void setPreferences(Preferences preferences);
+
+ void setQuoteAttrValues(boolean quoteAttrValues);
+
+ void setTagNameCase(int tagNameCase);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupProcessor.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupProcessor.java
new file mode 100644
index 0000000000..27bce4cb0d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/IStructuredCleanupProcessor.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+import java.io.IOException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.w3c.dom.Node;
+
+/**
+ * This interface and related classes are 'internal' and should not
+ * be treated as API, even though used across components in WTP.
+ * Consider it a work in progress.
+ */
+
+public interface IStructuredCleanupProcessor {
+ /**
+ * This form of the CleanupProcessor takes an input string as input,
+ * creates an InputStream from the input string, create a temporary model
+ * of the content type specified, cleanups the whole model, then returns
+ * the cleaned up input string.
+ */
+ String cleanupContent(String content) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes an input string as input,
+ * creates an InputStream from the input string, create a temporary model
+ * of the content type specified, cleanups the model within start and
+ * length, then returns the cleaned up input string.
+ */
+ String cleanupContent(String content, int start, int length) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes an IDocument as input, creates
+ * a temporary model of content type calculated using the IDocument's file
+ * extension, cleanups the whole model, then releases the model.
+ */
+ void cleanupDocument(IDocument document) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes an IDocument as input, creates
+ * a temporary model of content type calculated using the IDocument's file
+ * extension, cleanups the model within start and length, then releases
+ * the model.
+ */
+ void cleanupDocument(IDocument document, int start, int length) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes an IFile as input, creates a
+ * temporary model of content type calculated using the IFile's file
+ * extension, cleanups the whole model, then releases the model. The IFile
+ * is updated when the last reference of the model is released in the
+ * model manager.
+ */
+ void cleanupFile(IFile file) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes an IFile as input, creates a
+ * temporary model of content type calculated using the IFile's file
+ * extension, cleanups the model within start and length, then releases
+ * the model. The IFile is updated when the last reference of the model is
+ * released in the model manager.
+ */
+ void cleanupFile(IFile file, int start, int length) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes a file name as input,creates an
+ * InputStream from the file, create a temporary model of content type
+ * calculated using the file name's file extension, cleanups the whole
+ * model, then releases the model. The file is updated when the last
+ * reference of the model is released in the model manager.
+ */
+ void cleanupFileName(String fileName) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes a file name as input,creates an
+ * InputStream from the file, create a temporary model of content type
+ * calculated using the file name's file extension, cleanups the model
+ * within start and length, then releases the model. The file is updated
+ * when the last reference of the model is released in the model manager.
+ */
+ void cleanupFileName(String fileName, int start, int length) throws IOException, CoreException;
+
+ /**
+ * This form of the CleanupProcessor takes a model as input, and cleanups
+ * the whole model.
+ */
+ void cleanupModel(IStructuredModel structuredModel);
+
+ /**
+ * This form of the CleanupProcessor takes a model as input, and cleanups
+ * the model within start and length.
+ */
+ void cleanupModel(IStructuredModel structuredModel, int start, int length);
+
+ /**
+ * This form of the CleanupProcessor takes a node as input, and formats
+ * the node and all its children.
+ */
+ void cleanupNode(Node node);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredCleanupPreferences.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredCleanupPreferences.java
new file mode 100644
index 0000000000..a83441804c
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredCleanupPreferences.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+import org.eclipse.core.runtime.Preferences;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+
+
+public class StructuredCleanupPreferences implements IStructuredCleanupPreferences {
+ private int fAttrNameCase;
+ private boolean fCompressEmptyElementTags;
+ private boolean fConvertEOLCodes;
+ private String fEOLCode;
+ private boolean fFormatSource;
+ private boolean fInsertMissingTags;
+ private boolean fInsertRequiredAttrs;
+ //private IPreferenceStore fPreferenceStore = null;
+ private Preferences fPreferences = null;
+ private boolean fQuoteAttrValues;
+
+ private int fTagNameCase;
+
+ public int getAttrNameCase() {
+
+ return fAttrNameCase;
+ }
+
+ public boolean getCompressEmptyElementTags() {
+
+ return fCompressEmptyElementTags;
+ }
+
+ public boolean getConvertEOLCodes() {
+
+ return fConvertEOLCodes;
+ }
+
+ public String getEOLCode() {
+
+ return fEOLCode;
+ }
+
+ public boolean getFormatSource() {
+
+ return fFormatSource;
+ }
+
+ public boolean getInsertMissingTags() {
+
+ return fInsertMissingTags;
+ }
+
+ public boolean getInsertRequiredAttrs() {
+
+ return fInsertRequiredAttrs;
+ }
+
+ public Preferences getPreferences() {
+
+ if (fPreferences == null) {
+ fPreferences = SSECorePlugin.getDefault().getPluginPreferences();
+ }
+ return fPreferences;
+ }
+
+ public boolean getQuoteAttrValues() {
+
+ return fQuoteAttrValues;
+ }
+
+ public int getTagNameCase() {
+
+ return fTagNameCase;
+ }
+
+ public void setAttrNameCase(int attrNameCase) {
+
+ fAttrNameCase = attrNameCase;
+ }
+
+ public void setCompressEmptyElementTags(boolean compressEmptyElementTags) {
+
+ fCompressEmptyElementTags = compressEmptyElementTags;
+ }
+
+ public void setConvertEOLCodes(boolean convertEOLCodes) {
+
+ fConvertEOLCodes = convertEOLCodes;
+ }
+
+ public void setEOLCode(String EOLCode) {
+
+ fEOLCode = EOLCode;
+ }
+
+ public void setFormatSource(boolean formatSource) {
+
+ fFormatSource = formatSource;
+ }
+
+ public void setInsertMissingTags(boolean insertMissingTags) {
+
+ fInsertMissingTags = insertMissingTags;
+ }
+
+ public void setInsertRequiredAttrs(boolean insertRequiredAttrs) {
+
+ fInsertRequiredAttrs = insertRequiredAttrs;
+ }
+
+ public void setPreferences(Preferences prefs) {
+
+ fPreferences = prefs;
+ }
+
+ public void setQuoteAttrValues(boolean quoteAttrValues) {
+
+ fQuoteAttrValues = quoteAttrValues;
+ }
+
+ public void setTagNameCase(int tagNameCase) {
+
+ fTagNameCase = tagNameCase;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandler.java
new file mode 100644
index 0000000000..6c06ca0169
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandler.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+public interface StructuredContentCleanupHandler {
+
+ IStructuredCleanupProcessor getCleanupProcessor(String contentType);
+
+ void setCleanupProcessor(IStructuredCleanupProcessor cleanupProcessor, String contentType);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandlerImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandlerImpl.java
new file mode 100644
index 0000000000..27a6246787
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/cleanup/StructuredContentCleanupHandlerImpl.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.cleanup;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.text.Assert;
+
+public class StructuredContentCleanupHandlerImpl implements StructuredContentCleanupHandler {
+ protected Map fCleanupProcessors;
+
+ public IStructuredCleanupProcessor getCleanupProcessor(String contentType) {
+ Assert.isNotNull(contentType);
+
+ if (fCleanupProcessors == null)
+ return null;
+
+ return (IStructuredCleanupProcessor) fCleanupProcessors.get(contentType);
+ }
+
+ public void setCleanupProcessor(IStructuredCleanupProcessor cleanupProcessor, String contentType) {
+ Assert.isNotNull(contentType);
+
+ if (fCleanupProcessors == null)
+ fCleanupProcessors = new HashMap();
+
+ if (fCleanupProcessors == null)
+ fCleanupProcessors.remove(contentType);
+ else
+ fCleanupProcessors.put(contentType, cleanupProcessor);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/AbstractDocumentLoader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/AbstractDocumentLoader.java
new file mode 100644
index 0000000000..ea9959e8c0
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/AbstractDocumentLoader.java
@@ -0,0 +1,438 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.document;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.UnmappableCharacterException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ProjectScope;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension3;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.wst.sse.core.internal.encoding.CodedIO;
+import org.eclipse.wst.sse.core.internal.encoding.CodedReaderCreator;
+import org.eclipse.wst.sse.core.internal.encoding.ContentTypeEncodingPreferences;
+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.MalformedInputExceptionWithDetail;
+import org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning;
+
+
+
+/**
+ * This class reads a file and creates an Structured Model.
+ */
+public abstract class AbstractDocumentLoader implements IDocumentLoader {
+
+ private CodedReaderCreator fCodedReaderCreator;
+ protected IDocumentCharsetDetector fDocumentEncodingDetector;
+ // private boolean fPropertiesObtained;
+
+ protected EncodingMemento fEncodingMemento;
+ protected Reader fFullPreparedReader;
+
+ /**
+ * AbstractLoader constructor also initializes encoding converter/mapper
+ */
+ public AbstractDocumentLoader() {
+ super();
+ }
+
+ protected final StringBuffer convertLineDelimiters(StringBuffer allTextBuffer, String lineDelimiterToUse) {
+ // TODO: avoid use of String instance
+ String allText = allTextBuffer.toString();
+ IDocument tempDoc = new Document(allText);
+ if (lineDelimiterToUse == null)
+ lineDelimiterToUse = System.getProperty("line.separator"); //$NON-NLS-1$
+ StringBuffer newText = new StringBuffer();
+ int lineCount = tempDoc.getNumberOfLines();
+ for (int i = 0; i < lineCount; i++) {
+ try {
+ org.eclipse.jface.text.IRegion lineInfo = tempDoc.getLineInformation(i);
+ int lineStartOffset = lineInfo.getOffset();
+ int lineLength = lineInfo.getLength();
+ int lineEndOffset = lineStartOffset + lineLength;
+ newText.append(allText.substring(lineStartOffset, lineEndOffset));
+ if ((i < lineCount - 1) && (tempDoc.getLineDelimiter(i) != null))
+ newText.append(lineDelimiterToUse);
+ }
+ catch (org.eclipse.jface.text.BadLocationException exception) {
+ // should fix up to either throw nothing, or the right thing,
+ // but
+ // in the course of refactoring, this was easiest "quick fix".
+ throw new RuntimeException(exception);
+ }
+ }
+ return newText;
+ }
+
+ /**
+ * This method must return a new instance of IEncodedDocument, that has
+ * been initialized with appropriate parser. For many loaders, the
+ * (default) parser used is known for any input. For others, the correct
+ * parser (and its initialization) is normally dependent on the content of
+ * the file. This no-argument method should assume "empty input" and would
+ * therefore return the default parser for the default contentType.
+ */
+ public IEncodedDocument createNewStructuredDocument() {
+ IEncodedDocument structuredDocument = newEncodedDocument();
+ // Make sure every structuredDocument has an Encoding Memento,
+ // which is the default one for "empty" structuredDocuments
+ String charset = ContentTypeEncodingPreferences.useDefaultNameRules(getDocumentEncodingDetector());
+ String specDefaultCharset = getDocumentEncodingDetector().getSpecDefaultEncoding();
+ structuredDocument.setEncodingMemento(CodedIO.createEncodingMemento(charset, EncodingMemento.DEFAULTS_ASSUMED_FOR_EMPTY_INPUT, specDefaultCharset));
+
+ String lineDelimiter = getPreferredNewLineDelimiter(null);
+ if (lineDelimiter != null)
+ structuredDocument.setPreferredLineDelimiter(lineDelimiter);
+
+ IDocumentPartitioner defaultPartitioner = getDefaultDocumentPartitioner();
+ if (structuredDocument instanceof IDocumentExtension3) {
+ ((IDocumentExtension3) structuredDocument).setDocumentPartitioner(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, defaultPartitioner);
+ }
+ else {
+ structuredDocument.setDocumentPartitioner(defaultPartitioner);
+ }
+ defaultPartitioner.connect(structuredDocument);
+
+ return structuredDocument;
+ }
+
+ /**
+ * This abstract version should handle most cases, but won't if
+ * contentType is sensitive to encoding, and/or embedded types
+ */
+ public IEncodedDocument createNewStructuredDocument(IFile iFile) throws IOException, CoreException {
+ IEncodedDocument structuredDocument = createNewStructuredDocument();
+
+ String lineDelimiter = getPreferredNewLineDelimiter(iFile);
+ if (lineDelimiter != null)
+ structuredDocument.setPreferredLineDelimiter(lineDelimiter);
+
+ try {
+
+ CodedReaderCreator creator = getCodedReaderCreator();
+ creator.set(iFile);
+ fEncodingMemento = creator.getEncodingMemento();
+ structuredDocument.setEncodingMemento(fEncodingMemento);
+ fFullPreparedReader = getCodedReaderCreator().getCodedReader();
+
+ setDocumentContentsFromReader(structuredDocument, fFullPreparedReader);
+ }
+ finally {
+ if (fFullPreparedReader != null) {
+ fFullPreparedReader.close();
+ }
+ }
+ return structuredDocument;
+ }
+
+ public IEncodedDocument createNewStructuredDocument(String filename, InputStream inputStream) throws UnsupportedEncodingException, IOException {
+ return createNewStructuredDocument(filename, inputStream, EncodingRule.CONTENT_BASED);
+ }
+
+ public IEncodedDocument createNewStructuredDocument(String filename, InputStream inputStream, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException {
+ if (filename == null && inputStream == null) {
+ throw new IllegalArgumentException("can not have both null filename and inputstream"); //$NON-NLS-1$
+ }
+ IEncodedDocument structuredDocument = createNewStructuredDocument();
+ CodedReaderCreator codedReaderCreator = getCodedReaderCreator();
+ try {
+ codedReaderCreator.set(filename, inputStream);
+ codedReaderCreator.setEncodingRule(encodingRule);
+ fEncodingMemento = codedReaderCreator.getEncodingMemento();
+ fFullPreparedReader = codedReaderCreator.getCodedReader();
+ structuredDocument.setEncodingMemento(fEncodingMemento);
+ setDocumentContentsFromReader(structuredDocument, fFullPreparedReader);
+ }
+ catch (CoreException e) {
+ // impossible in this context
+ throw new Error(e);
+ }
+ finally {
+ if (fFullPreparedReader != null) {
+ fFullPreparedReader.close();
+ }
+ }
+
+ return structuredDocument;
+ }
+
+ private int getCharPostionOfFailure(BufferedReader inputStream) {
+ int charPosition = 1;
+ int charRead = -1;
+ boolean errorFound = false;
+ do {
+ try {
+ charRead = inputStream.read();
+ charPosition++;
+ }
+ catch (IOException e) {
+ // this is expected, since we're expecting failure,
+ // so no need to do anything.
+ errorFound = true;
+ break;
+ }
+ }
+ while (!(charRead == -1 || errorFound));
+
+ if (errorFound)
+ // dmw, blindly modified to +1 to get unit tests to work, moving
+ // from Java 1.3, to 1.4
+ // not sure how/why this behavior would have changed. (Its as if
+ // 'read' is reporting error
+ // one character early).
+ return charPosition + 1;
+ else
+ return -1;
+ }
+
+ /**
+ * @return Returns the codedReaderCreator.
+ */
+ protected CodedReaderCreator getCodedReaderCreator() {
+ if (fCodedReaderCreator == null) {
+ fCodedReaderCreator = new CodedReaderCreator();
+ }
+ return fCodedReaderCreator;
+ }
+
+ /**
+ * Creates the partitioner to be used with the
+ * IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING partitioning
+ *
+ * @return IDocumentPartitioner
+ */
+ public abstract IDocumentPartitioner getDefaultDocumentPartitioner();
+
+ /**
+ * Returns the encodingMemento.
+ *
+ * @return EncodingMemento
+ */
+ public EncodingMemento getEncodingMemento() {
+ if (fEncodingMemento == null) {
+ throw new IllegalStateException("Program Error: encodingMemento was accessed before it was set"); //$NON-NLS-1$
+ }
+ return fEncodingMemento;
+ }
+
+ /**
+ * @return Returns the fullPreparedReader.
+ */
+ protected Reader getFullPreparedReader() throws UnsupportedEncodingException, CoreException, IOException {
+ if (fFullPreparedReader == null) {
+ fFullPreparedReader = getCodedReaderCreator().getCodedReader();
+ }
+ return fFullPreparedReader;
+ }
+
+ /**
+ * Returns the default line delimiter preference for the given file.
+ *
+ * @param file
+ * the file
+ * @return the default line delimiter
+ * @since 3.1
+ */
+ private String getPlatformLineDelimiterPreference(IFile file) {
+ IScopeContext[] scopeContext;
+ if (file != null && file.getProject() != null) {
+ // project preference
+ scopeContext = new IScopeContext[]{new ProjectScope(file.getProject())};
+ String lineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, scopeContext);
+ if (lineDelimiter != null)
+ return lineDelimiter;
+ }
+ // workspace preference
+ scopeContext = new IScopeContext[]{new InstanceScope()};
+ return Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, scopeContext);
+ }
+
+ /**
+ * @deprecated use getPreferredNewLineDelimiter(IFile) instead
+ */
+ protected String getPreferredNewLineDelimiter() {
+ return getPreferredNewLineDelimiter(null);
+ }
+
+ /**
+ * If subclass doesn't implement, return platform default
+ */
+ protected String getPreferredNewLineDelimiter(IFile file) {
+ return getPlatformLineDelimiterPreference(file);
+ }
+
+ /**
+ * A utility method, but depends on subclasses to impliment the preferred
+ * end of line for a particular content type. Note: subclasses should not
+ * re-implement this method (there's no reason to, even though its part of
+ * interface). This method not only converts end-of-line characters, if
+ * needed, but sets the correct end-of-line delimiter in
+ * structuredDocument. Minor note: can't use this exact method in dumpers,
+ * since the decision to change or not is a little different, and since
+ * there we have to change text of structuredDocument if found to need
+ * conversion. (Where as for loading, we assume we haven't yet set text in
+ * structuredDocument, but will be done by other method just a tiny biy
+ * later). Needs to be public to handle interface. It is in the interface
+ * just so ModelManagerImpl can use it in a special circumstance.
+ */
+ public StringBuffer handleLineDelimiter(StringBuffer originalString, IEncodedDocument theFlatModel) {
+ // TODO: need to handle line delimiters so Marker Positions are
+ // updated
+ StringBuffer convertedText = null;
+ // based on text, make a guess on what's being used as
+ // line delimiter
+ String probableLineDelimiter = TextUtilities.determineLineDelimiter(originalString, theFlatModel.getLegalLineDelimiters(), System.getProperty("line.separator")); //$NON-NLS-1$
+ String preferredLineDelimiter = getPreferredNewLineDelimiter(null);
+ if (preferredLineDelimiter == null) {
+ // when preferredLineDelimiter is null, it means "leave alone"
+ // so no conversion needed.
+ // set here, only if null (should already be set, but if not,
+ // we'll set so any subsequent editing inserts what we're
+ // assuming)
+ if (!theFlatModel.getPreferredLineDelimiter().equals(probableLineDelimiter)) {
+ theFlatModel.setPreferredLineDelimiter(probableLineDelimiter);
+ }
+ convertedText = originalString;
+ }
+ else {
+ if (!preferredLineDelimiter.equals(probableLineDelimiter)) {
+ // technically, wouldn't have to convert line delimiters
+ // here at beginning, but when we save, if the preferred
+ // line delimter is "leave alone" then we do leave alone,
+ // so best to be right from beginning.
+ convertedText = convertLineDelimiters(originalString, preferredLineDelimiter);
+ theFlatModel.setPreferredLineDelimiter(preferredLineDelimiter);
+ }
+ else {
+ // they are already the same, no conversion needed
+ theFlatModel.setPreferredLineDelimiter(preferredLineDelimiter);
+ convertedText = originalString;
+ }
+ }
+ return convertedText;
+ }
+
+ protected abstract IEncodedDocument newEncodedDocument();
+
+ /**
+ * Very mechanical method, just to read the characters, once the reader is
+ * correctly created. Can throw MalFormedInputException.
+ */
+ private StringBuffer readInputStream(Reader reader) throws IOException {
+
+ int fBlocksRead = 0;
+ StringBuffer buffer = new StringBuffer();
+ int numRead = 0;
+ try {
+ char tBuff[] = new char[CodedIO.MAX_BUF_SIZE];
+ while (numRead != -1) {
+ numRead = reader.read(tBuff, 0, tBuff.length);
+ if (numRead > 0) {
+ buffer.append(tBuff, 0, numRead);
+ fBlocksRead++;
+ }
+ }
+ }
+ catch (MalformedInputException e) {
+ throw new MalformedInputExceptionWithDetail(fEncodingMemento.getJavaCharsetName(), fBlocksRead * CodedIO.MAX_BUF_SIZE + numRead + e.getInputLength());
+ }
+ catch (UnmappableCharacterException e) {
+ throw new MalformedInputExceptionWithDetail(fEncodingMemento.getJavaCharsetName(), fBlocksRead * CodedIO.MAX_BUF_SIZE + numRead + e.getInputLength());
+
+ }
+ return buffer;
+ }
+
+ public void reload(IEncodedDocument encodedDocument, Reader inputStreamReader) throws IOException {
+ if (inputStreamReader == null) {
+ throw new IllegalArgumentException("stream reader can not be null"); //$NON-NLS-1$
+ }
+ int READ_BUFFER_SIZE = 8192;
+ int MAX_BUFFERED_SIZE_FOR_RESET_MARK = 200000;
+ // temp .... eventually we'lll only read as needed
+ BufferedReader bufferedReader = new BufferedReader(inputStreamReader, MAX_BUFFERED_SIZE_FOR_RESET_MARK);
+ bufferedReader.mark(MAX_BUFFERED_SIZE_FOR_RESET_MARK);
+ StringBuffer buffer = new StringBuffer();
+ try {
+ int numRead = 0;
+ char tBuff[] = new char[READ_BUFFER_SIZE];
+ while ((numRead = bufferedReader.read(tBuff, 0, tBuff.length)) != -1) {
+ buffer.append(tBuff, 0, numRead);
+ }
+ // remember -- we didn't open stream ... so we don't close it
+ }
+ catch (MalformedInputException e) {
+ // int pos = e.getInputLength();
+ EncodingMemento localEncodingMemento = getEncodingMemento();
+ boolean couldReset = true;
+ String encodingNameInError = localEncodingMemento.getJavaCharsetName();
+ if (encodingNameInError == null) {
+ encodingNameInError = localEncodingMemento.getDetectedCharsetName();
+ }
+ try {
+ bufferedReader.reset();
+ }
+ catch (IOException resetException) {
+ // the only errro that can occur during reset is an
+ // IOException
+ // due to already being past the rest mark. In that case, we
+ // throw more generic message
+ couldReset = false;
+ }
+ // -1 can be used by UI layer as a code that "position could not
+ // be
+ // determined"
+ int charPostion = -1;
+ if (couldReset) {
+
+ charPostion = getCharPostionOfFailure(bufferedReader);
+ // getCharPostionOfFailure(new InputStreamReader(inStream,
+ // javaEncodingNameInError));
+ }
+ // all of that just to throw more accurate error
+ // note: we do the conversion to ianaName, instead of using the
+ // local
+ // variable,
+ // because this is ultimately only for the user error message
+ // (that
+ // is,
+ // the error occurred
+ // in context of javaEncodingName no matter what ianaEncodingName
+ // is
+ throw new MalformedInputExceptionWithDetail(encodingNameInError, CodedIO.getAppropriateJavaCharset(encodingNameInError), charPostion, !couldReset, MAX_BUFFERED_SIZE_FOR_RESET_MARK);
+ }
+ StringBuffer stringbuffer = buffer;
+ encodedDocument.set(stringbuffer.toString());
+
+ }
+
+ protected void setDocumentContentsFromReader(IEncodedDocument structuredDocument, Reader reader) throws IOException {
+
+ StringBuffer allText = readInputStream(reader);
+ structuredDocument.set(allText.toString());
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/DocumentReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/DocumentReader.java
new file mode 100644
index 0000000000..ae28004885
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/DocumentReader.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.document;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.eclipse.jface.text.IDocument;
+
+/**
+ * A java.io.Reader that can operate off of an IDocument.
+ */
+public class DocumentReader extends Reader {
+ private IDocument fDocument = null;
+ private int mark = 0;
+ private int position = 0;
+
+ public DocumentReader() {
+ super();
+ }
+
+ public DocumentReader(IDocument document) {
+ this(document, 0);
+ }
+
+ public DocumentReader(IDocument document, int initialPosition) {
+ super();
+ fDocument = document;
+ position = initialPosition;
+ }
+
+ public void close() throws IOException {
+ fDocument = null;
+ }
+
+ /**
+ * @return
+ */
+ public IDocument getDocument() {
+ return fDocument;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.Reader#mark(int)
+ */
+ public void mark(int readAheadLimit) throws IOException {
+ mark = position;
+ }
+
+ public boolean markSupported() {
+ return true;
+ }
+
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ if(fDocument == null)
+ return -1;
+
+ char[] readChars = null;
+ try {
+ if (position >= fDocument.getLength())
+ return -1;
+ // the IDocument is likely using a GapTextStore, so we can't
+ // retrieve a char[] directly
+ if (position + len > fDocument.getLength())
+ readChars = fDocument.get(position, fDocument.getLength() - position).toCharArray();
+ else
+ readChars = fDocument.get(position, len).toCharArray();
+ System.arraycopy(readChars, 0, cbuf, off, readChars.length);
+ // System.out.println("" + position + ":" + readChars.length + " "
+ // + StringUtils.escape(new String(readChars)));
+ position += readChars.length;
+ return readChars.length;
+ } catch (Exception e) {
+ throw new IOException("Exception while reading from IDocument: " + e); //$NON-NLS-1$
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.Reader#reset()
+ */
+ public void reset() throws IOException {
+ position = mark;
+ }
+
+ public void reset(IDocument document, int initialPosition) {
+ fDocument = document;
+ position = initialPosition;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.Reader#reset()
+ */
+ public void reset(int pos) throws IOException {
+ position = pos;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.Reader#skip(long)
+ */
+ public long skip(long n) throws IOException {
+ if(fDocument == null)
+ return 0;
+
+ long skipped = n;
+ if (position + n > fDocument.getLength()) {
+ skipped = fDocument.getLength() - position;
+ position = fDocument.getLength();
+ } else {
+ position += n;
+ }
+ return skipped;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentCharsetDetector.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentCharsetDetector.java
new file mode 100644
index 0000000000..4ea8b04d4e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentCharsetDetector.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.document;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.encoding.IResourceCharsetDetector;
+
+
+
+public interface IDocumentCharsetDetector extends IResourceCharsetDetector {
+ void set(IDocument document);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentLoader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentLoader.java
new file mode 100644
index 0000000000..b7eff1e3f4
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/IDocumentLoader.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.document;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingRule;
+import org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument;
+
+
+/**
+ * Provides methods for the creation of an IStructuredDocument correctly
+ * prepared to work with a particular type of content.
+ */
+public interface IDocumentLoader {
+
+ /**
+ * @return a new IStructuredDocument prepared by this loader
+ */
+ IEncodedDocument createNewStructuredDocument();
+
+ /**
+ * This API is like createNewStructuredDocument, except it should populate
+ * the structuredDocument with the contents of IFile. Also, those
+ * StructuredDocuments which are sensitive to the input (that is, the
+ * parser or parser initialization my require the input) should
+ * additionally initialize the parser, etc., appropriate to the input.
+ *
+ * As always, the appropriate decoding should be used.
+ */
+ IEncodedDocument createNewStructuredDocument(IFile iFile) throws java.io.IOException, CoreException;
+
+ /**
+ * This method must return a new instance of IEncodedDocument, that has
+ * been initialized with appropriate parser. For many loaders, the
+ * (default) parser used is known for any input. For others, the correct
+ * parser (and its initialization) is normally dependent on the content of
+ * the file. This no-argument method should assume "empty input" and would
+ * therefore return the default parser for the default contentType.
+ */
+ IEncodedDocument createNewStructuredDocument(String filename, InputStream istream) throws java.io.IOException;
+
+ IEncodedDocument createNewStructuredDocument(String filename, InputStream istream, EncodingRule encodingRule) throws java.io.IOException;
+
+ /**
+ * @return the document partitioner
+ */
+ IDocumentPartitioner getDefaultDocumentPartitioner();
+
+ IDocumentCharsetDetector getDocumentEncodingDetector();
+
+ /**
+ * A utility method, but depends on subclasses to implement the preferred
+ * end of line for a particular content type. Note: subclasses should not
+ * re-implement this method (there's no reason to, even though its part of
+ * interface). This method not only converts end-of-line characters, if
+ * needed, but sets the correct end-of-line delimiter in
+ * structuredDocument. The returned value is either the original string,
+ * if no conversion is needed, or a new string with end-of-lines
+ * converted.
+ *
+ * @deprecated - the content's line delimiters should be preserved
+ */
+ StringBuffer handleLineDelimiter(StringBuffer originalString, IEncodedDocument theStructuredDocument);
+
+ void reload(IEncodedDocument document, Reader reader) throws IOException;
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/StructuredDocumentFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/StructuredDocumentFactory.java
new file mode 100644
index 0000000000..41618fac2a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/StructuredDocumentFactory.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.document;
+
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument;
+import org.eclipse.wst.sse.core.internal.text.JobSafeStructuredDocument;
+
+
+/**
+ * At the moment, this is primarily intended as a convenience to help switch
+ * between various types of threading models in the document, all in a central
+ * piece of code.
+ */
+public class StructuredDocumentFactory {
+ private static final int WRITE_SYNCHRONIZED = 3;
+ private static final int DEFAULT = WRITE_SYNCHRONIZED;
+ private static final int UNSYNCHRONIZED = 1;
+
+ private static IStructuredDocument getNewStructuredDocumentInstance(int type, RegionParser parser) {
+ IStructuredDocument result = null;
+ switch (type) {
+ case UNSYNCHRONIZED :
+ result = new BasicStructuredDocument(parser);
+ break;
+ case WRITE_SYNCHRONIZED :
+ result = new JobSafeStructuredDocument(parser);
+ break;
+
+ default :
+ throw new IllegalArgumentException("request document type was not known"); //$NON-NLS-1$
+
+ }
+ return result;
+ }
+
+ /**
+ * Provides the (system default) structured document initialized with the
+ * parser.
+ *
+ * @param parser
+ * @return
+ */
+ public static IStructuredDocument getNewStructuredDocumentInstance(RegionParser parser) {
+ return getNewStructuredDocumentInstance(DEFAULT, parser);
+ }
+
+ /**
+ * Not intended to be instantiated
+ *
+ */
+ private StructuredDocumentFactory() {
+ super();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/TextUtilities.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/TextUtilities.java
new file mode 100644
index 0000000000..91ec59830d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/document/TextUtilities.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.document;
+
+
+/**
+ * Collection of text functions.
+ *
+ * @deprecated - marked as deprecated to remind us to phase this out (and/or
+ * move to "finished" version).
+ */
+public class TextUtilities {
+
+ /**
+ * @deprecated if possible, its best to use
+ * IDocument.getLegalLineDelimiters()
+ */
+ public final static String[] fgDelimiters = new String[]{"\n", "\r", "\r\n"};//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
+
+ /**
+ * Determines which one of fgDelimiters appears first in the text. If none
+ * of them the hint is returned.
+ */
+ public static String determineLineDelimiter(StringBuffer textBuffer, String[] possibles, String hint) {
+ try {
+ // TODO: avoid use of String instance
+ String text = textBuffer.toString();
+ int[] info = indexOf(possibles, text, 0);
+ return possibles[info[1]];
+ } catch (ArrayIndexOutOfBoundsException x) {
+ }
+ return hint;
+ }
+
+ /**
+ * Returns the position in the string greater than offset of the longest
+ * matching search string.
+ */
+ private static int[] indexOf(String[] searchStrings, String text, int offset) {
+
+ int[] result = {-1, -1};
+
+ for (int i = 0; i < searchStrings.length; i++) {
+ int index = text.indexOf(searchStrings[i], offset);
+ if (index >= 0) {
+
+ if (result[0] == -1) {
+ result[0] = index;
+ result[1] = i;
+ } else if (index < result[0]) {
+ result[0] = index;
+ result[1] = i;
+ } else if (index == result[0] && searchStrings[i].length() > searchStrings[result[1]].length()) {
+ result[0] = index;
+ result[1] = i;
+ }
+ }
+ }
+
+ return result;
+
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/filebuffers/BasicStructuredDocumentFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/filebuffers/BasicStructuredDocumentFactory.java
new file mode 100644
index 0000000000..16467b6d00
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/filebuffers/BasicStructuredDocumentFactory.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.filebuffers;
+
+import org.eclipse.core.filebuffers.IDocumentFactory;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExecutableExtension;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.core.runtime.content.IContentTypeManager;
+import org.eclipse.jface.text.IDocument;
+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.text.JobSafeStructuredDocument;
+
+
+/**
+ * Generic IDocumentFactory for IStructuredDocuments to be used by the
+ * org.eclipse.core.filebuffers.documentCreation extension point. This class
+ * is not meant to be subclassed.
+ *
+ * @plannedfor 1.0
+ */
+public class BasicStructuredDocumentFactory implements IDocumentFactory, IExecutableExtension {
+
+ /*
+ * The content type ID used to declare this factory; it is used to find
+ * the corresponding support for creating the document
+ */
+ private String fContentTypeIdentifier = null;
+
+ /**
+ * Constructor, only to be used by the
+ * org.eclipse.core.filebuffers.documentCreation extension point.
+ */
+ public BasicStructuredDocumentFactory() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.filebuffers.IDocumentFactory#createDocument()
+ */
+ public IDocument createDocument() {
+ IDocument document = null;
+ IContentType contentType = Platform.getContentTypeManager().getContentType(getContentTypeIdentifier());
+ IModelHandler handler = null;
+ while (handler == null && !IContentTypeManager.CT_TEXT.equals(contentType.getId())) {
+ handler = ModelHandlerRegistry.getInstance().getHandlerForContentTypeId(contentType.getId());
+ contentType = contentType.getBaseType();
+ }
+ if (handler != null) {
+ document = handler.getDocumentLoader().createNewStructuredDocument();
+ }
+ else {
+ document = new JobSafeStructuredDocument();
+ }
+ return document;
+ }
+
+ private String getContentTypeIdentifier() {
+ return fContentTypeIdentifier;
+ }
+
+ /*
+ * Loads the content type ID to be used when creating the Structured Document.
+ *
+ * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement,
+ * java.lang.String, java.lang.Object)
+ */
+ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
+ fContentTypeIdentifier = config.getAttribute("contentTypeId"); //$NON-NLS-1$
+ if (data != null) {
+ if (data instanceof String && data.toString().length() > 0) {
+ fContentTypeIdentifier = (String) data;
+ }
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/AbstractStructuredFormatProcessor.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/AbstractStructuredFormatProcessor.java
new file mode 100644
index 0000000000..1e8d6902a1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/AbstractStructuredFormatProcessor.java
@@ -0,0 +1,524 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * Jesper Steen Møller - initial IDocumentExtension4 support - #102822
+ * David Carver (Intalio) - bug 300443 - some constants aren't static final
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.format;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension4;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Node;
+
+
+public abstract class AbstractStructuredFormatProcessor implements IStructuredFormatProcessor {
+ protected IStructuredFormatContraints fFormatContraints = null;
+ protected IProgressMonitor fProgressMonitor = null;
+ public boolean refreshFormatPreferences = true; // special flag for JUnit
+ /*
+ * Max length of text to be formatted to be considered a "small change"
+ * Used for document rewrite session type.
+ */
+ private static final int MAX_SMALL_FORMAT_SIZE = 1000;
+
+ protected void ensureClosed(OutputStream outputStream, InputStream inputStream) {
+
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ catch (IOException e) {
+ Logger.logException(e); // hopeless
+ }
+ try {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ catch (IOException e) {
+ Logger.logException(e); // hopeless
+ }
+ }
+
+ public String formatContent(String input) throws IOException, CoreException {
+ if (input == null)
+ return input;
+
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for read. Will return formatted
+ // string and NOT save model.
+ inputStream = new ByteArrayInputStream(input.getBytes("UTF8")); //$NON-NLS-1$
+ String id = inputStream.toString() + "." + getFileExtension(); //$NON-NLS-1$
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(id, inputStream, null);
+
+ // format
+ formatModel(structuredModel);
+
+ // return output
+ return structuredModel.getStructuredDocument().get();
+ }
+ finally {
+ ensureClosed(null, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+
+ public String formatContent(String input, int start, int length) throws IOException, CoreException {
+ if (input == null)
+ return input;
+
+ if ((start >= 0) && (length >= 0) && (start + length <= input.length())) {
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for read. Will return formatted
+ // string and NOT save model.
+ inputStream = new ByteArrayInputStream(input.getBytes("UTF8")); //$NON-NLS-1$
+ String id = inputStream.toString() + "." + getFileExtension(); //$NON-NLS-1$
+ structuredModel = StructuredModelManager.getModelManager().getModelForRead(id, inputStream, null);
+
+ // format
+ formatModel(structuredModel, start, length);
+
+ // return output
+ return structuredModel.getStructuredDocument().get();
+ }
+ finally {
+ ensureClosed(null, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromRead();
+ }
+ }
+ else
+ return input;
+ }
+
+ public void formatDocument(IDocument document) throws IOException, CoreException {
+ if (document == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ structuredModel = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
+
+ // format
+ formatModel(structuredModel);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+
+ public void formatDocument(IDocument document, int start, int length) throws IOException, CoreException {
+ if (document == null)
+ return;
+
+ if ((start >= 0) && (length >= 0) && (start + length <= document.getLength())) {
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if
+ // model changed.
+ structuredModel = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
+
+ if (structuredModel != null) {
+ // format
+ formatModel(structuredModel, start, length);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ }
+ finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+ }
+
+ public void formatFile(IFile file) throws IOException, CoreException {
+ if (file == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ structuredModel = StructuredModelManager.getModelManager().getModelForEdit(file);
+
+ // format
+ formatModel(structuredModel);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null) {
+ structuredModel.releaseFromEdit();
+ }
+
+ }
+ }
+
+ public void formatFile(IFile file, int start, int length) throws IOException, CoreException {
+ if (file == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ structuredModel = StructuredModelManager.getModelManager().getModelForEdit(file);
+
+ // format
+ formatModel(structuredModel, start, length);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ finally {
+ // ensureClosed(outputStream, null);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+
+ public void formatFileName(String fileName) throws IOException, CoreException {
+ if (fileName == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ inputStream = new FileInputStream(fileName);
+ structuredModel = StructuredModelManager.getModelManager().getModelForEdit(fileName, inputStream, null);
+
+ // format
+ formatModel(structuredModel);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ finally {
+ // ensureClosed(outputStream, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+
+ public void formatFileName(String fileName, int start, int length) throws IOException, CoreException {
+ if (fileName == null)
+ return;
+
+ IStructuredModel structuredModel = null;
+ InputStream inputStream = null;
+ // OutputStream outputStream = null;
+ try {
+ // setup structuredModel
+ // Note: We are getting model for edit. Will save model if model
+ // changed.
+ inputStream = new FileInputStream(fileName);
+ structuredModel = StructuredModelManager.getModelManager().getModelForEdit(fileName, inputStream, null);
+
+ // format
+ formatModel(structuredModel, start, length);
+
+ // save model if needed
+ if (!structuredModel.isSharedForEdit() && structuredModel.isSaveNeeded())
+ structuredModel.save();
+ }
+ finally {
+ // ensureClosed(outputStream, inputStream);
+ // release from model manager
+ if (structuredModel != null)
+ structuredModel.releaseFromEdit();
+ }
+ }
+
+ public void formatModel(IStructuredModel structuredModel) {
+ int start = 0;
+ int length = structuredModel.getStructuredDocument().getLength();
+
+ formatModel(structuredModel, start, length);
+ }
+
+ public void formatModel(IStructuredModel structuredModel, int start, int length) {
+ if (structuredModel != null) {
+ // for debugging purposes
+ long startTime = System.currentTimeMillis();
+
+ IDocumentExtension4 docExt4 = null;
+ if (structuredModel.getStructuredDocument() instanceof IDocumentExtension4) {
+ docExt4 = (IDocumentExtension4) structuredModel.getStructuredDocument();
+ }
+ DocumentRewriteSession rewriteSession = null;
+
+ try {
+ // whenever formatting model, fire abouttochange/modelchanged
+ structuredModel.aboutToChangeModel();
+ DocumentRewriteSessionType rewriteType = (length > MAX_SMALL_FORMAT_SIZE) ? DocumentRewriteSessionType.UNRESTRICTED : DocumentRewriteSessionType.UNRESTRICTED_SMALL;
+ rewriteSession = (docExt4 == null || docExt4.getActiveRewriteSession() != null) ? null : docExt4.startRewriteSession(rewriteType);
+
+ if ((start == 0) && (length == structuredModel.getStructuredDocument().getLength()))
+ setFormatWithSiblingIndent(structuredModel, false);
+ else
+ setFormatWithSiblingIndent(structuredModel, true);
+
+ if ((start >= 0) && (length >= 0) && (start + length <= structuredModel.getStructuredDocument().getLength())) {
+ List activeNodes = getAllActiveNodes(structuredModel, start, length);
+ if (activeNodes.size() > 0) {
+ Node firstNode = (Node) activeNodes.get(0);
+ Node lastNode = (Node) activeNodes.get(activeNodes.size() - 1);
+
+ boolean done = false;
+ Node eachNode = firstNode;
+ Node nextNode = null;
+ while (!done) {
+ // update "done"
+ done = (eachNode == lastNode);
+
+ /*
+ * get next sibling before format because eachNode
+ * may be deleted, for example when it's an empty
+ * text node
+ */
+ nextNode = eachNode.getNextSibling();
+
+ // format each node
+ formatNode(eachNode);
+
+ // update each node
+ if ((nextNode != null) && (nextNode.getParentNode() == null))
+ // nextNode is deleted during format
+ eachNode = eachNode.getNextSibling();
+ else
+ eachNode = nextNode;
+
+ // This should not be needed, but just in case
+ // something went wrong with with eachNode.
+ // We don't want an infinite loop here.
+ if (eachNode == null)
+ done = true;
+ }
+
+ }
+ }
+ }
+ finally {
+ // we need two finally's, just in case first fails
+ try {
+ if ((docExt4 != null) && (rewriteSession != null))
+ docExt4.stopRewriteSession(rewriteSession);
+ }
+ finally {
+ // always make sure to fire changedmodel when done
+ structuredModel.changedModel();
+ }
+ }
+
+ if (Logger.DEBUG_FORMAT) {
+ long endTime = System.currentTimeMillis();
+ System.out.println("formatModel time: " + (endTime - startTime)); //$NON-NLS-1$
+ }
+ }
+ }
+
+ public void formatNode(Node node) {
+ if (node != null) {
+ Node newNode = node;
+
+ // format the owner node if it's an attribute node
+ if (node.getNodeType() == Node.ATTRIBUTE_NODE)
+ newNode = ((Attr) node).getOwnerElement();
+
+ // refresh format preferences before getting formatter
+ if (refreshFormatPreferences)
+ refreshFormatPreferences();
+
+ // get formatter and format contraints
+ IStructuredFormatter formatter = getFormatter(newNode);
+ // TODO_future: added assert to replace "redundant null check".
+ // if formatter is ever null, we should provide some
+ // default formatter to serve as place holder.
+ Assert.isNotNull(formatter, "formatter was null for a node, "); //$NON-NLS-1$
+ IStructuredFormatContraints formatContraints = formatter.getFormatContraints();
+ formatContraints.setFormatWithSiblingIndent(true);
+ // format each node
+ formatter.format(newNode, formatContraints);
+ }
+ }
+
+ /**
+ * @deprecated Use getAllActiveNodes instead
+ */
+ protected Vector getActiveNodes(IStructuredModel structuredModel, int startNodeOffset, int length) {
+ List allActiveNodes = getAllActiveNodes(structuredModel, startNodeOffset, length);
+ return new Vector(allActiveNodes);
+ }
+
+ protected List getAllActiveNodes(IStructuredModel structuredModel, int startNodeOffset, int length) {
+ List activeNodes = new ArrayList();
+
+ if (structuredModel != null) {
+ Node startNode = (Node) structuredModel.getIndexedRegion(startNodeOffset);
+ // see https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4711
+ //
+ // We have to watch for selection boundary conditions. Use this as
+ // an example: <a>text</a><b>text</b>,
+ // If the whole <a> node is selected, like:
+ // |<a>text</a>|<b>text</b>, we need to substract the length by 1
+ // to find
+ // the node at the end of the selection:
+ // structuredModel.getIndexedRegion(startNodeOffset + length - 1),
+ // or else
+ // we'd find the next adjacent node.
+ //
+ // However, when the selection length is 0 (meaning no text is
+ // selected), the cursor is at the beginning
+ // of the node we want to format: |<a>text</a><b>text</b>, the
+ // node at the end of the selection is:
+ // structuredModel.getIndexedRegion(startNodeOffset + length).
+ int endNodeOffset = length > 0 ? startNodeOffset + length - 1 : startNodeOffset + length;
+ Node endNode = (Node) structuredModel.getIndexedRegion(endNodeOffset);
+
+ // make sure it's an non-empty document
+ if (startNode != null) {
+ while (isSiblingOf(startNode, endNode) == false) {
+ if (endNode != null)
+ endNode = endNode.getParentNode();
+ if (endNode == null) {
+ startNode = startNode.getParentNode();
+ // see
+ // https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4711
+ // and notes above
+ endNodeOffset = length > 0 ? startNodeOffset + length - 1 : startNodeOffset + length;
+ endNode = (Node) structuredModel.getIndexedRegion(endNodeOffset);
+ }
+ }
+
+ while (startNode != endNode) {
+ activeNodes.add(startNode);
+ startNode = startNode.getNextSibling();
+ }
+ if (startNode != null)
+ activeNodes.add(startNode);
+ }
+ }
+
+ return activeNodes;
+ }
+
+ abstract protected String getFileExtension();
+
+ protected IStructuredFormatContraints getFormatContraints(IStructuredModel structuredModel) {
+ // 262135 - NPE during format of empty document
+ if ((fFormatContraints == null) && (structuredModel != null)) {
+ Node node = (Node) structuredModel.getIndexedRegion(0);
+
+ if (node != null) {
+ IStructuredFormatter formatter = getFormatter(node);
+ if (formatter != null) {
+ fFormatContraints = formatter.getFormatContraints();
+ }
+ }
+ }
+
+ return fFormatContraints;
+ }
+
+ abstract protected IStructuredFormatter getFormatter(Node node);
+
+ protected boolean isSiblingOf(Node node, Node endNode) {
+ if (endNode == null)
+ return true;
+ else {
+ Node siblingNode = node;
+ while (siblingNode != null) {
+ if (siblingNode == endNode)
+ return true;
+ else
+ siblingNode = siblingNode.getNextSibling();
+ }
+ return false;
+ }
+ }
+
+ abstract protected void refreshFormatPreferences();
+
+ protected void setFormatWithSiblingIndent(IStructuredModel structuredModel, boolean formatWithSiblingIndent) {
+ // 262135 - NPE during format of empty document
+ IStructuredFormatContraints formatContraints = getFormatContraints(structuredModel);
+
+ if (formatContraints != null)
+ formatContraints.setFormatWithSiblingIndent(formatWithSiblingIndent);
+ }
+
+ public void setProgressMonitor(IProgressMonitor monitor) {
+ fProgressMonitor = monitor;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IFormattingDelegate.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IFormattingDelegate.java
new file mode 100644
index 0000000000..29c23292d2
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IFormattingDelegate.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.format;
+
+/**
+ * The formatting delegate will pass off formatting of a document
+ * based on the text viewer context.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IFormattingDelegate {
+ /**
+ * Formats a document from the given context.
+ *
+ * @param context the <code>org.eclipse.wst.sse.ui.internal.StructuredTextViewer</code> that
+ * is used as context for performing the format operation. The type is <code>Object</code> to
+ * avoid dependencies on UI code.
+ */
+ void format(Object context);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatContraints.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatContraints.java
new file mode 100644
index 0000000000..2730043e60
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatContraints.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.format;
+
+/**
+ * These are items that change from element to element.
+ * Passed from node to node in a recursive call.
+ * eg. current indent is 2 deep, but for the next node might be 3...
+ */
+public interface IStructuredFormatContraints {
+ boolean getClearAllBlankLines();
+
+ String getCurrentIndent();
+
+ boolean getFormatWithSiblingIndent();
+
+ boolean getInPreserveSpaceElement();
+
+ /**
+ * some special elements can ignore clearing blank lines
+ * */
+ void setClearAllBlankLines(boolean clearAllBlankLines);
+
+ void setCurrentIndent(String currentIndent);
+
+ void setFormatWithSiblingIndent(boolean formatWithSiblingIndent);
+
+ void setInPreserveSpaceElement(boolean inPreserveSpaceElement);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatPreferences.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatPreferences.java
new file mode 100644
index 0000000000..2cef142eb1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatPreferences.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.format;
+
+/**
+ * These are items that do not change from element to element.
+ * Passed from node to node in a recursive call because sometimes
+ * child nodes don't have access to the preferences
+ */
+public interface IStructuredFormatPreferences {
+
+ boolean getClearAllBlankLines();
+
+ String getIndent();
+
+ int getLineWidth();
+
+ void setClearAllBlankLines(boolean clearAllBlankLines);
+
+ void setIndent(String indent);
+
+ void setLineWidth(int lineWidth);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatProcessor.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatProcessor.java
new file mode 100644
index 0000000000..b83aac34f8
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatProcessor.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.format;
+
+import java.io.IOException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.w3c.dom.Node;
+
+/**
+ * The main formatting engine.
+ * Loops through all the nodes in an IStructuredModel.
+ */
+public interface IStructuredFormatProcessor {
+
+ /**
+ * This form of the FormatProcessor takes an IDocument as input, creates a
+ * temporary model of content type calculated using the IDocument's file
+ * extension, formats the model within start and length, then releases the
+ * model.
+ */
+ void formatDocument(IDocument document, int start, int length) throws IOException, CoreException;
+
+ /**
+ * This form of the FormatProcessor takes an IFile as input, creates a
+ * temporary model of content type calculated using the IFile's file
+ * extension, formats the whole model, then releases the model.
+ */
+ void formatFile(IFile file) throws IOException, CoreException;
+
+ /**
+ * This form of the FormatProcessor takes a model as input, and formats
+ * the whole model.
+ */
+ void formatModel(IStructuredModel structuredModel);
+
+ /**
+ * This form of the FormatProcessor takes a model as input, and formats
+ * the model within start and length.
+ */
+ void formatModel(IStructuredModel structuredModel, int start, int length);
+
+ /**
+ * This form of the FormatProcessor takes a node as input, and formats the
+ * node and all its children.
+ */
+ void formatNode(Node node);
+
+ /**
+ * Sets the progress monitor for this <code>IStructuredFormatProcessor</code>.
+ * The monitor is used to display progress or cancel if the formatter is run
+ * in a background job.
+ * @param monitor
+ */
+ void setProgressMonitor(IProgressMonitor monitor);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatter.java
new file mode 100644
index 0000000000..6c23d12b5b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/IStructuredFormatter.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.format;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.w3c.dom.Node;
+
+/**
+ * Knows how to format a particular node.
+ *
+ * eg. generic node, text node, document node, comment, etc...
+ */
+public interface IStructuredFormatter {
+
+ void format(Node node);
+
+ void format(Node node, IStructuredFormatContraints formatContraints);
+
+ IStructuredFormatContraints getFormatContraints();
+
+ IStructuredFormatPreferences getFormatPreferences();
+
+ void setFormatPreferences(IStructuredFormatPreferences formatPreferences);
+
+ void setProgressMonitor(IProgressMonitor monitor);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatContraints.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatContraints.java
new file mode 100644
index 0000000000..ca47e41dfe
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatContraints.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ * Jesper Steen Møller - xml:space='preserve' support
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.format;
+
+public class StructuredFormatContraints implements IStructuredFormatContraints {
+ private boolean fClearAllBlankLines;
+ private String fCurrentIndent = ""; //$NON-NLS-1$
+ private boolean fFormatWithSiblingIndent = false;
+ private boolean fInPreserveSpaceElement = false;
+
+ public boolean getClearAllBlankLines() {
+ return fClearAllBlankLines;
+ }
+
+ public String getCurrentIndent() {
+ return fCurrentIndent;
+ }
+
+ public boolean getFormatWithSiblingIndent() {
+ return fFormatWithSiblingIndent;
+ }
+
+ public void setClearAllBlankLines(boolean clearAllBlankLines) {
+ fClearAllBlankLines = clearAllBlankLines;
+ }
+
+ public void setCurrentIndent(String currentIndent) {
+ fCurrentIndent = currentIndent;
+ }
+
+ public void setFormatWithSiblingIndent(boolean formatWithSiblingIndent) {
+ fFormatWithSiblingIndent = formatWithSiblingIndent;
+ }
+
+ public boolean getInPreserveSpaceElement() {
+ return fInPreserveSpaceElement;
+ }
+
+ public void setInPreserveSpaceElement(boolean inPreserveSpaceElement) {
+ fInPreserveSpaceElement = inPreserveSpaceElement;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatPreferences.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatPreferences.java
new file mode 100644
index 0000000000..96f1b92baf
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/format/StructuredFormatPreferences.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.format;
+
+public class StructuredFormatPreferences implements IStructuredFormatPreferences {
+ private boolean fClearAllBlankLines;
+ private String fIndent;
+ private int fLineWidth;
+
+ public boolean getClearAllBlankLines() {
+ return fClearAllBlankLines;
+ }
+
+ public String getIndent() {
+ return fIndent;
+ }
+
+ public int getLineWidth() {
+ return fLineWidth;
+ }
+
+ public void setClearAllBlankLines(boolean clearAllBlankLines) {
+ fClearAllBlankLines = clearAllBlankLines;
+ }
+
+ public void setIndent(String indent) {
+ fIndent = indent;
+ }
+
+ public void setLineWidth(int lineWidth) {
+ fLineWidth = lineWidth;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/AbstractModelHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/AbstractModelHandler.java
new file mode 100644
index 0000000000..6c45577def
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/AbstractModelHandler.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.modelhandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.document.IDocumentCharsetDetector;
+import org.eclipse.wst.sse.core.internal.modelhandler.PluginContributedFactoryReader;
+
+/**
+ * ISSUE: need to provide this functionality in improved API.
+ */
+
+public abstract class AbstractModelHandler implements IModelHandler {
+ private String associatedContentTypeId;
+ private boolean defaultSetting;
+ private String modelHandlerID;
+
+ public AbstractModelHandler() {
+ super();
+ }
+
+ /**
+ * These factories are added automatically by model manager
+ */
+ public List getAdapterFactories() {
+ List result = new ArrayList();
+ Collection holdFactories = PluginContributedFactoryReader.getInstance().getFactories(this);
+ if (holdFactories != null) {
+ result.addAll(holdFactories);
+ }
+ return result;
+ }
+
+ public String getAssociatedContentTypeId() {
+ return associatedContentTypeId;
+ }
+
+ public abstract IDocumentCharsetDetector getEncodingDetector();
+
+ public String getId() {
+ return modelHandlerID;
+ }
+
+ public boolean isDefault() {
+ return defaultSetting;
+ }
+
+ protected void setAssociatedContentTypeId(String contentTypeId) {
+ associatedContentTypeId = contentTypeId;
+ }
+
+ public void setDefault(boolean defaultParam) {
+ defaultSetting = defaultParam;
+ }
+
+ protected void setId(String id) {
+ modelHandlerID = id;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/EmbeddedTypeHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/EmbeddedTypeHandler.java
new file mode 100644
index 0000000000..b5511909e3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/EmbeddedTypeHandler.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.ltk.modelhandler;
+
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.model.FactoryRegistry;
+
+
+/**
+ */
+public interface EmbeddedTypeHandler {
+
+ /**
+ * These AdapterFactories are NOT added to IStructuredModel's
+ * IAdapterFactory Registry, but instead expected to be consulted as
+ * needed by functionality aware of embedded content types. Additions
+ * to the model's own factory registry should be done in
+ * {@link #initializeFactoryRegistry(FactoryRegistry)}
+ */
+ List getAdapterFactories();
+
+ /**
+ * Returns the unique identifier for the content type family this
+ * ContentTypeDescription belongs to.
+ */
+ String getFamilyId();
+
+ /**
+ * Returns a list of mime types (as Strings) this handler is appropriate
+ * for
+ */
+ List getSupportedMimeTypes();
+
+ /**
+ * If this hander can handle a given mimeType.
+ *
+ * This is a looser check than simply checking if a give mimeType
+ * in the list of supported types, so it should be used with that
+ * in mind. That is, the supported mimeType list should ideally be
+ * checked first.
+ *
+ * eg. if a mime type ends with "+xml", like voice+xml
+ * the EmbeddedXML handler should be able to handle it
+ *
+ * @return true if this handler thinks can handle the given mimeType
+ */
+ boolean canHandleMimeType(String mimeType);
+
+ /**
+ * This method is to give the EmbeddedContentType an opportunity to add
+ * factories directly to the IStructuredModel's IAdapterFactory registry.
+ */
+ void initializeFactoryRegistry(FactoryRegistry registry);
+
+ /**
+ * initializeParser, for example, setting up a "block" tags list using an
+ * extended interface
+ */
+ void initializeParser(RegionParser parser);
+
+ boolean isDefault();
+
+ EmbeddedTypeHandler newInstance();
+
+ void uninitializeFactoryRegistry(FactoryRegistry registry);
+
+ void uninitializeParser(RegionParser parser);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IDocumentTypeHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IDocumentTypeHandler.java
new file mode 100644
index 0000000000..aa629639a8
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IDocumentTypeHandler.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.ltk.modelhandler;
+
+import org.eclipse.wst.sse.core.internal.document.IDocumentCharsetDetector;
+import org.eclipse.wst.sse.core.internal.document.IDocumentLoader;
+
+/**
+ * Responsible for providing the mechanisms used in the correct loading of an
+ * IStructuredDocument's contents and determine its self-described encoding.
+ */
+public interface IDocumentTypeHandler {
+
+ /**
+ * The Loader is reponsible for decoding the Resource,
+ */
+ IDocumentLoader getDocumentLoader();
+
+ /**
+ * @deprecated - likely to go away, so I marked as deprecated to
+ * discoursage use
+ */
+ IDocumentCharsetDetector getEncodingDetector();
+
+ /**
+ * Must return unique ID that is the same as identified in plugin registry
+ */
+ String getId();
+
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IModelHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IModelHandler.java
new file mode 100644
index 0000000000..588f39e598
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/modelhandler/IModelHandler.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.ltk.modelhandler;
+
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.provisional.IModelLoader;
+
+
+/**
+ * Responsible for providing the mechanisms used in the correct loading of an
+ * IStructuredModel's contents and initialization of its adapter factories.
+ */
+public interface IModelHandler extends IDocumentTypeHandler {
+ /**
+ * This method should return Factories which are added automatically by
+ * IModelManager. This can and will often be an empty List (or null),
+ * since some AdapterFactories must be added by Loader directly, and most
+ * should be added by Editors. FormatAdapterFactory is an example of one
+ * that can be returned here, since the timing of adding it is not
+ * critical, but it may be needed even when an editor is not being used.
+ */
+ List getAdapterFactories();
+
+ /**
+ * Returns the ID for the associated ContentTypeHandler But is needed for
+ * now.
+ */
+ String getAssociatedContentTypeId();
+
+ /**
+ * The Loader is reponsible for decoding the Resource,
+ */
+ IModelLoader getModelLoader();
+
+ boolean isDefault();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockMarker.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockMarker.java
new file mode 100644
index 0000000000..468b360da1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockMarker.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2007 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.ltk.parser;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+
+
+/**
+ * ISSUE: need to provide functionality in improved API.
+ */
+public class BlockMarker extends TagMarker {
+
+ // allow for JSP expressions within the block
+ protected boolean fAllowJSP = true;
+
+ protected boolean fCaseSensitive = false;
+
+ // the context for the contents of this tag (BLOCK_TEXT, JSP_CONTENT,
+ // etc.)
+ protected String fContext;
+
+ public BlockMarker(String tagName, ITextRegion marker, String context) {
+ this(tagName, marker, context, true);
+ }
+
+ public BlockMarker(String tagName, ITextRegion marker, String context, boolean caseSensitive) {
+ this(tagName, marker, context, caseSensitive, true);
+ }
+
+ public BlockMarker(String tagName, ITextRegion marker, String context, boolean caseSensitive, boolean allowJSP) {
+ super(tagName, marker);
+ setContext(context);
+ setCaseSensitive(caseSensitive);
+ setAllowJSP(allowJSP);
+ }
+
+ public BlockMarker(String tagName, String regionContext, boolean caseSensitive) {
+ this(tagName, null, regionContext, caseSensitive, false);
+ }
+
+ /**
+ * Gets the allowJSP.
+ *
+ * @return Returns a boolean
+ */
+ public boolean allowsJSP() {
+ return fAllowJSP;
+ }
+
+ /**
+ * Gets the context.
+ *
+ * @return Returns a String
+ */
+ public String getContext() {
+ return fContext;
+ }
+
+ /**
+ *
+ * @return boolean
+ */
+ public final boolean isCaseSensitive() {
+ return fCaseSensitive;
+ }
+
+ /**
+ * Sets the allowJSP.
+ *
+ * @param allowJSP
+ * The allowJSP to set
+ */
+ public void setAllowJSP(boolean allowJSP) {
+ fAllowJSP = allowJSP;
+ }
+
+ public final void setCaseSensitive(boolean sensitive) {
+ fCaseSensitive = sensitive;
+ }
+
+ /**
+ * Sets the context.
+ *
+ * @param context
+ * The context to set
+ */
+ public void setContext(String context) {
+ fContext = context;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTagParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTagParser.java
new file mode 100644
index 0000000000..6bd6b70086
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTagParser.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+
+
+import java.util.List;
+
+public interface BlockTagParser {
+
+ void addBlockMarker(BlockMarker marker);
+
+ void beginBlockScan(String tagName);
+
+ BlockMarker getBlockMarker(String tagName);
+
+ List getBlockMarkers();
+
+ void removeBlockMarker(BlockMarker marker);
+
+ void removeBlockMarker(String tagName);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTokenizer.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTokenizer.java
new file mode 100644
index 0000000000..b5ad5347d1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/BlockTokenizer.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+
+
+public interface BlockTokenizer {
+
+ void addBlockMarker(BlockMarker marker);
+
+ void beginBlockMarkerScan(String newTagName, String context);
+
+ void beginBlockTagScan(String newTagName);
+
+ List getBlockMarkers();
+
+ ITextRegion getNextToken() throws IOException;
+
+ int getOffset();
+
+ boolean isEOF();
+
+ BlockTokenizer newInstance();
+
+ void removeBlockMarker(BlockMarker marker);
+
+ void removeBlockMarker(String tagname);
+
+ void reset(char[] charArray);
+
+ void reset(char[] charArray, int newOffset);
+
+ void reset(InputStream in);
+
+ void reset(InputStream in, int newOffset);
+
+ void reset(Reader in);
+
+ void reset(Reader in, int newOffset);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/IBlockedStructuredDocumentRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/IBlockedStructuredDocumentRegion.java
new file mode 100644
index 0000000000..30eab0c70e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/IBlockedStructuredDocumentRegion.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+
+/**
+ * IBlockedStructuredDocumentRegion is just like an IStructuredDocumentRegion
+ * except results from parsing a "block tag" (such as SCRIPT or STYLE).
+ * Because these are "variable" partition types, its often handy (efficient)
+ * to keep track of the partition type.
+ *
+ * @plannedfor 1.0
+ */
+public interface IBlockedStructuredDocumentRegion extends IStructuredDocumentRegion {
+ /**
+ * Return the partion type for this region.
+ *
+ * @return the partion type.
+ */
+ String getPartitionType();
+
+ /**
+ * Sets the partion type.
+ *
+ * For use by parsers and re-parsers only.
+ *
+ * @param partitionType
+ */
+ void setPartitionType(String partitionType);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/JSPCapableParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/JSPCapableParser.java
new file mode 100644
index 0000000000..3d55973a07
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/JSPCapableParser.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+import java.util.List;
+
+public interface JSPCapableParser extends RegionParser, BlockTagParser {
+ void addNestablePrefix(TagMarker marker);
+
+ /**
+ * returns the TagMarkers for prefixes that are allowed to be nestable
+ *
+ * @return
+ */
+ List getNestablePrefixes();
+
+ void removeNestablePrefix(String tagName);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/RegionParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/RegionParser.java
new file mode 100644
index 0000000000..628dfc6ad6
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/RegionParser.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+
+
+import java.io.Reader;
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+
+
+public interface RegionParser {
+
+ IStructuredDocumentRegion getDocumentRegions();
+
+ List getRegions();
+
+ /**
+ * The 'newInstance' method is similar to 'clone', but does not include
+ * the copying of any content. For a pure RegionParser itself, there would
+ * be little state to "clone", but for some subtypes, such as
+ * StructuredDocumentRegionParser and JSPCapableParser, there could the
+ * more internal data to "clone", such as the internal tokenizer should be
+ * cloned (including block tags, etc).
+ */
+ RegionParser newInstance();
+
+ void reset(Reader reader);
+
+ /**
+ * An additional offset for use with any position-dependant parsing rules
+ */
+ void reset(Reader reader, int offset);
+
+ void reset(String input);
+
+ /**
+ * An additional offset for use with any position-dependant parsing rules
+ */
+ void reset(String input, int offset);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandler.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandler.java
new file mode 100644
index 0000000000..31e5cba8f3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandler.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+
+public interface StructuredDocumentRegionHandler {
+
+ // Sent when a IStructuredDocumentRegion is first parsed
+ public void nodeParsed(IStructuredDocumentRegion aCoreStructuredDocumentRegion);
+
+ // Sent when the calling parser's model undergoes a full reset
+ // and any information based upon the old model should be
+ // cleared
+ public void resetNodes();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandlerExtension.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandlerExtension.java
new file mode 100644
index 0000000000..85f2d7b58d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionHandlerExtension.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+
+public interface StructuredDocumentRegionHandlerExtension extends StructuredDocumentRegionHandler {
+ void setStructuredDocument(IStructuredDocument newDocument);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParser.java
new file mode 100644
index 0000000000..d4bbe5dac0
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParser.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+
+
+public interface StructuredDocumentRegionParser extends RegionParser {
+
+ void addStructuredDocumentRegionHandler(StructuredDocumentRegionHandler handler);
+
+ void removeStructuredDocumentRegionHandler(StructuredDocumentRegionHandler handler);
+
+ void resetHandlers();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParserExtension.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParserExtension.java
new file mode 100644
index 0000000000..474950e5a8
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/StructuredDocumentRegionParserExtension.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+import java.util.List;
+
+
+
+public interface StructuredDocumentRegionParserExtension extends StructuredDocumentRegionParser {
+ /**
+ * Returns the current list of StructuredDocumentRegionHandlers listening
+ * to this parser.
+ *
+ * @return List - the list of listeners, the list may not be null and each
+ * element in it must implement StructuredDocumentRegionHandler
+ */
+ List getStructuredDocumentRegionHandlers();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/TagMarker.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/TagMarker.java
new file mode 100644
index 0000000000..3de225d699
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/ltk/parser/TagMarker.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.ltk.parser;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+
+/**
+ * ISSUE: need to provide functionality in improved API.
+ */
+
+public class TagMarker {
+
+ // a ITextRegion (meant to be updated with the model) marking the position
+ // where this tagname becomes effective
+ protected ITextRegion fMarker = null;
+
+ // the tagname
+ protected String fTagName = null;
+
+ /**
+ *
+ */
+ public TagMarker() {
+ super();
+ }
+
+ public TagMarker(String tagname) {
+ super();
+ setTagName(tagname);
+ }
+
+ public TagMarker(String tagname, ITextRegion marker) {
+ super();
+ setTagName(tagname);
+ setMarker(marker);
+ }
+
+ public final ITextRegion getMarker() {
+ return fMarker;
+ }
+
+ /**
+ * @return java.lang.String
+ */
+ public final String getTagName() {
+ return fTagName;
+ }
+
+ /**
+ * @return boolean
+ */
+ public boolean isGlobal() {
+ return fMarker == null;
+ }
+
+ /**
+ * @param newMarker
+ */
+ public final void setMarker(ITextRegion newMarker) {
+ fMarker = newMarker;
+ }
+
+ /**
+ * @param newTagname
+ * java.lang.String
+ */
+ public final void setTagName(String newTagName) {
+ fTagName = newTagName;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractModelLoader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractModelLoader.java
new file mode 100644
index 0000000000..caafaaf7e3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractModelLoader.java
@@ -0,0 +1,545 @@
+/*******************************************************************************
+ * 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);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractStructuredModel.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractStructuredModel.java
new file mode 100644
index 0000000000..a5a653d835
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/AbstractStructuredModel.java
@@ -0,0 +1,1517 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.SSECoreMessages;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingRule;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
+import org.eclipse.wst.sse.core.internal.provisional.DocumentChanged;
+import org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.events.AboutToBeChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.exceptions.ResourceInUse;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager;
+import org.eclipse.wst.sse.core.internal.util.URIResolver;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+
+
+public abstract class AbstractStructuredModel implements IStructuredModel {
+
+ private static final String MODEL_MANAGER_NULL = "Warning: AbstractStructuredModel::close: model manager was null during a close of a model (which should be impossible)"; //$NON-NLS-1$
+
+ class DirtyStateWatcher implements IStructuredDocumentListener {
+
+ public void newModel(NewDocumentEvent structuredDocumentEvent) {
+
+ // I don't think its safe to assume a new model
+ // is always "fresh", so we'll leave dirty state
+ // unchanged;
+ // but we'll tell everyone about it.
+ setDirtyState(fDirtyState);
+ }
+
+ public void noChange(NoChangeEvent structuredDocumentEvent) {
+
+ // don't change dirty state
+ }
+
+ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) {
+
+ setDirtyState(true);
+ // no need to listen any more
+ if (fStructuredDocument != null) {
+ fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher);
+ }
+ }
+
+ public void regionChanged(RegionChangedEvent structuredDocumentEvent) {
+
+ setDirtyState(true);
+ // no need to listen any more
+ if (fStructuredDocument != null) {
+ fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher);
+ }
+ }
+
+ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) {
+
+ setDirtyState(true);
+ // no need to listen any more
+ if (fStructuredDocument != null) {
+ fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher);
+ }
+ }
+ }
+
+ class DocumentToModelNotifier implements IStructuredDocumentListener, IModelAboutToBeChangedListener {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IModelAboutToBeChangedListener#modelAboutToBeChanged(org.eclipse.wst.sse.core.events.AboutToBeChangedEvent)
+ */
+ public void modelAboutToBeChanged(AboutToBeChangedEvent structuredDocumentEvent) {
+ // If we didn't originate the change, take note we are about to
+ // change based on our underlying document changing.
+ // If we did originate the change, we, or client, should have
+ // already called aboutToChangeModel.
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ aboutToChangeModel();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#newModel(org.eclipse.wst.sse.core.events.NewDocumentEvent)
+ */
+ public void newModel(NewDocumentEvent structuredDocumentEvent) {
+ // if we didn't originate the change, take note we have changed
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ changedModel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#noChange(org.eclipse.wst.sse.core.events.NoChangeEvent)
+ */
+ public void noChange(NoChangeEvent structuredDocumentEvent) {
+ // if we didn't originate the change, take note we have changed
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ changedModel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#nodesReplaced(org.eclipse.wst.sse.core.events.StructuredDocumentRegionsReplacedEvent)
+ */
+ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) {
+ // if we didn't originate the change, take note we have changed
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ changedModel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#regionChanged(org.eclipse.wst.sse.core.events.RegionChangedEvent)
+ */
+ public void regionChanged(RegionChangedEvent structuredDocumentEvent) {
+ // if we didn't originate the change, take note we have changed
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ changedModel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.events.IStructuredDocumentListener#regionsReplaced(org.eclipse.wst.sse.core.events.RegionsReplacedEvent)
+ */
+ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) {
+ // if we didn't originate the change, take note we have changed
+ if (structuredDocumentEvent.getOriginalRequester() != this) {
+ changedModel();
+ }
+ }
+
+ }
+
+ private FactoryRegistry factoryRegistry;
+ private String fBaseLocation;
+ boolean fDirtyState;
+ DirtyStateWatcher fDirtyStateWatcher;
+ DocumentToModelNotifier fDocumentToModelNotifier;
+ private String fExplicitContentTypeIdentifier;
+ private String fId;
+
+ private LifecycleNotificationManager fLifecycleNotificationManager;
+
+ private final Object fListenerLock = new byte[0];
+ protected ILock fLockObject;
+ // private String fLineDelimiter;
+ // private Object fType;
+ private IModelHandler fModelHandler;
+ // issue: we should not "hold on" to model manager, can
+ // easily get with StructuredModelManager.getModelManager();
+ // but will need to add more null checks.
+ private IModelManager fModelManager;
+ private int fModelStateChanging;
+ private Object[] fModelStateListeners;
+ private boolean fNewState = false;
+ private URIResolver fResolver;
+ protected IStructuredDocument fStructuredDocument;
+ /**
+ * The time stamp of the underlying resource's modification date, at the
+ * time this model was created, or the last time it was saved. Note: for
+ * this version, this variable is not set automatically, be needs to be
+ * managed by client. The FileModelProvider does this for most cases, but
+ * if client do not use FileModelProvider, they must set this variable
+ */
+ public long fSynchronizationStamp = IResource.NULL_STAMP;
+ private boolean reinitializationNeeded;
+ private Object reinitializeStateData;
+
+ /**
+ * AbstractStructuredModel constructor comment.
+ */
+ public AbstractStructuredModel() {
+
+ super();
+ fDirtyStateWatcher = new DirtyStateWatcher();
+ fDocumentToModelNotifier = new DocumentToModelNotifier();
+ }
+
+
+ /**
+ * This method is just for getting an instance of the model manager of the
+ * right Impl type, to be used "internally" for making protected calls
+ * directly to the impl class.
+ */
+ private ModelManagerImpl _getModelManager() {
+ // TODO_future: redesign so we don't need this 'Impl' version
+ if (fModelManager == null) {
+ fModelManager = StructuredModelManager.getModelManager();
+ }
+
+ return (ModelManagerImpl) fModelManager;
+ }
+
+ /**
+ * This API allows clients to declare that they are about to make a
+ * "large" change to the model. This change might be in terms of content
+ * or it might be in terms of the model id or base location. Note that in
+ * the case of embedded calls, notification to listeners is sent only
+ * once. Note that the client who is making these changes has the
+ * responsibility to restore the models state once finished with the
+ * changes. See getMemento and restoreState. The method
+ * isModelStateChanging can be used by a client to determine if the model
+ * is already in a change sequence.
+ */
+ public void aboutToChangeModel() {
+
+
+ // notice this is just a public avenue to our protected method
+ internalAboutToBeChanged();
+ }
+
+
+ public void aboutToReinitializeModel() {
+
+
+
+ // notice this is just a public avenue to our protected method
+ fireModelAboutToBeReinitialized();
+ }
+
+
+ public void addModelLifecycleListener(IModelLifecycleListener listener) {
+
+ synchronized (fListenerLock) {
+ if (fLifecycleNotificationManager == null) {
+ fLifecycleNotificationManager = new LifecycleNotificationManager();
+ }
+ fLifecycleNotificationManager.addListener(listener);
+ }
+ }
+
+ public void addModelStateListener(IModelStateListener listener) {
+
+ synchronized (fListenerLock) {
+
+ if (!Utilities.contains(fModelStateListeners, listener)) {
+ int oldSize = 0;
+ if (fModelStateListeners != null) {
+ // normally won't be null, but we need to be sure, for
+ // first
+ // time through
+ oldSize = fModelStateListeners.length;
+ }
+ int newSize = oldSize + 1;
+ Object[] newListeners = new Object[newSize];
+ if (fModelStateListeners != null) {
+ System.arraycopy(fModelStateListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fModelStateListeners = newListeners;
+ }
+ }
+ }
+
+ /**
+ * This lock to lock the small bits of data and operations in the models
+ * themselves. this lock is "shared" with document, so, eventually,
+ * changes can be made safely from either side.
+ *
+ * @deprecated
+ */
+ protected final void beginLock() {
+ }
+
+ public void beginRecording(Object requester) {
+
+ beginRecording(requester, null, null);
+ }
+
+ public void beginRecording(Object requester, int cursorPosition, int selectionLength) {
+
+ beginRecording(requester, null, null, cursorPosition, selectionLength);
+ }
+
+ public void beginRecording(Object requester, String label) {
+
+ beginRecording(requester, label, null);
+ }
+
+ public void beginRecording(Object requester, String label, int cursorPosition, int selectionLength) {
+
+ beginRecording(requester, label, null, cursorPosition, selectionLength);
+ }
+
+ public void beginRecording(Object requester, String label, String description) {
+
+ if (getUndoManager() != null)
+ getUndoManager().beginRecording(requester, label, description);
+ }
+
+ public void beginRecording(Object requester, String label, String description, int cursorPosition, int selectionLength) {
+
+ if (getUndoManager() != null)
+ getUndoManager().beginRecording(requester, label, description, cursorPosition, selectionLength);
+ }
+
+ /**
+ * This API allows a client controlled way of notifying all ModelEvent
+ * listners that the model has been changed. This method is a matched pair
+ * to aboutToChangeModel, and *must* be called after aboutToChangeModel
+ * ... or some listeners could be left waiting indefinitely for the
+ * changed event. So, its suggested that changedModel always be in a
+ * finally clause. Likewise, a client should never call changedModel
+ * without calling aboutToChangeModel first. In the case of embedded
+ * calls, the notification is just sent once.
+ */
+ public void changedModel() {
+
+
+ // notice this is just a public avenue to our protected method
+ internalModelChanged();
+ // also note!
+ // if we've been "changed" by a client, we might still need
+ // to be re-initialized, so we'll check and handle that here.
+ // Note only does this provide a solution to some "missed"
+ // re-inits, in provides a built in way for clients to
+ // "force" the model to handle itself, by bracketing any
+ // changes with aboutToChange and changed, the model itself
+ // will check. But only call re-init if all other pending
+ // modelChanged states have been handled.
+ if (fModelStateChanging == 0 && isReinitializationNeeded()) {
+ reinit();
+ }
+ }
+
+
+ /**
+ * Based on similar method in FileDocumentProvider. It will provide what
+ * the modificationStamp would be if resetSynchronzationStamp(resource)
+ * were used, although for this 'compute' API, no changes to the instance
+ * are made.
+ */
+ public long computeModificationStamp(IResource resource) {
+
+
+ long modificationStamp = resource.getModificationStamp();
+ IPath path = resource.getLocation();
+ if (path == null) {
+ return modificationStamp;
+ }
+ // Note: checking existence of file is a little different than
+ // impl in
+ // the FileDocumentProvider. See defect number 223790.
+ File file = path.toFile();
+ if (!file.exists()) {
+ return modificationStamp;
+ }
+ modificationStamp = file.lastModified();
+ return modificationStamp;
+ }
+
+
+ /**
+ * Provides a copy of the model, but a new ID must be provided. The
+ * principle of this copy is not to copy fields, etc., as is typically
+ * done in a clone method, but to return a model with the same content in
+ * the structuredDocument. Note: It is the callers responsibility to
+ * setBaseLocation, listners, etc., as appropriate. Type and Encoding are
+ * the only fields set by this method. If the newId provided already exist
+ * in the model manager, a ResourceInUse exception is thrown.
+ */
+ public IStructuredModel copy(String newId) throws ResourceInUse {
+
+
+ IStructuredModel newModel = null;
+ // this first one should fail, if not, its treated as an error
+ // If the caller wants to use an existing one, they can call
+ // getExisting
+ // after this failure
+ newModel = getModelManager().getExistingModelForEdit(newId);
+ if (newModel != null) {
+ // be sure to release the reference we got "by accident" (and
+ // no
+ // longer need)
+ newModel.releaseFromEdit();
+ throw new ResourceInUse();
+ }
+ newModel = getModelManager().copyModelForEdit(getId(), newId);
+ return newModel;
+ }
+
+
+ /**
+ * Disable undo management.
+ */
+ public void disableUndoManagement() {
+
+ if (getUndoManager() != null)
+ getUndoManager().disableUndoManagement();
+ }
+
+ /**
+ * Enable undo management.
+ */
+ public void enableUndoManagement() {
+
+ if (getUndoManager() != null)
+ getUndoManager().enableUndoManagement();
+ }
+
+ /**
+ * endLock is protected only for a very special purpose. So subclasses can
+ * call it to end the lock after updates have been made, but before
+ * notifications are sent
+ *
+ * @deprecated
+ */
+ protected final void endLock() {
+ }
+
+ public void endRecording(Object requester) {
+
+ if (getUndoManager() != null)
+ getUndoManager().endRecording(requester);
+ }
+
+ public void endRecording(Object requester, int cursorPosition, int selectionLength) {
+
+ if (getUndoManager() != null)
+ getUndoManager().endRecording(requester, cursorPosition, selectionLength);
+ }
+
+ /**
+ * Informs all registered model state listeners that the the model is
+ * about to under go a change. This change might be in terms of contents
+ * or might be in terms of the model's id or base location.
+ */
+ private void fireModelAboutToBeChanged() {
+
+ // we must assign listeners to local variable, since the add and
+ // remove listner
+ // methods can change the actual instance of the listener array
+ // from another thread
+ if (fModelStateListeners != null) {
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ ((IModelStateListener) holdListeners[i]).modelAboutToBeChanged(this);
+ }
+ }
+
+ }
+
+ protected void fireModelAboutToBeReinitialized() {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (fModelStateListeners != null) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelAboutToBeReinitialized"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ // NOTE: trick for transition. We actual use the same
+ // listeners
+ // as modelState, but only send this to those that have
+ // implemented ModelStateExtended.
+ IModelStateListener listener = (IModelStateListener) holdListeners[i];
+ listener.modelAboutToBeReinitialized(this);
+ }
+ }
+ }
+
+ private void fireModelChanged() {
+ // we must assign listeners
+ // to local variable, since the add
+ // and remove listner
+ // methods can change the actual instance of the listener
+ // array from another thread
+ if (fModelStateListeners != null) {
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ try {
+ ((IModelStateListener) holdListeners[i]).modelChanged(this);
+ }
+ // its so criticial that the begin/end arrive in
+ // pairs,
+ // if there happends to be an error in one of the
+ // modelChanged,
+ // they we want to be sure rest complete ok.
+ catch (Exception e) {
+ Logger.logException(e);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Informs all registered model state listeners about a change in the
+ * dirty state of the model. The dirty state is entirely about changes in
+ * the content of the model (not, for example, about changes to id, or
+ * base location -- see modelMoved).
+ */
+ protected void fireModelDirtyStateChanged(IStructuredModel element, boolean isDirty) {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (fModelStateListeners != null) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelDirtyStateChanged"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ ((IModelStateListener) holdListeners[i]).modelDirtyStateChanged(element, isDirty);
+ }
+ }
+ }
+
+ protected void fireModelReinitialized() {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (fModelStateListeners != null) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelReinitialized"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ IModelStateListener listener = (IModelStateListener) holdListeners[i];
+ listener.modelReinitialized(this);
+ }
+ }
+ }
+
+ /**
+ * Informs all registered model state listeners about the deletion of a
+ * model's underlying resource.
+ */
+ protected void fireModelResourceDeleted(IStructuredModel element) {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (fModelStateListeners != null) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelResourceDeleted"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ ((IModelStateListener) holdListeners[i]).modelResourceDeleted(element);
+ }
+ }
+ }
+
+ /**
+ * Informs all registered model state listeners that the resource
+ * underlying a model has been moved. This is typically reflected in a
+ * change to the id, baseLocation, or both.
+ */
+ protected void fireModelResourceMoved(IStructuredModel originalElement, IStructuredModel movedElement) {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (fModelStateListeners != null) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelResourceMoved"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Object[] holdListeners = fModelStateListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ ((IModelStateListener) holdListeners[i]).modelResourceMoved(originalElement, movedElement);
+ }
+ }
+ }
+
+ public Object getAdapter(Class adapter) {
+
+ return Platform.getAdapterManager().getAdapter(this, adapter);
+ }
+
+ /**
+ * @return java.lang.String
+ */
+ public java.lang.String getBaseLocation() {
+
+ return fBaseLocation;
+ }
+
+ /**
+ * @see org.eclipse.wst.sse.core.internal.provisional.IStructuredModel#getContentTypeIdentifier()
+ */
+ public String getContentTypeIdentifier() {
+ if (fExplicitContentTypeIdentifier != null)
+ return fExplicitContentTypeIdentifier;
+ return fModelHandler.getAssociatedContentTypeId();
+ }
+
+ /**
+ *
+ */
+ public FactoryRegistry getFactoryRegistry() {
+ if (factoryRegistry == null) {
+ factoryRegistry = new FactoryRegistry();
+ }
+ return factoryRegistry;
+ }
+
+ /**
+ * The id is the id that the model manager uses to identify this model
+ *
+ * @ISSUE - no one should need to know ID, so this should be default access eventually.
+ * If clients believe they do need ID, be sure to let us know (open a bug).
+ */
+ public String getId() {
+
+ return fId;
+ }
+
+ public abstract IndexedRegion getIndexedRegion(int offset);
+
+ /**
+ * Gets the contentTypeDescription.
+ *
+ * @return Returns a ContentTypeDescription
+ */
+ public IModelHandler getModelHandler() {
+
+ return fModelHandler;
+ }
+
+
+ public IModelManager getModelManager() {
+
+ return _getModelManager();
+ }
+
+ /**
+ * This function returns the reference count of underlying model.
+ */
+ // TODO: try to refine the design not to use this function
+ public int getReferenceCount() {
+
+
+ if (getModelManager() == null)
+ return 0;
+ return getModelManager().getReferenceCount(getId());
+ }
+
+
+ /**
+ * This function returns the reference count of underlying model.
+ */
+ // TODO: try to refine the design not to use this function
+ public int getReferenceCountForEdit() {
+
+
+
+ if (getModelManager() == null)
+ return 0;
+ return getModelManager().getReferenceCountForEdit(getId());
+ }
+
+
+ /**
+ * This function returns the reference count of underlying model.
+ */
+ // TODO: try to refine the design not to use this function
+ public int getReferenceCountForRead() {
+
+
+
+ if (getModelManager() == null)
+ return 0;
+ return getModelManager().getReferenceCountForRead(getId());
+ }
+
+ public Object getReinitializeStateData() {
+
+ return reinitializeStateData;
+ }
+
+
+
+ public URIResolver getResolver() {
+
+ return fResolver;
+ }
+
+
+ public IStructuredDocument getStructuredDocument() {
+
+ IStructuredDocument result = null;
+ result = fStructuredDocument;
+ return result;
+ }
+
+ /**
+ * Insert the method's description here. Creation date: (9/7/2001 2:30:26
+ * PM)
+ *
+ * @return long
+ */
+ public long getSynchronizationStamp() {
+
+ return fSynchronizationStamp;
+ }
+
+ public IStructuredTextUndoManager getUndoManager() {
+
+ IStructuredTextUndoManager structuredTextUndoManager = null;
+ IStructuredDocument structuredDocument = getStructuredDocument();
+ if (structuredDocument == null) {
+ structuredTextUndoManager = null;
+ }
+ else {
+ structuredTextUndoManager = structuredDocument.getUndoManager();
+ }
+ return structuredTextUndoManager;
+ }
+
+ public void initId(String id) {
+ fId = id;
+ }
+
+ private final Object STATE_LOCK = new Object();
+
+ final protected void internalAboutToBeChanged() {
+ int state = 0;
+ // we always increment counter, for every request (so *must* receive
+ // corresponding number of 'changedModel' requests)
+ synchronized (STATE_LOCK) {
+ state = fModelStateChanging++;
+ }
+
+ // notice we only fire this event if we are not
+ // already in a model state changing sequence
+ if (state == 0) {
+
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelAboutToBeChanged"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ try {
+ fireModelAboutToBeChanged();
+ }
+ catch (Exception e) {
+ Logger.logException("Exception while notifying model state listers of about to change", e); //$NON-NLS-1$
+ }
+
+ }
+
+ }
+
+ /**
+ * Informs all registered model state listeners that an impending change
+ * is now complete. This method must only be called by 'modelChanged'
+ * since it keeps track of counts.
+ */
+ final protected void internalModelChanged() {
+ int state = 0;
+ // always decrement
+ synchronized (STATE_LOCK) {
+ state = --fModelStateChanging;
+ }
+
+ // Check integrity
+ // to be less than zero is a programming error,
+ // but we'll reset to zero
+ // and try to continue
+ if (state < 0) {
+ state = 0;
+ throw new IllegalStateException("Program Error: modelStateChanging was less than zero"); //$NON-NLS-1$
+ }
+
+
+ // We only fire this event if all pending requests are done.
+ // That is, if we've received the same number of modelChanged as
+ // we have aboutToChangeModel.
+ if (state == 0) {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "IModelStateListener event for " + getId() + " : modelChanged"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ fireModelChanged();
+ }
+ }
+
+ public boolean isDirty() {
+
+ return fDirtyState;
+ }
+
+ /**
+ * This method has very special purpose, its used in subclass
+ * 'changedModel' to know when to do "ending" sorts of things, right
+ * before a call to super.ChangedModel would in deed put the model in
+ * 'end' state. Put another way, at the beginning of the subclasses's
+ * changedModel, the isModelStateChanging is true, but at end, it will be
+ * false. So, this method allows a small "peek ahead".
+ */
+ protected boolean isModelChangeStateOnVergeOfEnding() {
+
+
+ return fModelStateChanging == 1;
+ }
+
+ /**
+ * This method can be called to determine if the model is within a
+ * "aboutToChange" and "changed" sequence.
+ */
+ public boolean isModelStateChanging() {
+
+
+ return fModelStateChanging > 0;
+ }
+
+ public boolean isNew() {
+
+ return fNewState;
+ }
+
+ public boolean isReinitializationNeeded() {
+
+ return reinitializationNeeded;
+ }
+
+ public boolean isSaveNeeded() {
+
+
+ if (!isSharedForEdit())
+ return isDirty();
+ else
+ return false;
+ }
+
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ */
+ public boolean isShared() {
+ if (getModelManager() == null)
+ return false;
+ return getModelManager().isShared(getId());
+ }
+
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ */
+ public boolean isSharedForEdit() {
+
+
+ if (getModelManager() == null)
+ return false;
+ return getModelManager().isSharedForEdit(getId());
+ }
+
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ */
+ public boolean isSharedForRead() {
+
+
+ if (getModelManager() == null)
+ return false;
+ return getModelManager().isSharedForRead(getId());
+ }
+
+
+ public void modelReinitialized() {
+
+
+ // notice this is just a public avenue to our protected method
+ fireModelReinitialized();
+ }
+
+ public IStructuredModel newInstance() throws IOException {
+
+ IStructuredModel newModel = null;
+ // we delegate to the model manager, so loader, etc., can be
+ // used.
+ newModel = getModelManager().createNewInstance(this);
+ return newModel;
+ }
+
+ public IStructuredModel reinit() {
+
+
+ IStructuredModel result = null;
+ if (fModelStateChanging == 0) {
+ try {
+ aboutToChangeModel();
+ aboutToReinitializeModel();
+ result = _getModelManager().reinitialize(this);
+ }
+ finally {
+ setReinitializeNeeded(false);
+ setReinitializeStateData(null);
+ modelReinitialized();
+ changedModel();
+ }
+ }
+ else {
+ if (Logger.DEBUG_MODELSTATE) {
+ Logger.log(Logger.INFO, "indeed!!!"); //$NON-NLS-1$
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * This function allows the model to free up any resources it might be
+ * using. In particular, itself, as stored in the IModelManager.
+ */
+ public void releaseFromEdit() {
+
+
+ if (getModelManager() == null) {
+ throw new IllegalStateException(MODEL_MANAGER_NULL); //$NON-NLS-1$
+ }
+ else {
+ /*
+ * Be sure to check the shared state before releasing. (Since
+ * isShared assumes a count of 1 means not shared ... and we want
+ * our '1' to be that one.) The problem, of course, is that
+ * between pre-cycle notification and post-release notification,
+ * the model could once again have become shared, rendering the
+ * release notification incorrect.
+ */
+ boolean isShared = isShared();
+
+ if (!isShared) {
+ signalPreLifeCycleEventRelease(this);
+ }
+
+ _getModelManager().releaseFromEdit(this);
+ if (!isShared) {
+ signalPostLifeCycleListenerRelease(this);
+ }
+ }
+
+ }
+
+ /**
+ * This function allows the model to free up any resources it might be
+ * using. In particular, itself, as stored in the IModelManager.
+ */
+ public void releaseFromRead() {
+
+ if (getModelManager() == null) {
+ throw new IllegalStateException(MODEL_MANAGER_NULL); //$NON-NLS-1$
+ }
+ else {
+ /*
+ * Be sure to check the shared state before releasing. (Since
+ * isShared assumes a count of 1 means not shared ... and we want
+ * our '1' to be that one.) The problem, of course, is that
+ * between pre-cycle notification and post-release notification,
+ * the model could once again have become shared, rendering the
+ * release notification incorrect.
+ */
+ boolean isShared = isShared();
+
+ if (!isShared) {
+ signalPreLifeCycleEventRelease(this);
+ }
+
+ _getModelManager().releaseFromRead(this);
+
+ if (!isShared) {
+ signalPostLifeCycleListenerRelease(this);
+ }
+ }
+ }
+
+
+ /**
+ * This function replenishes the model with the resource without saving
+ * any possible changes. It is used when one editor may be closing, and
+ * specifially says not to save the model, but another "display" of the
+ * model still needs to hang on to some model, so needs a fresh copy.
+ */
+ public IStructuredModel reload(InputStream inputStream) throws IOException {
+ IStructuredModel result = null;
+ try {
+ aboutToChangeModel();
+ result = _getModelManager().reloadModel(getId(), inputStream);
+ }
+ catch (UnsupportedEncodingException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ finally {
+ changedModel();
+ }
+ return result;
+ }
+
+ public void removeModelLifecycleListener(IModelLifecycleListener listener) {
+
+ // if manager is null, then none have been added, so
+ // no need to remove any
+ if (fLifecycleNotificationManager == null)
+ return;
+ synchronized (fListenerLock) {
+ fLifecycleNotificationManager.removeListener(listener);
+ }
+ }
+
+
+ public void removeModelStateListener(IModelStateListener listener) {
+
+ if (listener == null)
+ return;
+ if (fModelStateListeners == null)
+ return;
+ // if its not in the listeners, we'll ignore the request
+ synchronized (fListenerLock) {
+ if (Utilities.contains(fModelStateListeners, listener)) {
+ int oldSize = fModelStateListeners.length;
+ int newSize = oldSize - 1;
+ Object[] newListeners = new Object[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fModelStateListeners[i] == listener) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are
+ // removing
+ newListeners[index++] = fModelStateListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the
+ // old
+ // one
+ fModelStateListeners = newListeners;
+ }
+ }
+ }
+
+
+ /**
+ * A method that modifies the model's synchronization stamp to match the
+ * resource. Turns out there's several ways of doing it, so this ensures a
+ * common algorithm.
+ */
+ public void resetSynchronizationStamp(IResource resource) {
+
+
+ setSynchronizationStamp(computeModificationStamp(resource));
+ }
+
+
+ /**
+ * This API allows a client to initiate notification to all interested
+ * parties that a model's underlying resource has been deleted.
+ */
+ public void resourceDeleted() {
+
+
+ // notice this is just a public avenue to our protected method
+ fireModelResourceDeleted(this);
+ }
+
+
+ /**
+ * This method allows a model client to initiate notification to all
+ * interested parties that a model's underlying resource location has
+ * changed. Note: we assume caller has already changed baseLocation, Id,
+ * etc., since its really up to the client to determine what's "new" about
+ * a moved model. Caution: 'this' and 'newModel' may be the same object.
+ * This is the case for current working with FileModelProvider, but have
+ * left the dual argument for future possibilities.
+ */
+ public void resourceMoved(IStructuredModel newModel) {
+
+
+ // notice this is just a public avenue to our protected method
+ fireModelResourceMoved(this, newModel);
+ }
+
+
+ public void save() throws UnsupportedEncodingException, IOException, CoreException {
+
+ int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+ try {
+ String stringId = getId();
+ _getModelManager().saveModel(stringId, EncodingRule.CONTENT_BASED);
+ }
+
+ finally {
+ // we put end notification in finally block, so even if
+ // error occurs during save, listeners are still notified,
+ // since their code could depend on receiving, to clean up
+ // some state, or coordinate other resources.
+ type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+
+ public void save(EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException {
+
+ int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+ try {
+ String stringId = getId();
+ _getModelManager().saveModel(stringId, encodingRule);
+ }
+ finally {
+ // we put end notification in finally block, so even if
+ // error occurs during save, listeners are still notified,
+ // since their code could depend on receiving, to clean up
+ // some state, or coordinate other resources.
+ type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+
+ public void save(IFile iFile) throws UnsupportedEncodingException, IOException, CoreException {
+
+ int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+ try {
+ String stringId = getId();
+ _getModelManager().saveModel(iFile, stringId, EncodingRule.CONTENT_BASED);
+ }
+
+ finally {
+ // we put end notification in finally block, so even if
+ // error occurs during save, listeners are still notified,
+ // since their code could depend on receiving, to clean up
+ // some state, or coordinate other resources.
+ type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+
+ public void save(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException {
+
+ int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+ try {
+ String stringId = getId();
+ _getModelManager().saveModel(iFile, stringId, encodingRule);
+ }
+ finally {
+ // we put end notificatioon in finally block, so even if
+ // error occurs during save, listeners are still notified,
+ // since their code could depend on receiving, to clean up
+ // some state, or coordinate other resources.
+ type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+
+ public void save(OutputStream outputStream) throws UnsupportedEncodingException, CoreException, IOException {
+
+ int type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+ try {
+ String stringId = getId();
+ _getModelManager().saveModel(stringId, outputStream, EncodingRule.CONTENT_BASED);
+ }
+
+ finally {
+ // we put end notification in finally block, so even if
+ // error occurs during save, listeners are still notified,
+ // since their code could depend on receiving, to clean up
+ // some state, or coordinate other resources.
+ type = ModelLifecycleEvent.MODEL_SAVED | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+
+ /**
+ * This attribute is typically used to denote the model's underlying
+ * resource.
+ */
+ public void setBaseLocation(java.lang.String newBaseLocation) {
+ fBaseLocation = newBaseLocation;
+ if (fResolver != null) {
+ fResolver.setFileBaseLocation(newBaseLocation);
+ }
+ }
+
+ public void setContentTypeIdentifier(String contentTypeIdentifier) {
+ fExplicitContentTypeIdentifier = contentTypeIdentifier;
+ }
+
+ /**
+ *
+ */
+ public void setDirtyState(boolean dirtyState) {
+
+ // no need to process (set or fire event), if same value
+ if (fDirtyState != dirtyState) {
+ // pre-change notification
+ int type = ModelLifecycleEvent.MODEL_DIRTY_STATE | ModelLifecycleEvent.PRE_EVENT;
+ ModelLifecycleEvent modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+
+
+ // the actual change
+ fDirtyState = dirtyState;
+
+ // old notification
+ // TODO: C3 remove old notification
+ if (fDirtyState == false) {
+ // if we are being set to not dirty (such as just been saved)
+ // then we need to start listening for changes
+ // again to know when to set state to true;
+ getStructuredDocument().addDocumentChangedListener(fDirtyStateWatcher);
+ }
+ fireModelDirtyStateChanged(this, dirtyState);
+
+
+ // post change notification
+ type = ModelLifecycleEvent.MODEL_DIRTY_STATE | ModelLifecycleEvent.POST_EVENT;
+ modelLifecycleEvent = new ModelLifecycleEvent(this, type);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+ /**
+ * @deprecated - will likely be deprecated soon, in favor of direct 'adds'
+ * ... but takes some redesign.
+ */
+ public void setFactoryRegistry(FactoryRegistry factoryRegistry) {
+ this.factoryRegistry = factoryRegistry;
+ }
+
+ /**
+ * The id is the id that the model manager uses to identify this model. If
+ * it is being set here, it means the model manger is already managing the
+ * model with another id, so we have to keep it in sync. This method calls
+ * notifies listners, if they haven't been notified already, that a "model
+ * state change" is about to occur.
+ */
+ public void setId(String newId) throws ResourceInUse {
+
+
+ // It makes no sense, I don't think, to have an id of null, so
+ // we'll throw an illegal argument exception if someone trys. Note:
+ // the IModelManager could not manage a model with an id of null,
+ // since it uses hashtables, and you can't have a null id for a
+ // hashtable.
+ if (newId == null)
+ throw new IllegalArgumentException(SSECoreMessages.A_model_s_id_can_not_be_nu_EXC_); //$NON-NLS-1$ = "A model's id can not be null"
+ // To guard against throwing a spurious ResourceInUse exception,
+ // which can occur when two pieces of code both want to change the id,
+ // so the second request is spurious, we'll ignore any requests that
+ // attempt to change the id to what it already is ... note, we use
+ // 'equals', not identity ('==') so that things like
+ // strings can be used. This is the same criteria that ids are
+ // found in model manager -- well, actually, I just checked, and for
+ // the hashtable impl, the criteria uses .equals AND the condition
+ // that the hash values be identical (I'm assuming this is always
+ // true, if equals is true, for now, I'm not sure
+ // we can assume that hashtable will always be used, but in
+ // general, should match.)
+ //
+ if (newId.equals(fId))
+ return;
+ // we must guard against reassigning an id to one that we already
+ // are managing.
+ if (getModelManager() != null) {
+ boolean inUse = ((ModelManagerImpl)getModelManager()).isIdInUse(newId);
+ if (inUse) {
+ throw new ResourceInUse();
+ }
+ }
+ try {
+ // normal code path
+ aboutToChangeModel();
+ String oldId = fId;
+ fId = newId;
+ if (getModelManager() != null) {
+ // if managed and the id has changed, notify to
+ // IModelManager
+ // TODO: try to refine the design not to do that
+ if (oldId != null && newId != null && !newId.equals(oldId)) {
+ getModelManager().moveModel(oldId, newId);
+ }
+ }
+ }
+ finally {
+ // make sure this finally is only executed if 'about to Change
+ // model' has
+ // been executed.
+ changedModel();
+ }
+ }
+
+ /**
+ * Sets the contentTypeDescription.
+ *
+ * @param contentTypeDescription
+ * The contentTypeDescription to set
+ */
+ public void setModelHandler(IModelHandler modelHandler) {
+
+ // no need to fire events if modelHandler has been null
+ // for this model --
+ // this is an attempt at initialization optimization and may need
+ // to change in future.
+ boolean trueChange = false;
+ if (fModelHandler != null)
+ trueChange = true;
+ if (trueChange) {
+ internalAboutToBeChanged();
+ }
+ fModelHandler = modelHandler;
+ if (trueChange) {
+ internalModelChanged();
+ }
+ }
+
+
+
+ public void setModelManager(IModelManager newModelManager) {
+
+ fModelManager = newModelManager;
+ }
+
+ /**
+ *
+ */
+ public void setNewState(boolean newState) {
+
+ fNewState = newState;
+ }
+
+ /**
+ * Sets a "flag" that reinitialization is needed.
+ */
+ public void setReinitializeNeeded(boolean needed) {
+
+ reinitializationNeeded = needed;
+ }
+
+ /**
+ * Holds any data that the reinit procedure might find useful in
+ * reinitializing the model. This is handy, since the reinitialization may
+ * not take place at once, and some "old" data may be needed to properly
+ * undo previous settings. Note: the parameter was intentionally made to
+ * be of type 'Object' so different models can use in different ways.
+ */
+ public void setReinitializeStateData(Object object) {
+
+ reinitializeStateData = object;
+ }
+
+
+ public void setResolver(URIResolver newResolver) {
+
+ fResolver = newResolver;
+ }
+
+
+ public void setStructuredDocument(IStructuredDocument newStructuredDocument) {
+ boolean lifeCycleNotification = false;
+ if (fStructuredDocument != null) {
+ fStructuredDocument.removeDocumentChangedListener(fDirtyStateWatcher);
+ fStructuredDocument.removeDocumentAboutToChangeListener(fDocumentToModelNotifier);
+ fStructuredDocument.removeDocumentChangedListener(fDocumentToModelNotifier);
+ // prechange notification
+ lifeCycleNotification = true;
+ ModelLifecycleEvent modelLifecycleEvent = new DocumentChanged(ModelLifecycleEvent.PRE_EVENT, this, fStructuredDocument, newStructuredDocument);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+
+ // hold for life cycle notification
+ IStructuredDocument previousDocument = fStructuredDocument;
+ // the actual change
+ fStructuredDocument = newStructuredDocument;
+
+
+ // at the super class level, we'll listen for structuredDocument
+ // changes
+ // so we can set our dirty state flag
+ if (fStructuredDocument != null) {
+ fStructuredDocument.addDocumentChangedListener(fDirtyStateWatcher);
+ fStructuredDocument.addDocumentAboutToChangeListener(fDocumentToModelNotifier);
+ fStructuredDocument.addDocumentChangedListener(fDocumentToModelNotifier);
+ }
+
+ if (lifeCycleNotification) {
+ // post change notification
+ ModelLifecycleEvent modelLifecycleEvent = new DocumentChanged(ModelLifecycleEvent.POST_EVENT, this, previousDocument, newStructuredDocument);
+ signalLifecycleEvent(modelLifecycleEvent);
+ }
+ }
+
+ /**
+ * Insert the method's description here. Creation date: (9/7/2001 2:30:26
+ * PM)
+ *
+ * @param newSynchronizationStamp
+ * long
+ */
+ protected void setSynchronizationStamp(long newSynchronizationStamp) {
+
+ fSynchronizationStamp = newSynchronizationStamp;
+ }
+
+ public void setUndoManager(IStructuredTextUndoManager undoManager) {
+
+ IStructuredDocument structuredDocument = getStructuredDocument();
+ if (structuredDocument == null) {
+ throw new IllegalStateException("document was null when undo manager set on model"); //$NON-NLS-1$
+ }
+ structuredDocument.setUndoManager(undoManager);
+ }
+
+ /**
+ * To be called only by "friendly" classes, such as ModelManager, and
+ * subclasses.
+ */
+ void signalLifecycleEvent(ModelLifecycleEvent event) {
+ if (fLifecycleNotificationManager == null)
+ return;
+ fLifecycleNotificationManager.signalLifecycleEvent(event);
+ }
+
+ private void signalPostLifeCycleListenerRelease(IStructuredModel structuredModel) {
+ int type = ModelLifecycleEvent.MODEL_RELEASED | ModelLifecycleEvent.POST_EVENT;
+ // what's wrong with this design that a cast is needed here!?
+ ModelLifecycleEvent event = new ModelLifecycleEvent(structuredModel, type);
+ ((AbstractStructuredModel) structuredModel).signalLifecycleEvent(event);
+ }
+
+ private void signalPreLifeCycleEventRelease(IStructuredModel structuredModel) {
+ int type = ModelLifecycleEvent.MODEL_RELEASED | ModelLifecycleEvent.PRE_EVENT;
+ // what's wrong with this design that a cast is needed here!?
+ ModelLifecycleEvent event = new ModelLifecycleEvent(structuredModel, type);
+ ((AbstractStructuredModel) structuredModel).signalLifecycleEvent(event);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/FactoryRegistry.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/FactoryRegistry.java
new file mode 100644
index 0000000000..d030390362
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/FactoryRegistry.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+
+
+/**
+ * This class simply maintains the list of factories and returns singleton
+ * instances of them. Some "built in" types are automatically created form
+ * FactoryConfig, if not found registerd, but normally clients can/should
+ * register their own factories.
+ *
+ * Not intended for clients to subclass or instantiate.
+ *
+ */
+public final class FactoryRegistry {
+
+ private List factories;
+
+ /**
+ * intentionally default access
+ */
+ FactoryRegistry() {
+ super();
+
+ }
+
+ private List _getFactories() {
+
+ if (factories == null) {
+ // may need to use java.util.Collections.synchronizedList() if
+ // syncronization becomes
+ // necessary (and if so, remember to synchronize on factories)
+ factories = new ArrayList();
+ }
+ return factories;
+
+ }
+
+ public void addFactory(INodeAdapterFactory factory) {
+ _getFactories().add(factory);
+ }
+
+ public void clearFactories() {
+ factories.clear();
+ }
+
+ /*
+ * @see FactoryRegistry#contains(Object)
+ */
+ public boolean contains(Object type) {
+ boolean result = false;
+ // note: we're not using cloned list, so strictly speaking
+ // is not thread safe.
+ List internalList = _getFactories();
+ for (int i = 0; i < internalList.size(); i++) {
+ INodeAdapterFactory factory = (INodeAdapterFactory) internalList.get(i);
+ if (factory.isFactoryForType(type)) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a shallow copy of the list of factories in the registry. Note:
+ * this can not be used to add/remove factories. Its primarily provided
+ * for those few cases where a list of factories must be copied from one
+ * model and added to another.
+ */
+ public List getFactories() {
+ // note: for object integrity, we don't let anyone get
+ // our main list (so they have to add through addFactory),
+ // but we will return a shallow "cloned" list.
+ List factoryList = new ArrayList(_getFactories());
+ return factoryList;
+ }
+
+ /**
+ * This method is a not a pure resistry. Factories retrieved based on
+ * their response to "isFactoryForType(type)". Note that if there is more
+ * than one factory that can answer 'true' that the most recently added
+ * factory is used.
+ */
+ public INodeAdapterFactory getFactoryFor(Object type) {
+
+ INodeAdapterFactory result = null;
+ if (factories == null)
+ return null;
+ int listSize = factories.size();
+ for (int i = listSize - 1; i >= 0; i--) {
+ // It is the adapter factories responsibility to answer
+ // isFactoryForType so it gets choosen.
+ // Notice we are going through the list backwards to get the
+ // factory added last.
+ INodeAdapterFactory a = (INodeAdapterFactory) factories.get(i);
+ if (a.isFactoryForType(type)) {
+ result = a;
+ break;
+ }
+ }
+ return result;
+
+ }
+
+ /**
+ *
+ */
+ public void release() {
+ // modified to work on copy of list, for V5PTF1
+ // send release to local copy of list
+ // of factories, since some factories, during
+ // their release function, may remove
+ // themselves from the registry.
+ List localList = getFactories();
+ for (int i = 0; i < localList.size(); i++) {
+ INodeAdapterFactory a = (INodeAdapterFactory) localList.get(i);
+ // To help bullet proof code, we'll catch and log
+ // any messages thrown by factories during release,
+ // but we'll attempt to keep going.
+ // In nearly all cases, though, such errors are
+ // severe for product/client, and need to be fixed.
+ try {
+ a.release();
+ }
+ catch (Exception e) {
+ Logger.logException("Program problem releasing factory" + a, e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Removes a factory if it can be retrieved by getFactoryFor(type). If
+ * there is more than one, all are removed. If there is none, the call
+ * simply returns (that is, it is not considered an error).
+ */
+ public void removeFactoriesFor(java.lang.Object type) {
+ if (factories != null) {
+ int listSize = factories.size();
+ // we'll go backwards through list, since we're removing, so
+ // 'size' change won't matter.
+ // Note: I'm assuming other items in the collection do not change
+ // position
+ // simply because another was removed.
+ for (int i = listSize - 1; i >= 0; i--) {
+ // It is the adapter factories responsibility to answer
+ // isFactoryForType so it gets choosen.
+ INodeAdapterFactory a = (INodeAdapterFactory) factories.get(i);
+ if (a.isFactoryForType(type)) {
+ factories.remove(a);
+ }
+ }
+ }
+ }
+
+ public void removeFactory(INodeAdapterFactory factory) {
+ _getFactories().remove(factory);
+
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/LifecycleNotificationManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/LifecycleNotificationManager.java
new file mode 100644
index 0000000000..adcf010f0a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/LifecycleNotificationManager.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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 org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.IModelLifecycleListener;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+
+
+/**
+ * For "internal use" only by AbstractStructuredModel
+ */
+
+class LifecycleNotificationManager {
+ private Object[] fListeners;
+
+ LifecycleNotificationManager() {
+ super();
+ }
+
+ /**
+ * Adds a new copy of the given listener to the list of Life Cycle
+ * Listeners.
+ *
+ * Multiple copies of the same listener are allowed. This is required to
+ * support threaded listener management properly and for model-driven move
+ * to work. For example, two adds and a single remove should result in the
+ * listener still listening for events.
+ *
+ * @param listener
+ */
+ void addListener(IModelLifecycleListener listener) {
+ if (Logger.DEBUG && Utilities.contains(fListeners, listener)) {
+ Logger.log(Logger.WARNING, "IModelLifecycleListener " + listener + " listening more than once"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ int oldSize = 0;
+ if (fListeners != null) {
+ // normally won't be null, but we need to be sure, for first
+ // time through
+ oldSize = fListeners.length;
+ }
+ int newSize = oldSize + 1;
+ Object[] newListeners = new Object[newSize];
+ if (fListeners != null) {
+ System.arraycopy(fListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fListeners = newListeners;
+ }
+
+ /**
+ * Removes a single copy of the given listener from the list of Life Cycle
+ * Listeners.
+ *
+ * @param listener
+ */
+ void removeListener(IModelLifecycleListener listener) {
+ if (Utilities.contains(fListeners, listener)) {
+ // if its not in the listeners, we'll ignore the request
+ int oldSize = fListeners.length;
+ int newSize = oldSize - 1;
+ Object[] newListeners = new Object[newSize];
+ int index = 0;
+ boolean removedOnce = false;
+ for (int i = 0; i < oldSize; i++) {
+ if (fListeners[i] == listener && !removedOnce) {
+ // ignore on the first match
+ removedOnce = true;
+ } else {
+ // copy old to new if it's not the one we are removing
+ newListeners[index++] = fListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the old
+ // one
+ fListeners = newListeners;
+ }
+ if (Logger.DEBUG && Utilities.contains(fListeners, listener)) {
+ Logger.log(Logger.WARNING, "IModelLifecycleListener " + listener + " removed once but still listening"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ void signalLifecycleEvent(ModelLifecycleEvent event) {
+ if (Logger.DEBUG_LIFECYCLE) {
+ Logger.log(Logger.INFO, "ModelLifecycleEvent fired for " + event.getModel().getId() + ": " + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ System.out.println("ModelLifecycleEvent fired for " + event.getModel().getId() + ": " + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ // We must assign listeners to local variable, since the add and
+ // remove listener methods can change the actual instance of the
+ // listener array from another thread
+ if (fListeners != null) {
+ Object[] holdListeners = fListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ IModelLifecycleListener listener = (IModelLifecycleListener) holdListeners[i];
+ // only one type of listener for now ... this could become
+ // more complex
+ if ((event.getInternalType() & ModelLifecycleEvent.PRE_EVENT) == ModelLifecycleEvent.PRE_EVENT) {
+ listener.processPreModelEvent(event);
+ }
+ if ((event.getInternalType() & ModelLifecycleEvent.POST_EVENT) == ModelLifecycleEvent.POST_EVENT) {
+ listener.processPostModelEvent(event);
+ }
+ }
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelLifecycleEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelLifecycleEvent.java
new file mode 100644
index 0000000000..def66d069d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelLifecycleEvent.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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 org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+
+/**
+ * This is an early version of a class that may change over the next few
+ * milestones.
+ */
+
+
+public class ModelLifecycleEvent {
+
+
+ // this list is for "public" consumption
+ public static final int MODEL_SAVED = 0x0001;
+ public static final int MODEL_RELEASED = 0x00002;
+ public static final int MODEL_DOCUMENT_CHANGED = 0x0003;
+ public static final int MODEL_DIRTY_STATE = 0x0004;
+ public static final int MODEL_REVERT= 0x0005;
+
+ // TODO: finish support for these
+ // following not implemented yet
+ public static final int MODEL_REINITIALIZED = 0x0006;
+ //public static final int ADAPTERS_NOTIFIED = 0x0007;
+ //public static final int MODEL_RESOURCE_MOVED = 0x0008;
+ //public static final int MODEL_RESOURCE_DELETED = 0x0009;
+
+ // This list (upper two bytes) is for only internal mechanisms and
+ // subclasses
+ // For simplicity they are "masked out" when client calls getType()
+ protected static final int PRE_EVENT = 0x0100;
+ private final static int MASK = 0x00FF;
+ protected static final int POST_EVENT = 0x0200;
+
+
+ private IStructuredModel fModel;
+ private int fType;
+
+ public ModelLifecycleEvent() {
+ super();
+ }
+
+ public ModelLifecycleEvent(int type) {
+ this();
+ fType = type;
+ }
+
+ public ModelLifecycleEvent(IStructuredModel structuredModel, int type) {
+ this(type);
+ fModel = structuredModel;
+ }
+
+ private String debugString(int type) {
+ String result = null;
+ switch (type & MASK) {
+ case MODEL_SAVED :
+ result = "MODEL_SAVED"; //$NON-NLS-1$
+ break;
+ case MODEL_RELEASED :
+ result = "MODEL_RELEASED"; //$NON-NLS-1$
+ break;
+ case MODEL_DOCUMENT_CHANGED :
+ result = "MODEL_DOCUMENT_CHANGED"; //$NON-NLS-1$
+ break;
+ case MODEL_DIRTY_STATE :
+ result = "MODEL_DIRTY_STATE"; //$NON-NLS-1$
+ break;
+ /*
+ * case MODEL_REINITIALIZED : result = "MODEL_REINITIALIZED";
+ * break; case MODEL_RELOADED : result = "MODEL_RELOADED"; break;
+ * case ADAPTERS_NOTIFIED : result = "ADAPTERS_NOTIFIED"; break;
+ * case MODEL_RESOURCE_MOVED : result = "MODEL_RESOURCE_MOVED";
+ * break; case MODEL_RESOURCE_DELETED : result =
+ * "MODEL_RESOURCE_DELETED"; break;
+ */
+ default :
+ throw new IllegalStateException("ModelLifecycleEvent did not have valid type"); //$NON-NLS-1$
+ }
+ return result;
+ }
+
+ protected int getInternalType() {
+
+ return fType;
+ }
+
+ public IStructuredModel getModel() {
+
+ return fModel;
+ }
+
+ public int getType() {
+
+ // for now, we'll mask type to "public" ones this easy
+ // way ... but I know there must be a better way
+ return fType & MASK;
+ }
+
+ public String toString() {
+ String result = null;
+ result = "ModelLifecycleEvent: " + debugString(fType); //$NON-NLS-1$
+ return result;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelManagerImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelManagerImpl.java
new file mode 100644
index 0000000000..db78447d6a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelManagerImpl.java
@@ -0,0 +1,2187 @@
+/*******************************************************************************
+ * 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;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelResourceFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelResourceFactory.java
new file mode 100644
index 0000000000..2edfea3415
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/ModelResourceFactory.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 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
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.model;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+
+public class ModelResourceFactory implements IAdapterFactory {
+
+ private static final Class[] TYPES = new Class[] { IResource.class };
+
+ public Object getAdapter(Object adaptableObject, Class adapterType) {
+ if (adaptableObject instanceof IStructuredModel && IResource.class.equals(adapterType)) {
+ String baseLocation = ((IStructuredModel) adaptableObject).getBaseLocation();
+ if (baseLocation != null && !IModelManager.DUPLICATED_MODEL.equals(baseLocation) && !IModelManager.UNMANAGED_MODEL.equals(baseLocation)) {
+ IPath path = new Path(baseLocation);
+ if (path.segmentCount() > 1 && ResourcesPlugin.getWorkspace().getRoot().exists(path)) {
+ return ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+ }
+ }
+ }
+ return null;
+ }
+
+ public Class[] getAdapterList() {
+ return TYPES;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/PrefUtil.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/PrefUtil.java
new file mode 100644
index 0000000000..ee29ad3375
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/model/PrefUtil.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.model;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.ConfigurationScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.osgi.service.prefs.Preferences;
+
+class PrefUtil {
+
+ static long WAIT_INTERVAL_MS = 500;
+ static int WAIT_DELAY = getInt("org.eclipse.wst.sse.core.modelmanager.maxWaitDuringConcurrentLoad");
+ static boolean ALLOW_INTERRUPT_WAITING_THREAD = getBoolean("org.eclipse.wst.sse.core.modelmanager.allowInterruptsDuringConcurrentLoad");
+
+ /** Base of millisecond timings, to avoid wrapping */
+ private static final long MILLI_ORIGIN = System.currentTimeMillis();
+
+ /**
+ * Returns millisecond time offset by origin
+ */
+ static final long now() {
+ return System.currentTimeMillis() - MILLI_ORIGIN;
+ }
+
+ private static IEclipsePreferences.IPreferenceChangeListener LISTENER;
+ static {
+ InstanceScope scope = new InstanceScope();
+ IEclipsePreferences instancePrefs = scope.getNode(SSECorePlugin.ID);
+ LISTENER = new IEclipsePreferences.IPreferenceChangeListener() {
+
+ public void preferenceChange(PreferenceChangeEvent event) {
+
+ if ("modelmanager.maxWaitDuringConcurrentLoad".equals(event.getKey())) {
+ WAIT_DELAY = getInt("org.eclipse.wst.sse.core.modelmanager.maxWaitDuringConcurrentLoad");
+ }
+ else if ("modelmanager.allowInterruptsDuringConcurrentLoad".equals(event.getKey())) {
+ ALLOW_INTERRUPT_WAITING_THREAD = getBoolean("org.eclipse.wst.sse.core.modelmanager.allowInterruptsDuringConcurrentLoad");
+ }
+ }
+ };
+ instancePrefs.addPreferenceChangeListener(LISTENER);
+ }
+
+ private static String getProperty(String property) {
+ // Importance order is:
+ // default-default < instanceScope < configurationScope < systemProperty
+ // < envVar
+ String value = null;
+
+ if (value == null) {
+ value = System.getenv(property);
+ }
+ if (value == null) {
+ value = System.getProperty(property);
+ }
+ if (value == null) {
+ IPreferencesService preferencesService = Platform.getPreferencesService();
+
+ String key = property;
+ if (property != null && property.startsWith(SSECorePlugin.ID)) {
+ // +1, include the "."
+ key = property.substring(SSECorePlugin.ID.length() + 1, property.length());
+ }
+ InstanceScope instance = new InstanceScope();
+ ConfigurationScope config = new ConfigurationScope();
+
+ Preferences instanceNode = instance.getNode(SSECorePlugin.ID);
+ Preferences configNode = config.getNode(SSECorePlugin.ID);
+ value = preferencesService.get(key, getDefault(property), new Preferences[]{configNode,instanceNode});
+ }
+
+ return value;
+ }
+
+ private static String getDefault(String property) {
+ // this is the "default-default"
+ if ("org.eclipse.wst.sse.core.modelmanager.maxWaitDuringConcurrentLoad".equals(property)) {
+ return "0";
+ }
+ else if ("org.eclipse.wst.sse.core.modelmanager.allowInterruptsDuringConcurrentLoad".equals(property)) {
+ return "false";
+ }
+ return null;
+ }
+
+ private static boolean getBoolean(String key) {
+ String property = getProperty(key);
+ // if (property != null) {
+ // System.out.println("Tweak: " + key + "=" + Boolean.parseBoolean(property)); //$NON-NLS-1$ //$NON-NLS-2$
+ // }
+ return (property != null ? Boolean.valueOf(property) : Boolean.valueOf(getDefault(key)))
+ .booleanValue();
+ }
+
+ private static int getInt(String key) {
+ String property = getProperty(key);
+ int size = 0;
+ if (property != null) {
+ try {
+ size = Integer.parseInt(property);
+ // System.out.println("Tweak: " + key + "=" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ catch (NumberFormatException e) {
+ size = getDefaultInt(key, property, size);
+ }
+ }
+ else {
+ size = getDefaultInt(key, property, size);
+ }
+ return size;
+ }
+
+ private static int getDefaultInt(String key, String property, int size) {
+ // ignored
+ try {
+ size = Integer.parseInt(getDefault(key));
+ }
+ catch (NumberFormatException e1) {
+ handleIntParseException(key, property, e1);
+ size = 0;
+ }
+ return size;
+ }
+
+ private static void handleIntParseException(String key, String property, NumberFormatException e1) {
+ Exception n = new Exception(NLS.bind(
+ "Exception during parse of default value for key ''{0}'' value was ''{1}''. Using 0 instead", //$NON-NLS-1$
+ key, property), e1);
+ n.printStackTrace();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistry.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistry.java
new file mode 100644
index 0000000000..60a10a886f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistry.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler;
+
+/**
+ * The entries in this registry are, conceptually, singleton's Since only one
+ * instance is created in the registry, and then that instance returned when
+ * required.
+ *
+ * Note that there is intentionally no 'remove' method, Since the registry
+ * itself is read it when once, from the platform's plugin registry, and is
+ * not intended to be modified after that. A change in an extenstion in a
+ * plugin.xml will only take effect when the workbench is re-started.
+ *
+ */
+public interface EmbeddedTypeRegistry {
+
+ /**
+ * Method to return the specific type for the specific mimetype.
+ */
+ public EmbeddedTypeHandler getTypeFor(String mimeType);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryImpl.java
new file mode 100644
index 0000000000..8aa890f59b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryImpl.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IDocumentTypeHandler;
+
+
+/**
+ *
+ */
+public class EmbeddedTypeRegistryImpl implements EmbeddedTypeRegistry {
+
+ private static EmbeddedTypeRegistry instance = null;
+
+ public synchronized static EmbeddedTypeRegistry getInstance() {
+ if (instance == null) {
+ instance = new EmbeddedTypeRegistryImpl();
+ }
+ return instance;
+ }
+
+ private HashSet hashSet = null;
+ private EmbeddedTypeHandler registryDefaultHandler = null;
+
+ /*
+ * @see ContentTypeRegistry#getTypeFor(String)
+ */
+ /**
+ * Constructor for ContentTypeRegistryImpl.
+ */
+ private EmbeddedTypeRegistryImpl() {
+ super();
+ hashSet = new HashSet();
+ new EmbeddedTypeRegistryReader().readRegistry(hashSet);
+ }
+
+ /**
+ * @see ContentTypeRegistry#add(ContentTypeDescription)
+ */
+ void add(IDocumentTypeHandler contentTypeDescription) {
+ hashSet.add(contentTypeDescription);
+ }
+
+ private EmbeddedTypeHandler getJSPDefaultEmbeddedType() {
+ return getTypeFor("text/html"); //$NON-NLS-1$
+ }
+
+ /**
+ * Method getRegistryDefault. We cache the default handler, since can't
+ * change once plugin descriptors are loaded.
+ *
+ * @return EmbeddedTypeHandler
+ */
+ private EmbeddedTypeHandler getRegistryDefault() {
+ if (registryDefaultHandler == null) {
+ Iterator it = hashSet.iterator();
+ while ((registryDefaultHandler == null) && (it.hasNext())) { // safe
+ // cast
+ // since
+ // 'add'
+ // requires
+ // EmbeddedContentTypeDescription
+ EmbeddedTypeHandler item = (EmbeddedTypeHandler) it.next();
+ if ((item != null) && (item.isDefault())) {
+ registryDefaultHandler = item;
+ break;
+ }
+ }
+ }
+ return registryDefaultHandler;
+ }
+
+ /**
+ * Finds the contentTypeDescription based on literal id. Its basically a
+ * "first found first returned". Note the order is fairly unpredictable,
+ * so non-unique ids would cause problems.
+ */
+ public EmbeddedTypeHandler getTypeFor(String mimeType) {
+ // Note: the reason we have this precondition is that the
+ // default is different inside the registry than when called,
+ // for example, from the JSPLoader. For the JSPLoader, if there
+ // is no mimetype, the default should be HTML. Here, if there is
+ // some mimetype, but it is not recognized, we return a default
+ // for XML. This is required for various voice xml types, etc.
+ EmbeddedTypeHandler found = null;
+ if (mimeType == null || mimeType.trim().length() == 0) {
+ found = getJSPDefaultEmbeddedType();
+ } else {
+ Iterator it = hashSet.iterator();
+ while ((found == null) && (it.hasNext())) { // safe cast since
+ // 'add' requires
+ // EmbeddedContentTypeDescription
+ EmbeddedTypeHandler item = (EmbeddedTypeHandler) it.next();
+ if ((item != null) && (item.getSupportedMimeTypes().contains(mimeType))) {
+ found = item;
+ break;
+ }
+ }
+ // if no exact match, do the "looser" check
+ if(found == null) {
+ it = hashSet.iterator();
+ while ((found == null) && (it.hasNext())) {
+ EmbeddedTypeHandler item = (EmbeddedTypeHandler) it.next();
+ if ((item != null) && (item.canHandleMimeType(mimeType))) {
+ found = item;
+ break;
+ }
+ }
+ }
+ }
+ // no matches, use default
+ if (found == null) {
+ found = getRegistryDefault();
+ }
+ return found;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryReader.java
new file mode 100644
index 0000000000..698f7a134f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/EmbeddedTypeRegistryReader.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import java.util.Set;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+
+
+public class EmbeddedTypeRegistryReader {
+ protected String ATT_CLASS = "class"; //$NON-NLS-1$
+ protected String EXTENSION_POINT_ID = "embeddedTypeHandler"; //$NON-NLS-1$
+
+
+ protected String PLUGIN_ID = "org.eclipse.wst.sse.core"; //$NON-NLS-1$
+ protected String TAG_NAME = "embeddedTypeHandler"; //$NON-NLS-1$
+
+ EmbeddedTypeRegistryReader() {
+ super();
+ }
+
+ protected EmbeddedTypeHandler readElement(IConfigurationElement element) {
+
+ EmbeddedTypeHandler contentTypeDescription = null;
+ if (element.getName().equals(TAG_NAME)) {
+ try {
+ contentTypeDescription = (EmbeddedTypeHandler) element.createExecutableExtension(ATT_CLASS);
+ } catch (Exception e) {
+ Logger.logException(e);
+ }
+ }
+ Assert.isNotNull(contentTypeDescription, "Error reading content type description"); //$NON-NLS-1$
+ return contentTypeDescription;
+ }
+
+ /**
+ * We simply require an 'add' method, of what ever it is we are to read
+ * into
+ */
+ void readRegistry(Set set) {
+ IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
+ IExtensionPoint point = extensionRegistry.getExtensionPoint(PLUGIN_ID, EXTENSION_POINT_ID);
+ if (point != null) {
+ IConfigurationElement[] elements = point.getConfigurationElements();
+ for (int i = 0; i < elements.length; i++) {
+ EmbeddedTypeHandler embeddedContentType = readElement(elements[i]);
+ // null can be returned if there's an error reading the
+ // element
+ if (embeddedContentType != null) {
+ set.add(embeddedContentType);
+ }
+ }
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistry.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistry.java
new file mode 100644
index 0000000000..72bfa13e49
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistry.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.modelhandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.encoding.CodedIO;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+
+
+public class ModelHandlerRegistry {
+ private static ModelHandlerRegistry instance = null;
+ static final String INTERNAL_DEFAULT_EXTENSION = "org.eclipse.wst.xml.core.internal.modelhandler"; //$NON-NLS-1$
+
+ public synchronized static ModelHandlerRegistry getInstance() {
+ if (instance == null) {
+ instance = new ModelHandlerRegistry();
+ }
+ return instance;
+ }
+
+ private IModelHandler defaultHandler = null;
+ private ModelHandlerRegistryReader reader = new ModelHandlerRegistryReader();
+
+ private ModelHandlerRegistry() {
+ super();
+ reader = new ModelHandlerRegistryReader().readRegistry();
+ }
+
+ /**
+ * Finds the default model handler. Note: we still go through the registry
+ * to be sure to get the existing instance, but then we do remember it, so
+ * subsequent requests will be faster. The first time through, we do check
+ * the whole list, to be sure there is only one.
+ *
+ */
+ final public IModelHandler getDefault() {
+ if (defaultHandler == null) {
+ IConfigurationElement[] elements = reader.elements;
+ for (int i = 0; i < elements.length; i++) {
+ boolean ofInterest = reader.isElementDefault(elements[i]);
+ if (ofInterest) {
+ /*
+ * If, here within the search loop we've already found one
+ * defaultHandler, then something is wrong!
+ */
+ if (defaultHandler == null) {
+ defaultHandler = reader.getInstance(elements[i]);
+ }
+ else {
+ String errorString = "Program or configuration error. More than one default content handler found"; //$NON-NLS-1$
+ Logger.log(Logger.ERROR, errorString);
+ throw new IllegalStateException(errorString);
+ }
+ }
+ }
+ }
+ if (defaultHandler == null) {
+ String errorString = "Program or configuration error. No default content type handler found."; //$NON-NLS-1$
+ Logger.log(Logger.ERROR, errorString);
+ throw new IllegalStateException(errorString);
+ }
+ return defaultHandler;
+ }
+
+ /**
+ * Finds a ModelHandler based on literal extension id. It's basically a
+ * "first found first returned". No specific order is guaranteed and the
+ * uniqueness of IDs is not considered.
+ *
+ * @param extensionId
+ * @return the given extension, or null
+ */
+ private IModelHandler getHandlerExtension(String extensionId) {
+ IModelHandler found = null;
+ IConfigurationElement[] elements = reader.elements;
+ if (elements != null) {
+ for (int i = 0; i < elements.length; i++) {
+ String currentId = reader.getId(elements[i]);
+ if (extensionId.equals(currentId)) {
+ IModelHandler item = reader.getInstance(elements[i]);
+ found = item;
+ }
+ }
+ }
+ else if (Logger.DEBUG){
+ Logger.log(Logger.WARNING, "There were no Model Handler found in registry"); //$NON-NLS-1$
+ }
+ return found;
+ }
+
+ /**
+ * Finds the registered IModelHandler for a given named file's content
+ * type.
+ *
+ * @param file
+ * @param provideDefault should the default extension be used in the absence of other methods
+ * @return The IModelHandler registered for the content type of the given
+ * file. If an exact match is not found, the most-specific match
+ * according to IContentType.isKindOf() will be returned. If none
+ * are found, either a default or null will be returned.
+ * @throws CoreException
+ */
+ public IModelHandler getHandlerFor(IFile file, boolean provideDefault) throws CoreException {
+ IModelHandler modelHandler = null;
+ IContentDescription contentDescription = null;
+ IContentType contentType = null;
+ boolean accessible = file.isAccessible();
+ if (accessible) {
+ /* Try the optimized method first as the description may be cached */
+ contentDescription = file.getContentDescription();
+ if (contentDescription != null) {
+ // use the provided description
+ contentType = contentDescription.getContentType();
+ }
+ else {
+ /* use the more thorough discovery method to get a description */
+ InputStream contents = null;
+ try {
+ contents = file.getContents(false);
+ contentDescription = Platform.getContentTypeManager().getDescriptionFor(contents, file.getName(), IContentDescription.ALL);
+ if (contentDescription != null) {
+ contentType = contentDescription.getContentType();
+ }
+ }
+ catch (IOException e) {
+ // nothing further can be done, but will log for debugging
+ Logger.logException(e);
+ }
+ finally {
+ if (contents != null) {
+ try {
+ contents.close();
+ }
+ catch (IOException e1) {
+ // nothing can be done
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * If we couldn't get the content type from a description, try basing
+ * it on just the filename
+ */
+ if (contentType == null) {
+ contentType = Platform.getContentTypeManager().findContentTypeFor(file.getName());
+ }
+
+ if (contentType != null) {
+ modelHandler = getHandlerForContentType(contentType);
+ }
+ else if (contentType == null && provideDefault) {
+ // hard coding for null content type
+ modelHandler = getHandlerExtension(INTERNAL_DEFAULT_EXTENSION); //$NON-NLS-1$
+ }
+
+ return modelHandler;
+ }
+
+ /**
+ * Finds the registered IModelHandler for a given named file's content
+ * type. Will check for a default.
+ *
+ * @param file
+ * @return The IModelHandler registered for the content type of the given
+ * file. If an exact match is not found, the most-specific match
+ * according to IContentType.isKindOf() will be returned. If none
+ * are found, either a default or null will be returned.
+ * @throws CoreException
+ */
+ public IModelHandler getHandlerFor(IFile file) throws CoreException {
+ return getHandlerFor(file, true);
+ }
+
+
+ /**
+ * Finds the registered IModelHandler for a given named InputStream.
+ *
+ * @param inputName
+ * @param inputStream
+ * @return The IModelHandler registered for the content type of the given
+ * input. If an exact match is not found, the most-specific match
+ * according to IContentType.isKindOf() will be returned. If none
+ * are found, either a default or null will be returned.
+ * @throws IOException
+ */
+ public IModelHandler getHandlerFor(String inputName, InputStream inputStream) throws IOException {
+ InputStream iStream = Utilities.getMarkSupportedStream(inputStream);
+ IModelHandler modelHandler = null;
+ IContentType contentType = null;
+ if (inputStream != null) {
+ try {
+ iStream.mark(CodedIO.MAX_MARK_SIZE);
+ contentType = Platform.getContentTypeManager().findContentTypeFor(Utilities.getLimitedStream(iStream), inputName);
+ }
+ finally {
+ if (iStream != null && iStream.markSupported()) {
+ iStream.reset();
+ }
+ }
+
+ }
+ if (contentType == null) {
+ contentType = Platform.getContentTypeManager().findContentTypeFor(inputName);
+ }
+ // if all else failed, try to detect solely on contents; done last for
+ // performance reasons
+ if (contentType == null) {
+ contentType = Platform.getContentTypeManager().findContentTypeFor(Utilities.getLimitedStream(iStream), null);
+ }
+ modelHandler = getHandlerForContentType(contentType);
+ return modelHandler;
+ }
+
+ /**
+ * Finds the registered IModelHandler for a given IContentType.
+ *
+ * @param contentType
+ * @return The IModelHandler registered for the given content type. If an
+ * exact match is not found, the most-specific match according to
+ * IContentType.isKindOf() will be returned. If none are found,
+ * either a default or null will be returned.
+ */
+ private IModelHandler getHandlerForContentType(IContentType contentType) {
+ IModelHandler handler = null;
+ if (contentType != null) {
+ IConfigurationElement exactContentTypeElement = null;
+ IConfigurationElement kindOfContentTypeElement = null;
+ int kindOfContentTypeDepth = 0;
+ IConfigurationElement[] elements = reader.elements;
+ if (elements != null) {
+ for (int i = 0; i < elements.length && exactContentTypeElement == null; i++) {
+ String currentId = reader.getAssociatedContentTypeId(elements[i]);
+ IContentType associatedContentType = Platform.getContentTypeManager().getContentType(currentId);
+ if (contentType.equals(associatedContentType)) {
+ exactContentTypeElement = elements[i];
+ }
+ else if (contentType.isKindOf(associatedContentType)) {
+ /*
+ * Update the kindOfElement variable only if this
+ * element's content type is "deeper" (depth test
+ * ensures the first content type is remembered)
+ */
+ IContentType testContentType = associatedContentType;
+ int testDepth = 0;
+ while (testContentType != null) {
+ testDepth++;
+ testContentType = testContentType.getBaseType();
+ }
+ if (testDepth > kindOfContentTypeDepth) {
+ kindOfContentTypeElement = elements[i];
+ kindOfContentTypeDepth = testDepth;
+ }
+ }
+ }
+ }
+ else if (Logger.DEBUG){
+ Logger.log(Logger.WARNING, "There were no Model Handler found in registry"); //$NON-NLS-1$
+ }
+ if (exactContentTypeElement != null) {
+ handler = reader.getInstance(exactContentTypeElement);
+ }
+ else if (kindOfContentTypeElement != null) {
+ handler = reader.getInstance(kindOfContentTypeElement);
+ }
+ }
+
+ if (handler == null) {
+ // temp hard coding for null content type arguments
+ handler = getHandlerExtension(INTERNAL_DEFAULT_EXTENSION); //$NON-NLS-1$
+ }
+ return handler;
+ }
+
+ /**
+ * Finds the registered IModelHandler for a given content type ID. No
+ * specific order is guaranteed and the uniqueness of IDs is not
+ * considered.
+ *
+ * @param contentType
+ * @return The IModelHandler registered for the given content type ID. If
+ * an exact match is not found, the most-specific match according
+ * to IContentType.isKindOf() will be returned. If none are found,
+ * either a default or null will be returned.
+ */
+ public IModelHandler getHandlerForContentTypeId(String contentTypeId) {
+ IContentType contentType = Platform.getContentTypeManager().getContentType(contentTypeId);
+ return getHandlerForContentType(contentType);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistryReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistryReader.java
new file mode 100644
index 0000000000..c6d611f255
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerRegistryReader.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import java.util.HashMap;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.AbstractModelHandler;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
+
+
+/**
+ * This class just converts what's in the plugins registry into a form more
+ * easily useable by others, the ContentTypeRegistry.
+ */
+class ModelHandlerRegistryReader {
+ private HashMap allReadyCreateInstances = new HashMap();
+ protected String ATT_ASSOCIATED_CONTENT_TYPE = "associatedContentTypeId"; //$NON-NLS-1$
+ protected String ATT_CLASS = "class"; //$NON-NLS-1$
+ protected String ATT_DEFAULT = "default"; //$NON-NLS-1$
+ protected String ATT_ID = "id"; //$NON-NLS-1$
+ IConfigurationElement[] elements;
+ protected String EXTENSION_POINT_ID = "modelHandler"; //$NON-NLS-1$
+ //
+ protected String PLUGIN_ID = "org.eclipse.wst.sse.core"; //$NON-NLS-1$
+ protected String TAG_NAME = "modelHandler"; //$NON-NLS-1$
+
+ //
+ /**
+ * ContentTypeRegistryReader constructor comment.
+ */
+ ModelHandlerRegistryReader() {
+ super();
+ }
+
+ String getAssociatedContentTypeId(IConfigurationElement element) {
+ String value = element.getAttribute(ATT_ASSOCIATED_CONTENT_TYPE);
+ return value;
+ }
+
+ String getId(IConfigurationElement element) {
+ String idValue = element.getAttribute(ATT_ID);
+ return idValue;
+ }
+
+ synchronized IModelHandler getInstance(IConfigurationElement element) {
+ // May need to reconsider, but for now, we'll assume all clients must
+ // subclass AbstractContentTypeIdentifier. Its easier and safer, for
+ // this
+ // low level "system" object. (That is, we can check and set "package
+ // protected"
+ // attributes.
+ AbstractModelHandler modelHandler = (AbstractModelHandler) allReadyCreateInstances.get(getId(element));
+ if (modelHandler == null) {
+ try {
+ modelHandler = (AbstractModelHandler) element.createExecutableExtension(ATT_CLASS);
+ if (modelHandler != null) {
+ allReadyCreateInstances.put(getId(element), modelHandler);
+ String defaultValue = element.getAttribute(ATT_DEFAULT);
+ if (defaultValue != null && "true".equals(defaultValue)) //$NON-NLS-1$
+ modelHandler.setDefault(true);
+ else
+ modelHandler.setDefault(false);
+ // TODO -- set and check attributes vs. created instance
+ //contentTypeIdentifier.setOrCheckId(element.getAttribute(ATT_ID));
+ }
+ } catch (CoreException e) {
+ org.eclipse.wst.sse.core.internal.Logger.logException(e);
+ }
+ }
+ return modelHandler;
+ }
+
+ public boolean isElementDefault(IConfigurationElement element) {
+ String defaultValue = element.getAttribute(ATT_DEFAULT);
+ if (defaultValue != null && "true".equals(defaultValue)) //$NON-NLS-1$
+ return true;
+ else
+ return false;
+ }
+
+ ModelHandlerRegistryReader readRegistry() {
+ IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
+ IExtensionPoint point = extensionRegistry.getExtensionPoint(PLUGIN_ID, EXTENSION_POINT_ID);
+ if (point != null) {
+ // just remember the elements, so plugins don't have to
+ // be activated, unless extension matches those "of interest".
+ elements = point.getConfigurationElements();
+ }
+ return this;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerUtility.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerUtility.java
new file mode 100644
index 0000000000..f0b2a4bb87
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/ModelHandlerUtility.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.EmbeddedTypeHandler;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IDocumentTypeHandler;
+
+/**
+ *
+ * Likely a temporary class to be replaced by plugin, eventually.
+ */
+public class ModelHandlerUtility {
+
+ private static ModelHandlerRegistry contentTypeRegistry;
+
+ public static IDocumentTypeHandler getContentTypeFor(String string) {
+ return getContentTypeRegistry().getHandlerForContentTypeId(string);
+ }
+
+ private static ModelHandlerRegistry getContentTypeRegistry() {
+ if (contentTypeRegistry == null) {
+ contentTypeRegistry = ModelHandlerRegistry.getInstance();
+ }
+ return contentTypeRegistry;
+ }
+
+ public static EmbeddedTypeHandler getDefaultEmbeddedType() {
+ return getEmbeddedContentTypeFor("text/html"); //$NON-NLS-1$
+ }
+
+ public static EmbeddedTypeHandler getEmbeddedContentTypeFor(String string) {
+ EmbeddedTypeHandler instance = null;
+ instance = EmbeddedTypeRegistryImpl.getInstance().getTypeFor(string);
+ return instance;
+ }
+
+ public ModelHandlerUtility() {
+ super();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/PluginContributedFactoryReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/PluginContributedFactoryReader.java
new file mode 100644
index 0000000000..7d2e2dd23a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/modelhandler/PluginContributedFactoryReader.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.modelhandler;
+
+import java.util.List;
+import java.util.Vector;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IDocumentTypeHandler;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+
+
+/**
+ *
+ * Clients can make use of IExecutableExtension to handle the optional adapter
+ * class and key. Typically, many clients use a typical pattern of providing
+ * an adapter class and key in their null argument constructor anyway, so
+ * they'd only have to use IExecutableExtension if the factory was for more
+ * than one.
+ */
+public class PluginContributedFactoryReader {
+ // protected final String ATTR_ADAPTERKEY = "adapterKeyClass";
+ // //$NON-NLS-1$
+ // protected final String ATTR_REGISTERADAPTER = "registerAdapters";
+ // //$NON-NLS-1$
+ private static PluginContributedFactoryReader reader = null;
+
+ public synchronized static PluginContributedFactoryReader getInstance() {
+ if (reader == null) {
+ reader = new PluginContributedFactoryReader();
+ }
+ return reader;
+ }
+
+ protected final String ATTR_CLASS = "class"; //$NON-NLS-1$
+ protected final String ATTR_CONTENTTYPE = "contentTypeIdentiferId"; //$NON-NLS-1$
+
+ protected final String EXTENSION_POINT_ID = "contentTypeFactoryContribution"; //$NON-NLS-1$
+ protected final String TAG_NAME = "factory"; //$NON-NLS-1$
+
+ protected PluginContributedFactoryReader() {
+ super();
+ }
+
+ public List getFactories(IDocumentTypeHandler handler) {
+ return loadRegistry(handler.getId());
+ }
+
+ public List getFactories(String type) {
+ return loadRegistry(type);
+ }
+
+ protected INodeAdapterFactory loadFactoryFromConfigurationElement(IConfigurationElement element, Object requesterType) {
+ INodeAdapterFactory factory = null;
+ if (element.getName().equals(TAG_NAME)) {
+ String contentType = element.getAttribute(ATTR_CONTENTTYPE);
+ if (!requesterType.equals(contentType))
+ return null;
+ String className = element.getAttribute(ATTR_CLASS);
+ // String adapterKeyClass = element.getAttribute(ATTR_ADAPTERKEY);
+ // String registerAdapters =
+ // element.getAttribute(ATTR_REGISTERADAPTER);
+
+ // if className is null, then no one defined the extension point
+ // for adapter factories
+ if (className != null) {
+ try {
+ factory = (INodeAdapterFactory) element.createExecutableExtension(ATTR_CLASS);
+ } catch (CoreException e) {
+ // if an error occurs here, its probably that the plugin
+ // could not be found/loaded
+ org.eclipse.wst.sse.core.internal.Logger.logException("Could not find class: " + className, e); //$NON-NLS-1$
+ } catch (Exception e) {
+ // if an error occurs here, its probably that the plugin
+ // could not be found/loaded -- but in any case we just
+ // want
+ // to log the error and continue running and best we can.
+ org.eclipse.wst.sse.core.internal.Logger.logException("Could not find class: " + className, e); //$NON-NLS-1$
+ }
+ // if (plugin != null) {
+ // factory = oldAttributesCode(element, factory, className,
+ // plugin);
+ //
+ }
+ }
+
+ return factory;
+ }
+
+ protected List loadRegistry(Object contentType) {
+ List factoryList = null; // new Vector();
+ IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
+ IExtensionPoint point = extensionRegistry.getExtensionPoint(SSECorePlugin.ID, EXTENSION_POINT_ID);
+ if (point != null) {
+ IConfigurationElement[] elements = point.getConfigurationElements();
+ if (elements.length > 0) {
+ // this is called a lot, so don't create vector unless really
+ // needed
+ // TODO: could eventually cache in a hashtable, or something,
+ // to avoid repeat processing
+ factoryList = new Vector();
+ for (int i = 0; i < elements.length; i++) {
+ INodeAdapterFactory factory = loadFactoryFromConfigurationElement(elements[i], contentType);
+ if (factory != null)
+ factoryList.add(factory);
+ }
+ }
+ }
+ return factoryList;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ContextRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ContextRegion.java
new file mode 100644
index 0000000000..caaafd544f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ContextRegion.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.parser;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+
+
+/**
+ * Regions of this class are intended specifically for XML/HTML/JSPs. Other
+ * languages may need their own subclasses. (See the updateModel method).
+ */
+public class ContextRegion implements ITextRegion {
+ protected int fLength;
+
+ protected int fStart;
+ protected int fTextLength;
+ protected String fType;
+
+ protected ContextRegion() {
+ super();
+ }
+
+ public ContextRegion(String newContext, int newStart, int newTextLength, int newLength) {
+ fType = newContext;
+ fStart = newStart;
+ fTextLength = newTextLength;
+ fLength = newLength;
+ }
+
+
+ public void adjust(int i) {
+ fStart += i;
+
+ }
+
+ public void adjustLength(int i) {
+ fLength += i;
+ }
+
+ public void adjustStart(int i) {
+ fStart += i;
+ }
+
+ public void adjustTextLength(int i) {
+ fTextLength += i;
+
+ }
+
+ boolean allLetterOrDigit(String changes) {
+ boolean result = true;
+ for (int i = 0; i < changes.length(); i++) {
+ // TO_DO_FUTURE: check that a Java Letter or Digit is
+ // the same thing as an XML letter or digit
+ if (!(Character.isLetterOrDigit(changes.charAt(i)))) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ boolean allWhiteSpace(String changes) {
+ boolean result = true;
+ for (int i = 0; i < changes.length(); i++) {
+ if (!Character.isWhitespace(changes.charAt(i))) {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ boolean canHandleAsLetterOrDigit(String changes, int requestStart, int lengthToReplace) {
+ boolean result = false;
+ // Make sure we are in a non-white space area
+ if ((requestStart <= (getTextEnd())) && (allLetterOrDigit(changes))) {
+ result = true;
+ }
+ return result;
+ }
+
+ boolean canHandleAsWhiteSpace(String changes, int requestStart, int lengthToReplace) {
+ boolean result = false;
+ // if we are in the "white space" area of a region, then
+ // we don't want to handle, a reparse is needed.
+ // the white space region is consider anywhere that would
+ // leave whitespace between this character and the text part.
+ // and of course, we can insert whitespace in whitespace region
+ //
+ // if there is no whitespace in this region, no need to look further
+ if (getEnd() > getTextEnd()) {
+ // no need to add one to end of text, as we used to, since we
+ // change definition of length to equate to offset plus one.
+ if (requestStart > getTextEnd()) {
+ // ok, we are in the whitespace region, so we can't handle,
+ // unless
+ // we are just inserting whitespace.
+ if (allWhiteSpace(changes)) {
+ result = true;
+ }
+ else {
+ result = false;
+ }
+
+ }
+ }
+
+ return result;
+ }
+
+ public boolean contains(int position) {
+
+ return fStart <= position && position < fStart + fLength;
+ }
+
+ public void equatePositions(ITextRegion region) {
+ fStart = region.getStart();
+ fLength = region.getLength();
+ fTextLength = region.getTextLength();
+ }
+
+ public int getEnd() {
+ return fStart + fLength;
+ }
+
+ public int getLength() {
+ return fLength;
+ }
+
+ public int getStart() {
+ return fStart;
+ }
+
+ public int getTextEnd() {
+ return fStart + fTextLength;
+ }
+
+ public int getTextLength() {
+ return fTextLength;
+ }
+
+ public String getType() {
+ return fType;
+ }
+
+ public void setLength(int i) {
+ fLength = i;
+ }
+
+ public void setStart(int i) {
+ fStart = i;
+ }
+
+ public void setTextLength(int i) {
+ fTextLength = i;
+ }
+
+ public void setType(String string) {
+ fType = string;
+ }
+
+ public String toString() {
+ String className = getClass().getName();
+ String shortClassName = className.substring(className.lastIndexOf(".") + 1); //$NON-NLS-1$
+ String result = shortClassName + "--> " + getType() + ": " + getStart() + "-" + getTextEnd() + (getTextEnd() != getEnd() ? ("/" + getEnd()) : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+ // NOTE: if the document held by any region has been updated and the
+ // region offsets have not
+ // yet been updated, the output from this method invalid.
+ return result;
+ }
+
+ public StructuredDocumentEvent updateRegion(Object requester, IStructuredDocumentRegion parent, String changes, int requestStart, int lengthToReplace) {
+ // the four types we used to handle here, have all been moved to
+ // specific region classes.
+ // XML_TAG_ATTRIBUTE_VALUE
+ // XML_TAG_ATTRIBUTE_NAME
+ // XML_CONTENT
+ // XML_CDATA_TEXT
+ return null;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ForeignRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ForeignRegion.java
new file mode 100644
index 0000000000..9bb5a3671e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/parser/ForeignRegion.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2007 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.parser;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+
+
+public class ForeignRegion extends ContextRegion {
+
+ private String language = null;
+ private String surroundingTag = null;
+
+ public ForeignRegion(String newContext, int newStart, int newTextLength, int newLength) {
+ super(newContext, newStart, newTextLength, newLength);
+ }
+
+ public ForeignRegion(String newContext, int newStart, int newTextLength, int newLength, String newLanguage) {
+ super(newContext, newStart, newTextLength, newLength);
+ setLanguage(newLanguage);
+ }
+
+ /**
+ *
+ * @return java.lang.String
+ */
+ public java.lang.String getLanguage() {
+ return language;
+ }
+
+ /**
+ * @return java.lang.String
+ */
+ public java.lang.String getSurroundingTag() {
+ return surroundingTag;
+ }
+
+ /**
+ *
+ * @param newLanguage
+ * java.lang.String
+ */
+ public void setLanguage(java.lang.String newLanguage) {
+ language = newLanguage;
+ }
+
+ /**
+ * @param newSurroundingTag
+ * java.lang.String
+ */
+ public void setSurroundingTag(java.lang.String newSurroundingTag) {
+ surroundingTag = newSurroundingTag;
+ }
+
+ public String toString() {
+ return "FOREIGN: " + super.toString();//$NON-NLS-1$
+ }
+
+ public StructuredDocumentEvent updateRegion(Object requester, IStructuredDocumentRegion flatnode, String changes, int requestStart, int lengthToReplace) {
+ org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent result = null;
+ int lengthDifference = org.eclipse.wst.sse.core.internal.util.Utilities.calculateLengthDifference(changes, lengthToReplace);
+ fLength += lengthDifference;
+ fTextLength += lengthDifference;
+ result = new RegionChangedEvent(flatnode.getParentDocument(), requester, flatnode, this, changes, requestStart, lengthToReplace);
+
+ return result;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/CommonModelPreferenceNames.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/CommonModelPreferenceNames.java
new file mode 100644
index 0000000000..a7ef2780eb
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/CommonModelPreferenceNames.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.preferences;
+
+/**
+ * @deprecated CommonModelPreferenceNames are now managed by each individual
+ * content type. (XXCorePreferenceNames)
+ *
+ */
+public interface CommonModelPreferenceNames {
+ String TAB_WIDTH = "tabWidth";//$NON-NLS-1$
+ String LINE_WIDTH = "lineWidth";//$NON-NLS-1$
+ String SPLIT_MULTI_ATTRS = "splitMultiAttrs";//$NON-NLS-1$
+ String INDENT_USING_TABS = "indentUsingTabs";//$NON-NLS-1$
+ String CLEAR_ALL_BLANK_LINES = "clearAllBlankLines";//$NON-NLS-1$
+
+ String TAG_NAME_CASE = "tagNameCase";//$NON-NLS-1$
+ String ATTR_NAME_CASE = "attrNameCase";//$NON-NLS-1$
+
+ String FORMATTING_SUPPORTED = "formattingSupported";//$NON-NLS-1$
+
+ String PREFERRED_MARKUP_CASE_SUPPORTED = "preferredMarkupCaseSupported";//$NON-NLS-1$
+
+ String TASK_TAG_TAGS = "task-tag-tags"; //$NON-NLS-1$
+ String TASK_TAG_PRIORITIES = "task-tag-priorities"; //$NON-NLS-1$
+ String TASK_TAG_ENABLE = "task-tags"; //$NON-NLS-1$
+ String TASK_TAG_PROJECTS_IGNORED = "task-tag-projects-toIgnore"; //$NON-NLS-1$
+
+
+ /**
+ * these are preferences that should be inherited from the "embedded
+ * preference store" for example: if you ask for th OVERVIEW_RULER
+ * preference for JSP, you will automatically get the preference from the
+ * HTML preference store.
+ */
+ String EMBEDDED_CONTENT_TYPE_PREFERENCES[] = {TAB_WIDTH, LINE_WIDTH, SPLIT_MULTI_ATTRS, INDENT_USING_TABS, CLEAR_ALL_BLANK_LINES, TAG_NAME_CASE, ATTR_NAME_CASE,};
+
+ String FORMATTING_PREFERENCES[] = {TAB_WIDTH, LINE_WIDTH, SPLIT_MULTI_ATTRS, INDENT_USING_TABS, CLEAR_ALL_BLANK_LINES,};
+
+ String AUTO = "Auto";//$NON-NLS-1$
+ String UTF_8 = "UTF-8";//$NON-NLS-1$
+ String ISO_8859_1 = "ISO-8859-1";//$NON-NLS-1$
+
+ int ASIS = 0;
+ int LOWER = 1;
+ int UPPER = 2;
+
+ // cleanup preference names
+ String CLEANUP_TAG_NAME_CASE = "cleanupTagNameCase";//$NON-NLS-1$
+ String CLEANUP_ATTR_NAME_CASE = "cleanupAttrNameCase";//$NON-NLS-1$
+ String COMPRESS_EMPTY_ELEMENT_TAGS = "compressEmptyElementTags";//$NON-NLS-1$
+ String INSERT_REQUIRED_ATTRS = "insertRequiredAttrs";//$NON-NLS-1$
+ String INSERT_MISSING_TAGS = "insertMissingTags";//$NON-NLS-1$
+ String QUOTE_ATTR_VALUES = "quoteAttrValues";//$NON-NLS-1$
+ String FORMAT_SOURCE = "formatSource";//$NON-NLS-1$
+ String CONVERT_EOL_CODES = "convertEOLCodes";//$NON-NLS-1$
+ String CLEANUP_EOL_CODE = "cleanupEOLCode";//$NON-NLS-1$
+
+ String LAST_ACTIVE_PAGE = "lastActivePage";//$NON-NLS-1$
+
+ // need to put default tab width preference here so it is accessible by
+ // both model and editor
+ // this way, editor does not need to query model's default tab width
+ // preference
+ int DEFAULT_TAB_WIDTH = 4;
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/PreferenceInitializer.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/PreferenceInitializer.java
new file mode 100644
index 0000000000..7505633dc3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/preferences/PreferenceInitializer.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.core.runtime.preferences.DefaultScope;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.wst.sse.core.internal.tasks.TaskTagPreferenceKeys;
+
+public class PreferenceInitializer extends AbstractPreferenceInitializer {
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences()
+ */
+ public void initializeDefaultPreferences() {
+ IEclipsePreferences taskTagDefaults = new DefaultScope().getNode(TaskTagPreferenceKeys.TASK_TAG_NODE);
+ taskTagDefaults.putBoolean(TaskTagPreferenceKeys.TASK_TAG_ENABLE, false);
+ taskTagDefaults.put(TaskTagPreferenceKeys.TASK_TAG_TAGS, "TODO,FIXME,XXX"); //$NON-NLS-1$
+ taskTagDefaults.put(TaskTagPreferenceKeys.TASK_TAG_PRIORITIES, "1,2,1"); //$NON-NLS-1$
+ taskTagDefaults.put(TaskTagPreferenceKeys.TASK_TAG_CONTENTTYPES_IGNORED, ""); //$NON-NLS-1$
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/propertytester/StructuredFilePropertyTester.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/propertytester/StructuredFilePropertyTester.java
new file mode 100644
index 0000000000..184bc03107
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/propertytester/StructuredFilePropertyTester.java
@@ -0,0 +1,85 @@
+/******************************************************************************
+ * Copyright (c) 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
+ ******************************************************************************/
+package org.eclipse.wst.sse.core.internal.propertytester;
+
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.wst.sse.core.internal.Logger;
+
+/**
+ * A Property Tester that operates on IFiles and validates
+ * that the expected content type id matches that of the content
+ * type of the file, or any of the base content types.
+ *
+ * Based on org.eclipse.core.internal.propertytester.FilePropertyTester
+ *
+ * @deprecated use org.eclipse.core.resources.contentTypeId instead
+ *
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=288216
+ */
+public class StructuredFilePropertyTester extends PropertyTester {
+
+ /**
+ * A property indicating that we are looking to verify that the file matches
+ * the content type matching the given identifier. The identifier is
+ * provided as the expected value.
+ */
+ private static final String PROPERTY_CONTENT_TYPE_ID = "contentTypeId"; //$NON-NLS-1$
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object, java.lang.String, java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if(PROPERTY_CONTENT_TYPE_ID.equals(property) && (expectedValue != null) && (receiver instanceof IFile) && ((IFile) receiver).exists())
+ return testContentType((IFile) receiver, expectedValue.toString());
+ return false;
+ }
+
+ /**
+ * Tests whether the content type for <code>file</code> (or any base content types)
+ * matches the <code>contentTypeId</code>. It is possible that this method call could
+ * cause the file to be read. It is also possible (through poor plug-in
+ * design) for this method to load plug-ins.
+ *
+ * @param file
+ * The file for which the content type should be determined; must
+ * not be <code>null</code>.
+ * @param contentTypeId
+ * The expected content type; must not be <code>null</code>.
+ * @return <code>true</code> if the file's content type (or base content types)
+ * has an identifier that matches <code>contentTypeId</code>;
+ * <code>false</code> otherwise.
+ */
+ private boolean testContentType(final IFile file, String contentTypeId) {
+ final String expectedValue = contentTypeId.trim();
+
+ try {
+ IContentDescription contentDescription = file.getContentDescription();
+ if (contentDescription != null) {
+ IContentType contentType = contentDescription.getContentType();
+ while (contentType != null) {
+ if (expectedValue.equals(contentType.getId()))
+ return true;
+ contentType = contentType.getBaseType();
+ }
+ }
+ }
+ catch (Exception e) {
+ // [232831] - Log messages only when debugging
+ if(Logger.DEBUG)
+ Logger.logException(e);
+ }
+ return false;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractAdapterFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractAdapterFactory.java
new file mode 100644
index 0000000000..4aa9c807d9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractAdapterFactory.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+/**
+ * An abstract implementation of IAdapterFactory. All implementers of
+ * IAdapterFactory should subclass this class. The default constructor uses
+ * itself (this) as the key. Subclasses need to provide a way to create the
+ * adapter, and can override or call other methods.
+ */
+abstract public class AbstractAdapterFactory implements INodeAdapterFactory {
+
+
+
+ private Object fAdapterKey;
+
+ private boolean fShouldRegisterAdapter;
+
+ /**
+ * If this default constructor used, setAdapterKey and setShouldRegister
+ * should be used to properly initialize.
+ */
+ protected AbstractAdapterFactory() {
+ super();
+
+ }
+
+
+ public AbstractAdapterFactory(Object adapterKey) {
+ this(adapterKey, true);
+ }
+
+ /**
+ * Suclasses may extended this constructor, if needed.
+ */
+
+ public AbstractAdapterFactory(Object adapterKey, boolean registerAdapters) {
+
+ super();
+ fAdapterKey = adapterKey;
+ fShouldRegisterAdapter = registerAdapters;
+
+ }
+
+ /**
+ * ISSUE: should be final. See those that implement it
+ * for complicating details and "unknowns".
+ */
+ public INodeAdapter adapt(INodeNotifier target) {
+ INodeAdapter adapter = null;
+ if (target != null) {
+ adapter = target.getExistingAdapter(fAdapterKey);
+ if (adapter == null) {
+ adapter = adaptNew(target);
+ }
+ }
+ return adapter;
+ }
+
+ /**
+ * Subclasses should normally implement their own 'copy' method. By
+ * default, we'll return the same instance, for convenience of those using
+ * singleton factories (which have no state, and so need to do anything on
+ * 'release').
+ *
+ */
+ public INodeAdapterFactory copy() {
+ return this;
+ }
+
+ /**
+ * This method needs to return true of this factory is for adapters of
+ * type 'type'. It is required that it return true if 'equals' and this
+ * default behavior is provided by this super class. Clients may extend
+ * this behavior if more complex logic is required.
+ */
+ public boolean isFactoryForType(Object type) {
+ return type.equals(fAdapterKey);
+ }
+
+ /**
+ * Subclasses may need to "cleanup" their adapter factory, release
+ * adapters, resources, etc. Subclasses may extend this method if such
+ * clean up is required. Note: while current behavior is to do nothing,
+ * subclasses should not assume this would always be true, so should
+ * always call super.release at the end of their method.
+ */
+ public void release() {
+ // default for superclass is to do nothing
+ }
+
+ /**
+ * Only for special cases there the adapter key can be set in the
+ * constructor. It can be set here. If it is set more than, and
+ */
+ final protected void setAdapterKey(Object key) {
+ if (fAdapterKey != null && !(fAdapterKey.equals(key)))
+ throw new IllegalStateException("INodeAdapter Key cannot be changed."); //$NON-NLS-1$
+ fAdapterKey = key;
+ }
+
+ /**
+ * Can be called by subclasses during 'adapt' process, but must not be
+ * overridden or reimplemented by subclasses.
+ *
+ * @param target
+ * @return
+ */
+ protected final INodeAdapter adaptNew(INodeNotifier target) {
+ INodeAdapter adapter = createAdapter(target);
+ if (adapter != null) {
+ if (fShouldRegisterAdapter) {
+ target.addAdapter(adapter);
+ }
+ }
+ return adapter;
+ }
+
+ /**
+ * Subclasses must implement this method. It is called by infrastructure
+ * when an instance is needed. It is provided the node notifier, which may
+ * or may not be relevent when creating the adapter. Note: the adapter
+ * does not have to literally be a new intance and is actually recommended
+ * to typically be a singleton for performance reasons.
+ *
+ * @param target
+ * @return
+ */
+ abstract protected INodeAdapter createAdapter(INodeNotifier target);
+
+
+ protected final boolean isShouldRegisterAdapter() {
+ return fShouldRegisterAdapter;
+ }
+
+
+ protected final void setShouldRegisterAdapter(boolean shouldRegisterAdapter) {
+ // ISSUE: technically we probably should not allow this value to
+ // change, after initialization, but is not so easy to do,
+ // and I'm not sure its "worth" the protection.
+ fShouldRegisterAdapter = shouldRegisterAdapter;
+ }
+
+
+ protected final Object getAdapterKey() {
+ return fAdapterKey;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractNotifier.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractNotifier.java
new file mode 100644
index 0000000000..6f18471e5d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/AbstractNotifier.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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.provisional;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.model.FactoryRegistry;
+
+
+
+/**
+ * AbstractNotifier is similar to (and based on) the EMF NotifierImpl class,
+ * but is not related to EMF per se. This class is simpler (that is, not as
+ * many functions).
+ *
+ * Implementers of this INodeNotifier must subclass this class.
+ */
+public abstract class AbstractNotifier implements INodeNotifier {
+ private final static int growthConstant = 3;
+ private int adapterCount = 0;
+
+ private INodeAdapter[] fAdapters;
+
+ /**
+ * AbstractNotifier constructor comment.
+ */
+ public AbstractNotifier() {
+ super();
+ }
+
+ /**
+ * addAdapter method comment.
+ */
+ public synchronized void addAdapter(INodeAdapter adapter) {
+
+ if (adapter == null)
+ return;
+ ensureCapacity(adapterCount + 1);
+ fAdapters[adapterCount++] = adapter;
+ }
+
+ private synchronized void ensureCapacity(int needed) {
+ if (fAdapters == null) {
+ // first time
+ fAdapters = new INodeAdapter[needed + growthConstant];
+ return;
+ }
+ int oldLength = fAdapters.length;
+ if (oldLength < needed) {
+ INodeAdapter[] oldAdapters = fAdapters;
+ INodeAdapter[] newAdapters = new INodeAdapter[needed + growthConstant];
+ System.arraycopy(oldAdapters, 0, newAdapters, 0, adapterCount);
+ fAdapters = newAdapters;
+ }
+ }
+
+ /**
+ * NOT API: used only for testing.
+ *
+ * @return int
+ */
+ public int getAdapterCount() {
+ return adapterCount;
+ }
+
+ /**
+ * Default behavior for getting an adapter.
+ */
+ public synchronized INodeAdapter getAdapterFor(Object type) {
+ // first, we'll see if we already have one
+ INodeAdapter result = getExistingAdapter(type);
+ // if we didn't find one in our list already,
+ // let's create it
+ if (result == null) {
+ FactoryRegistry reg = getFactoryRegistry();
+ if (reg != null) {
+ INodeAdapterFactory factory = reg.getFactoryFor(type);
+ if (factory != null) {
+ result = factory.adapt(this);
+ }
+ }
+ // We won't prevent null from being returned, but it would be
+ // unusual.
+ // It might be because Factory is not working correctly, or
+ // not installed, so we'll allow warning message.
+ if ((result == null) && (org.eclipse.wst.sse.core.internal.util.Debug.displayWarnings)) {
+ System.out.println("Warning: no adapter was found or created for " + type); //$NON-NLS-1$
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a shallow clone of list, since clients should not manipulate
+ * our list directly. Instead, they should use add/removeAdapter.
+ */
+ public synchronized Collection getAdapters() {
+ if (fAdapters != null) {
+ if (adapterCount == 0) {
+ fAdapters = null;
+ return Collections.EMPTY_LIST;
+ }
+ else {
+ // we need to make a new array, to be sure
+ // it doesn't contain nulls at end, which may be
+ // present there for "growth".
+ INodeAdapter[] tempAdapters = new INodeAdapter[adapterCount];
+ System.arraycopy(fAdapters, 0, tempAdapters, 0, adapterCount);
+ // EMF uses the unmodifiableCollection. Its a bit of a
+ // performance
+ // drain, but may want to leave in since
+ // it would "fail fast" if someone was trying to modify the
+ // list.
+ return Collections.unmodifiableCollection(Arrays.asList(tempAdapters));
+ // return Arrays.asList(newAdapters);
+ }
+ }
+ else
+ return Collections.EMPTY_LIST;
+ }
+
+ private long getAdapterTimeCriteria() {
+ // to "re-get" the property each time is a little awkward, but we
+ // do it that way to avoid adding instance variable just for
+ // debugging.
+ // This method should only be called if debugAdapterNotifcationTime
+ // is true.
+ final String criteriaStr = Platform.getDebugOption("org.eclipse.wst.sse.core/dom/adapter/notification/time/criteria"); //$NON-NLS-1$
+ long criteria = -1;
+ if (criteriaStr != null) {
+ try {
+ criteria = Long.parseLong(criteriaStr);
+ }
+ catch (NumberFormatException e) {
+ // catch to be sure we don't burb in notification loop,
+ // but ignore, since just a debug aid
+ }
+ }
+ return criteria;
+ }
+
+ public synchronized INodeAdapter getExistingAdapter(Object type) {
+ INodeAdapter result = null;
+ for (int i = 0; i < adapterCount; i++) {
+ INodeAdapter a = fAdapters[i];
+ if (a != null && a.isAdapterForType(type)) {
+ result = a;
+ break;
+ }
+ }
+ // if we didn't find one in our list,
+ // return the null result
+ return result;
+ }
+
+ abstract public FactoryRegistry getFactoryRegistry();
+
+ public void notify(int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) {
+
+ int localAdapterCount = 0;
+ INodeAdapter[] localAdapters = null;
+
+ // lock object while making local assignments
+ synchronized (this) {
+ if (fAdapters != null) {
+ localAdapterCount = adapterCount;
+ localAdapters = new INodeAdapter[localAdapterCount];
+ System.arraycopy(fAdapters, 0, localAdapters, 0, localAdapterCount);
+ }
+ }
+
+ for (int i = 0; i < localAdapterCount; i++) {
+ INodeAdapter a = localAdapters[i];
+
+ if (Logger.DEBUG_ADAPTERNOTIFICATIONTIME) {
+ long getAdapterTimeCriteria = getAdapterTimeCriteria();
+ long startTime = System.currentTimeMillis();
+ // ** keep this line identical with non-debug version!!
+ a.notifyChanged(this, eventType, changedFeature, oldValue, newValue, pos);
+ long notifyDuration = System.currentTimeMillis() - startTime;
+ if (getAdapterTimeCriteria >= 0 && notifyDuration > getAdapterTimeCriteria) {
+ System.out.println("adapter notifyDuration: " + notifyDuration + " class: " + a.getClass()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ else {
+ try {
+ // ** keep this line identical with debug version!!
+ a.notifyChanged(this, eventType, changedFeature, oldValue, newValue, pos);
+ }
+ catch (Exception e) {
+ // Its important to "keep going", since notifications
+ // occur between an
+ // aboutToChange event and a changed event -- the
+ // changed event typically being require
+ // to restore state, etc. So, we just log message, do
+ // not re-throw it, but
+ // typically the exception does indicate a serious
+ // program error.
+ Logger.logException("A structured model client, " + a + " threw following exception during adapter notification (" + INodeNotifier.EVENT_TYPE_STRINGS[eventType] + " )", e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+
+ }
+ }
+
+ public synchronized void removeAdapter(INodeAdapter a) {
+ if (fAdapters == null || a == null)
+ return;
+ int newIndex = 0;
+ INodeAdapter[] newAdapters = new INodeAdapter[fAdapters.length];
+ int oldAdapterCount = adapterCount;
+ boolean found = false;
+ for (int oldIndex = 0; oldIndex < oldAdapterCount; oldIndex++) {
+ INodeAdapter candidate = fAdapters[oldIndex];
+ if (a == candidate) {
+ adapterCount--;
+ found = true;
+ }
+ else
+ newAdapters[newIndex++] = fAdapters[oldIndex];
+ }
+ if (found)
+ fAdapters = newAdapters;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/DocumentChanged.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/DocumentChanged.java
new file mode 100644
index 0000000000..6d7a4ecfeb
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/DocumentChanged.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+import org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+public class DocumentChanged extends ModelLifecycleEvent {
+ private IStructuredDocument fNewDocument;
+
+ private IStructuredDocument fOldDocument;
+
+ protected DocumentChanged() {
+
+ super(ModelLifecycleEvent.MODEL_DOCUMENT_CHANGED);
+
+ }
+
+ protected DocumentChanged(int additionalType, IStructuredModel model) {
+
+ super(model, ModelLifecycleEvent.MODEL_DOCUMENT_CHANGED | additionalType);
+
+ }
+
+ public DocumentChanged(int additionalType, IStructuredModel model, IStructuredDocument oldDocument, IStructuredDocument newDocument) {
+
+ this(additionalType, model);
+ fOldDocument = oldDocument;
+ fNewDocument = newDocument;
+ }
+
+ public IStructuredDocument getNewDocument() {
+
+ return fNewDocument;
+ }
+
+ public IStructuredDocument getOldDocument() {
+
+ return fOldDocument;
+ }
+
+ void setNewDocument(IStructuredDocument newDocument) {
+
+ fNewDocument = newDocument;
+ }
+
+ void setOldDocument(IStructuredDocument oldDocument) {
+
+ fOldDocument = oldDocument;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLifecycleListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLifecycleListener.java
new file mode 100644
index 0000000000..87f83535b9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLifecycleListener.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+import org.eclipse.wst.sse.core.internal.model.ModelLifecycleEvent;
+
+/**
+ * This is an early version of a class that may change over the next few
+ * milestones.
+ */
+
+public interface IModelLifecycleListener {
+
+ void processPostModelEvent(ModelLifecycleEvent event);
+
+ void processPreModelEvent(ModelLifecycleEvent event);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLoader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLoader.java
new file mode 100644
index 0000000000..53308f0ced
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelLoader.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingRule;
+import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+
+/**
+ * Responsible for creating a new Model from a resource, or as a new, empty
+ * instance.
+ *
+ */
+public interface IModelLoader {
+ /**
+ * This method should perform all the model initialization required before
+ * it contains content, namely, it should call newModel, the
+ * createNewStructuredDocument(), then setAdapterFactories. (this is
+ * tentative)
+ */
+ IStructuredModel createModel();
+
+ /**
+ * Method createModel. Creates a new model based on old one.
+ *
+ * @param oldModel
+ * @return IStructuredModel
+ */
+ IStructuredModel createModel(IStructuredModel oldModel);
+
+ /**
+ * This method must return those factories which must be attached to the
+ * structuredModel before content is applied.
+ */
+ List getAdapterFactories();
+
+ void load(IFile file, IStructuredModel model) throws IOException, CoreException;
+
+ void load(InputStream inputStream, IStructuredModel model, EncodingRule encodingRule) throws IOException;
+
+ void load(String filename, InputStream inputStream, IStructuredModel model, String encodingName, String lineDelimiter) throws IOException;
+
+ IModelLoader newInstance();
+
+ /**
+ * This method should always return an new, empty Structured Model
+ * appropriate for itself.
+ */
+ IStructuredModel newModel();
+
+ IStructuredModel reinitialize(IStructuredModel model);
+
+ /**
+ * This method should get a fresh copy of the data, and repopulate the
+ * models ... normally by a call to setText on the structuredDocument, for
+ * StructuredModels. 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.
+ */
+ void reload(InputStream inputStream, IStructuredModel model);
+
+ /**
+ * Create a Structured Model with the given StructuredDocument instance as
+ * its document (instead of a new document instance as well)
+ */
+ IStructuredModel createModel(IStructuredDocument document, String baseLocation, IModelHandler handler);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelManager.java
new file mode 100644
index 0000000000..e770795d77
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelManager.java
@@ -0,0 +1,564 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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.provisional;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingRule;
+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.URIResolver;
+
+/**
+ * <p>
+ * Provides APIs for managing (get, release, save, and save as) SSE Structured
+ * Models.
+ * </p>
+ * <p>
+ * Structured Models created from an implementor of this interface can be
+ * either managed or unmanaged. Managed models are shared using reference
+ * counts, so until that count has been decremented to zero, the model will
+ * continue to exist in memory. When managed, models can be looked up using
+ * their IDs or their IStructuredDocuments, which can be advantageous when
+ * building on APIs that aren't specifically designed for SSE (such as those
+ * revolving around IDocuments). Unmanaged models offer no such features, and
+ * are largely used for tasks where their contents are ephemeral, such as for
+ * populating a source viewer with syntax-colored content. Both types of models
+ * must be released when no longer needed.
+ * </p>
+ * <p>
+ * There are two types of access used when retrieving a model from the model
+ * manager: READ and EDIT. The contents of a model can be modified regardless
+ * of which access type is used, but any client who gets a model for EDIT is
+ * explicitly declaring that they are interested in saving those changed
+ * contents. The EDIT and READ reference counts are visible to everyone, as
+ * are convenience methods for determining whether a managed model is shared
+ * among multiple clients accessing it for READ or EDIT.
+ * </p>
+ * <p>
+ * Managed models whose contents are "dirty" with READ and EDIT counts above
+ * zero will be reverted to the on-disk content if the EDIT count drops to
+ * zero while the READ count remains above zero.
+ * </p>
+ * <p>
+ * Shared models for which the read and edit counts have both dropped to zero
+ * are no longer valid for use, regardless of whether they have been garbage
+ * collected or not. It is possible, but not guaranteed, that the underlying
+ * structured document is still valid and may even be used in constructing a
+ * new shared model.
+ * </p>
+ * <p>
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should obtain an instance of the IModelManager interface through
+ * {@link StructuredModelManager#getModelManager()}.</p>
+ * <p>@see {@link StructuredModelManager}</p>
+ */
+public interface IModelManager {
+
+ /**
+ * A fixed ID used for models which were created as duplicates of existing
+ * models
+ */
+ public final static String DUPLICATED_MODEL = "org.eclipse.wst.sse.core.IModelManager.DUPLICATED_MODEL"; //$NON-NLS-1$
+
+ /**
+ * A fixed ID used for unmanaged models
+ */
+ public final static String UNMANAGED_MODEL = "org.eclipse.wst.sse.core.IModelManager.UNMANAGED_MODEL"; //$NON-NLS-1$
+
+ /**
+ * 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);
+
+ /**
+ * Copies a model with the old id
+ * @param oldId - the old model's ID
+ * @param newId - the new model's ID
+ * @return the new model
+ * @throws ResourceInUse if the given new ID is already in use by a managed model
+ */
+ IStructuredModel copyModelForEdit(String oldId, String newId) throws ResourceInUse;
+
+ /**
+ * Creates a new, but empty, unmanaged model of the same kind as the one
+ * given. For a managed model with the same contents, use "copy".
+ *
+ * @param model
+ * @return the model, or null of one could not be created
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ public IStructuredModel createNewInstance(IStructuredModel model) throws IOException;
+
+ /**
+ * Factory method, since a proper IStructuredDocument must have a proper
+ * parser assigned. If the resource does already exist, then
+ * createStructuredDocumentFor is the right API to use.
+ *
+ * @param iFile
+ * @return the document, or null if one could not be created
+ * @throws ResourceAlreadyExists
+ * if the IFile already exists
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ * @throws ResourceAlreadyExists if the give file already exists
+ */
+ IStructuredDocument createNewStructuredDocumentFor(IFile iFile) throws ResourceAlreadyExists, IOException, CoreException;
+
+ /**
+ * 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
+ * {@link #createNewStructuredDocumentFor(IFile)} is the correct API to use.
+ *
+ * @param iFile - the file
+ * @return the document, or null if one could not be created
+ *
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ IStructuredDocument createStructuredDocumentFor(IFile iFile) throws IOException, CoreException;
+
+ /**
+ * Convenience 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.
+ *
+ * @param contentTypeId
+ * @return a structured document with the correct parsing setup for the
+ * given content type ID, or null if one could not be created or
+ * the given content type ID is unknown or unsupported
+ */
+ IStructuredDocument createStructuredDocumentFor(String contentTypeId);
+
+ /**
+ * @deprecated - use IFile form instead as the correct encoding and content rules may not be applied otherwise
+ *
+ * Creates and returns a properly configured structured document for the given contents with the given name
+ *
+ * @param filename - the filename, which may be used to guess the content type
+ * @param contents - the contents to load
+ * @param resolver - the URIResolver to use for locating any needed resources
+ * @return the IStructuredDocument or null of one could not be created
+ * @throws IOException if the file's contents can not be read or its content type can not be determined
+ */
+ IStructuredDocument createStructuredDocumentFor(String filename, InputStream contents, URIResolver resolver) throws IOException;
+
+ /**
+ * Creates and returns a properly configured structured document for the given contents with the given name
+ *
+ * @param filename - the filename, which may be used to guess the content type
+ * @param inputStream - the contents to load
+ * @param resolver - the URIResolver to use for locating any needed resources
+ * @param ianaEncodingName - the IANA specified encoding to use when reading the contents
+ * @return the IStructuredDocument or null if one could not be created
+ * @throws IOException if the file's contents can not be read or its content type can not be determined
+ * @deprecated - clients should convert the InputStream into text themselves
+ * and then use the version of this method taking a String for its
+ * content
+ */
+ IStructuredDocument createStructuredDocumentFor(String filename, InputStream inputStream, URIResolver resolver, String ianaEncodingName) throws IOException;
+
+ /**
+ * Creates and returns a properly configured structured document for the given contents with the given name
+ *
+ * @param filename - the filename, which may be used to guess the content type
+ * @param content - the contents to load
+ * @param resolver - the URIResolver to use for locating any referenced resources
+ * @return a structured document with the correct parsing setup for the
+ * given filename, or null if one could not be created
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ IStructuredDocument createStructuredDocumentFor(String filename, String content, URIResolver resolver) throws IOException;
+
+ /**
+ * <p>Creates and returns an unmanaged model populated with the given IFile's
+ * contents.</p>
+ * <p>
+ * {@link IStructuredModel#releaseFromRead()} or
+ * {@link IStructuredModel#releaseFromEdit()} should still be called directly
+ * on returned instances to properly dispose of them.
+ * </p>
+ *
+ * @param iFile
+ * @return a structured model, or null if one could not be created
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ IStructuredModel createUnManagedStructuredModelFor(IFile iFile) throws IOException, CoreException;
+
+ /**
+ * <p>Creates and returns an unmanaged model populated with the given IFile's
+ * contents.</p>
+ * <p>
+ * {@link IStructuredModel#releaseFromRead()} or
+ * {@link IStructuredModel#releaseFromEdit()} should still be called directly
+ * on returned instances to properly dispose of them.
+ * </p>
+ *
+ * @param contentTypeId
+ * @return a structured model for the given content type, or null if one
+ * could not be created or the content type is unsupported
+ */
+ IStructuredModel createUnManagedStructuredModelFor(String contentTypeId);
+
+ /**
+ * @deprecated
+ */
+ IStructuredModel createUnManagedStructuredModelFor(String contentTypeId, URIResolver resolver);
+
+ /**
+ * Note: callers of this method must still release the model when finished.
+ *
+ * @param document
+ * @return the structured model containing the give document, incrementing
+ * its edit count, or null if there is not a model corresponding
+ * to this document.
+ */
+ IStructuredModel getExistingModelForEdit(IDocument document);
+
+ /**
+ * @param file
+ * @return the structured model for the given file, incrementing its edit
+ * count, or null if one does not already exist for this file.
+ */
+ IStructuredModel getExistingModelForEdit(IFile file);
+
+ /**
+ * @param id
+ * @return the structured model with the given ID, incrementing its edit
+ * count, or null if one does not already exist for this ID
+ */
+ public IStructuredModel getExistingModelForEdit(Object id);
+
+ /**
+ * Note: callers of this method must still release the model when finished.
+ *
+ * @param document
+ * @return the structured model containing the give document, incrementing
+ * its read count, or null if there is not a model corresponding
+ * to this document.
+ */
+ IStructuredModel getExistingModelForRead(IDocument document);
+
+ /**
+ * @param file
+ * @return the structured model for the given file, incrementing its read
+ * count, or null if one does not already exist for this file.
+ */
+ public IStructuredModel getExistingModelForRead(IFile iFile);
+
+ /**
+ * @param id
+ * @return the structured model with the given ID, incrementing its edit
+ * count, or null if one does not already exist for this ID
+ */
+ public IStructuredModel getExistingModelForRead(Object id);
+
+ /**
+ * @deprecated - internal information
+ */
+ public Enumeration getExistingModelIds();
+
+ /**
+ * Returns a structured model for the given file. If one does not already
+ * exists, one will be created with an edit count of 1. If one already
+ * exists, its edit count will be incremented before it is returned.
+ *
+ * @param iFile
+ * @return a structured model for the given file, or null if one could not
+ * be found or created
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ public IStructuredModel getModelForEdit(IFile iFile) throws IOException, CoreException;
+
+ /**
+ * Returns a structured model for the given file. If one does not already
+ * exists, one will be created with an edit count of 1. If one already
+ * exists, its edit count will be incremented before it is returned.
+ *
+ * @param iFile
+ * @param encodingRule the rule for handling encoding
+ * @return a structured model for the given file, or null if one could not
+ * be found or created
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ * @deprecated - encoding is handled automatically based on the file's
+ * contents or user preferences
+ */
+ public IStructuredModel getModelForEdit(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * @deprecated - Encoding and the line delimiter used are handled
+ * automatically based on the file's contents or user
+ * preferences.
+ */
+ public IStructuredModel getModelForEdit(IFile iFile, String encoding, String lineDelimiter) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * Returns a structured model for the given document. If one does not
+ * already exists, one will be created with an edit count of 1. If one
+ * already exists, its edit count will be incremented before it is
+ * returned. This method is intended only to interact with documents
+ * contained within File Buffers.
+ *
+ * @param textFileBufferDocument
+ * @return a structured model for the given document, or null if there is
+ * insufficient information known about the document instance to
+ * do so
+ */
+ public IStructuredModel getModelForEdit(IStructuredDocument textFileBufferDocument);
+
+ /**
+ * Returns a structured model for the given contents using the given ID.
+ * If one does not already exist, one will be created with an edit count
+ * of 1. If one already exists, its edit count will be incremented before
+ * it is returned.
+ *
+ * @param id
+ * - the id for the model
+ * @param inStream
+ * - the initial contents of the model
+ * @param resolver
+ * - the URIResolver to use for locating any needed resources
+ * @return a structured model for the given content, or null if one could
+ * not be found or created
+ * @throws UnsupportedEncodingException
+ * @throws IOException
+ * if the contents can not be read or its detected encoding
+ * does not support its contents
+ * @deprecated - a URI resolver should be automatically created when
+ * needed
+ */
+ public IStructuredModel getModelForEdit(String id, InputStream inStream, URIResolver resolver) throws UnsupportedEncodingException, IOException;
+
+ /**
+ * Returns a structured model for the given file. If one does not already
+ * exists, one will be created with a read count of 1. If one already
+ * exists, its read count will be incremented before it is returned.
+ *
+ * @param iFile
+ * @return a structured model for the given file, or null if one could not
+ * be found or created
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ public IStructuredModel getModelForRead(IFile iFile) throws IOException, CoreException;
+
+ /**
+ * @deprecated - encoding is handled automatically based on the file's
+ * contents or user preferences
+ */
+ public IStructuredModel getModelForRead(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * @deprecated - Encoding and the line delimiter used are handled
+ * automatically based on the file's contents or user
+ * preferences.
+ */
+ public IStructuredModel getModelForRead(IFile iFile, String encoding, String lineDelimiter) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * Returns a structured model for the given document. If one does not
+ * already exists, one will be created with a read count of 1. If one
+ * already exists, its read count will be incremented before it is
+ * returned. This method is intended only to interact with documents
+ * contained within File Buffers.
+ *
+ * @param textFileBufferDocument
+ * @return a structured model for the given document, or null if there is
+ * insufficient information known about the document instance to
+ * do so
+ */
+ public IStructuredModel getModelForRead(IStructuredDocument textFileBufferDocument);
+
+ /**
+ * Returns a structured model for the given contents using the given ID.
+ * If one does not already exist, one will be created with an read count
+ * of 1. If one already exists, its read count will be incremented before
+ * it is returned.
+ *
+ * @param id
+ * - the id for the model
+ * @param inStream
+ * - the initial contents of the model
+ * @param resolver
+ * - the URIResolver to use for locating any needed resources
+ * @return a structured model for the given content, or null if one could
+ * not be found or created
+ * @throws UnsupportedEncodingException
+ * @throws IOException
+ * if the contents can not be read or its detected encoding
+ * does not support its contents
+ * @deprecated - a URI resolver should be automatically created when
+ * needed
+ */
+ public IStructuredModel getModelForRead(String filename, InputStream inStream, URIResolver resolver) throws UnsupportedEncodingException, IOException;
+
+ /**
+ * This method will not create a new model if it already exists ... if
+ * force is false. The idea is that a client should call this method once
+ * with force set to false. If the exception is thrown, then prompt client
+ * if they want to overwrite.
+ *
+ * @param iFile
+ * @param force
+ * @return the new structured model, or
+ * @throws ResourceInUse if the given new ID is already in use by a managed model
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ * @throws ResourceAlreadyExists if the give file already exists
+ */
+ IStructuredModel getNewModelForEdit(IFile iFile, boolean force) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException;
+
+ /**
+ * This method will not create a new model if it already exists ... if
+ * force is false. The idea is that a client should call this method once
+ * with force set to false. If the exception is thrown, then prompt client
+ * if they want to overwrite.
+ *
+ * @param iFile
+ * @param force
+ * @return the new structured model, or
+ * @throws ResourceInUse if the given new ID is already in use by a managed model
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ * @throws ResourceAlreadyExists if the give file already exists
+ */
+ IStructuredModel getNewModelForRead(IFile iFile, boolean force) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException;
+
+ /**
+ * This function returns the combined "read" and "edit" reference counts
+ * of underlying model.
+ *
+ * @param id
+ * Object The id of the model
+ * @deprecated - internal information that can be obtained from the model
+ * itself
+ */
+ int getReferenceCount(Object id);
+
+ /**
+ * This function returns the "edit" reference count of underlying model.
+ *
+ * @param id
+ * Object The id of the model
+ * @deprecated - internal information that can be obtained from the model itself
+ */
+ int getReferenceCountForEdit(Object id);
+
+ /**
+ * This function returns the "read" reference count of underlying model.
+ *
+ * @param id
+ * Object The id of the model TODO: try to refine the design
+ * not to use this function
+ * @deprecated - internal information that can be obtained from the model itself
+ */
+ int getReferenceCountForRead(Object id);
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ *
+ * @param id
+ * Object The id of the model
+ */
+ boolean isShared(Object id);
+
+ /**
+ * This function returns true if there are other "edit" references to the
+ * underlying model.
+ *
+ * @param id
+ * Object The id of the model
+ */
+ boolean isSharedForEdit(Object id);
+
+ /**
+ * This function returns true if there are other "read" references to the
+ * underlying model.
+ *
+ * @param id
+ * Object The id of the model
+ */
+ boolean isSharedForRead(Object id);
+
+ /**
+ * @deprecated - not granular enough
+ *
+ * This method can be called to determine if the model manager is within a
+ * "aboutToChange" and "changed" sequence.
+ */
+ public boolean isStateChanging();
+
+ /**
+ * This method changes the id of the model.
+ *
+ * TODO: try to refine the design
+ * not to use this function
+ *
+ * @deprecated
+ */
+ void moveModel(Object oldId, Object newId);
+
+ /**
+ * This method can be called when the content type of a model changes. It's
+ * assumed the contentType has already been changed, and this method uses
+ * the text of the old one, to repopulate the text of the new one. In
+ * theory, the actual instance could change, (e.g. using 'saveAs' to go
+ * from xml to dtd), but in practice, the intent of this API is to return
+ * the same instance, just using different handlers, adapter factories,
+ * etc.
+ */
+ IStructuredModel reinitialize(IStructuredModel model) throws IOException;
+
+ /**
+ * 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
+ */
+ IStructuredModel reloadModel(Object id, InputStream inStream) throws UnsupportedEncodingException;
+
+ /**
+ * Saves the contents of the given structured document to the given file. If
+ * the document belongs to a managed model, that model will be saved and
+ * marked as non-dirty.
+ *
+ * @param structuredDocument
+ * - the structured document
+ * @param iFile
+ * - the file to save to
+ * @throws UnsupportedEncodingException
+ * @throws CoreException if the file's contents or description can not be read
+ * @throws IOException if the file's contents can not be read or its detected encoding does not support its contents
+ */
+ void saveStructuredDocument(IStructuredDocument structuredDocument, IFile iFile) throws UnsupportedEncodingException, IOException, CoreException;
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelStateListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelStateListener.java
new file mode 100644
index 0000000000..b14e473736
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IModelStateListener.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+
+
+/**
+ * Interface for those wanting to listen to a model's state changing.
+ */
+public interface IModelStateListener {
+
+ /**
+ * A model is about to be changed. This typically is initiated by one
+ * client of the model, to signal a large change and/or a change to the
+ * model's ID or base Location. A typical use might be if a client might
+ * want to suspend processing until all changes have been made.
+ */
+ void modelAboutToBeChanged(IStructuredModel model);
+
+ /**
+ * Signals that the changes foretold by modelAboutToBeChanged have been
+ * made. A typical use might be to refresh, or to resume processing that
+ * was suspended as a result of modelAboutToBeChanged.
+ */
+ void modelChanged(IStructuredModel model);
+
+ /**
+ * Notifies that a model's dirty state has changed, and passes that state
+ * in isDirty. A model becomes dirty when any change is made, and becomes
+ * not-dirty when the model is saved.
+ */
+ void modelDirtyStateChanged(IStructuredModel model, boolean isDirty);
+
+ /**
+ * A modelDeleted means the underlying resource has been deleted. The
+ * model itself is not removed from model management until all have
+ * released it. Note: baseLocation is not (necessarily) changed in this
+ * event, but may not be accurate.
+ */
+ void modelResourceDeleted(IStructuredModel model);
+
+ /**
+ * A model has been renamed or copied (as in saveAs..). In the renamed
+ * case, the two paramenters are the same instance, and only contain the
+ * new info for id and base location.
+ */
+ void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel);
+
+ void modelAboutToBeReinitialized(IStructuredModel structuredModel);
+
+ void modelReinitialized(IStructuredModel structuredModel);
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapter.java
new file mode 100644
index 0000000000..e88a24f5bd
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapter.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+/**
+ * This interface allows nodes to be adapted.
+ *
+ * The main difference between this type of adapter (IAdaptable) and base
+ * adapter is that these adapters are notified of changes.
+ *
+ * @plannedfor 1.0
+ */
+
+public interface INodeAdapter {
+
+ /**
+ * The infrastructure calls this method to determine if the adapter is
+ * appropriate for 'type'. Typically, adapters return true based on
+ * identity comparison to 'type', but this is not required, that is, the
+ * decision can be based on complex logic.
+ *
+ */
+ boolean isAdapterForType(Object type);
+
+ /**
+ * Sent to adapter when notifier changes. Each notifier is responsible for
+ * defining specific eventTypes, feature changed, etc.
+ *
+ * ISSUE: may be more evolvable if the argument was one big 'notifier
+ * event' instance.
+ */
+ void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapterFactory.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapterFactory.java
new file mode 100644
index 0000000000..d6a25062ae
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeAdapterFactory.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional;
+
+/**
+ * INodeNotifiers can be adapted by INodeAdapters. This factory interface
+ * provides a way to provide factories which are invoked by the infrastructure
+ * to manage this process, from creating, to adapting, to releasing, if
+ * required.
+ *
+ * @plannedfor 1.0
+ *
+ */
+public interface INodeAdapterFactory {
+
+ /**
+ * The primary purpose of an adapter factory is to create an adapter and
+ * associate it with an INodeNotifier. This adapt method Method that
+ * returns the adapter associated with the given object. The
+ * implementation of this method should call addAdapter on the adapted
+ * object with the correct instance of the adapter, if appropriate.
+ *
+ * Note: the instance of the adapter returned may be a singleton or not
+ * ... depending on the needs of the INodeAdapter ... but in general it is
+ * recommended for an adapter to be stateless, so the efficiencies of a
+ * singleton can be gained.
+ *
+ * @param object
+ * the node notifier to be adapted
+ */
+ INodeAdapter adapt(INodeNotifier object);
+
+ /**
+ * Unlike clone, this method may or may not return the same instance, such
+ * as in the case where the IAdapterFactory is intended to be a singleton.
+ *
+ * @return an instance of this adapter factory.
+ */
+ public INodeAdapterFactory copy();
+
+ /**
+ * isFactoryForType is called by infrastructure to decide if this adapter
+ * factory is apporiate to use for an adapter request that specifies
+ * 'type'.
+ *
+ * @param type -
+ * same object used to identify/request adapters.
+ * @return true if factory is appropriate for type, false otherwise.
+ */
+ boolean isFactoryForType(Object type);
+
+ /**
+ * release is called by infrastructure when the factory registry is
+ * released (which is done when a structured model is released). This
+ * intened for the factory to be allowed to clean up any state information
+ * it may have.
+ *
+ * Note: while not recommended, due to performance reasons, if individual
+ * adapters need some cleanup (or need to be released) it is (typically)
+ * the responsibility of the adapter factory to track them, and initiate
+ * what ever clean up is needed. In other works, cleanup at the adatper
+ * level is not provided by infrastructure.
+ */
+ public void release();
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeNotifier.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeNotifier.java
new file mode 100644
index 0000000000..a76fe75b74
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/INodeNotifier.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.provisional;
+
+
+
+import java.util.Collection;
+
+/**
+ * INodeNotifiers and INodeAdapters form a collaboration that allows clients
+ * to use the typical adapter pattern but with notification added, that is,
+ * client's adapters will be notified when the nodeNotifier changes.
+ *
+ * @plannedfor 1.0
+ */
+
+public interface INodeNotifier {
+
+ /**
+ * The change represents a non-structural change, sent to node notifier's
+ * parent.
+ */
+ static final int CHANGE = 1;
+ /**
+ * The change represents an add event.
+ */
+ static final int ADD = 2;
+
+ /**
+ * The change represents a remove event.
+ */
+ static final int REMOVE = 3;
+
+ /**
+ * The change represents a structural change, sent to least-common parent
+ * of node notifiers involved in the structural change
+ */
+ static final int STRUCTURE_CHANGED = 4;
+
+ /**
+ * The change represents a notification to parent notifier than its
+ * contents have changed.
+ */
+ static final int CONTENT_CHANGED = 5;
+
+
+ /**
+ * NOT API: these strings are for printing, such as during debugging
+ */
+ static final String[] EVENT_TYPE_STRINGS = new String[]{"undefined", "CHANGE", "ADD", "REMOVE", "STRUCTURE_CHANGED", "CONTENT_CHANGED"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
+
+
+ /**
+ * Add an adapter of this notifier.
+ *
+ * @param adapter
+ * the adapter to be added
+ *
+ */
+ void addAdapter(INodeAdapter adapter);
+
+ /**
+ * Return an exisiting adapter of type "type" or if none found create a
+ * new adapter using a registered adapter factory
+ */
+ INodeAdapter getAdapterFor(Object type);
+
+ /**
+ * Return a read-only Collection of the Adapters to this notifier.
+ *
+ * @return collection of adapters.
+ */
+ Collection getAdapters();
+
+ /**
+ * Return an exisiting adapter of type "type" or null if none found
+ */
+ INodeAdapter getExistingAdapter(Object type);
+
+ /**
+ * sent to adapter when its nodeNotifier changes.
+ */
+ void notify(int eventType, Object changedFeature, Object oldValue, Object newValue, int pos);
+
+ /**
+ * Remove an adapter of this notifier. If the adapter does not exist for
+ * this node notifier, this request is ignored.
+ *
+ * @param adapter
+ * the adapter to remove
+ */
+ void removeAdapter(INodeAdapter adapter);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IStructuredModel.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IStructuredModel.java
new file mode 100644
index 0000000000..86690199d5
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IStructuredModel.java
@@ -0,0 +1,419 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+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.model.FactoryRegistry;
+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.undo.IStructuredTextUndoManager;
+import org.eclipse.wst.sse.core.internal.util.URIResolver;
+
+
+/**
+ * IStructuredModels are mainly interesting by their extensions and
+ * implementers. The main purposed of this abstraction is to provide a common
+ * means to manage models that have an associated structured document.
+ *
+ * <p>
+ * TODO: this interface needs ton of cleanup!
+ * </p>
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IStructuredModel extends IAdaptable {
+
+
+ /**
+ * This API allows clients to declare that they are about to make a
+ * "large" change to the model. This change might be in terms of content
+ * or it might be in terms of the model id or base location.
+ *
+ * Note that in the case of embedded calls, notification to listeners is
+ * sent only once.
+ *
+ * Note that the client who is making these changes has the responsibility
+ * to restore the model's state once finished with the changes. See
+ * getMemento and restoreState.
+ *
+ * The method isModelStateChanging can be used by a client to determine if
+ * the model is already in a change sequence.
+ *
+ * This method is a matched pair to changedModel, and must be called
+ * before changedModel. A client should never call changedModel without
+ * calling aboutToChangeModel first nor call aboutToChangeModel without
+ * calling changedModel later from the same Thread.
+ */
+ void aboutToChangeModel();
+
+ void addModelLifecycleListener(IModelLifecycleListener listener);
+
+ void addModelStateListener(IModelStateListener listener);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, int cursorPosition, int selectionLength);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, int cursorPosition, int selectionLength);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, String description);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, String description, int cursorPosition, int selectionLength);
+
+ /**
+ * This API allows a client controlled way of notifying all ModelEvent
+ * listeners that the model has been changed. This method is a matched
+ * pair to aboutToChangeModel, and must be called after aboutToChangeModel
+ * ... or some listeners could be left waiting indefinitely for the
+ * changed event. So, its suggested that changedModel always be in a
+ * finally clause. Likewise, a client should never call changedModel
+ * without calling aboutToChangeModel first.
+ *
+ * In the case of embedded calls, the notification is just sent once.
+ *
+ */
+ void changedModel();
+
+ long computeModificationStamp(IResource resource);
+
+ /**
+ * @deprecated
+ * @see IModelManager#copyModelForEdit(String, String)
+ */
+ IStructuredModel copy(String id) throws ResourceInUse, ResourceAlreadyExists;
+
+ /**
+ * Disable undo management.
+ *
+ * @deprecated - the ability to enable and disable Undo management for the
+ * model cannot be guaranteed as it implicitly requires
+ * knowledge of the underlying undo/redo implementation
+ */
+ void disableUndoManagement();
+
+ /**
+ * Enable undo management.
+ *
+ * @deprecated - the ability to enable and disable Undo management for the
+ * model cannot be guaranteed as it implicitly requires
+ * knowledge of the underlying undo/redo implementation
+ */
+ void enableUndoManagement();
+
+ /**
+ * End recording undo transactions.
+ */
+ void endRecording(Object requester);
+
+ /**
+ * End recording undo transactions.
+ */
+ void endRecording(Object requester, int cursorPosition, int selectionLength);
+
+ /**
+ * This is a client-defined value for what that client (and/or loader)
+ * considers the "base" of the structured model. Frequently the location
+ * is either a workspace root-relative path of a workspace resource or an
+ * absolute location in the local file system.
+ *
+ * @return the base location of the model or <code>null</code> when one
+ * has not been set, such as in models resulting from calling
+ * {@link IModelManager#createNewInstance(IStructuredModel)}
+ */
+ String getBaseLocation();
+
+ /**
+ * @return The associated content type identifier (String) for this model.
+ * This value may be more accurate than the content type against
+ * which the model handler was registered.
+ *
+ * @see IModelHandler#getAssociatedContentTypeId()
+ */
+ String getContentTypeIdentifier();
+
+ /**
+ *
+ * @return The model's FactoryRegistry. A model is not valid without one.
+ */
+ FactoryRegistry getFactoryRegistry();
+
+ /**
+ * The id is the id that the model manager uses to identify this model
+ */
+ String getId();
+
+ /**
+ * @param offset
+ * a text offset within the structured document
+ * @return an IndexedRegion containing this offset or null if one could
+ * not be found
+ */
+ IndexedRegion getIndexedRegion(int offset);
+
+ /**
+ * @return the model's handler
+ */
+ IModelHandler getModelHandler();
+
+ IModelManager getModelManager();
+
+ /**
+ * @param id
+ * Object The id of the model TODO: try to refine the design
+ * not to use this function
+ *
+ * @return the reference count of underlying model
+ */
+ int getReferenceCount();
+
+ /**
+ * This function returns the edit-responsible reference count of
+ * underlying model.
+ *
+ * @param id
+ * Object The id of the model TODO: try to refine the design
+ * not to use this function
+ */
+ int getReferenceCountForEdit();
+
+ /**
+ * This function returns the reader reference count of underlying model.
+ *
+ * @param id
+ * Object The id of the model TODO: try to refine the design
+ * not to use this function
+ */
+ int getReferenceCountForRead();
+
+ Object getReinitializeStateData();
+
+ /**
+ * Get URI resolution helper
+ *
+ * @deprecated - use org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin.createResolver(*) instead
+ */
+ URIResolver getResolver();
+
+ IStructuredDocument getStructuredDocument();
+
+ /**
+ * modification date of underlying resource, when this model was open, or
+ * last saved. (Note: for this version, the client must manage the
+ * accuracy of this data)
+ */
+ long getSynchronizationStamp();
+
+ /**
+ * Get undo manager.
+ */
+ IStructuredTextUndoManager getUndoManager();
+
+ /**
+ *
+ */
+ boolean isDirty();
+
+ /**
+ * This method can be called to determine if the model is within a
+ * "aboutToChange" and "changed" sequence.
+ */
+ public boolean isModelStateChanging();
+
+ /**
+ *
+ */
+ boolean isNew();
+
+ boolean isReinitializationNeeded();
+
+ /**
+ * This is a combination of if the model is dirty and if the model is
+ * shared for write access. The last writer as the responsibility to be
+ * sure the user is prompted to save.
+ */
+ public boolean isSaveNeeded();
+
+ /**
+ * This function returns true if either isSharedForRead or isSharedForEdit
+ * is true.
+ */
+ boolean isShared();
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ */
+ boolean isSharedForEdit();
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ */
+ boolean isSharedForRead();
+
+ /**
+ * newInstance is similar to clone, except that the newInstance contains
+ * no content. Its purpose is so clients can get a temporary, unmanaged,
+ * model of the same "type" as the original. Note: the client may still
+ * need to do some initialization of the model returned by newInstance,
+ * depending on desired use. For example, the only factories in the
+ * newInstance are those that would be normally be created for a model of
+ * the given contentType. Others are not copied automatically, and if
+ * desired, should be added by client.
+ */
+ IStructuredModel newInstance() throws IOException;
+
+ /**
+ * Performs a reinitialization procedure. For this model. Note for future:
+ * there may be a day where the model returned from this method is a
+ * different instance than the instance it was called on. This will occur
+ * when there is full support for "save as" type functions, where the
+ * model could theoretically change completely.
+ */
+ IStructuredModel reinit() throws IOException;
+
+ /**
+ * This function allows the model to free up any resources it might be
+ * using. In particular, itself, as stored in the IModelManager.
+ *
+ */
+ void releaseFromEdit();
+
+ /**
+ * This function allows the model to free up any resources it might be
+ * using. In particular, itself, as stored in the IModelManager.
+ *
+ */
+ void releaseFromRead();
+
+ /**
+ * This function replenishes the model with the resource without saving
+ * any possible changes. It is used when one editor may be closing, and
+ * specifically says not to save the model, but another "display" of the
+ * model still needs to hang on to some model, so needs a fresh copy.
+ *
+ * Only valid for use with managed models.
+ */
+ IStructuredModel reload(InputStream inputStream) throws IOException;
+
+ void removeModelLifecycleListener(IModelLifecycleListener listener);
+
+ void removeModelStateListener(IModelStateListener listener);
+
+ /**
+ * A method that modifies the model's synchronization stamp to match the
+ * resource. Turns out there's several ways of doing it, so this ensures a
+ * common algorithm.
+ */
+ void resetSynchronizationStamp(IResource resource);
+
+ void resourceDeleted();
+
+ void resourceMoved(IStructuredModel newModel);
+
+ void save() throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * @deprecated - will save according to the encoding priorities specified for the IFile
+ */
+ void save(EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException;
+
+ void save(IFile iFile) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * @deprecated - will save according to the encoding priorities specified for the IFile
+ */
+ void save(IFile iFile, EncodingRule encodingRule) throws UnsupportedEncodingException, IOException, CoreException;
+
+ void save(OutputStream outputStream) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * Sets the base location of this model to the new value, also updating
+ * the value in its URI resolution helper if one is present.
+ *
+ * @param newBaseLocation
+ */
+ void setBaseLocation(String newBaseLocation);
+
+ public void setDirtyState(boolean dirtyState);
+
+ void setFactoryRegistry(FactoryRegistry registry);
+
+ /**
+ * The id is the id that the model manager uses to identify this model
+ */
+ void setId(String id) throws ResourceInUse;
+
+ void setModelHandler(IModelHandler modelHandler);
+
+ void setModelManager(IModelManager modelManager);
+
+ public void setNewState(boolean newState);
+
+ /**
+ * Sets a "flag" that reinitialization is needed.
+ */
+ void setReinitializeNeeded(boolean b);
+
+ /**
+ * Holds any data that the reinitialization procedure might find useful in
+ * reinitializing the model. This is handy, since the reinitialization may
+ * not take place at once, and some "old" data may be needed to properly
+ * undo previous settings. Note: the parameter was intentionally made to
+ * be of type 'Object' so different models can use in different ways.
+ */
+ void setReinitializeStateData(Object object);
+
+ /**
+ * Set the URI resolution helper
+ */
+ void setResolver(URIResolver uriResolver);
+
+ void setStructuredDocument(IStructuredDocument structuredDocument);
+
+ /**
+ * Set undo manager.
+ */
+ void setUndoManager(IStructuredTextUndoManager undoManager);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IndexedRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IndexedRegion.java
new file mode 100644
index 0000000000..a9e953861b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/IndexedRegion.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.provisional;
+
+
+
+/**
+ * This type is used to indicate positions and lengths in source. Notice that
+ * while getEndOffset and getLength are redundant, given that
+ *
+ * <pre>
+ * <code>
+ * getEndOffset() == getStartOffset() + getLength();
+ * </code>
+ * </pre>
+ *
+ * we provide (require) both since in some cases implementors may be able to
+ * provide one or the other more efficiently.
+ *
+ * Note: it is not part of the API contract that implementors of IndexedRegion --
+ * as a whole collection for a particular source -- must completely cover the
+ * original source. They currently often do, so thought I'd mention explicitly
+ * this may not always be true.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IndexedRegion {
+
+ /**
+ * Can be used to test if the indexed regions contains the test position.
+ *
+ * @param testPosition
+ * @return true if test position is greater than or equal to start offset
+ * and less than start offset plus length.
+ */
+ boolean contains(int testPosition);
+
+ /**
+ * Can be used to get end offset of source text, relative to beginning of
+ * documnt. Implementers should return -1 if, or some reason, the region
+ * is not valid.
+ *
+ * @return endoffset
+ */
+ int getEndOffset();
+
+ /**
+ * Can be used to get source postion of beginning of indexed region.
+ * Implementers should return -1 if, or some reason, the region is not
+ * valid.
+ *
+ * @return int position of start of index region.
+ */
+ int getStartOffset();
+
+ /**
+ * Can be used to get the length of the source text. Implementers should
+ * return -1 if, or some reason, the region is not valid.
+ *
+ * @return int position of length of index region.
+ */
+ int getLength();
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/StructuredModelManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/StructuredModelManager.java
new file mode 100644
index 0000000000..61e071834a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/StructuredModelManager.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.eclipse.wst.sse.core.internal.model.ModelManagerImpl;
+import org.osgi.framework.Bundle;
+
+/**
+ * Class to allow access to ModelManager. Not intended to be subclassed.
+ *
+ * @deprecated - use {@link org.eclipse.wst.sse.core.StructuredModelManager} instead
+ */
+final public class StructuredModelManager {
+ /**
+ * Do not allow instances to be created.
+ */
+ private StructuredModelManager() {
+ super();
+ }
+
+ /**
+ * Provides access to the instance of IModelManager. Returns null if model
+ * manager can not be created or is not valid (such as, when workbench is
+ * shutting down).
+ *
+ * @return IModelManager - returns the one model manager for structured
+ * model
+ * @deprecated - use the one that is in
+ * org.eclipse.wst.sse.core.StructuredModelManager
+ */
+ public static IModelManager getModelManager() {
+ boolean isReady = false;
+ IModelManager modelManager = null;
+ while (!isReady) {
+ Bundle localBundle = Platform.getBundle(SSECorePlugin.ID);
+ int state = localBundle.getState();
+ if (state == Bundle.ACTIVE) {
+ isReady = true;
+ // getInstance is a synchronized static method.
+ modelManager = ModelManagerImpl.getInstance();
+ }
+ else if (state == Bundle.STARTING) {
+ try {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e) {
+ // ignore, just loop again
+ }
+ }
+ else if (state == Bundle.STOPPING || state == Bundle.UNINSTALLED) {
+ isReady = true;
+ modelManager = null;
+ }
+ else {
+ // not sure about other states, 'resolved', 'installed'
+ isReady = true;
+ modelManager = null;
+ }
+ }
+ return modelManager;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IEncodedDocument.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IEncodedDocument.java
new file mode 100644
index 0000000000..2d64715749
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IEncodedDocument.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.document;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+
+/**
+ * This interface is strictly to define important "document properties" not
+ * found in IDocument, but not central to "StructuredDocument".
+ *
+ * Its not to be be implmented by clients.
+ *
+ * @plannedfor 1.0
+ */
+
+public interface IEncodedDocument extends IDocument {
+
+ /**
+ * Returns the encoding memento for this document.
+ *
+ * @return the encoding memento for this document.
+ */
+ EncodingMemento getEncodingMemento();
+
+ /**
+ * Returns the preferred line delimiter for this document.
+ */
+ String getPreferredLineDelimiter();
+
+ /**
+ * Sets the encoding memento for this document.
+ *
+ * Is not to be called by clients, only document creation classes.
+ *
+ * @param localEncodingMemento
+ */
+ void setEncodingMemento(EncodingMemento localEncodingMemento);
+
+ /**
+ * Sets the preferredLineDelimiter. Is not to be called by clients, only
+ * document creation classes.
+ *
+ * @param probableLineDelimiter
+ */
+ void setPreferredLineDelimiter(String probableLineDelimiter);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IStructuredDocumentProposed.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IStructuredDocumentProposed.java
new file mode 100644
index 0000000000..0c0f47d18f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/document/IStructuredDocumentProposed.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.document;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.Position;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+
+
+/**
+ * A IStructuredDocument is a collection of StructuredDocumentRegions. It's
+ * often called a "flat model" because its does contain some structural
+ * information, but not very much, usually, at most, a few levels of
+ * containment.
+ *
+ * Clients should not implement.
+ *
+ * @deprecated - was never used
+ */
+public interface IStructuredDocumentProposed extends IDocument, IDocumentExtension, IAdaptable {
+
+ /**
+ * The document changing listeners receives the same events as the
+ * document listeners, but the difference is the timing and
+ * synchronization of data changes and notifications.
+ */
+ void addDocumentChangingListener(IDocumentListener listener);
+
+ /**
+ * this API ensures that any portion of the document within startOff to
+ * length is not readonly (that is, that its editable). Note that if the
+ * range overlaps with other readonly regions, those other readonly
+ * regions will be adjusted.
+ *
+ * @param startOffset
+ * @param length
+ */
+ void clearReadOnly(int startOffset, int length);
+
+ /**
+ * returns true if any portion of startOffset to length is readonly
+ *
+ * @param startOffset
+ * @param length
+ * @return
+ */
+ boolean containsReadOnly(int startOffset, int length);
+
+ /**
+ * Returns the region contained by offset.
+ *
+ * @param offset
+ * @return
+ */
+ IStructuredDocumentRegion getRegionAtCharacterOffset(int offset);
+
+ /**
+ * Resturns a list of the structured document regions.
+ *
+ * Note: possibly expensive call, not to be used casually.
+ *
+ * @return a list of the structured document regions.
+ */
+ IStructuredDocumentRegionList getRegionList();
+
+
+ /**
+ * Returns the text of this document.
+ *
+ * Same as 'get' in super class, added for descriptiveness.
+ *
+ * @return the text of this document.
+ */
+ String getText();
+
+ /**
+ * causes that portion of the document from startOffset to length to be
+ * marked as readonly. Note that if this range overlaps with some other
+ * region with is readonly, the regions are effectivly combined.
+ *
+ * @param startOffset
+ * @param length
+ */
+ void makeReadOnly(int startOffset, int length);
+
+ /**
+ * newInstance is similar to clone, except it contains no data. One
+ * important thing to duplicate is the parser, with the parser correctly
+ * "cloned", including its tokeninzer, block tags, etc.
+ *
+ * NOTE: even after obtaining a 'newInstance' the client may have to do
+ * some initialization, for example, it may need to add its own model
+ * listeners. Or, as another example, if the IStructuredDocument has a
+ * parser of type StructuredDocumentRegionParser, then the client may need
+ * to add its own StructuredDocumentRegionHandler to that parser, if it is
+ * in fact needed.
+ */
+ IStructuredDocumentProposed newInstance();
+
+ /**
+ * The document changing listeners receives the same events as the
+ * document listeners, but the difference is the timing and
+ * synchronization of data changes and notifications.
+ */
+ void removeDocumentChangingListener(IDocumentListener listener);
+
+ /**
+ * One of the APIs to manipulate the IStructuredDocument.
+ *
+ * replaceText replaces the text from oldStart to oldEnd with the new text
+ * found in the requestedChange string. If oldStart and oldEnd are equal,
+ * it is an insertion request. If requestedChange is null (or empty) it is
+ * a delete request. Otherwise it is a replace request.
+ *
+ * Similar to 'replace' in super class.
+ */
+ StructuredDocumentEvent replaceText(Object requester, int oldStart, int replacementLength, String requestedChange);
+
+ /**
+ * Note, same as replaceText API, but will allow readonly areas to be
+ * replaced. This method is not to be called by clients, only
+ * infrastructure. For example, one case where its ok is with undo
+ * operations (since, presumably, if user just did something that
+ * happended to involve some inserting readonly text, they should normally
+ * be allowed to still undo that operation. There might be other cases
+ * where its used to give the user a choice, e.g. "you are about to
+ * overwrite read only portions, do you want to continue".
+ */
+ StructuredDocumentEvent overrideReadOnlyreplaceText(Object requester, int oldStart, int replacementLength, String requestedChange);
+
+ /**
+ * One of the APIs to manipulate the IStructuredDocument in terms of Text.
+ *
+ * The setText method replaces all text in the model.
+ *
+ * @param requester -
+ * the object requesting the document be created.
+ * @param allText -
+ * all the text of the document.
+ * @return NewDocumentEvent - besides causing this event to be sent to
+ * document listeners, the event is returned.
+ */
+ NewDocumentEvent setText(Object requester, String allText);
+
+ /**
+ * Returns the encoding memento for this document.
+ *
+ * @return the encoding memento for this document.
+ */
+ EncodingMemento getEncodingMemento();
+
+ /**
+ * Returns the line delimiter detected when this document was read from
+ * storage.
+ *
+ * @return line delimiter detected when this document was read from
+ * storage.
+ */
+ String getDetectedLineDelimiter();
+
+ /**
+ * Sets the encoding memento for this document.
+ *
+ * Is not to be called by clients, only document creation classes.
+ *
+ * @param localEncodingMemento
+ */
+ void setEncodingMemento(EncodingMemento localEncodingMemento);
+
+ /**
+ * Sets the detected line delimiter when the document was read. Is not to
+ * be called by clients, only document creation classes.
+ *
+ * @param probableLineDelimiter
+ */
+ void setDetectedLineDelimiter(String probableLineDelimiter);
+
+ /**
+ * This function provides a way for clients to compare a string with a
+ * region of the documnet, without having to retrieve (create) a string
+ * from the document, thus more efficient of lots of comparisons being
+ * done.
+ *
+ * @param ignoreCase -
+ * if true the characters are compared based on identity. If
+ * false, the string are compared with case accounted for.
+ * @param testString -
+ * the string to compare.
+ * @param documentOffset -
+ * the document in the offset to start the comparison.
+ * @param length -
+ * the length in the document to compare (Note: technically,
+ * clients could just provide the string, and we could infer
+ * the length from the string supplied, but this leaves every
+ * client to correctly not even ask us if the the string length
+ * doesn't match the expected length, so this is an effort to
+ * maximize performance with correct code.
+ * @return true if matches, false otherwise.
+ */
+ boolean stringMatches(boolean ignoreCase, String testString, int documentOffset, int length);
+
+ Position createPosition(int offset, String category, String type);
+
+ Position createPosition(int offset, int length, String category, String type);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/AboutToBeChangedEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/AboutToBeChangedEvent.java
new file mode 100644
index 0000000000..e549692688
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/AboutToBeChangedEvent.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+/**
+ * This event is send to structured document listeners. It is perfectly
+ * analagous to its corresponding jface DocumentEvent and is provided simply
+ * to allow clients to distinguish the source of the event.
+ *
+ * @plannedfor 1.0
+ */
+public class AboutToBeChangedEvent extends StructuredDocumentEvent {
+
+
+ /**
+ * Creates an instance of this event.
+ *
+ * @param document
+ * document involved in the change
+ * @param originalRequester
+ * source of original request
+ * @param changes
+ * the text changes
+ * @param offset
+ * offset of request
+ * @param lengthToReplace
+ * amount, if any, of replaced text
+ */
+ public AboutToBeChangedEvent(IStructuredDocument document, Object originalRequester, String changes, int offset, int lengthToReplace) {
+ super(document, originalRequester, changes, offset, lengthToReplace);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IModelAboutToBeChangedListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IModelAboutToBeChangedListener.java
new file mode 100644
index 0000000000..df44547d51
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IModelAboutToBeChangedListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+
+
+/**
+ * Clients can implement this interface, and register with the
+ * structuredDocument using addAboutToBeChangedListner to be notified that the
+ * structuredDocument is about to be changed, but hasn't been changed at the
+ * time this event is fired.
+ */
+public interface IModelAboutToBeChangedListener {
+
+ void modelAboutToBeChanged(AboutToBeChangedEvent structuredDocumentEvent);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IStructuredDocumentListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IStructuredDocumentListener.java
new file mode 100644
index 0000000000..26535af336
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/IStructuredDocumentListener.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+/**
+ * @deprecated will be removed since we now subclass DocumentEvent.
+ */
+
+public interface IStructuredDocumentListener {
+
+ public void newModel(NewDocumentEvent structuredDocumentEvent);
+
+ public void noChange(NoChangeEvent structuredDocumentEvent);
+
+ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent);
+
+ public void regionChanged(RegionChangedEvent structuredDocumentEvent);
+
+ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentContentEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentContentEvent.java
new file mode 100644
index 0000000000..038db7707e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentContentEvent.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+/**
+ * The NewDocumentContentEvent is fired when an instance of a
+ * IStructuredDocument replaces all of its text.
+ *
+ * ISSUE: not currently used, but there's still some efficiencies to be had so
+ * plan to implement.
+ *
+ * @plannedfor 1.0
+ */
+public class NewDocumentContentEvent extends NewDocumentEvent {
+ /**
+ * Creates an instance of this event.
+ *
+ * @param document
+ * the document being changed
+ * @param originalRequester
+ * the original requester of the change
+ */
+ public NewDocumentContentEvent(IStructuredDocument document, Object originalRequester) {
+ super(document, originalRequester);
+ }
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentEvent.java
new file mode 100644
index 0000000000..fe9a73ae0d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NewDocumentEvent.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+/**
+ * The NewDocumentEvent is fired when an instance of a IStructuredDocument
+ * sets or replaces all of its text.
+ *
+ * ISSUE: need to change so this is used for 'set' only.
+ *
+ * @plannedfor 1.0
+ */
+public class NewDocumentEvent extends StructuredDocumentEvent {
+
+
+ /**
+ * Creates a new instance of the NewDocumentEvent.
+ *
+ * @param document
+ * being changed
+ * @param originalRequester
+ * source of request
+ */
+ public NewDocumentEvent(IStructuredDocument document, Object originalRequester) {
+ super(document, originalRequester);
+ }
+
+ /**
+ * This returns the length of the new document.
+ *
+ * @return int returns the length of the new document.
+ */
+ public int getLength() {
+ return getStructuredDocument().getLength();
+ }
+
+ /**
+ * This doesn't mean quite the same thing as the IStructuredDocument
+ * Events in the super class. It always will return zero.
+ *
+ * @return int for a newDocument, the offset of is always 0
+ */
+ public int getOffset() {
+ return 0;
+ }
+
+ /**
+ * For a new document, the text involved is the complete text.
+ *
+ * @return String the text that is the complete text of the documnet.
+ */
+ public String getText() {
+ String results = getStructuredDocument().getText();
+ return results;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NoChangeEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NoChangeEvent.java
new file mode 100644
index 0000000000..d5004bcdc9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/NoChangeEvent.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+/**
+ * This event is sent if, after analysis, it is found there is no reason to
+ * change the structuredDocument. This might occur, for example, if someone
+ * pastes in the exact same text that they are replacing, or if someone tries
+ * to change a read-only region.
+ *
+ * This might be important, for example, if some state variables are set on an
+ * "about the change" event. then if there is no change (i.e.no other event is
+ * fired), those state variables could reset, or whatever, upon receiving this
+ * event.
+ *
+ * @plannedfor 1.0
+ */
+public class NoChangeEvent extends StructuredDocumentEvent {
+ /**
+ * NO_CONTENT_CHANGE means that a some text was requested to be replaced
+ * with identical text, so no change is actually done.
+ */
+ public final static int NO_CONTENT_CHANGE = 2;
+ /**
+ * NO_EVENT is used in rare error conditions, when, basically, a request
+ * to change the document is made before the previous request has
+ * completed. This event to used so aboutToChange/Changed cycles can
+ * complete as required, but the document is most likely not modified as
+ * expected.
+ */
+ public final static int NO_EVENT = 8;
+ /**
+ * READ_ONLY_STATE_CHANGE means that the "read only" state of the text was
+ * changed, not the content itself.
+ */
+ public final static int READ_ONLY_STATE_CHANGE = 4;
+
+ /**
+ * set to one of the above detailed reasons for why no change was done.
+ */
+ public int reason = 0;
+
+ /**
+ * NoChangeEvent constructor. This event can occur if there was a request
+ * to modify a document or its properties, but there as not really is no
+ * change to a document's content.
+ *
+ * @param source
+ * @param originalSource
+ * java.lang.Object
+ * @param changes
+ * java.lang.String
+ * @param offset
+ * int
+ * @param lengthToReplace
+ * int
+ */
+ public NoChangeEvent(IStructuredDocument source, Object originalSource, String changes, int offset, int lengthToReplace) {
+ super(source, originalSource, changes, offset, lengthToReplace);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionChangedEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionChangedEvent.java
new file mode 100644
index 0000000000..29d4381ef6
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionChangedEvent.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+
+/**
+ * This event is used when a document region changes in a non-structural way.
+ * Non-structural, that is, as far as the IStructuredDocument is concerned.
+ * The whole region, along with the new text is sent, just in case a listener
+ * (e.g. a tree model) might make its own determination of what to do, and
+ * needs the whole region to act appropriately.
+ *
+ * Note: users should not make assumptions about whether the region is
+ * literally the same instance or not -- it is currently a different instance
+ * that is identical to the old except for the changed region, but this
+ * implementation may change.
+ *
+ * @plannedfor 1.0
+ */
+public class RegionChangedEvent extends StructuredDocumentEvent {
+ private ITextRegion fChangedRegion;
+ private IStructuredDocumentRegion fStructuredDocumentRegion;
+
+ /**
+ * Creates instance of a RegionChangedEvent.
+ *
+ * @param document
+ * the document being changed.
+ * @param originalRequester
+ * the object making the request for the change.
+ * @param structuredDocumentRegion
+ * the containing region
+ * @param changedRegion
+ * the region that has changed.
+ * @param changes
+ * the string representing the change.
+ * @param offset
+ * the offset of the change.
+ * @param lengthToReplace
+ * the length specified to be replaced.
+ */
+ public RegionChangedEvent(IStructuredDocument document, Object originalRequester, IStructuredDocumentRegion structuredDocumentRegion, ITextRegion changedRegion, String changes, int offset, int lengthToReplace) {
+ super(document, originalRequester, changes, offset, lengthToReplace);
+ fStructuredDocumentRegion = structuredDocumentRegion;
+ fChangedRegion = changedRegion;
+ }
+
+
+ /**
+ * Returns the text region changed.
+ *
+ * @return the text region changed
+ */
+ public ITextRegion getRegion() {
+ return fChangedRegion;
+ }
+
+
+ /**
+ * Returns the document region changed.
+ *
+ * @return the document region changed
+ */
+ public IStructuredDocumentRegion getStructuredDocumentRegion() {
+ return fStructuredDocumentRegion;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionsReplacedEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionsReplacedEvent.java
new file mode 100644
index 0000000000..21a2e4f76a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/RegionsReplacedEvent.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.events;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+
+/**
+ * This event is used when a node's regions change, but the document region
+ * itself doesn't. This says nothing about the semantics of the document
+ * region, that may still have changed. Also, its assumed/required that all
+ * the regions are replaced (even those that may not have changed).
+ *
+ * @plannedfor 1.0
+ */
+public class RegionsReplacedEvent extends StructuredDocumentEvent {
+ private ITextRegionList fNewRegions;
+ private ITextRegionList fOldRegions;
+ private IStructuredDocumentRegion fStructuredDocumentRegion;
+
+ /**
+ * Creates an instance of the RegionsReplacedEvent.
+ *
+ * @param document -
+ * document being changed.
+ * @param originalRequester -
+ * requester of the change.
+ * @param structuredDocumentRegion -
+ * the region containing the change.
+ * @param oldRegions -
+ * the old Regions being replaced.
+ * @param newRegions -
+ * the new regions being added.
+ * @param changes -
+ * the String representing the change.
+ * @param offset -
+ * the offset of the change.
+ * @param lengthToReplace -
+ * the length of text to replace.
+ */
+ public RegionsReplacedEvent(IStructuredDocument document, Object originalRequester, IStructuredDocumentRegion structuredDocumentRegion, ITextRegionList oldRegions, ITextRegionList newRegions, String changes, int offset, int lengthToReplace) {
+ super(document, originalRequester, changes, offset, lengthToReplace);
+ fStructuredDocumentRegion = structuredDocumentRegion;
+ fOldRegions = oldRegions;
+ fNewRegions = newRegions;
+ }
+
+ /**
+ * Returns the new text regions.
+ *
+ * @return the new text regions.
+ */
+ public ITextRegionList getNewRegions() {
+ return fNewRegions;
+ }
+
+ /**
+ * Returns the old text regions.
+ *
+ * @return the old text regions.
+ */
+ public ITextRegionList getOldRegions() {
+ return fOldRegions;
+ }
+
+ /**
+ * Returns the structured document region.
+ *
+ * @return the structured document region.
+ */
+ public IStructuredDocumentRegion getStructuredDocumentRegion() {
+ return fStructuredDocumentRegion;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentEvent.java
new file mode 100644
index 0000000000..e3930ff991
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentEvent.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2007 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.provisional.events;
+
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+
+/**
+ * IStructuredDocument events are generated by the IStructuredDocument, after
+ * the IStructuredDocument acts on a request. Not intended to be instantiated,
+ * except by subclasses in infrastructure. Not intended to be subclassed by
+ * clients.
+ *
+ * @plannedfor 1.0
+ */
+public abstract class StructuredDocumentEvent extends DocumentEvent {
+ private String fDeletedText;
+ private Object fOriginalRequester;
+
+ /**
+ * There is no public null-arg version of this constructor.
+ */
+ private StructuredDocumentEvent() {
+ super();
+ }
+
+ /**
+ * We assume (and require) that an IStructuredDocument's are always the
+ * source of StructuredDocument events.
+ *
+ * @param document -
+ * the document being changed
+ */
+ private StructuredDocumentEvent(IStructuredDocument document) {
+ this();
+ if (document == null)
+ throw new IllegalArgumentException("null source"); //$NON-NLS-1$
+ fDocument = document;
+ fOriginalRequester = document;
+ }
+
+ /**
+ * We assume (and require) that an IStructuredDocument's are always the
+ * source of StructuredDocument events.
+ *
+ * @param document -
+ * the document being changed.
+ * @param originalRequester -
+ * the original requester of the change.
+ */
+ StructuredDocumentEvent(IStructuredDocument document, Object originalRequester) {
+ this(document);
+ fOriginalRequester = originalRequester;
+ }
+
+ /**
+ * We assume (and require) that an IStructuredDocument's are always the
+ * source of StructuredDocument events.
+ *
+ * @param document -
+ * the document being changed.
+ * @param originalRequester -
+ * the requester of the change.
+ * @param changes -
+ * the String representing the new text
+ * @param offset -
+ * the offset of the change.
+ * @param lengthToReplace -
+ * the length of text to replace.
+ */
+ StructuredDocumentEvent(IStructuredDocument document, Object originalRequester, String changes, int offset, int lengthToReplace) {
+ this(document);
+ fOriginalRequester = originalRequester;
+ fText = changes;
+ fOffset = offset;
+ fLength = lengthToReplace;
+ }
+
+ /**
+ * Provides the text that is being deleted.
+ *
+ * @return the text that is being deleted, or null if none is being
+ * deleted.
+ */
+ public String getDeletedText() {
+ return fDeletedText;
+ }
+
+ /**
+ * This method returns the object that originally caused the event to
+ * fire. This is typically not the object that created the event (the
+ * IStructuredDocument) but instead the object that made a request to the
+ * IStructuredDocument.
+ *
+ * @return the object that made the request to the document
+ */
+ public Object getOriginalRequester() {
+ return fOriginalRequester;
+ }
+
+ /**
+ * This method is equivalent to 'getDocument' except it returns an object
+ * of the appropriate type (namely, a IStructuredDocument, instead of
+ * IDocument).
+ *
+ * @return IStructuredDocumnt - the document being changed
+ */
+ public IStructuredDocument getStructuredDocument() {
+ // a safe case, since constructor can only be called with a
+ // IStructuredDocument
+ return (IStructuredDocument) fDocument;
+ }
+
+ /**
+ * Not to be called by clients, only parsers and reparsers. (will
+ * eventually be moved to an SPI package).
+ *
+ * @param newDeletedText -
+ * the text that has been deleted.
+ */
+ public void setDeletedText(String newDeletedText) {
+ fDeletedText = newDeletedText;
+ }
+
+ /**
+ * for debugging only
+ *
+ * @deprecated - need to fix unit tests which depend on this exact format,
+ * then delete this
+ */
+ public String toString() {
+ // return getClass().getName() + "[source=" + source + "]";
+ return getClass().getName();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentRegionsReplacedEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentRegionsReplacedEvent.java
new file mode 100644
index 0000000000..8f08b88887
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/events/StructuredDocumentRegionsReplacedEvent.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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.provisional.events;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+
+/**
+ * This event is used when a IStructuredDocumentRegion is deleted, or replaced
+ * with more than one IStructuredDocumentRegion, or when simply more than one
+ * IStructuredDocumentRegion changes.
+ *
+ * @plannedfor 1.0
+ */
+public class StructuredDocumentRegionsReplacedEvent extends StructuredDocumentEvent {
+
+ private IStructuredDocumentRegionList fNewStructuredDocumentRegions;
+ private IStructuredDocumentRegionList fOldStructuredDocumentRegions;
+
+ private boolean fIsEntireDocumentReplaced;
+
+ /**
+ * Creates an instance of StructuredDocumentRegionsReplacedEvent
+ *
+ * @param document -
+ * the document being changed.
+ * @param originalRequester -
+ * the requester of the change.
+ * @param oldStructuredDocumentRegions -
+ * the old document regions removed.
+ * @param newStructuredDocumentRegions -
+ * the new document regions added.
+ * @param changes -
+ * a string representing the text change.
+ * @param offset -
+ * the offset of the change.
+ * @param lengthToReplace -
+ * the length of text requested to be replaced.
+ */
+ public StructuredDocumentRegionsReplacedEvent(IStructuredDocument document, Object originalRequester, IStructuredDocumentRegionList oldStructuredDocumentRegions, IStructuredDocumentRegionList newStructuredDocumentRegions, String changes, int offset, int lengthToReplace) {
+ super(document, originalRequester, changes, offset, lengthToReplace);
+ fOldStructuredDocumentRegions = oldStructuredDocumentRegions;
+ fNewStructuredDocumentRegions = newStructuredDocumentRegions;
+ }
+
+ public StructuredDocumentRegionsReplacedEvent(IStructuredDocument document, Object originalRequester, IStructuredDocumentRegionList oldStructuredDocumentRegions, IStructuredDocumentRegionList newStructuredDocumentRegions, String changes, int offset, int lengthToReplace, boolean entireDocumentReplaced) {
+ this(document, originalRequester, oldStructuredDocumentRegions, newStructuredDocumentRegions, changes, offset, lengthToReplace);
+ fIsEntireDocumentReplaced = entireDocumentReplaced;
+ }
+
+ /**
+ * Returns the new structured document regions.
+ *
+ * @return the new structured document regions.
+ */
+ public IStructuredDocumentRegionList getNewStructuredDocumentRegions() {
+ return fNewStructuredDocumentRegions;
+ }
+
+ /**
+ * Returns the old structured document regions.
+ *
+ * @return the old structured document regions.
+ */
+ public IStructuredDocumentRegionList getOldStructuredDocumentRegions() {
+ return fOldStructuredDocumentRegions;
+ }
+
+ public boolean isEntireDocumentReplaced() {
+ return fIsEntireDocumentReplaced;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceAlreadyExists.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceAlreadyExists.java
new file mode 100644
index 0000000000..79ff0e67de
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceAlreadyExists.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.provisional.exceptions;
+
+
+
+/**
+ * Indicates that a Resource which a model or document was expected to create
+ * already exists.
+ */
+public class ResourceAlreadyExists extends Exception {
+
+ /**
+ * Comment for <code>serialVersionUID</code>
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ResourceAlreadyExists constructor comment.
+ */
+ public ResourceAlreadyExists() {
+ super();
+ }
+
+ /**
+ * ResourceAlreadyExists constructor comment.
+ *
+ * @param s
+ * java.lang.String
+ */
+ public ResourceAlreadyExists(String s) {
+ super(s);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceInUse.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceInUse.java
new file mode 100644
index 0000000000..5ecbfad60e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/exceptions/ResourceInUse.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.provisional.exceptions;
+
+
+
+/**
+ * Indicates that a model with a particular ID already exists
+ */
+public class ResourceInUse extends Exception {
+
+ /**
+ * Comment for <code>serialVersionUID</code>
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ResourceAlreadyExists constructor comment.
+ */
+ public ResourceInUse() {
+ super();
+ }
+
+ /**
+ * ResourceAlreadyExists constructor comment.
+ *
+ * @param s
+ * java.lang.String
+ */
+ public ResourceInUse(String s) {
+ super(s);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelManagerProposed.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelManagerProposed.java
new file mode 100644
index 0000000000..ace9420a04
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelManagerProposed.java
@@ -0,0 +1,338 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional.model;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+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;
+
+/**
+ * Responsible for managing IStructuredModels.
+ *
+ * This allows clients to share models, so they do not have to be re-created
+ * or passed around from one client to another. Clients should internally
+ * enforce that models are gotten and released from locations close to each
+ * other to prevent model leaks.
+ *
+ * There are three ways to get a model based on usage and responsibility: for
+ * 'MODIFY', just for 'SHARED', and 'UNSHARED'. Contrary to their names, a
+ * model can technically be modified, and all modifications directly affect
+ * the commonly shared version of the model. It is part of the API contract,
+ * however, that clients who get a model for SHARED do not modify it. The
+ * significance of the 'MODIFY' model is that the client is registering their
+ * interest in saving changes to the model.
+ *
+ * Clients can reference this interface, but should not implement.
+ *
+ * @see org.eclipse.wst.sse.core.StructuredModelManager
+ * @plannedfor 1.0
+ */
+public interface IModelManagerProposed {
+
+ /**
+ * AccessType is used internally as a JRE 1.4 compatible enumerated type.
+ * Not intended to be referenced by clients.
+ */
+ static class AccessType {
+ private String fType;
+
+ /**
+ * default access contructor we use to create our specific constants.
+ *
+ * @param type
+ */
+ AccessType(String type) {
+ super();
+ fType = type;
+ }
+
+ /**
+ * For debug purposes only.
+ */
+ public String toString() {
+ return "Model Access Type: " + fType; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Constant to provide compile time safe parameter. <code>NOTSHARED</code>signifies
+ * the client intentially wants a model that is not shared with other
+ * clients. NOTSHARED models can not be saved.
+ */
+ final AccessType NOTSHARED = new AccessType("NOTSHARED"); //$NON-NLS-1$
+
+ /**
+ * Constant to provide compile-time safe parameter. <code>SHARED</code>signifies
+ * the client is not intending to make changes and does not care whether
+ * the model accessed is saved.
+ */
+ final AccessType SHARED = new AccessType("SHARED"); //$NON-NLS-1$
+
+ /**
+ * Constant to provide compile-time safe parameter. <code>MODIFY</code>signifies
+ * the client is intending to make changes and is responsible for saving
+ * changes (or not) if they are the last one holding MODIFY access to the
+ * model before it's released.
+ */
+ final AccessType MODIFY = new AccessType("MODIFY"); //$NON-NLS-1$
+
+ /**
+ * copyModel is similar to a deep clone. The resulting model is shared,
+ * according to the value of ReadEditType. If a model already is being
+ * managed for 'newLocation' then a ResourceInUse exception is thrown,
+ * unless the ReadEditType is NOTSHARED, in which case the resulting model
+ * can not be saved.
+ *
+ * @param oldLocation
+ * @param newLocation
+ * @param type
+ * @return
+ * @throws ResourceInUse
+ *
+ * ISSUE: is this important enough to be API, or can clients solve
+ * themselves
+ */
+ IStructuredModel copyModel(IPath oldLocation, IPath newLocation, AccessType type) throws ResourceInUse;
+
+ /**
+ * createNewInstance is similar to clone, except the new instance has no
+ * text content. Note: this produces an UNSHARED model, for temporary use,
+ * that has the same characteristics as original model. If a true shared
+ * model is desired, use "copy".
+ *
+ * ISSUE: still needed?
+ *
+ * @param requester
+ * @param model
+ * @return
+ * @throws IOException
+ */
+ public IStructuredModel createNewInstance(Object requester, IStructuredModel model) throws IOException;
+
+ /**
+ * Factory method, since a proper IStructuredDocument must have a proper
+ * parser assigned. Note: its assume that IPath 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.
+ *
+ * ISSUE: do we want to support this via model manager, or else where?
+ * ISSUE: do we need two of these? What's legacy use case?
+ *
+ * @param location
+ * @param progressMonitor
+ * @return
+ * @throws ResourceAlreadyExists
+ * @throws IOException
+ * @throws CoreException
+ */
+ IStructuredDocument createNewStructuredDocumentFor(IPath location, IProgressMonitor progressMonitor) throws ResourceAlreadyExists, IOException, CoreException;
+
+ /**
+ * Factory method, since a proper IStructuredDocument must have a proper
+ * parser assigned. Note: clients should verify that the resource
+ * identified by the IPath exists before using this method. If this IFile
+ * does not exist, then createNewStructuredDocument is the correct API to
+ * use.
+ *
+ * ISSUE: do we want to support this via model manager, or else where?
+ * ISSUE: do we need two of these? What's legacy use case?
+ *
+ * @param location
+ * @param progressMonitor
+ * @return
+ * @throws IOException
+ * @throws CoreException
+ */
+ IStructuredDocument createStructuredDocumentFor(IPath location, IProgressMonitor progressMonitor) throws IOException, CoreException;
+
+ /**
+ * Note: callers of this method must still release the model when
+ * finished. Returns the model for this document if it already exists and
+ * is being shared. Returns null if this is not the case. The ReadEditType
+ * must be either MODIFY or SHARED.
+ *
+ * ISSUE: should we accept IDocument on parameter for future evolution,
+ * and constrain to StructuredDocuments at runtime?
+ *
+ * @param requester
+ * @param type
+ * @param document
+ * @return
+ */
+ IStructuredModel getExistingModel(Object requester, AccessType type, IDocument document);
+
+ /**
+ * Callers of this method must still release the model when finished.
+ * Returns the model for this location if it already exists and is being
+ * shared. Returns null if this is not the case. The ReadEditType must be
+ * either MODIFY or SHARED.
+ *
+ * @param requester
+ * @param type
+ * @param location
+ * @return
+ */
+ public IStructuredModel getExistingModel(Object requester, AccessType type, IPath location);
+
+ /**
+ * Returns the model that has the specified document as its structured
+ * document.
+ *
+ * @param requester
+ * @param type
+ * @param progressMonitor
+ * @param document
+ * @return
+ */
+ public IStructuredModel getModel(Object requester, AccessType type, IProgressMonitor progressMonitor, IDocument document);
+
+ /**
+ * Returns the model based on the content at the specified location.
+ *
+ * Note: if the ModelManager does not know how to create a model for
+ * such a file for content, null is return.
+ * ISSUE: should we throw some special, meaningful, checked
+ * exception instead?
+ *
+ * @param requester
+ * @param type
+ * @param progressMonitor
+ * @param location
+ * @return
+ * @throws IOException
+ * @throws CoreException
+ */
+ public IStructuredModel getModel(Object requester, AccessType type, IProgressMonitor progressMonitor, IPath location) throws IOException, CoreException;
+
+ /**
+ * This method will not create a new model if it already exists ... if
+ * force is false. The idea is that a client should call this method once
+ * with force set to false. If the exception is thrown, then prompt client
+ * if they want to overwrite.
+ *
+ * @param requester
+ * @param location
+ * @param force
+ * @param type
+ * @param progressMonitor
+ * @return
+ * @throws ResourceAlreadyExists
+ * @throws ResourceInUse
+ * @throws IOException
+ * @throws CoreException
+ */
+ IStructuredModel getNewModel(Object requester, IPath location, boolean force, AccessType type, IProgressMonitor progressMonitor) throws ResourceAlreadyExists, ResourceInUse, IOException, CoreException;
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model.
+ *
+ * @param location
+ * @return
+ */
+ boolean isShared(IPath location);
+
+ /**
+ * This function returns true if there are other references to the
+ * underlying model, shared in the specified way. The ReadEditType must be
+ * either MODIFY or SHARED.
+ *
+ * @param location
+ * @param type
+ * @return
+ */
+ boolean isShared(IPath location, AccessType type);
+
+ /**
+ * This method can be called when the content type of a model changes. Its
+ * assumed the contentType has already been changed, and this method uses
+ * the text of the old one, to repopulate the text of the new one.
+ *
+ * @param model
+ * @return
+ * @throws IOException
+ */
+ IStructuredModel reinitialize(IStructuredModel model) throws IOException;
+
+
+ /**
+ * This is used by clients to signify that they are finished with a model
+ * and will no longer access it or any of its underlying data (such as its
+ * structured document). The ReadEditType must match what ever the client
+ * used in the corresponding 'get' method.
+ *
+ * This method must be called for every 'get'. Clients should use the
+ * try/finally pattern to ensure the release is called even if there is an
+ * unexpected exception.
+ *
+ * @param requester
+ * @param structuredModel
+ * @param type
+ */
+ void releaseModel(Object requester, IStructuredModel structuredModel, AccessType type);
+
+ /**
+ * Writes the underlying document to the IPath.
+ *
+ * ISSUE: we want to just "dump" contents to location, but need to spec.
+ * all the different cases of shared, etc.
+ *
+ * ?If to same location as 'get', then same instance of model, If to
+ * different location (esp. if shared) then need to leave old instance of
+ * model, create new model, and save to new location. ?
+ *
+ * Cases: IPath is null, write to IPath created with IPath specificed and
+ * equals IPath created with, write to IPath IPath specified and not
+ * equals IPath created with, dumps to new IPath, no change in current
+ * model state.
+ *
+ * ISSUE: think through 'normalization' cases
+ *
+ *
+ * @param structuredModel
+ * @param location - already normalized?
+ * @param progressMonitor
+ * @throws UnsupportedEncodingException
+ * @throws IOException
+ * @throws CoreException
+ *
+ * ISSUE: resource aleady exists? veto override
+ */
+ void saveModel(IStructuredModel structuredModel, IPath location, IProgressMonitor progressMonitor) throws UnsupportedEncodingException, IOException, CoreException;
+
+ /**
+ * Writes the underlying document to the IPath if the model is only shared
+ * for EDIT by one client. This is the recommended way for 'background
+ * jobs' to save models, in case the model is being shared by an editor,
+ * or other client that might desire user intervention to save a resource.
+ *
+ * @param structuredModel
+ * @param location - already normalized?
+ * @param progressMonitor
+ * @throws UnsupportedEncodingException
+ * @throws IOException
+ * @throws CoreException
+ *
+ * ISSUE: is locaiton needed in this case, or just use the one it was
+ * created with
+ */
+ void saveModelIfNotShared(IStructuredModel structuredModel, IPath location, IProgressMonitor progressMonitor) throws UnsupportedEncodingException, IOException, CoreException;
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelStateListenerProposed.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelStateListenerProposed.java
new file mode 100644
index 0000000000..484fc70496
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IModelStateListenerProposed.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.model;
+
+
+/**
+ * Interface for those wanting to listen to a model's state changing.
+ *
+ * @plannedfor 1.0
+ */
+public interface IModelStateListenerProposed {
+
+ /**
+ * A model is about to be changed. The event object itself signifies which
+ * model, and any pertinent information.
+ */
+ void modelAboutToBeChanged(IStructuredModelEvent event);
+
+ /**
+ * Signals that the changes foretold by modelAboutToBeChanged have been
+ * made. A typical use might be to refresh, or to resume processing that
+ * was suspended as a result of modelAboutToBeChanged.
+ */
+ void modelChanged(IStructuredModelEvent event);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/INodeAdapterFactoryManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/INodeAdapterFactoryManager.java
new file mode 100644
index 0000000000..6c2d165167
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/INodeAdapterFactoryManager.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.wst.sse.core.internal.provisional.model;
+
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory;
+/**
+ * Provides a means for clients to register IAdapterFactories for use
+ * by infrastructure when StructuredModels are created.
+ */
+public interface INodeAdapterFactoryManager {
+
+ /**
+ *
+ * @param factory
+ * @param contentType
+ */
+ void addAdapterFactory(INodeAdapterFactory factory, IContentType contentType);
+
+ void removeAdapterFactory(INodeAdapterFactory factory, IContentType contentType);
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelEvent.java
new file mode 100644
index 0000000000..73bc3bef54
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelEvent.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+
+package org.eclipse.wst.sse.core.internal.provisional.model;
+
+public interface IStructuredModelEvent {
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelProposed.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelProposed.java
new file mode 100644
index 0000000000..7d984daed2
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/model/IStructuredModelProposed.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional.model;
+
+import java.io.IOException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.content.IContentType;
+import org.eclipse.wst.sse.core.internal.model.FactoryRegistry;
+import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+
+/**
+ * IStructuredModel's are mainly interesting by their extensions and
+ * implementers. The main purposed of this abstraction it to provide a common
+ * way to manage models that have an associated structured documnet.
+ *
+ * @plannedfor 1.0
+ *
+ */
+public interface IStructuredModelProposed extends IAdaptable {
+
+
+ /**
+ * This API allows clients to declare that they are about to make a
+ * "large" change to the model. This change might be in terms of content
+ * or it might be in terms of the model id or base location.
+ *
+ * Note that in the case of embedded calls, notification to listeners is
+ * sent only once.
+ *
+ * Note that the client who is making these changes has the responsibility
+ * to restore the model's state once finished with the changes. See
+ * getMemento and restoreState.
+ *
+ * The method isModelStateChanging can be used by a client to determine if
+ * the model is already in a change sequence.
+ *
+ * This method is a matched pair to changedModel, and must be called
+ * before changedModel. A client should never call changedModel without
+ * calling aboutToChangeModel first nor call aboutToChangeModel without
+ * calling changedModel later from the same Thread.
+ */
+ void aboutToChangeModel();
+
+ void addModelStateListener(IModelStateListener listener);
+
+ /**
+ * This API allows a client controlled way of notifying all ModelEvent
+ * listners that the model has been changed. This method is a matched pair
+ * to aboutToChangeModel, and must be called after aboutToChangeModel ...
+ * or some listeners could be left waiting indefinitely for the changed
+ * event. So, its suggested that changedModel always be in a finally
+ * clause. Likewise, a client should never call changedModel without
+ * calling aboutToChangeModel first.
+ *
+ * In the case of embedded calls, the notification is just sent once.
+ *
+ */
+ void changedModel();
+
+ /**
+ * This is a client-defined value for what that client (and/or loader)
+ * considers the "base" of the structured model. Frequently the location
+ * is either a workspace root-relative path of a workspace resource or an
+ * absolute path in the local file system.
+ */
+ IPath getLocation();
+
+ /**
+ * @return The associated content type identifier (String) for this model.
+ */
+ IContentType getContentType() throws CoreException;
+
+ /**
+ *
+ * @return The model's FactoryRegistry. A model is not valid without one.
+ */
+ FactoryRegistry getFactoryRegistry();
+
+ /**
+ * Return the index region at offset. Returns null if there is no
+ * IndexedRegion that contains offset.
+ */
+ IndexedRegion getIndexedRegion(int offset);
+
+ /**
+ * ISSUE: do we want to provide this? How to ensure job/thread safety
+ *
+ * @return
+ */
+ IndexedRegion[] getIndexedRegions();
+
+ /**
+ * Rreturns the IStructuredDocument that underlies this model
+ *
+ * @return
+ */
+ IStructuredDocument getStructuredDocument();
+
+ /**
+ *
+ */
+ boolean isDirty();
+
+ /**
+ * This method can be called to determine if the model is within a
+ * "aboutToChange" and "changed" sequence.
+ */
+ public boolean isModelStateChanging();
+
+ /**
+ *
+ */
+ boolean isNew();
+
+ boolean isReinitializationNeeded();
+
+ /**
+ * This is a combination of if the model is dirty and if the model is
+ * shared for write access. The last writer as the responsibility to be
+ * sure the user is prompted to save.
+ */
+ public boolean isSaveNeeded();
+
+ /**
+ * newInstance is similar to clone, except that the newInstance contains
+ * no content. Its purpose is so clients can get a temporary, unmanaged,
+ * model of the same "type" as the original. Note: the client may still
+ * need to do some intialization of the model returned by newInstance,
+ * depending on desired use. For example, the only factories in the
+ * newInstance are those that would be normally be created for a model of
+ * the given contentType. Others are not copied automatically, and if
+ * desired, should be added by client.
+ */
+ IStructuredModelProposed newInstance() throws IOException;
+
+ void removeModelStateListener(IModelStateListener listener);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocument.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocument.java
new file mode 100644
index 0000000000..97d4ef68a3
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocument.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.text.IDocumentExtension;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument;
+import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager;
+
+
+/**
+ * A IStructuredDocument is a collection of StructuredDocumentRegions. It's
+ * often called "flat" because its contents by design do not contain much
+ * structural information beyond containment. Clients should not implement.
+ */
+public interface IStructuredDocument extends IEncodedDocument, IDocumentExtension, IAdaptable {
+
+ void addDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener);
+
+ /**
+ * The StructuredDocumentListeners and ModelChangedListeners are very
+ * similar. They both receive identical events. The difference is the
+ * timing. The "pure" StructuredDocumentListeners are notified after the
+ * structuredDocument has been changed, but before other, related models
+ * may have been changed such as the Structural Model. The Structural
+ * model is in fact itself a "pure" StructuredDocumentListner. The
+ * ModelChangedListeners can rest assured that all models and data have
+ * been updated from the change by the tiem they are notified. This is
+ * especially important for the text widget, for example, which may rely
+ * on both structuredDocument and structural model information.
+ */
+ void addDocumentChangedListener(IStructuredDocumentListener listener);
+
+ /**
+ * The StructuredDocumentListeners and ModelChangedListeners are very
+ * similar. They both receive identical events. The difference is the
+ * timing. The "pure" StructuredDocumentListeners are notified after the
+ * structuredDocument has been changed, but before other, related models
+ * may have been changed such as the Structural Model. The Structural
+ * model is in fact itself a "pure" StructuredDocumentListner. The
+ * ModelChangedListeners can rest assured that all models and data have
+ * been updated from the change by the tiem they are notified. This is
+ * especially important for the text widget, for example, which may rely
+ * on both structuredDocument and structural model information.
+ */
+ void addDocumentChangingListener(IStructuredDocumentListener listener);
+
+ /**
+ * this API ensures that any portion of the document within startOff to
+ * length is not readonly (that is, that its editable). Note that if the
+ * range overlaps with other readonly regions, those other readonly
+ * regions will be adjusted.
+ *
+ * @param startOffset
+ * @param length
+ */
+ void clearReadOnly(int startOffset, int length);
+
+ /**
+ * returns true if any portion of startOffset to length is readonly
+ *
+ * @param startOffset
+ * @param length
+ * @return
+ */
+ boolean containsReadOnly(int startOffset, int length);
+
+ /**
+ * This method is to remember info about the encoding When the resource
+ * was last loaded or saved. Note: it is not kept "current", that is, can
+ * not be depended on blindly to reflect what encoding to use. For that,
+ * must go through the normal rules expressed in Loaders and Dumpers.
+ */
+
+ EncodingMemento getEncodingMemento();
+
+ org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion getFirstStructuredDocumentRegion();
+
+ org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion getLastStructuredDocumentRegion();
+
+ /**
+ * This can be considered the preferred delimiter.
+ */
+ public String getLineDelimiter();
+
+ int getLineOfOffset(int offset); // throws SourceEditingException;
+
+ /**
+ * The parser is now required on constructor, so there are occasions it
+ * needs to be retrieved, such as to be initialized by EmbeddedContentType
+ */
+ RegionParser getParser();
+
+ /**
+ * @deprecated use getStructuredDocumentRegions()
+ * @return
+ */
+ IStructuredDocumentRegionList getRegionList();
+
+ /**
+ * Returns the <code>IStructuredDocumentRegion</code> at the given character offset.
+ * @param offset
+ * @return the <code>IStructuredDocumentRegion</code> at the given character offset.
+ */
+ IStructuredDocumentRegion getRegionAtCharacterOffset(int offset);
+
+ /**
+ * Returns <code>IStructuredDocumentRegion</code>s in the specified range.
+ * @param offset
+ * @param length
+ * @return <code>IStructuredDocumentRegion</code>s in the specified range.
+ */
+ IStructuredDocumentRegion[] getStructuredDocumentRegions(int offset, int length);
+
+ /**
+ * Returns all <code>IStructuredDocumentRegion</code>s in the document.
+ * @return all <code>IStructuredDocumentRegion</code>s in the document.
+ */
+ IStructuredDocumentRegion[] getStructuredDocumentRegions();
+
+ /**
+ * Note: this method was made public, and part of the interface, for
+ * easier testing. Clients normally never manipulate the reparser directly
+ * (nor should they need to).
+ */
+ IStructuredTextReParser getReParser();
+
+ String getText();
+
+ IStructuredTextUndoManager getUndoManager();
+
+ /**
+ * causes that portion of the document from startOffset to length to be
+ * marked as readonly. Note that if this range overlaps with some other
+ * region with is readonly, the regions are effectivly combined.
+ *
+ * @param startOffset
+ * @param length
+ */
+ void makeReadOnly(int startOffset, int length);
+
+ /**
+ * newInstance is similar to clone, except it contains no data. One
+ * important thing to duplicate is the parser, with the parser correctly
+ * "cloned", including its tokeninzer, block tags, etc.
+ *
+ * NOTE: even after obtaining a 'newInstance' the client may have to do
+ * some initialization, for example, it may need to add its own model
+ * listeners. Or, as another example, if the IStructuredDocument has a
+ * parser of type StructuredDocumentRegionParser, then the client may need
+ * to add its own StructuredDocumentRegionHandler to that parser, if it is
+ * in fact needed.
+ */
+ IStructuredDocument newInstance();
+
+ void removeDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener);
+
+ void removeDocumentChangedListener(IStructuredDocumentListener listener);
+
+ void removeDocumentChangingListener(IStructuredDocumentListener listener);
+
+ /**
+ * One of the APIs to manipulate the IStructuredDocument.
+ *
+ * replaceText replaces the text from oldStart to oldEnd with the new text
+ * found in the requestedChange string. If oldStart and oldEnd are equal,
+ * it is an insertion request. If requestedChange is null (or empty) it is
+ * a delete request. Otherwise it is a replace request.
+ */
+ StructuredDocumentEvent replaceText(Object source, int oldStart, int replacementLength, String requestedChange);
+
+ /**
+ * Note, same as replaceText API, but will allow readonly areas to be
+ * replaced. This should seldom be called with a value of "true" for
+ * ignoreReadOnlySetting. One case where its ok is with undo operations
+ * (since, presumably, if user just did something that happended to
+ * involve some inserting readonly text, they should normally be allowed
+ * to still undo that operation. Otherwise, I can't think of a single
+ * example, unless its to give the user a choice, e.g. "you are about to
+ * overwrite read only portions, do you want to continue".
+ */
+ StructuredDocumentEvent replaceText(Object source, int oldStart, int replacementLength, String requestedChange, boolean ignoreReadOnlySetting);
+
+ /**
+ * This method is to remember info about the encoding When the resource
+ * was last loaded or saved. Note: it is not kept "current", that is, can
+ * not be depended on blindly to reflect what encoding to use. For that,
+ * must go through the normal rules expressed in Loaders and Dumpers.
+ */
+ void setEncodingMemento(EncodingMemento encodingMemento);
+
+ public void setLineDelimiter(String delimiter);
+
+ /**
+ * One of the APIs to manipulate the IStructuredDocument in terms of Text.
+ *
+ * The setText method replaces all text in the model.
+ */
+ StructuredDocumentEvent setText(Object requester, String allText);
+
+ void setUndoManager(IStructuredTextUndoManager undoManager);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegion.java
new file mode 100644
index 0000000000..8b03b2ac92
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegion.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+/**
+ * A ITextRegionCollection is a collection of ITextRegions. It is a structural
+ * unit, but a minimal one. For example, in might consist of a "start tag" but
+ * not a whole XML element.
+ */
+public interface IStructuredDocumentRegion extends ITextRegionCollection {
+
+ /**
+ * Adds a text region to the end of the collection of regions contained by
+ * this region. It is the parsers responsibility to make sure its a
+ * correct region (that is, its start offset is one greater than previous
+ * regions end offset)
+ *
+ * For use by parsers and reparsers only.
+ */
+ void addRegion(ITextRegion aRegion);
+
+ /**
+ * Returns the structured document region that follows this one or null if
+ * at end of document.
+ *
+ * @return the structured document region that follows this one.
+ *
+ * ISSUE: for thread safety, this should be more restrictive.
+ */
+ IStructuredDocumentRegion getNext();
+
+ /**
+ * Returns this regions parent document.
+ *
+ * @return this regions parent document.
+ */
+ IStructuredDocument getParentDocument();
+
+ /**
+ * Returns the structured document region that preceeds this one or null
+ * if at beginning of document.
+ *
+ * @return the structured document region that follows this one.
+ *
+ * ISSUE: for thread safety, this should be more restrictive.
+ */
+ IStructuredDocumentRegion getPrevious();
+
+ /**
+ * Returns true if this document has been deleted, and is no longer part
+ * of the actual structured document. This field can be used in
+ * multi-threaded operations, which may retrieve a long list of regions
+ * and be iterating through them at the same time the document is
+ * modified, and regions deleted.
+ *
+ * @return true if this region is no longer part of document.
+ */
+ boolean isDeleted();
+
+ /**
+ * Returns true if this structured document region was ended "naturally"
+ * by syntactic rules, or if simply assumed to end so another could be
+ * started.
+ *
+ * @return true if region has syntactic end.
+ */
+ boolean isEnded();
+
+ /**
+ * Tests is region is equal to this one, ignoring position offsets of
+ * shift.
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param region
+ * @param shift
+ * @return
+ */
+ boolean sameAs(IStructuredDocumentRegion region, int shift);
+
+ /**
+ * Tests if <code>oldRegion</code> is same as <code>newRegion</code>,
+ * ignoring position offsets of <code>shift</code>.
+ *
+ * ISSUE: which document region are old and new in?
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param oldRegion
+ * @param documentRegion
+ * @param newRegion
+ * @param shift
+ * @return
+ */
+ boolean sameAs(ITextRegion oldRegion, IStructuredDocumentRegion documentRegion, ITextRegion newRegion, int shift);
+
+ /**
+ * Set to true if/when this region is removed from a document, during the
+ * process of re-parsing.
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param deleted
+ */
+ void setDeleted(boolean deleted);
+
+ /**
+ * Set to true by parser/reparser if region deemed to end syntactically.
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param hasEnd
+ */
+ void setEnded(boolean hasEnd);
+
+ /**
+ * Sets length of region.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void setLength(int newLength);
+
+ /**
+ * Assigns pointer to next region, or null if last region.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void setNext(IStructuredDocumentRegion newNext);
+
+ /**
+ * Assigns parent documnet.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void setParentDocument(IStructuredDocument document);
+
+ /**
+ * Assigns pointer to previous region, or null if first region.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void setPrevious(IStructuredDocumentRegion newPrevious);
+
+ /**
+ * Sets start offset of region, relative to beginning of document.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void setStart(int newStart);
+
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegionList.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegionList.java
new file mode 100644
index 0000000000..734431e416
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredDocumentRegionList.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+import java.util.Enumeration;
+
+/**
+ * This is a class used to provide a list of StructuredDocumentRegions, so the
+ * implementation of how the list is formed can be hidden (encapsulated by
+ * this class).
+ *
+ * ISSUE: should extend ITextRegionList instead?
+ *
+ * @plannedfor 1.0
+ *
+ */
+public interface IStructuredDocumentRegionList {
+
+ /**
+ * Returns enumeration.
+ *
+ * @return enumeration.
+ */
+ Enumeration elements();
+
+ /**
+ * Returns size of list.
+ *
+ * @return size of list.
+ */
+ int getLength();
+
+ /**
+ * Returns the structured document region at index i.
+ *
+ * @param i
+ * index of region to return
+ * @return the region at specified offset.
+ */
+ IStructuredDocumentRegion item(int i);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitionTypes.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitionTypes.java
new file mode 100644
index 0000000000..17c2019980
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitionTypes.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional.text;
+
+/**
+ * This interface is not intended to be implemented.
+ * It defines the partitioning for StructuredDocuments.
+ * Clients should reference the partition type Strings defined here directly.
+ *
+ * @deprecated use org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitions
+ */
+public interface IStructuredPartitionTypes {
+
+ String DEFAULT_PARTITION = "org.eclipse.wst.sse.ST_DEFAULT"; //$NON-NLS-1$
+ String UNKNOWN_PARTITION = "org.eclipse.wst.sse.UNKNOWN_PARTITION_TYPE"; //$NON-NLS-1$
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitioning.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitioning.java
new file mode 100644
index 0000000000..c41cd387bb
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredPartitioning.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional.text;
+
+/**
+ * Identifies the way that Documents are partitioned.
+ *
+ * @plannedfor 1.0
+ */
+public interface IStructuredPartitioning {
+
+ /** String to identify default partitioning*/
+ String DEFAULT_STRUCTURED_PARTITIONING = "org.eclipse.wst.sse.core.default_structured_text_partitioning"; //$NON-NLS-1$
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextPartitioner.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextPartitioner.java
new file mode 100644
index 0000000000..9e53f8f26f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextPartitioner.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.provisional.text;
+
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.wst.sse.core.internal.text.rules.IStructuredTypedRegion;
+
+
+/**
+ * A partitioner interface required for handling the embedded content type
+ * properly.
+ *
+ * ISSUE: need to investigate necessity of these methods
+ *
+ * @plannedfor 1.0
+ */
+
+public interface IStructuredTextPartitioner extends IDocumentPartitioner {
+
+ /**
+ * Used by JSP partitioner to ensure that the partitioner of the
+ * embedded content type gets to create the partition in case the specific
+ * classes are important.
+ *
+ * ISSUE: investigate if we really need this...
+ */
+ IStructuredTypedRegion createPartition(int offset, int length, String partitionType);
+
+ /**
+ * Returns the Default partition type for this partitioner.
+ * <p>
+ * eg:
+ * <br><code>org.eclipse.wst.xml.core.text.IXMLPartitions.XML_DEFAULT</code>
+ * <br><code>org.eclipse.wst.html.core.text.IHTMLPartitions.HTML_DEFAULT</code>
+ * <br><code>org.eclipse.wst.jsp.core.text.IJSPPartitions.JSP_DEFAULT</code>
+ * </p>
+ * @see org.eclipse.wst.sse.core.text.IStructuredPartitions
+ * @see org.eclipse.wst.xml.core.text.IXMLPartitions
+ * @see org.eclipse.wst.html.core.text.IHTMLPartitions
+ * @see org.eclipse.wst.jsp.core.text.IJSPPartitions
+ *
+ * @return the Default partition type for this partitioner.
+ */
+ String getDefaultPartitionType();
+
+ /**
+ * Returns the particular partition type for the given region/offset.
+ * <p>
+ * eg:
+ * <br><code>org.eclipse.wst.xml.core.text.IXMLPartitions.XML_DEFAULT</code>
+ * <br><code>org.eclipse.wst.html.core.text.IHTMLPartitions.HTML_DEFAULT</code>
+ * <br><code>org.eclipse.wst.jsp.core.text.IJSPPartitions.JSP_DEFAULT</code>
+ * </p>
+ *
+ * @param region of the IStructuredDocument
+ * @param offset in the IStructuredDoucment
+ * @return the particular partition type for the given region/offset.
+ */
+ String getPartitionType(ITextRegion region, int offset);
+
+ /**
+ * Returns the partition type String of the IStructuredDocumentRegion
+ * between the 2 region parameters.
+ * Useful for finding the partition type of a 0 length region.
+ * eg.
+ * <pre>
+ * <script type="text/javascript">|</script>
+ * </pre>
+ * @param previousNode
+ * @param nextNode
+ * @return the partition type of the node between previousNode and nextNode
+ * @deprecated move to IDocumentPartitionerExtension2 ->
+ * String getContentType(int offset, boolean preferOpenPartitions);
+ */
+ String getPartitionTypeBetween(IStructuredDocumentRegion previousNode, IStructuredDocumentRegion nextNode);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextReParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextReParser.java
new file mode 100644
index 0000000000..5acdfa1e2f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/IStructuredTextReParser.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+
+/**
+ * IStructuredTextReParser describes the characteristics and responsibilities
+ * for reparsing a structured document.
+ *
+ * @plannedfor 1.0
+ */
+public interface IStructuredTextReParser {
+
+ /**
+ * Begins the process of reparsing, by providing the information needed
+ * for the reparse. The reparse is actually performed when the reparse
+ * method is called. Will through an IllegalStateException if document as
+ * not be set.
+ *
+ * @param requester
+ * @param start
+ * @param lengthToReplace
+ * @param changes
+ */
+ void initialize(Object requester, int start, int lengthToReplace, String changes);
+
+ /**
+ * This method is provided to enable multiple threads to reparse a
+ * document. This is needed since the intialize method sets state
+ * variables that must be "in sync" with the structuredDocument.
+ */
+ public boolean isParsing();
+
+ /**
+ * Returns a new instance of this reparser, used when cloning documents.
+ *
+ * @return a new instance of this reparser.
+ */
+ public IStructuredTextReParser newInstance();
+
+ /**
+ * An entry point for reparsing. It needs to calculates the dirty start
+ * and dirty end in terms of structured document regions based on the
+ * start point and length of the changes, which are provided by the
+ * initialize method. Will through an IllegalStateException if document as
+ * not be set.
+ *
+ */
+ public StructuredDocumentEvent reparse();
+
+
+
+ /**
+ * The reparser is initialized with its document, either in or shortly
+ * after its constructed is called.
+ *
+ * @param structuredDocument
+ */
+ public void setStructuredDocument(IStructuredDocument newStructuredDocument);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegion.java
new file mode 100644
index 0000000000..b1281ab82c
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegion.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+
+/**
+ * A simple description of a bit of text (technically, a bit of a text buffer)
+ * that has a "type" associated with it. For example, for the XML text
+ * "&LT;IMG&GT;", the ' <' might form a region of type "open bracket" where as
+ * the text "IMG" might form a region of type "tag name".
+ *
+ * Note that there are three positions associated with a region, the start,
+ * the end, and the end of the text. The end of the region should always be
+ * greater than or equal to the end of the text, because the end of the text
+ * simply includes any white space that might follow the non-whitespace
+ * portion of the region. This whitespace is assumed to be ignorable except
+ * for reasons of maintaining it in the original document for formatting,
+ * appearance, etc.
+ *
+ * Follows the Prime Directives:
+ *
+ * getEnd() == getStart() + getLength()
+ *
+ * getTextEnd() == getStart() + getTextLength();
+ *
+ * Event though 'end' and 'length' are redundant (given start), both methods
+ * are provided, since some parsers/implementations favor one or the other for
+ * efficiency.
+ *
+ * @plannedfor 1.0
+ */
+public interface ITextRegion {
+
+ /**
+ * Changes length of region. May be less than, equal to, or greater than
+ * zero. It may not, however, cause the length of the region to be less
+ * than or equal to zero, or an illegal argument acception may be thrown
+ * at runtime.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void adjustLength(int i);
+
+ /**
+ * Changes start offset of region. May be less than, equal to, or greater
+ * than zero. It may not, however, cause the offset of the region to be
+ * less than zero, or an illegal argument acception may be thrown at
+ * runtime.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void adjustStart(int i);
+
+ /**
+ * Changes text length of region.
+ *
+ * May be less than, equal to, or greater than zero. It may not, however,
+ * cause the text length of the region to be greater than the length of a
+ * region, or an illegal argument acception may be thrown at runtime.
+ *
+ * For use by parsers and reparsers only.
+ */
+ void adjustTextLength(int i);
+
+ /**
+ * Makes this regions start, length, and text length equal to the
+ * paremter's start, length, and text length.
+ *
+ * @param region
+ */
+ void equatePositions(ITextRegion region);
+
+ /**
+ * Returns the end offset of this region. Whether is relative to the
+ * document, or another region depends on the subtype.
+ *
+ * Follows the Prime Directive: getEnd() == getStart() + getLength()
+ *
+ * @return the end offset of this region.
+ */
+ int getEnd();
+
+ /**
+ * Returns the length of the region.
+ *
+ * Follows the Prime Directive: getEnd() == getStart() + getLength()
+ *
+ * @return the length of the region.
+ */
+ int getLength();
+
+ /**
+ * Returns the start of the region. Whether is relative to the document,
+ * or another region depends on the subtype.
+ *
+ * Follows the Prime Directive: getEnd() == getStart() + getLength()
+ *
+ * @return the start of the region.
+ */
+ int getStart();
+
+ /**
+ * Returns the end offset of the text of this region.
+ *
+ * In some implementations, the "end" of the region (accessible via
+ * getEnd()) also contains any and all white space that may or may not be
+ * present after the "token" (read: relevant) part of the region. This
+ * method, getTextEnd(), is specific for the "token" part of the region,
+ * without the whitespace.
+ *
+ * @return the end offset of the text of this region.
+ */
+ int getTextEnd();
+
+ /**
+ * Returns the length of the text of this region.
+ *
+ * The text length is equal to length if there is no white space at the
+ * end of a region. Otherwise it is smaller than length.
+ *
+ * @return the length of the text of this region.
+ */
+ int getTextLength();
+
+ /**
+ * Returns the type of this region.
+ *
+ * @see regiontypes, structureddocumentregiontypes
+ * @return the type of this region.
+ */
+ String getType();
+
+ /**
+ * Allows the region itself to participate in reparsing process.
+ *
+ * The region itself can decide if it can determine the appropriate event
+ * to return, based on the requested change. If it can not, this method
+ * must return null, so a "higher level" reparsing process will be given
+ * the oppurtunity to decide. If it returns an Event, that's it, not other
+ * reparsing process will get an oppurtunity to reparse.
+ *
+ * For use by parsers and reparsers only.
+ *
+ */
+ StructuredDocumentEvent updateRegion(Object requester, IStructuredDocumentRegion parent, String changes, int requestStart, int lengthToReplace);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionCollection.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionCollection.java
new file mode 100644
index 0000000000..08890ae4e1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionCollection.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+/**
+ * ITextRegionCollection, as its name implies, is a collection of
+ * ITextRegions.
+ *
+ * @plannedfor 1.0
+ */
+public interface ITextRegionCollection extends ITextRegion {
+
+ /**
+ * Used to determine if a region contains a particular offset, where
+ * offset is relative to the beginning of a document.
+ *
+ * @param offset
+ * @return true if offset is greater than or equal to regions start and
+ * less than its end offset.
+ */
+ boolean containsOffset(int offset);
+
+ /**
+ * Used to determine if a region contains a particular offset.
+ *
+ * ISSUE: I need to figure out what this is really for! (that is, how to
+ * describe it, or if still needed).
+ *
+ * @param offset
+ * @return true if offset is greater than or equal to regions start and
+ * less than its end offset.
+ */
+ boolean containsOffset(ITextRegion region, int offset);
+
+ /**
+ * Returns the end offset of this region, relative to beginning of
+ * document.
+ *
+ * For some subtypes, but not all, it is equivilent to getEnd().
+ *
+ * @return the end offset of this region.
+ */
+ int getEndOffset();
+
+ /**
+ * Returns the end offset, relative to the beginning of the document of
+ * the contained region.
+ *
+ * @param containedRegion
+ * @return the end offset of the contained region.
+ */
+ int getEndOffset(ITextRegion containedRegion);
+
+ /**
+ * Returns the first region of those contained by this region collection.
+ *
+ * @return the first region.
+ */
+ ITextRegion getFirstRegion();
+
+ /**
+ * Returns the full text of this region, including whitespace.
+ *
+ * @return the full text of this region, including whitespace.
+ */
+ String getFullText();
+
+ /**
+ * Returns the full text of the contained region, including whitespace.
+ *
+ * @return the full text of the contained region, including whitespace.
+ */
+ String getFullText(ITextRegion containedRegion);
+
+ /**
+ * Returns the last region of those contained by this region collection.
+ *
+ * @return the last region.
+ */
+ ITextRegion getLastRegion();
+
+ /**
+ * Returns the number of regions contained by this region.
+ *
+ * @return the number of regions contained by this region.
+ */
+ int getNumberOfRegions();
+
+ /**
+ * Returns the region that contains offset. In the case of "nested"
+ * regions, returns the top most region.
+ *
+ * @param offset
+ * relative to beginning of document.
+ * @return the region that contains offset. In the case of "nested"
+ * regions, returns the top most region.
+ */
+ ITextRegion getRegionAtCharacterOffset(int offset);
+
+ /**
+ * Returns the regions contained by this region.
+ *
+ * Note: no assumptions should be made about the object identity of the
+ * regions returned. Put another way, due to memory use optimizations,
+ * even if the underlying text has not changed, the regions may or may not
+ * be the same ones returned from one call to the next.
+ *
+ * @return the regions contained by this region.
+ */
+ ITextRegionList getRegions();
+
+ /**
+ * Returns the start offset of this region, relative to the beginning of
+ * the document.
+ *
+ * @return the start offset of this region
+ */
+ int getStartOffset();
+
+ /**
+ * Returns the start offset of the contained region, relative to the
+ * beginning of the document.
+ *
+ * @return the start offset of the contained region
+ */
+ int getStartOffset(ITextRegion containedRegion);
+
+ /**
+ * Returns the text of this region, not including white space.
+ *
+ * @return the text of this region, not including white space.
+ */
+ String getText();
+
+ /**
+ * Returns the text of the contained region, not including white space.
+ *
+ * @return the text of the contained region, not including white space.
+ */
+ String getText(ITextRegion containedRegion);
+
+ /**
+ * Returns the end offset of the text of this region, not including white
+ * space.
+ *
+ * @return the end offset of the text of this region, not including white
+ * space.
+ */
+ int getTextEndOffset();
+
+ /**
+ * Returns the end offset of the text of the contained region, not
+ * including white space.
+ *
+ * @return the end offset of the text of the contained region, not
+ * including white space.
+ */
+ int getTextEndOffset(ITextRegion containedRegion);
+
+ /**
+ * Assigns the collection contained in this region.
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param containedRegions
+ */
+ void setRegions(ITextRegionList containedRegions);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionContainer.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionContainer.java
new file mode 100644
index 0000000000..654ca8116e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionContainer.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.provisional.text;
+
+/**
+ * ITextRegionContainer contains other regions, like a ITextRegionCollection
+ * but is itself a region in an ITextRegionCollection, so its "parent" region
+ * is maintained.
+ *
+ * @plannedfor 1.0
+ */
+public interface ITextRegionContainer extends ITextRegionCollection {
+
+ /**
+ * Returns the parent region.
+ *
+ * @return the parent region.
+ */
+ ITextRegionCollection getParent();
+
+ /**
+ * Sets the parent region.
+ *
+ * For use by parsers and reparsers only.
+ *
+ * @param parent
+ * the ITextRegionCollection this region is contained in.
+ */
+ void setParent(ITextRegionCollection parent);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionList.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionList.java
new file mode 100644
index 0000000000..594d3e8167
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/provisional/text/ITextRegionList.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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.provisional.text;
+
+import java.util.Iterator;
+
+/**
+ * ITextRegionList is to provide a list of regions. It can be used so clients
+ * do not need to be aware of underlying implementation.
+ */
+public interface ITextRegionList {
+
+ /**
+ * Adds region to the list.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ * @param region
+ * @return
+ */
+ public boolean add(ITextRegion region);
+
+ /**
+ * Adds new regions to the list.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ * @param insertPos
+ * @param newRegions
+ * @return whether the contents of this list were modified
+ */
+ public boolean addAll(int insertPos, ITextRegionList newRegions);
+
+ /**
+ * Removes all regions from the list.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ */
+ public void clear();
+
+
+ /**
+ * Returns the region at <code>index</code>, where 0 is first one in
+ * the list. Throws an <code>ArrayIndexOutOfBoundsException</code> if
+ * list is empty, or if index is out of range.
+ *
+ * @param index
+ * @return
+ */
+ public ITextRegion get(int index);
+
+ /**
+ * Returns the index of <code>region</code> or -1 if <code>region</code>
+ * is not in the list.
+ *
+ * @param region
+ * @return
+ */
+ public int indexOf(ITextRegion region);
+
+ /**
+ * Returns true if list has no regions.
+ *
+ * @return true if list has no regions.
+ */
+ public boolean isEmpty();
+
+
+ /**
+ * Returns an iterator for this list.
+ *
+ * @return an iterator for this list.
+ */
+ public Iterator iterator();
+
+ /**
+ * Removes the region at index.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ */
+ public ITextRegion remove(int index);
+
+ /**
+ * Removes the region.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ */
+ public void remove(ITextRegion region);
+
+
+ /**
+ * Removes all regionList from this list.
+ *
+ * For use by parsers and reparsers only, while list is being created.
+ *
+ */
+ public void removeAll(ITextRegionList regionList);
+
+ /**
+ * Returns the size of the list.
+ *
+ * @return the size of the list.
+ */
+ public int size();
+
+
+ /**
+ * Creates and returns the regions in an array. No assumptions should be
+ * made if the regions in the array are clones are same instance of
+ * original region.
+ *
+ * ISSUE: do we need to specify if cloned copies or not?
+ *
+ * @return an array of regions.
+ */
+ public ITextRegion[] toArray();
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocument.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocument.java
new file mode 100644
index 0000000000..c4821c1e8f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocument.java
@@ -0,0 +1,2979 @@
+/*******************************************************************************
+ * 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
+ * Jesper Steen Møller - initial IDocumentExtension4 support - #102822
+ * (see also #239115)
+ * David Carver (Intalio) - bug 300434 - Make inner classes static where possible
+ * David Carver (Intalio) - bug 300443 - some constants aren't static final
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPartitioningException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultLineTracker;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.DocumentPartitioningChangedEvent;
+import org.eclipse.jface.text.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionEvent;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
+import org.eclipse.jface.text.FindReplaceDocumentAdapter;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension;
+import org.eclipse.jface.text.IDocumentExtension3;
+import org.eclipse.jface.text.IDocumentExtension4;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.jface.text.IDocumentPartitionerExtension;
+import org.eclipse.jface.text.IDocumentPartitionerExtension2;
+import org.eclipse.jface.text.IDocumentPartitionerExtension3;
+import org.eclipse.jface.text.IDocumentPartitioningListener;
+import org.eclipse.jface.text.IDocumentPartitioningListenerExtension;
+import org.eclipse.jface.text.IDocumentPartitioningListenerExtension2;
+import org.eclipse.jface.text.IDocumentRewriteSessionListener;
+import org.eclipse.jface.text.ILineTracker;
+import org.eclipse.jface.text.ILineTrackerExtension;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextStore;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TypedRegion;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.document.StructuredDocumentFactory;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.provisional.events.AboutToBeChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
+import org.eclipse.wst.sse.core.internal.text.rules.StructuredTextPartitioner;
+import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager;
+import org.eclipse.wst.sse.core.internal.undo.StructuredTextUndoManager;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+import org.eclipse.wst.sse.core.internal.util.Debug;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+
+
+/**
+ * The standard implementation of structured document.
+ */
+public class BasicStructuredDocument implements IStructuredDocument, IDocumentExtension, IDocumentExtension3, IDocumentExtension4, CharSequence, IRegionComparible {
+
+ /**
+ * This ThreadLocal construct is used so each thread can maintain its only
+ * pointer to the double linked list that manages the documents regions.
+ * The only thing we "gaurd" for is that a previously cached region has
+ * been deleted.
+ *
+ * The object that is kept in the thread local's map, is just a pointer to
+ * an array position. That's because the object there needs to be "free"
+ * from references to other objects, or it will not be garbage collected.
+ */
+ private class CurrentDocumentRegionCache {
+ // I'm assuming for now there would never be so many threads that
+ // this arrayList needs to be bounded, or 'cleaned up'.
+ // this assumption should be tested in practice and long running
+ // jobs -- found not to be a good assumption. See below.
+ private List cachedRegionPositionArray = Collections.synchronizedList(new ArrayList());
+ private final boolean DEBUG = false;
+ private static final int MAX_SIZE = 50;
+
+
+ private ThreadLocal threadLocalCachePosition = new ThreadLocal();
+
+ IStructuredDocumentRegion get() {
+ IStructuredDocumentRegion region = null;
+ int pos = getThreadLocalPosition();
+ try {
+ region = (IStructuredDocumentRegion) cachedRegionPositionArray.get(pos);
+ }
+ catch (IndexOutOfBoundsException e) {
+ // even though the cachedRegionPosition is synchronized,
+ // that just means each access is syncronized, its
+ // still possible for another thread to cause it to
+ // be cleared, after this thread gets it position.
+ // So, if that happens, all we can do is reset to beginning.
+ // This should be extremely rare (in other words, probably
+ // not worth using synchronized blocks
+ // to access cachedRegionPositionArray.
+ reinitThreadLocalPosition();
+ resetToInitialState();
+ }
+ if (region == null) {
+ region = resetToInitialState();
+ }
+ else
+ // region not null
+ if (region.isDeleted()) {
+ region = resetToInitialState();
+ }
+ return region;
+ }
+
+ private int getThreadLocalPosition() {
+ Object threadLocalObject = threadLocalCachePosition.get();
+ int pos = -1;
+ if (threadLocalObject == null) {
+
+ pos = reinitThreadLocalPosition();
+ }
+ else {
+ pos = ((Integer) threadLocalObject).intValue();
+ }
+ return pos;
+ }
+
+ /**
+ * @return
+ */
+ private int reinitThreadLocalPosition() {
+ Integer position;
+ int pos;
+ // TODO_future: think of a better solution that doesn't
+ // require this kludge. This is especially required because
+ // some infrasture, such as reconciler, actually null out
+ // their thread object and recreate it, 500 msecs later
+ // (approximately).
+ // Note: the likely solution in future is to clear after every
+ // heavy use of getCachedRegion, such as in creating node
+ // lists, or reparsing or partioning.
+ if (cachedRegionPositionArray.size() > MAX_SIZE) {
+ cachedRegionPositionArray.clear();
+ if (DEBUG) {
+ System.out.println("cachedRegionPositionArray cleared at size " + MAX_SIZE); //$NON-NLS-1$
+ }
+ }
+ position = new Integer(cachedRegionPositionArray.size());
+ threadLocalCachePosition.set(position);
+ cachedRegionPositionArray.add(position.intValue(), null);
+ pos = position.intValue();
+ return pos;
+ }
+
+ private IStructuredDocumentRegion resetToInitialState() {
+ IStructuredDocumentRegion region;
+ region = getFirstStructuredDocumentRegion();
+ set(region);
+ return region;
+ }
+
+ void set(IStructuredDocumentRegion region) {
+ try {
+ int pos = getThreadLocalPosition();
+ cachedRegionPositionArray.set(pos, region);
+ }
+ catch (IndexOutOfBoundsException e) {
+ // even though the cachedRegionPosition is synchronized,
+ // that just means each access is syncronized, its
+ // still possible for another thread to cause it to
+ // be cleared, after this thread gets it position.
+ // So, if that happens, all we can do is reset to beginning.
+ // This should be extremely rare (in other words, probably
+ // not worth using synchronized blocks
+ // to access cachedRegionPositionArray.
+ reinitThreadLocalPosition();
+ resetToInitialState();
+ }
+ }
+ }
+
+ /**
+ * This NullDocumentEvent is used to complete the "aboutToChange" and
+ * "changed" cycle, when in fact the original change is no longer valid.
+ * The only known (valid) case of this is when a model re-initialize takes
+ * place, which causes setText to be called in the middle of some previous
+ * change. [This architecture will be improved in future].
+ */
+ public class NullDocumentEvent extends DocumentEvent {
+ public NullDocumentEvent() {
+ this(BasicStructuredDocument.this, 0, 0, ""); //$NON-NLS-1$
+ }
+
+ private NullDocumentEvent(IDocument doc, int offset, int length, String text) {
+ super(doc, offset, length, text);
+ }
+ }
+
+ static class RegisteredReplace {
+ /** The owner of this replace operation. */
+ IDocumentListener fOwner;
+ /** The replace operation */
+ IDocumentExtension.IReplace fReplace;
+
+ /**
+ * Creates a new bundle object.
+ *
+ * @param owner
+ * the document listener owning the replace operation
+ * @param replace
+ * the replace operation
+ */
+ RegisteredReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
+ fOwner = owner;
+ fReplace = replace;
+ }
+ }
+
+ /**
+ * these control variable isn't mark as 'final' since there's some unit
+ * tests that manipulate it. For final product, it should be.
+ */
+
+ private static boolean USE_LOCAL_THREAD = true;
+
+ /**
+ * purely for debugging/performance measurements In practice, would always
+ * be 'true'. (and should never be called by called by clients). Its not
+ * 'final' or private just so it can be varied during
+ * debugging/performance measurement runs.
+ *
+ * @param use_local_thread
+ */
+ public static void setUSE_LOCAL_THREAD(final boolean use_local_thread) {
+ USE_LOCAL_THREAD = use_local_thread;
+ }
+
+ private IStructuredDocumentRegion cachedDocumentRegion;
+ private EncodingMemento encodingMemento;
+ private boolean fAcceptPostNotificationReplaces = true;
+ private CurrentDocumentRegionCache fCurrentDocumentRegionCache;
+ private DocumentEvent fDocumentEvent;
+ private IDocumentListener[] fDocumentListeners;
+
+ /**
+ * The registered document partitioners.
+ */
+ private Map fDocumentPartitioners;
+ /** The registered document partitioning listeners */
+ private List fDocumentPartitioningListeners;
+ private IStructuredDocumentRegion firstDocumentRegion;
+ private RegionParser fParser;
+ private GenericPositionManager fPositionManager;
+ private List fPostNotificationChanges;
+ private IDocumentListener[] fPrenotifiedDocumentListeners;
+ private int fReentranceCount = 0;
+ private IStructuredTextReParser fReParser;
+ private int fStoppedCount = 0;
+
+ private ITextStore fStore;
+ private Object[] fStructuredDocumentAboutToChangeListeners;
+ private Object[] fStructuredDocumentChangedListeners;
+ private Object[] fStructuredDocumentChangingListeners;
+
+ private List fDocumentRewriteSessionListeners;
+
+ private ILineTracker fTracker;
+ private IStructuredTextUndoManager fUndoManager;
+ private IStructuredDocumentRegion lastDocumentRegion;
+
+ private byte[] listenerLock = new byte[0];
+ private NullDocumentEvent NULL_DOCUMENT_EVENT;
+
+ /**
+ * Theoretically, a document can contain mixed line delimiters, but the
+ * user's preference is usually to be internally consistent.
+ */
+ private String fInitialLineDelimiter;
+ private static final String READ_ONLY_REGIONS_CATEGORY = "_READ_ONLY_REGIONS_CATEGORY_"; //$NON-NLS-1$
+ /**
+ * Current rewrite session, or none if not presently rewriting.
+ */
+ private DocumentRewriteSession fActiveRewriteSession;
+ /**
+ * Last modification stamp, automatically updated on change.
+ */
+ private long fModificationStamp;
+ /**
+ * Keeps track of next modification stamp.
+ */
+ private long fNextModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+ /**
+ * debug variable only
+ *
+ * @param parser
+ */
+ private long startStreamTime;
+ /**
+ * debug variable only
+ *
+ * @param parser
+ */
+ private long startTime;
+
+ public BasicStructuredDocument() {
+ super();
+ fCurrentDocumentRegionCache = new CurrentDocumentRegionCache();
+ setTextStore(new StructuredDocumentTextStore(50, 300));
+ setLineTracker(new DefaultLineTracker());
+ NULL_DOCUMENT_EVENT = new NullDocumentEvent();
+
+ internal_addPositionCategory(READ_ONLY_REGIONS_CATEGORY);
+ internal_addPositionUpdater(new DeleteEqualPositionUpdater(READ_ONLY_REGIONS_CATEGORY));
+
+ }
+
+ /**
+ * This is the primary way to get a new structuredDocument. Its best to
+ * use the factory methods in ModelManger to create a new
+ * IStructuredDocument, since it will get and initialize the parser
+ * according to the desired content type.
+ */
+ public BasicStructuredDocument(RegionParser parser) {
+ this();
+ Assert.isNotNull(parser, "Program Error: IStructuredDocument can not be created with null parser"); //$NON-NLS-1$
+ // go through setter in case there is side effects
+ internal_setParser(parser);
+ }
+
+ private void _clearDocumentEvent() {
+ // no hard and fast requirement to null out ... just seems like
+ // a good idea, since we are done with it.
+ fDocumentEvent = null;
+ }
+
+ private void _fireDocumentAboutToChange(Object[] listeners) {
+ // most DocumentAboutToBeChanged listeners do not anticipate
+ // DocumentEvent == null. So make sure documentEvent is not
+ // null. (this should never happen, yet it does sometimes)
+ if (fDocumentEvent == null) {
+ fDocumentEvent = new NullDocumentEvent();
+ }
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ // Note: the docEvent is created in replaceText API
+ // fire
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IDocumentListener) holdListeners[i]).documentAboutToBeChanged(fDocumentEvent);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void notifyDocumentPartitionersAboutToChange(DocumentEvent documentEvent) {
+ if (fDocumentPartitioners != null) {
+ Iterator e = fDocumentPartitioners.values().iterator();
+ while (e.hasNext()) {
+ IDocumentPartitioner p = (IDocumentPartitioner) e.next();
+ // safeguard from listeners that throw exceptions
+ try {
+ p.documentAboutToBeChanged(documentEvent);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ }
+ }
+ }
+
+ private void _fireDocumentChanged(Object[] listeners, StructuredDocumentEvent event) {
+
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ // NOTE: document event is created in replace Text API and setText
+ // API
+ // now fire
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ // Notes: fDocumentEvent can be "suddenly" null, if one of
+ // the
+ // previous changes
+ // caused a "setText" to be called. The only known case of
+ // this
+ // is a model reset
+ // due to page directive changing. Eventually we should
+ // change
+ // archetecture to have
+ // event que and be able to "cancel" pending events, but
+ // for
+ // now, we'll just pass a
+ // NullDocumentEvent. By the way, it is important to send
+ // something, since clients might
+ // have indeterminant state due to "aboutToChange" being
+ // sent
+ // earlier.
+ if (fDocumentEvent == null) {
+ ((IDocumentListener) holdListeners[i]).documentChanged(NULL_DOCUMENT_EVENT);
+ }
+ else {
+ ((IDocumentListener) holdListeners[i]).documentChanged(fDocumentEvent);
+ }
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void notifyDocumentPartitionersDocumentChanged(DocumentEvent documentEvent) {
+ if (fDocumentPartitioners != null) {
+ Iterator e = fDocumentPartitioners.values().iterator();
+ while (e.hasNext()) {
+ IDocumentPartitioner p = (IDocumentPartitioner) e.next();
+ // safeguard from listeners that throw exceptions
+ try {
+ if (p instanceof IDocumentPartitionerExtension) {
+ // IRegion changedPartion =
+ ((IDocumentPartitionerExtension) p).documentChanged2(documentEvent);
+ }
+ else {
+ p.documentChanged(documentEvent);
+ }
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ }
+ }
+ }
+
+
+ private void _fireEvent(Object[] listeners, NoChangeEvent event) {
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IStructuredDocumentListener) holdListeners[i]).noChange(event);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void _fireEvent(Object[] listeners, RegionChangedEvent event) {
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IStructuredDocumentListener) holdListeners[i]).regionChanged(event);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void _fireEvent(Object[] listeners, RegionsReplacedEvent event) {
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IStructuredDocumentListener) holdListeners[i]).regionsReplaced(event);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void _fireEvent(Object[] listeners, StructuredDocumentRegionsReplacedEvent event) {
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IStructuredDocumentListener) holdListeners[i]).nodesReplaced(event);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ private void _fireStructuredDocumentAboutToChange(Object[] listeners) {
+ // we must assign listeners to local variable, since the add and
+ // remove
+ // listner
+ // methods can change the actual instance of the listener array from
+ // another thread
+ if (listeners != null) {
+ Object[] holdListeners = listeners;
+ // Note: the docEvent is created in replaceText API
+ // fire
+ for (int i = 0; i < holdListeners.length; i++) {
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ startTime = System.currentTimeMillis();
+ }
+ // safeguard from listeners that throw exceptions
+ try {
+ // notice the AboutToBeChangedEvent is created from the
+ // DocumentEvent, since it is (nearly)
+ // the same information. ?What to do about
+ // originalRequester?
+ if (fDocumentEvent == null) {
+ fDocumentEvent = new NullDocumentEvent();
+ }
+ AboutToBeChangedEvent aboutToBeChangedEvent = new AboutToBeChangedEvent(this, null, fDocumentEvent.getText(), fDocumentEvent.getOffset(), fDocumentEvent.getLength());
+ // this is a safe cast, since addListners requires a
+ // IStructuredDocumentListener
+ ((IModelAboutToBeChangedListener) holdListeners[i]).modelAboutToBeChanged(aboutToBeChangedEvent);
+ }
+ catch (Exception exception) {
+ Logger.logException(exception);
+ }
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
+ long stopTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ protected void acquireLock() {
+ // do nothing here in super class
+ }
+
+ /**
+ * addModelAboutToBeChangedListener method comment.
+ */
+ public void addDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
+ synchronized (listenerLock) {
+
+ // make sure listener is not already in listening
+ // (and if it is, print a warning to aid debugging, if needed)
+ if (!Utilities.contains(fStructuredDocumentAboutToChangeListeners, listener)) {
+ int oldSize = 0;
+ if (fStructuredDocumentAboutToChangeListeners != null) {
+ // normally won't be null, but we need to be sure, for
+ // first
+ // time through
+ oldSize = fStructuredDocumentAboutToChangeListeners.length;
+ }
+ int newSize = oldSize + 1;
+ Object[] newListeners = new Object[newSize];
+ if (fStructuredDocumentAboutToChangeListeners != null) {
+ System.arraycopy(fStructuredDocumentAboutToChangeListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fStructuredDocumentAboutToChangeListeners = newListeners;
+ //
+ }
+ }
+ }
+
+ /**
+ * The StructuredDocumentListners and ModelChagnedListeners are very
+ * similar. They both receive identical events. The difference is the
+ * timing. The "pure" StructuredDocumentListners are notified after the
+ * structuredDocument has been changed, but before other, related models
+ * may have been changed such as the Structural Model. The Structural
+ * model is in fact itself a "pure" StructuredDocumentListner. The
+ * ModelChangedListeners can rest assured that all models and data have
+ * been updated from the change by the tiem they are notified. This is
+ * especially important for the text widget, for example, which may rely
+ * on both structuredDocument and structural model information.
+ */
+ public void addDocumentChangedListener(IStructuredDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ if (Debug.debugStructuredDocument) {
+ System.out.println("IStructuredDocument::addModelChangedListener. Request to add an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ // make sure listener is not already in listening
+ // (and if it is, print a warning to aid debugging, if needed)
+ if (Utilities.contains(fStructuredDocumentChangedListeners, listener)) {
+ if (Debug.displayWarnings) {
+ System.out.println("IStructuredDocument::addModelChangedListener. listener " + listener + " was addeded more than once. "); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ else {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("IStructuredDocument::addModelChangedListener. Adding an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ int oldSize = 0;
+ if (fStructuredDocumentChangedListeners != null) {
+ // normally won't be null, but we need to be sure, for
+ // first
+ // time through
+ oldSize = fStructuredDocumentChangedListeners.length;
+ }
+ int newSize = oldSize + 1;
+ Object[] newListeners = new Object[newSize];
+ if (fStructuredDocumentChangedListeners != null) {
+ System.arraycopy(fStructuredDocumentChangedListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fStructuredDocumentChangedListeners = newListeners;
+ //
+ // when a listener is added,
+ // send the new model event to that one particular listener,
+ // so it
+ // can initialize itself with the current state of the model
+ // listener.newModel(new NewModelEvent(this, listener));
+ }
+ }
+ }
+
+ public void addDocumentChangingListener(IStructuredDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ if (Debug.debugStructuredDocument) {
+ System.out.println("IStructuredDocument::addStructuredDocumentListener. Request to add an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ // make sure listener is not already in listening
+ // (and if it is, print a warning to aid debugging, if needed)
+ if (Utilities.contains(fStructuredDocumentChangingListeners, listener)) {
+ if (Debug.displayWarnings) {
+ System.out.println("IStructuredDocument::addStructuredDocumentListener. listener " + listener + " was addeded more than once. "); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ else {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("IStructuredDocument::addStructuredDocumentListener. Adding an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ int oldSize = 0;
+ if (fStructuredDocumentChangingListeners != null) {
+ // normally won't be null, but we need to be sure, for
+ // first
+ // time through
+ oldSize = fStructuredDocumentChangingListeners.length;
+ }
+ int newSize = oldSize + 1;
+ Object[] newListeners = new Object[newSize];
+ if (fStructuredDocumentChangingListeners != null) {
+ System.arraycopy(fStructuredDocumentChangingListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fStructuredDocumentChangingListeners = newListeners;
+ //
+ // when a listener is added,
+ // send the new model event to that one particular listener,
+ // so it
+ // can initialize itself with the current state of the model
+ // listener.newModel(new NewModelEvent(this, listener));
+ }
+ }
+ }
+
+ /**
+ * We manage our own document listners, instead of delegating to our
+ * parentDocument, so we can fire at very end (and not when the
+ * parentDocument changes).
+ *
+ */
+ public void addDocumentListener(IDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ // make sure listener is not already in listening
+ // (and if it is, print a warning to aid debugging, if needed)
+ if (!Utilities.contains(fDocumentListeners, listener)) {
+ int oldSize = 0;
+ if (fDocumentListeners != null) {
+ // normally won't be null, but we need to be sure, for
+ // first
+ // time through
+ oldSize = fDocumentListeners.length;
+ }
+ int newSize = oldSize + 1;
+ IDocumentListener[] newListeners = null;
+ newListeners = new IDocumentListener[newSize];
+ if (fDocumentListeners != null) {
+ System.arraycopy(fDocumentListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ // now switch new for old
+ fDocumentListeners = newListeners;
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#addDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
+ *
+ * Registers the document partitioning listener with the document. After
+ * registration the IDocumentPartitioningListener is informed about each
+ * partition change cause by a document manipulation. If a document
+ * partitioning listener is also a document listener, the following
+ * notification sequence is guaranteed if a document manipulation changes
+ * the document partitioning: 1)
+ * listener.documentAboutToBeChanged(DocumentEvent); 2)
+ * listener.documentPartitioningChanged(); 3)
+ * listener.documentChanged(DocumentEvent); If the listener is already
+ * registered nothing happens.
+ *
+ * @see IDocumentPartitioningListener
+ */
+
+ public void addDocumentPartitioningListener(IDocumentPartitioningListener listener) {
+ synchronized (listenerLock) {
+
+ Assert.isNotNull(listener);
+ if (fDocumentPartitioningListeners == null) {
+ fDocumentPartitioningListeners = new ArrayList(1);
+ }
+ if (!fDocumentPartitioningListeners.contains(listener))
+ fDocumentPartitioningListeners.add(listener);
+ }
+ }
+
+ /**
+ * Adds the position to the document's default position category. The
+ * default category must be specified by the implementer. A position that
+ * has been added to a position category is updated at each change applied
+ * to the document.
+ *
+ * @exception BadLocationException
+ * If position is not a valid range in the document
+ */
+ public void addPosition(Position position) throws BadLocationException {
+ getPositionManager().addPosition(position);
+ }
+
+ /**
+ * @see IDocument#addPosition
+ * @exception BadLocationException
+ * If position is not a valid range in the document
+ * @exception BadPositionCategoryException
+ * If the category is not defined for the document
+ */
+ public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException {
+ getPositionManager().addPosition(category, position);
+ }
+
+ /**
+ * @see IDocument#addPositionCategory
+ */
+ public void addPositionCategory(String category) {
+ internal_addPositionCategory(category);
+ }
+
+ /**
+ * @see IDocument#addPositionUpdater
+ */
+ public void addPositionUpdater(IPositionUpdater updater) {
+ internal_addPositionUpdater(updater);
+ }
+
+ /**
+ * Adds the given document listener as one which is notified before those
+ * document listeners added with <code>addDocumentListener</code> are
+ * notified. If the given listener is also registered using
+ * <code>addDocumentListener</code> it will be notified twice. If the
+ * listener is already registered nothing happens.
+ * <p>
+ *
+ * This method is not for public use, it may only be called by
+ * implementers of <code>IDocumentAdapter</code> and only if those
+ * implementers need to implement <code>IDocumentListener</code>.
+ *
+ * @param documentAdapter
+ * the listener to be added as prenotified document listener
+ */
+ public void addPrenotifiedDocumentListener(IDocumentListener documentAdapter) {
+ synchronized (listenerLock) {
+
+ if (fPrenotifiedDocumentListeners != null) {
+ int previousSize = fPrenotifiedDocumentListeners.length;
+ IDocumentListener[] listeners = new IDocumentListener[previousSize + 1];
+ System.arraycopy(fPrenotifiedDocumentListeners, 0, listeners, 0, previousSize);
+ listeners[previousSize] = documentAdapter;
+ fPrenotifiedDocumentListeners = listeners;
+ }
+ else {
+ fPrenotifiedDocumentListeners = new IDocumentListener[1];
+ fPrenotifiedDocumentListeners[0] = documentAdapter;
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#charAt(int)
+ */
+ public char charAt(int arg0) {
+ try {
+ return getChar(0);
+ }
+ catch (BadLocationException e) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * This form of the API removes all read only positions, as should be done
+ * we 'setText' is called. Note: an alternative algorithm may simply
+ * remove the category (and it would get added back in later, if/when
+ * readonly regions added.
+ */
+ private void clearReadOnly() {
+ Position[] positions = null;
+ try {
+ positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
+ }
+ catch (BadPositionCategoryException e) {
+ Logger.logException("program error: should never occur", e); //$NON-NLS-1$
+ }
+ for (int i = 0; i < positions.length; i++) {
+ Position position = positions[i];
+ // note we don't fire the "about to change" or "changed" events,
+ // since presumably, text is all going away and being replaced
+ // anyway.
+ position.delete();
+ }
+ }
+
+
+ public void clearReadOnly(int startOffset, int length) {
+ // TODO DW I still need to implement smarter algorithm that
+ // adust existing RO regions, if needed. For now, I'll just
+ // remove any that overlap.
+ try {
+ Position[] positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
+ for (int i = 0; i < positions.length; i++) {
+ Position position = positions[i];
+ if (position.overlapsWith(startOffset, length)) {
+ String effectedText = this.get(startOffset, length);
+ // fDocumentEvent = new DocumentEvent(this, startOffset,
+ // length, effectedText);
+ fireReadOnlyAboutToBeChanged();
+ position.delete();
+ NoChangeEvent noChangeEvent = new NoChangeEvent(this, null, effectedText, startOffset, length);
+ noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
+ fireReadOnlyStructuredDocumentEvent(noChangeEvent);
+ }
+ }
+ }
+ catch (BadPositionCategoryException e) {
+ // just means no readonly regions been defined yet
+ // so nothing to do.
+ }
+ }
+
+ /**
+ * Computes the index at which a <code>Position</code> with the
+ * specified offset would be inserted into the given category. As the
+ * ordering inside a category only depends on the offset, the index must
+ * be choosen to be the first of all positions with the same offset.
+ *
+ * @param category
+ * the category in which would be added
+ * @param offset
+ * the position offset to be considered
+ * @return the index into the category
+ * @exception BadLocationException
+ * if offset is invalid in this document
+ * @exception BadPositionCategoryException
+ * if category is undefined in this document
+ */
+ public int computeIndexInCategory(String category, int offset) throws org.eclipse.jface.text.BadPositionCategoryException, org.eclipse.jface.text.BadLocationException {
+ return getPositionManager().computeIndexInCategory(category, offset);
+ }
+
+ /**
+ * Computes the number of lines in the given text. For a given implementer
+ * of this interface this method returns the same result as
+ * <code>set(text); getNumberOfLines()</code>.
+ *
+ * @param text
+ * the text whose number of lines should be computed
+ * @return the number of lines in the given text
+ */
+ public int computeNumberOfLines(String text) {
+ return getTracker().computeNumberOfLines(text);
+ }
+
+ /**
+ * Computes the partitioning of the given document range using the
+ * document's partitioner.
+ *
+ * @param offset
+ * the document offset at which the range starts
+ * @param length
+ * the length of the document range
+ * @return a specification of the range's partitioning
+ * @throws BadLocationException
+ * @throws BadPartitioningException
+ */
+ public ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException {
+ ITypedRegion[] typedRegions = null;
+ try {
+ typedRegions = computePartitioning(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, offset, length, false);
+ }
+ catch (BadPartitioningException e) {
+ // impossible in this context
+ throw new Error(e);
+ }
+ if (typedRegions == null) {
+ typedRegions = new ITypedRegion[0];
+ }
+ return typedRegions;
+ }
+
+
+ public ITypedRegion[] computePartitioning(String partitioning, int offset, int length, boolean includeZeroLengthPartitions) throws BadLocationException, BadPartitioningException {
+ if ((0 > offset) || (0 > length) || (offset + length > getLength()))
+ throw new BadLocationException();
+
+ IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
+
+ if (partitioner instanceof IDocumentPartitionerExtension2)
+ return ((IDocumentPartitionerExtension2) partitioner).computePartitioning(offset, length, includeZeroLengthPartitions);
+ else if (partitioner != null)
+ return partitioner.computePartitioning(offset, length);
+ else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
+ return new TypedRegion[]{new TypedRegion(offset, length, DEFAULT_CONTENT_TYPE)};
+ else
+ throw new BadPartitioningException();
+ }
+
+ /**
+ * @see IDocument#containsPosition
+ */
+ public boolean containsPosition(String category, int offset, int length) {
+ return getPositionManager().containsPosition(category, offset, length);
+ }
+
+ /**
+ * @see IDocument#containsPositionCategory
+ */
+ public boolean containsPositionCategory(String category) {
+ return getPositionManager().containsPositionCategory(category);
+ }
+
+ public boolean containsReadOnly(int startOffset, int length) {
+ boolean result = false;
+ try {
+ Position[] positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
+ for (int i = 0; i < positions.length; i++) {
+ Position position = positions[i];
+ if (position.overlapsWith(startOffset, length)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ catch (BadPositionCategoryException e) {
+ // just means no readonly regions been defined yet
+ // so obviously false
+ result = false;
+ }
+ return result;
+ }
+
+ private void executePostNotificationChanges() {
+ if (fStoppedCount > 0)
+ return;
+ while (fPostNotificationChanges != null) {
+ List changes = fPostNotificationChanges;
+ fPostNotificationChanges = null;
+ Iterator e = changes.iterator();
+ while (e.hasNext()) {
+ RegisteredReplace replace = (RegisteredReplace) e.next();
+ replace.fReplace.perform(this, replace.fOwner);
+ }
+ }
+ }
+
+ private void fireDocumentAboutToChanged() {
+ // most DocumentAboutToBeChanged listeners do not anticipate
+ // DocumentEvent == null. So make sure documentEvent is not
+ // null. (this should never happen, yet it does sometimes)
+ if (fDocumentEvent == null) {
+ fDocumentEvent = new NullDocumentEvent();
+ }
+
+ _fireStructuredDocumentAboutToChange(fStructuredDocumentAboutToChangeListeners);
+ // Note: the docEvent is created in replaceText API! (or set Text)
+ _fireDocumentAboutToChange(fPrenotifiedDocumentListeners);
+ notifyDocumentPartitionersAboutToChange(fDocumentEvent);
+ _fireDocumentAboutToChange(fDocumentListeners);
+ }
+
+ /**
+ * Fires the document partitioning changed notification to all registered
+ * document partitioning listeners. Uses a robust iterator.
+ *
+ * @param event
+ * the document partitioning changed event
+ *
+ * @see IDocumentPartitioningListenerExtension2
+ */
+ protected void fireDocumentPartitioningChanged(DocumentPartitioningChangedEvent event) {
+ if (fDocumentPartitioningListeners == null || fDocumentPartitioningListeners.size() == 0)
+ return;
+
+ List list = new ArrayList(fDocumentPartitioningListeners);
+ Iterator e = list.iterator();
+ while (e.hasNext()) {
+ IDocumentPartitioningListener l = (IDocumentPartitioningListener) e.next();
+ if (l instanceof IDocumentPartitioningListenerExtension2) {
+ IDocumentPartitioningListenerExtension2 extension2 = (IDocumentPartitioningListenerExtension2) l;
+ extension2.documentPartitioningChanged(event);
+ }
+ else if (l instanceof IDocumentPartitioningListenerExtension) {
+ IDocumentPartitioningListenerExtension extension = (IDocumentPartitioningListenerExtension) l;
+ extension.documentPartitioningChanged(this, event.getCoverage());
+ }
+ else {
+ l.documentPartitioningChanged(this);
+ }
+ }
+
+ }
+
+ private void fireReadOnlyAboutToBeChanged() {
+ _fireStructuredDocumentAboutToChange(fStructuredDocumentAboutToChangeListeners);
+ // Note: the docEvent is created in replaceText API! (or set Text)
+ // _fireDocumentAboutToChange(fPrenotifiedDocumentListeners);
+ // _fireDocumentAboutToChange(fDocumentListeners);
+ }
+
+ private void fireReadOnlyStructuredDocumentEvent(NoChangeEvent event) {
+ _fireEvent(fStructuredDocumentChangingListeners, event);
+ _fireEvent(fStructuredDocumentChangedListeners, event);
+ // _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
+ // _fireDocumentChanged(fDocumentListeners, event);
+ // _clearDocumentEvent();
+ }
+
+ private void fireStructuredDocumentEvent(NoChangeEvent event) {
+ _fireEvent(fStructuredDocumentChangingListeners, event);
+ _fireEvent(fStructuredDocumentChangedListeners, event);
+ _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
+ notifyDocumentPartitionersDocumentChanged(event);
+ _fireDocumentChanged(fDocumentListeners, event);
+ _clearDocumentEvent();
+ }
+
+ private void fireStructuredDocumentEvent(RegionChangedEvent event) {
+ _fireEvent(fStructuredDocumentChangingListeners, event);
+ _fireEvent(fStructuredDocumentChangedListeners, event);
+ _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
+ notifyDocumentPartitionersDocumentChanged(event);
+ _fireDocumentChanged(fDocumentListeners, event);
+ _clearDocumentEvent();
+ }
+
+ private void fireStructuredDocumentEvent(RegionsReplacedEvent event) {
+ _fireEvent(fStructuredDocumentChangingListeners, event);
+ _fireEvent(fStructuredDocumentChangedListeners, event);
+ _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
+ notifyDocumentPartitionersDocumentChanged(event);
+ _fireDocumentChanged(fDocumentListeners, event);
+ _clearDocumentEvent();
+ }
+
+ private void fireStructuredDocumentEvent(StructuredDocumentRegionsReplacedEvent event) {
+ _fireEvent(fStructuredDocumentChangingListeners, event);
+ _fireEvent(fStructuredDocumentChangedListeners, event);
+ _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
+ notifyDocumentPartitionersDocumentChanged(event);
+ _fireDocumentChanged(fDocumentListeners, event);
+ _clearDocumentEvent();
+ }
+
+ /**
+ * Returns the document's complete text.
+ */
+ public String get() {
+ return getStore().get(0, getLength());
+ }
+
+ /**
+ * Returns length characters from the document's text starting from the
+ * specified position.
+ *
+ * @throws BadLocationException
+ *
+ * @exception BadLocationException
+ * If the range is not valid in the document
+ */
+ public String get(int offset, int length) {
+ String result = null;
+ int myLength = getLength();
+ if (0 > offset)
+ offset = 0;
+ if (0 > length)
+ length = 0;
+ if (offset + length > myLength) {
+ // first try adjusting length to fit
+ int lessLength = myLength - offset;
+ if ((lessLength >= 0) && (offset + lessLength == myLength)) {
+ length = lessLength;
+ }
+ else {
+ // second, try offset
+ int moreOffset = myLength - length;
+ if ((moreOffset >= 0) && (moreOffset + length == myLength)) {
+ offset = moreOffset;
+ }
+ else {
+ // can happen if myLength is 0.
+ // no adjustment possible.
+ result = new String();
+ }
+ }
+
+ }
+ if (result == null) {
+ result = getStore().get(offset, length);
+ }
+ return result;
+ }
+
+ public Object getAdapter(Class adapter) {
+ return Platform.getAdapterManager().getAdapter(this, adapter);
+ }
+
+ IStructuredDocumentRegion getCachedDocumentRegion() {
+ IStructuredDocumentRegion result = null;
+ if (USE_LOCAL_THREAD) {
+ result = fCurrentDocumentRegionCache.get();
+ }
+ else {
+ result = cachedDocumentRegion;
+ }
+ return result;
+ }
+
+ /**
+ * @see IDocument#getChar
+ * @exception BadLocationException
+ * If position is not a valid range in the document
+ */
+ public char getChar(int pos) throws BadLocationException {
+ char result = 0x00;
+ try {
+ result = getStore().get(pos);
+ }
+ catch (IndexOutOfBoundsException e) {
+ throw new BadLocationException(e.getLocalizedMessage());
+ }
+ return result;
+ }
+
+ /**
+ * Returns the type of the document partition containing the given
+ * character position.
+ */
+ public String getContentType(int offset) throws BadLocationException {
+ return getDocumentPartitioner().getContentType(offset);
+ }
+
+
+ public String getContentType(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException {
+ if ((0 > offset) || (offset > getLength()))
+ throw new BadLocationException();
+
+ IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
+
+ if (partitioner instanceof IDocumentPartitionerExtension2)
+ return ((IDocumentPartitionerExtension2) partitioner).getContentType(offset, preferOpenPartitions);
+ else if (partitioner != null)
+ return partitioner.getContentType(offset);
+ else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
+ return DEFAULT_CONTENT_TYPE;
+ else
+ throw new BadPartitioningException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
+ */
+ public String getDefaultLineDelimiter() {
+
+ String lineDelimiter= null;
+
+ try {
+ lineDelimiter= getLineDelimiter(0);
+ } catch (BadLocationException x) {
+ }
+
+ if (lineDelimiter != null)
+ return lineDelimiter;
+
+ if (fInitialLineDelimiter != null)
+ return fInitialLineDelimiter;
+
+ String sysLineDelimiter= System.getProperty("line.separator"); //$NON-NLS-1$
+ String[] delimiters= getLegalLineDelimiters();
+ Assert.isTrue(delimiters.length > 0);
+ for (int i= 0; i < delimiters.length; i++) {
+ if (delimiters[i].equals(sysLineDelimiter)) {
+ lineDelimiter= sysLineDelimiter;
+ break;
+ }
+ }
+
+ if (lineDelimiter == null)
+ lineDelimiter= delimiters[0];
+
+ return lineDelimiter;
+
+ }
+
+ /**
+ * Returns the document's partitioner.
+ *
+ * @see IDocumentPartitioner
+ */
+ public IDocumentPartitioner getDocumentPartitioner() {
+ return getDocumentPartitioner(IDocumentExtension3.DEFAULT_PARTITIONING);
+ }
+
+
+ public IDocumentPartitioner getDocumentPartitioner(String partitioning) {
+
+ IDocumentPartitioner documentPartitioner = null;
+ if (fDocumentPartitioners != null) {
+ documentPartitioner = (IDocumentPartitioner) fDocumentPartitioners.get(partitioning);
+ }
+ return documentPartitioner;
+ }
+
+ public EncodingMemento getEncodingMemento() {
+ return encodingMemento;
+ }
+
+ public IStructuredDocumentRegion getFirstStructuredDocumentRegion() {
+ // should we update cachedNode?
+ // We should to keep consistent philosophy of remembering last
+ // requested position,
+ // for efficiency.
+ setCachedDocumentRegion(firstDocumentRegion);
+ return firstDocumentRegion;
+ }
+
+ public IStructuredDocumentRegion getLastStructuredDocumentRegion() {
+ // should we update cachedNode?
+ // We should to keep consistent philosophy of remembering last
+ // requested position,
+ // for efficiency.
+ setCachedDocumentRegion(lastDocumentRegion);
+ return lastDocumentRegion;
+ }
+
+ /*
+ * -------------------------- partitions
+ * ----------------------------------
+ */
+ public String[] getLegalContentTypes() {
+ String[] result = null;
+ try {
+ result = getLegalContentTypes(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING);
+ }
+ catch (BadPartitioningException e) {
+ // impossible in this context
+ throw new Error(e);
+ }
+ return result;
+ }
+
+ public String[] getLegalContentTypes(String partitioning) throws BadPartitioningException {
+ IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
+ if (partitioner != null)
+ return partitioner.getLegalContentTypes();
+ if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
+ return new String[]{DEFAULT_CONTENT_TYPE};
+ throw new BadPartitioningException();
+ }
+
+ /*
+ * ------------------ line delimiter conversion
+ * ---------------------------
+ */
+ public String[] getLegalLineDelimiters() {
+ return getTracker().getLegalLineDelimiters();
+ }
+
+ /**
+ * @see IDocument#getLength
+ */
+ public int getLength() {
+ return getStore().getLength();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#getLineDelimiter()
+ */
+ public String getLineDelimiter() {
+ return getDefaultLineDelimiter();
+ }
+
+ /**
+ * Returns the line delimiter of that line
+ *
+ * @exception BadLocationException
+ * If the line number is invalid in the document
+ */
+ public String getLineDelimiter(int line) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getLineDelimiter(line);
+ }
+
+ /**
+ * Returns a description of the specified line. The line is described by
+ * its offset and its length excluding the line's delimiter.
+ *
+ * @param line
+ * the line of interest
+ * @return a line description
+ * @exception BadLocationException
+ * if the line number is invalid in this document
+ */
+ public org.eclipse.jface.text.IRegion getLineInformation(int line) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getLineInformation(line);
+ }
+
+ /**
+ * Returns a description of the line at the given offset. The description
+ * contains the offset and the length of the line excluding the line's
+ * delimiter.
+ *
+ * @param offset
+ * the offset whose line should be described
+ * @return a region describing the line
+ * @exception BadLocationException
+ * if offset is invalid in this document
+ */
+ public org.eclipse.jface.text.IRegion getLineInformationOfOffset(int offset) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getLineInformationOfOffset(offset);
+ }
+
+ /*
+ * ---------------------- line information
+ * --------------------------------
+ */
+ public int getLineLength(int line) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getLineLength(line);
+ }
+
+ /**
+ * Determines the offset of the first character of the given line.
+ *
+ * @param line
+ * the line of interest
+ * @return the document offset
+ * @exception BadLocationException
+ * if the line number is invalid in this document
+ */
+ public int getLineOffset(int line) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getLineOffset(line);
+ }
+
+ public int getLineOfOffset(int offset) {
+ int result = -1;
+ try {
+ result = getTracker().getLineNumberOfOffset(offset);
+ }
+ catch (BadLocationException e) {
+ if (Logger.DEBUG_DOCUMENT)
+ Logger.log(Logger.INFO, "Dev. Program Info Only: IStructuredDocument::getLineOfOffset: offset out of range, zero assumed. offset = " + offset, e); //$NON-NLS-1$ //$NON-NLS-2$
+ result = 0;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the number of lines in this document
+ *
+ * @return the number of lines in this document
+ */
+ public int getNumberOfLines() {
+ return getTracker().getNumberOfLines();
+ }
+
+ /**
+ * Returns the number of lines which are occupied by a given text range.
+ *
+ * @param offset
+ * the offset of the specified text range
+ * @param length
+ * the length of the specified text range
+ * @return the number of lines occupied by the specified range
+ * @exception BadLocationException
+ * if specified range is invalid in this tracker
+ */
+ public int getNumberOfLines(int offset, int length) throws org.eclipse.jface.text.BadLocationException {
+ return getTracker().getNumberOfLines(offset, length);
+ }
+
+ /**
+ * This is public, temporarily, for use by tag lib classes.
+ */
+ public RegionParser getParser() {
+ if (fParser == null) {
+ throw new IllegalStateException("IStructuredDocument::getParser. Parser needs to be set before use"); //$NON-NLS-1$
+ }
+ return fParser;
+ }
+
+ /**
+ * Returns the document partition in which the position is located. The
+ * partition is specified as typed region.
+ */
+ public ITypedRegion getPartition(int offset) throws BadLocationException {
+ ITypedRegion partition = null;
+ try {
+ partition = getPartition(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, offset, false);
+ }
+ catch (BadPartitioningException e) {
+ throw new Error(e);
+ }
+ if (partition == null) {
+ throw new Error();
+ }
+ return partition;
+ }
+
+
+ public ITypedRegion getPartition(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException {
+ if ((0 > offset) || (offset > getLength()))
+ throw new BadLocationException();
+ ITypedRegion result = null;
+
+ IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
+
+ if (partitioner instanceof IDocumentPartitionerExtension2) {
+ result = ((IDocumentPartitionerExtension2) partitioner).getPartition(offset, preferOpenPartitions);
+ }
+ else if (partitioner != null) {
+ result = partitioner.getPartition(offset);
+ }
+ else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning)) {
+ result = new TypedRegion(0, getLength(), DEFAULT_CONTENT_TYPE);
+ }
+ else
+ throw new BadPartitioningException();
+ return result;
+ }
+
+
+ public String[] getPartitionings() {
+ if (fDocumentPartitioners == null)
+ return new String[0];
+ String[] partitionings = new String[fDocumentPartitioners.size()];
+ fDocumentPartitioners.keySet().toArray(partitionings);
+ return partitionings;
+ }
+
+ /**
+ * Returns all position categories added to this document.
+ */
+ public String[] getPositionCategories() {
+ return getPositionManager().getPositionCategories();
+ }
+
+ /**
+ * @return Returns the positionManager.
+ */
+ private GenericPositionManager getPositionManager() {
+ if (fPositionManager == null) {
+ fPositionManager = new GenericPositionManager(this);
+ }
+ return fPositionManager;
+ }
+
+ /**
+ * Returns all Positions of the given position category.
+ *
+ * @exception BadPositionCategoryException
+ * If category is not defined for the document
+ */
+ public Position[] getPositions(String category) throws org.eclipse.jface.text.BadPositionCategoryException {
+ return getPositionManager().getPositions(category);
+ }
+
+ /**
+ * @see IDocument#getPositionUpdaters
+ */
+ public IPositionUpdater[] getPositionUpdaters() {
+ return getPositionManager().getPositionUpdaters();
+ }
+
+ /**
+ * This method can return null, which is the case if the offset is just
+ * before or just after the existing text. Compare with
+ * getNodeAtCharacterOffset.
+ */
+ public IStructuredDocumentRegion getRegionAtCharacterOffset(int offset) {
+ IStructuredDocumentRegion result = null;
+
+ // FIXME: need to synch on 'cachedRegion' (but since that's a
+ // constantly changing object, we
+ // can't, so need to add a "region_lock" object, and use it here, and
+ // in re-parser.
+ // Oh, and need to make sure, after synch, that the region is not
+ // deleted, and if so, I guess go back
+ // to the beginning!
+
+ // cached node can be null when document is empty
+ IStructuredDocumentRegion potentialCachedRegion = getCachedDocumentRegion();
+ if (potentialCachedRegion != null) {
+
+ //
+
+ // if we already have the right node, return that.
+ if (potentialCachedRegion.containsOffset(offset)) {
+ result = potentialCachedRegion;
+ }
+ else {
+ // first, find out what direction to go, relative to
+ // cachedNode.
+ // negative means "towards the front" of the file,
+ // postitive
+ // means
+ // towards the end.
+ int direction = offset - potentialCachedRegion.getStart();
+ if (direction < 0) {
+ // search towards beginning
+ while (!potentialCachedRegion.containsOffset(offset)) {
+ IStructuredDocumentRegion tempNode = potentialCachedRegion.getPrevious();
+ if (tempNode == null) {
+ break;
+ }
+ else {
+ potentialCachedRegion = tempNode;
+ }
+ }
+ }
+ else {
+ // search towards end
+ // There is a legitamat condition where the
+ // offset will not be contained in any node,
+ // which is if the offset is just past the last
+ // character of text.
+ // And, we must gaurd against setting cachedNode to
+ // null!
+ while (!potentialCachedRegion.containsOffset(offset)) {
+ IStructuredDocumentRegion tempNode = potentialCachedRegion.getNext();
+ if (tempNode == null)
+ break;
+ else
+ potentialCachedRegion = tempNode;
+ }
+ }
+ }
+ result = potentialCachedRegion;
+ }
+ // just to be doubly sure we never assign null to an already valid
+ // cachedRegion.
+ // I believe any time 'result' is null at this point, that just means
+ // we have an
+ // empty document, and the cachedRegion is already null, but we check
+ // and print
+ // warning, just so during development we be sure we never accidently
+ // break this assumption.
+ if (result != null)
+ setCachedDocumentRegion(result);
+ else if (getCachedDocumentRegion() != null) {
+ throw new IllegalStateException("Program Error: no region could be found to cache, but cache was non null. Indicates corrupted model or region list"); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+
+ public IStructuredDocumentRegionList getRegionList() {
+ CoreNodeList result = null;
+ if (getCachedDocumentRegion() == null)
+ result = new CoreNodeList(null);
+ else
+ result = new CoreNodeList(getFirstStructuredDocumentRegion());
+
+ return result;
+ }
+
+
+ public IStructuredDocumentRegion[] getStructuredDocumentRegions() {
+ return getStructuredDocumentRegions(0, getLength());
+ }
+
+ /**
+ * <p>
+ * In the case of 0 length, the <code>IStructuredDocumentRegion</code>
+ * at the character offset is returened. In other words, the region to the
+ * right of the caret is returned. except for at the end of the document,
+ * then the last region is returned.
+ * </p>
+ * <p>
+ * Otherwise all the regions "inbetween" the indicated range are returned,
+ * including the regions which overlap the region.
+ * </p>
+ *
+ * <br>
+ * eg.
+ * <p>
+ * <br>
+ * eg.
+ *
+ * <pre>
+ * &lt;html&gt;[&lt;head&gt;&lt;/head&gt;]&lt;/html&gt; returns &lt;head&gt;,&lt;/head&gt;
+ * </pre>
+ * <pre>
+ * &lt;ht[ml&gt;&lt;head&gt;&lt;/he]ad&gt;&lt;/html&gt; returns &lt;html&gt;,&lt;head&gt;,&lt;/head&gt;
+ * </pre>
+ *
+ * <pre>
+ * &lt;html&gt;[&lt;head&gt;&lt;/head&gt;]&lt;/html&gt; returns &lt;head&gt;,&lt;/head&gt;
+ * </pre>
+ * <pre>
+ * &lt;ht[ml&gt;&lt;head&gt;&lt;/he]ad&gt;&lt;/html&gt; returns &lt;html&gt;,&lt;head&gt;,&lt;/head&gt;
+ * </pre>
+ *
+ * </p>
+ */
+ public IStructuredDocumentRegion[] getStructuredDocumentRegions(int start, int length) {
+
+ if (length < 0)
+ throw new IllegalArgumentException("can't have negative length"); //$NON-NLS-1$
+
+ // this will make the right edge of the range point into the selection
+ // eg. <html>[<head></head>]</html>
+ // will return <head>,</head> instead of <head>,</head>,</html>
+ if (length > 0)
+ length--;
+
+ List results = new ArrayList();
+
+ // start thread safe block
+ try {
+ acquireLock();
+
+ IStructuredDocumentRegion currentRegion = getRegionAtCharacterOffset(start);
+ IStructuredDocumentRegion endRegion = getRegionAtCharacterOffset(start + length);
+ while (currentRegion != endRegion && currentRegion != null) {
+ results.add(currentRegion);
+ currentRegion = currentRegion.getNext();
+ }
+ // need to add that last end region
+ // can be null in the case of an empty document
+ if (endRegion != null)
+ results.add(endRegion);
+ }
+ finally {
+ releaseLock();
+ }
+ // end thread safe block
+
+ return (IStructuredDocumentRegion[]) results.toArray(new IStructuredDocumentRegion[results.size()]);
+ }
+
+ /**
+ * was made public for easier testing. Normally should never be used by
+ * client codes.
+ */
+ public IStructuredTextReParser getReParser() {
+ if (fReParser == null) {
+ fReParser = new StructuredDocumentReParser();
+ fReParser.setStructuredDocument(this);
+ }
+ return fReParser;
+ }
+
+ private ITextStore getStore() {
+ return fStore;
+ }
+
+ public String getText() {
+ String result = get();
+ return result;
+ }
+
+ /**
+ * Returns the document's line tracker. Assumes that the document has been
+ * initialized with a line tracker.
+ *
+ * @return the document's line tracker
+ */
+ private ILineTracker getTracker() {
+ return fTracker;
+ }
+
+ public IStructuredTextUndoManager getUndoManager() {
+ if (fUndoManager == null) {
+ fUndoManager = new StructuredTextUndoManager();
+ }
+ return fUndoManager;
+ }
+
+ void initializeFirstAndLastDocumentRegion() {
+ // cached Node must also be first, at the initial point. Only
+ // valid
+ // to call this method right after the first parse.
+ //
+ // when starting afresh, our cachedNode should be our firstNode,
+ // so be sure to initialize the firstNode
+ firstDocumentRegion = getCachedDocumentRegion();
+ // be sure to use 'getNext' for this initial finding of the last
+ // node,
+ // since the implementation of node.getLastNode may simply call
+ // structuredDocument.getLastStructuredDocumentRegion!
+ IStructuredDocumentRegion aNode = firstDocumentRegion;
+ if (aNode == null) {
+ // defect 254607: to handle empty documents right, if
+ // firstnode is
+ // null, make sure last node is null too
+ lastDocumentRegion = null;
+ }
+ else {
+ while (aNode != null) {
+ lastDocumentRegion = aNode;
+ aNode = aNode.getNext();
+ }
+ }
+ }
+
+ /**
+ * @see IDocument#insertPositionUpdater
+ */
+ public void insertPositionUpdater(IPositionUpdater updater, int index) {
+ getPositionManager().insertPositionUpdater(updater, index);
+ }
+
+ private void internal_addPositionCategory(String category) {
+ getPositionManager().addPositionCategory(category);
+ }
+
+ private void internal_addPositionUpdater(IPositionUpdater updater) {
+ getPositionManager().addPositionUpdater(updater);
+ }
+
+ private void internal_setParser(RegionParser newParser) {
+ fParser = newParser;
+ }
+
+ String internalGet(int offset, int length) {
+ String result = null;
+ // int myLength = getLength();
+ // if ((0 > offset) || (0 > length) || (offset + length > myLength))
+ // throw new BadLocationException();
+ result = getStore().get(offset, length);
+ return result;
+ }
+
+ /**
+ * @param requester
+ * @param start
+ * @param replacementLength
+ * @param changes
+ * @param modificationStamp
+ * @param ignoreReadOnlySettings
+ * @return
+ */
+ private StructuredDocumentEvent internalReplaceText(Object requester, int start, int replacementLength, String changes, long modificationStamp, boolean ignoreReadOnlySettings) {
+ StructuredDocumentEvent result = null;
+
+ stopPostNotificationProcessing();
+ if (changes == null)
+ changes = ""; //$NON-NLS-1$
+ //
+ if (Debug.debugStructuredDocument)
+ System.out.println(getClass().getName() + "::replaceText(" + start + "," + replacementLength + "," + changes + ")"); //$NON-NLS-4$//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
+ if (Debug.perfTestStructuredDocumentOnly || Debug.perfTest || Debug.perfTestRawStructuredDocumentOnly) {
+ startStreamTime = System.currentTimeMillis();
+ }
+ try {
+ // Note: event must be computed before 'fire' method called
+ fDocumentEvent = new DocumentEvent(this, start, replacementLength, changes);
+ fireDocumentAboutToChanged();
+
+ try {
+ acquireLock();
+
+ if (!ignoreReadOnlySettings && (containsReadOnly(start, replacementLength))) {
+ NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
+ noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
+ result = noChangeEvent;
+ }
+ else {
+ result = updateModel(requester, start, replacementLength, changes);
+ }
+ }
+ finally {
+ releaseLock();
+ }
+
+
+ if (Debug.perfTestRawStructuredDocumentOnly || Debug.perfTest) {
+ long stopStreamTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t Time for IStructuredDocument raw replaceText: " + (stopStreamTime - startStreamTime)); //$NON-NLS-1$
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("event type returned by replaceTextWithNoDebuggingThread: " + result); //$NON-NLS-1$
+ }
+ }
+ finally {
+ // FUTURE_TO_DO: implement callback mechanism? to avoid instanceof
+ // and casting
+ // fireStructuredDocumentEvent must be called in order to end
+ // documentAboutToBeChanged state
+
+
+ // increment modification stamp if modifications were made
+ if (result != null && !(result instanceof NoChangeEvent)) {
+ fModificationStamp= modificationStamp;
+ fNextModificationStamp= Math.max(fModificationStamp, fNextModificationStamp);
+ fDocumentEvent.fModificationStamp = fModificationStamp;
+ }
+
+ if (result == null) {
+ // result should not be null, but if an exception was thrown,
+ // it will be
+ // so send a noChangeEvent and log the problem
+ NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
+ noChangeEvent.reason = NoChangeEvent.NO_EVENT;
+ fireStructuredDocumentEvent(noChangeEvent);
+ Logger.log(Logger.ERROR, "Program Error: invalid structured document event"); //$NON-NLS-1$
+ }
+ else {
+ if (result instanceof RegionChangedEvent) {
+ fireStructuredDocumentEvent((RegionChangedEvent) result);
+ }
+ else {
+ if (result instanceof RegionsReplacedEvent) {
+ fireStructuredDocumentEvent((RegionsReplacedEvent) result);
+ }
+ else {
+ if (result instanceof StructuredDocumentRegionsReplacedEvent) {
+ // probably more efficient to mark old regions as
+ // 'deleted' at the time
+ // that are determined to be deleted, but I'll do
+ // here
+ // in then central spot
+ // for programming ease.
+ updateDeletedFields((StructuredDocumentRegionsReplacedEvent) result);
+ fireStructuredDocumentEvent((StructuredDocumentRegionsReplacedEvent) result);
+ }
+ else {
+ if (result instanceof NoChangeEvent) {
+ fireStructuredDocumentEvent((NoChangeEvent) result);
+ }
+ else {
+ // if here, this means a new event was created
+ // and not handled here
+ // just send a no event until this issue is
+ // resolved.
+ NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
+ noChangeEvent.reason = NoChangeEvent.NO_EVENT;
+ fireStructuredDocumentEvent(noChangeEvent);
+ Logger.log(Logger.INFO, "Program Error: unexpected structured document event: " + result); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+ }
+
+ if (Debug.perfTest || Debug.perfTestStructuredDocumentOnly) {
+ long stopStreamTime = System.currentTimeMillis();
+ System.out.println("\n\t\t\t\t Total Time for IStructuredDocument event signaling/processing in replaceText: " + (stopStreamTime - startStreamTime)); //$NON-NLS-1$
+ }
+ resumePostNotificationProcessing();
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#length()
+ */
+ public int length() {
+
+ return getLength();
+ }
+
+ public void makeReadOnly(int startOffset, int length) {
+ makeReadOnly(startOffset, length, false, false);
+ }
+
+ public void makeReadOnly(int startOffset, int length, boolean canInsertBefore, boolean canInsertAfter) {
+ // doesn't make sense to have a readonly region of 0 length,
+ // so we'll ignore those requests
+ if (length <= 0)
+ return;
+ String affectedText = this.get(startOffset, length);
+ // a document event for "read only" change ... must
+ // be followed by "no change" structuredDocument event
+ // fDocumentEvent = new DocumentEvent(this, startOffset, length,
+ // affectedText);
+ fireReadOnlyAboutToBeChanged();
+ // if (containsReadOnly(startOffset, length)) {
+ // adjustReadOnlyRegions(startOffset, length);
+ // } else {
+ // we can blindly add category, since no harm done if already
+ // exists.
+ addPositionCategory(READ_ONLY_REGIONS_CATEGORY);
+ Position newPosition = new ReadOnlyPosition(startOffset, length, canInsertBefore);
+ try {
+ addPosition(READ_ONLY_REGIONS_CATEGORY, newPosition);
+ // FIXME: need to change API to pass in requester, so this event
+ // can be
+ // created correctly, instead of using null.
+ NoChangeEvent noChangeEvent = new NoChangeEvent(this, null, affectedText, startOffset, length);
+ noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
+ fireReadOnlyStructuredDocumentEvent(noChangeEvent);
+ }
+ catch (BadLocationException e) {
+ // for now, log and ignore. Perhaps later we
+ // could adjust to handle some cases?
+ Logger.logException(("could not create readonly region at " + startOffset + " to " + length), e); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ catch (BadPositionCategoryException e) {
+ // should never occur, since we add category
+ Logger.logException(e);
+ }
+ }
+
+ public IStructuredDocument newInstance() {
+ IStructuredDocument newInstance = StructuredDocumentFactory.getNewStructuredDocumentInstance(getParser().newInstance());
+ ((BasicStructuredDocument) newInstance).setReParser(getReParser().newInstance());
+ if (getDocumentPartitioner() instanceof StructuredTextPartitioner) {
+ newInstance.setDocumentPartitioner(((StructuredTextPartitioner) getDocumentPartitioner()).newInstance());
+ newInstance.getDocumentPartitioner().connect(newInstance);
+ }
+ newInstance.setLineDelimiter(getLineDelimiter());
+ if (getEncodingMemento() != null) {
+ newInstance.setEncodingMemento((EncodingMemento) getEncodingMemento().clone());
+ }
+ return newInstance;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.internal.text.IRegionComparible#regionMatches(int,
+ * int, java.lang.String)
+ */
+ public boolean regionMatches(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ ITextStore store = getStore();
+ if (store instanceof IRegionComparible) {
+ result = ((IRegionComparible) store).regionMatches(offset, length, stringToCompare);
+ }
+ else {
+ result = get(offset, length).equals(stringToCompare);
+ }
+ return result;
+ }
+
+ public boolean regionMatchesIgnoreCase(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ ITextStore store = getStore();
+ if (store instanceof IRegionComparible) {
+ result = ((IRegionComparible) store).regionMatchesIgnoreCase(offset, length, stringToCompare);
+ }
+ else {
+ result = get(offset, length).equalsIgnoreCase(stringToCompare);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#registerPostNotificationReplace(org.eclipse.jface.text.IDocumentListener,
+ * org.eclipse.jface.text.IDocumentExtension.IReplace)
+ */
+ public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
+ if (fAcceptPostNotificationReplaces) {
+ if (fPostNotificationChanges == null)
+ fPostNotificationChanges = new ArrayList(1);
+ fPostNotificationChanges.add(new RegisteredReplace(owner, replace));
+ }
+ }
+
+ protected void releaseLock() {
+ // do nothing here in super class
+ }
+
+ public void removeDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
+ synchronized (listenerLock) {
+
+ if ((fStructuredDocumentAboutToChangeListeners != null) && (listener != null)) {
+ // if its not in the listeners, we'll ignore the request
+ if (Utilities.contains(fStructuredDocumentAboutToChangeListeners, listener)) {
+ int oldSize = fStructuredDocumentAboutToChangeListeners.length;
+ int newSize = oldSize - 1;
+ Object[] newListeners = new Object[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fStructuredDocumentAboutToChangeListeners[i] == listener) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are
+ // removing
+ newListeners[index++] = fStructuredDocumentAboutToChangeListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the
+ // old
+ // one
+ fStructuredDocumentAboutToChangeListeners = newListeners;
+ }
+ }
+ }
+ }
+
+ /**
+ * removeModelChangedListener method comment.
+ */
+ public void removeDocumentChangedListener(IStructuredDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ if ((fStructuredDocumentChangedListeners != null) && (listener != null)) {
+ // if its not in the listeners, we'll ignore the request
+ if (Utilities.contains(fStructuredDocumentChangedListeners, listener)) {
+ int oldSize = fStructuredDocumentChangedListeners.length;
+ int newSize = oldSize - 1;
+ Object[] newListeners = new Object[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fStructuredDocumentChangedListeners[i] == listener) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are
+ // removing
+ newListeners[index++] = fStructuredDocumentChangedListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the
+ // old
+ // one
+ fStructuredDocumentChangedListeners = newListeners;
+ }
+ }
+ }
+ }
+
+ public void removeDocumentChangingListener(IStructuredDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ if ((fStructuredDocumentChangingListeners != null) && (listener != null)) {
+ // if its not in the listeners, we'll ignore the request
+ if (Utilities.contains(fStructuredDocumentChangingListeners, listener)) {
+ int oldSize = fStructuredDocumentChangingListeners.length;
+ int newSize = oldSize - 1;
+ Object[] newListeners = new Object[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fStructuredDocumentChangingListeners[i] == listener) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are
+ // removing
+ newListeners[index++] = fStructuredDocumentChangingListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the
+ // old
+ // one
+ fStructuredDocumentChangingListeners = newListeners;
+ }
+ }
+ }
+ }
+
+ public void removeDocumentListener(IDocumentListener listener) {
+ synchronized (listenerLock) {
+
+ if ((fDocumentListeners != null) && (listener != null)) {
+ // if its not in the listeners, we'll ignore the request
+ if (Utilities.contains(fDocumentListeners, listener)) {
+ int oldSize = fDocumentListeners.length;
+ int newSize = oldSize - 1;
+ IDocumentListener[] newListeners = new IDocumentListener[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fDocumentListeners[i] == listener) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are
+ // removing
+ newListeners[index++] = fDocumentListeners[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the
+ // old
+ // one
+ fDocumentListeners = newListeners;
+ }
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#removeDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
+ */
+ public void removeDocumentPartitioningListener(IDocumentPartitioningListener listener) {
+ synchronized (listenerLock) {
+
+ Assert.isNotNull(listener);
+ if (fDocumentPartitioningListeners != null)
+ fDocumentPartitioningListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Removes the given <code>Position</code> from the document's default
+ * position category. The default position category is to be defined by
+ * the implementers. If the position is not part of the document's default
+ * category nothing happens.
+ */
+ public void removePosition(Position position) {
+ getPositionManager().removePosition(position);
+ }
+
+ /**
+ * @see IDocument#removePosition
+ * @exception BadPositionCategoryException
+ * If the category is not defined for the document
+ */
+ public void removePosition(String category, Position position) throws BadPositionCategoryException {
+ getPositionManager().removePosition(category, position);
+ }
+
+ /**
+ * @see IDocument#removePositionCategory
+ * @exception BadPositionCategoryException
+ * If the category is not defined for the document
+ */
+ public void removePositionCategory(String category) throws BadPositionCategoryException {
+ getPositionManager().removePositionCategory(category);
+ }
+
+ /**
+ * @see IDocument#removePositionUpdater
+ */
+ public void removePositionUpdater(IPositionUpdater updater) {
+ getPositionManager().removePositionUpdater(updater);
+ }
+
+ /**
+ * Removes the given document listener from teh document's list of
+ * prenotified document listeners. If the listener is not registered with
+ * the document nothing happens.
+ * <p>
+ *
+ * This method is not for public use, it may only be called by
+ * implementers of <code>IDocumentAdapter</code> and only if those
+ * implementers need to implement <code>IDocumentListener</code>.
+ *
+ * @param documentAdapter
+ * the listener to be removed
+ *
+ * @see #addPrenotifiedDocumentListener(IDocumentListener)
+ */
+ public void removePrenotifiedDocumentListener(org.eclipse.jface.text.IDocumentListener documentAdapter) {
+ synchronized (listenerLock) {
+
+ if (Utilities.contains(fPrenotifiedDocumentListeners, documentAdapter)) {
+ int previousSize = fPrenotifiedDocumentListeners.length;
+ if (previousSize > 1) {
+ IDocumentListener[] listeners = new IDocumentListener[previousSize - 1];
+ int previousIndex = 0;
+ int newIndex = 0;
+ while (previousIndex < previousSize) {
+ if (fPrenotifiedDocumentListeners[previousIndex] != documentAdapter)
+ listeners[newIndex++] = fPrenotifiedDocumentListeners[previousIndex];
+ previousIndex++;
+ }
+ fPrenotifiedDocumentListeners = listeners;
+ }
+ else {
+ fPrenotifiedDocumentListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is for INTERNAL USE ONLY and is NOT API.
+ *
+ * Rebuilds the StructuredDocumentRegion chain from the existing text.
+ * FileBuffer support does not allow clients to know the document's
+ * location before the text contents are set.
+ *
+ * @see set(String)
+ */
+ public void reparse(Object requester) {
+ // check if we're already making document-wide changes on this thread
+ if (fStoppedCount > 0)
+ return;
+
+ stopPostNotificationProcessing();
+ clearReadOnly();
+
+ try {
+ acquireLock();
+
+ CharSequenceReader subSetTextStoreReader = new CharSequenceReader((CharSequence) getStore(), 0, getStore().getLength());
+ resetParser(subSetTextStoreReader, 0);
+ //
+ setCachedDocumentRegion(getParser().getDocumentRegions());
+ // when starting afresh, our cachedNode should be our firstNode,
+ // so be sure to initialize the firstNode and lastNode
+ initializeFirstAndLastDocumentRegion();
+ StructuredDocumentRegionIterator.setParentDocument(getCachedDocumentRegion(), this);
+ }
+ finally {
+ releaseLock();
+ }
+
+ resumePostNotificationProcessing();
+ }
+
+ /**
+ * @see IDocument#replace
+ * @exception BadLocationException
+ * If position is not a valid range in the document
+ */
+ public void replace(int offset, int length, String text) throws BadLocationException {
+ if (Debug.displayWarnings) {
+ System.out.println("Note: IStructuredDocument::replace(int, int, String) .... its better to use replaceText(source, string, int, int) API for structuredDocument updates"); //$NON-NLS-1$
+ }
+ replaceText(this, offset, length, text);
+ }
+
+ /**
+ * Replace the text with "newText" starting at position "start" for a
+ * length of "replaceLength".
+ * <p>
+ *
+ * @param pos
+ * start offset of text to replace None of the offsets include
+ * delimiters of preceeding lines. Offset 0 is the first
+ * character of the document.
+ * @param length
+ * start offset of text to replace
+ * @param text
+ * start offset of text to replace
+ * <p>
+ * Implementors have to notify TextChanged listeners after the
+ * content has been updated. The TextChangedEvent should be set
+ * as follows:
+ *
+ * event.type = SWT.TextReplaced event.start = start of the replaced text
+ * event.numReplacedLines = number of replaced lines event.numNewLines =
+ * number of new lines event.replacedLength = length of the replaced text
+ * event.newLength = length of the new text
+ *
+ * NOTE: numNewLines is the number of inserted lines and numReplacedLines
+ * is the number of deleted lines based on the change that occurs
+ * visually. For example:
+ *
+ * replacedText newText numReplacedLines numNewLines "" "\n" 0 1 "\n\n"
+ * "a" 2 0 "a" "\n\n" 0 2
+ */
+ /**
+ * One of the APIs to manipulate the IStructuredDocument in terms of text.
+ */
+ public StructuredDocumentEvent replaceText(Object requester, int pos, int length, String text) {
+ if (length == 0 && (text == null || text.length() == 0))
+ return replaceText(requester, pos, length, text, getModificationStamp(), true);
+ else
+ return replaceText(requester, pos, length, text, getNextModificationStamp(), true);
+ }
+
+ public StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes, boolean ignoreReadOnlySettings) {
+ long modificationStamp;
+
+ if (replacementLength == 0 && (changes == null || changes.length() == 0))
+ modificationStamp = getModificationStamp();
+ else
+ modificationStamp = getNextModificationStamp();
+
+ return replaceText(requester, start, replacementLength, changes, modificationStamp, ignoreReadOnlySettings);
+ }
+
+ private StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes, long modificationStamp, boolean ignoreReadOnlySettings) {
+ StructuredDocumentEvent event = internalReplaceText(requester, start, replacementLength, changes, modificationStamp, ignoreReadOnlySettings);
+ return event;
+ }
+
+ void resetParser(int startOffset, int endOffset) {
+
+ RegionParser parser = getParser();
+ ITextStore textStore = getStore();
+ if (textStore instanceof CharSequence) {
+ CharSequenceReader subSetTextStoreReader = new CharSequenceReader((CharSequence) textStore, startOffset, endOffset - startOffset);
+ parser.reset(subSetTextStoreReader, startOffset);
+ }
+ else {
+ String newNodeText = get(startOffset, endOffset - startOffset);
+ parser.reset(newNodeText, startOffset);
+
+ }
+
+ }
+
+ void resetParser(Reader reader, int startOffset) {
+ RegionParser parser = getParser();
+ parser.reset(reader, startOffset);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#resumePostNotificationProcessing()
+ */
+ public void resumePostNotificationProcessing() {
+ --fStoppedCount;
+ if (fStoppedCount == 0 && fReentranceCount == 0)
+ executePostNotificationChanges();
+ }
+
+ /**
+ * @deprecated in superclass in 3.0 - use a FindReplaceDocumentAdapter
+ * directly
+ * @see IDocument#search
+ */
+ public int search(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException {
+ // (dmw) I added this warning, to know if still being used. I'm not
+ // sure it
+ // works as expected any longer.
+ // but the warning should be removed, once know.
+ Logger.log(Logger.INFO, "WARNING: using unsupported deprecated method 'search'"); //$NON-NLS-1$
+ int offset = -1;
+ IRegion match = new FindReplaceDocumentAdapter(this).find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, false);
+ if (match != null) {
+ offset = match.getOffset();
+ }
+ return offset;
+ }
+
+ /**
+ * @see IDocument#setText
+ */
+ public void set(String string) {
+ if (Debug.displayInfo) {
+ System.out.println("Note: IStructuredDocument::setText(String) .... its better to use setText(source, string) API for structuredDocument updates"); //$NON-NLS-1$
+ }
+ setText(null, string);
+ }
+
+ /**
+ * This may be marked public, but should be packaged protected, once
+ * refactoring is complete (in other words, not for client use).
+ */
+ public void setCachedDocumentRegion(IStructuredDocumentRegion structuredRegion) {
+ if (USE_LOCAL_THREAD) {
+ fCurrentDocumentRegionCache.set(structuredRegion);
+ }
+ else {
+ cachedDocumentRegion = structuredRegion;
+ }
+ }
+
+ /**
+ * Sets the document's partitioner.
+ *
+ * @see IDocumentPartitioner
+ */
+ public void setDocumentPartitioner(IDocumentPartitioner partitioner) {
+ setDocumentPartitioner(IDocumentExtension3.DEFAULT_PARTITIONING, partitioner);
+ }
+
+
+ public void setDocumentPartitioner(String partitioning, IDocumentPartitioner partitioner) {
+ if (partitioner == null) {
+ if (fDocumentPartitioners != null) {
+ fDocumentPartitioners.remove(partitioning);
+ if (fDocumentPartitioners.size() == 0)
+ fDocumentPartitioners = null;
+ }
+ }
+ else {
+ if (fDocumentPartitioners == null)
+ fDocumentPartitioners = new HashMap();
+ fDocumentPartitioners.put(partitioning, partitioner);
+ }
+ DocumentPartitioningChangedEvent event = new DocumentPartitioningChangedEvent(this);
+ event.setPartitionChange(partitioning, 0, getLength());
+ fireDocumentPartitioningChanged(event);
+ }
+
+ public void setEncodingMemento(EncodingMemento encodingMemento) {
+ this.encodingMemento = encodingMemento;
+ }
+
+ void setFirstDocumentRegion(IStructuredDocumentRegion region) {
+ firstDocumentRegion = region;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#setInitialLineDelimiter(java.lang.String)
+ */
+ public void setInitialLineDelimiter(String lineDelimiter) {
+ // make sure our preferred delimiter is
+ // one of the legal ones
+ if (Utilities.containsString(getLegalLineDelimiters(), lineDelimiter)) {
+ fInitialLineDelimiter= lineDelimiter;
+ }
+ else {
+ if (Logger.DEBUG_DOCUMENT)
+ Logger.log(Logger.INFO, "Attempt to set linedelimiter to non-legal delimiter"); //$NON-NLS-1$ //$NON-NLS-2$
+ fInitialLineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, System.getProperty("line.separator"), new IScopeContext[] { new InstanceScope() });//$NON-NLS-1$
+ }
+ }
+
+ void setLastDocumentRegion(IStructuredDocumentRegion region) {
+ lastDocumentRegion = region;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#setLineDelimiter(java.lang.String)
+ */
+ public void setLineDelimiter(String delimiter) {
+ setInitialLineDelimiter(delimiter);
+ }
+
+ /**
+ * Sets the document's line tracker. Must be called at the beginning of
+ * the constructor.
+ *
+ * @param tracker
+ * the document's line tracker
+ */
+ private void setLineTracker(ILineTracker tracker) {
+ Assert.isNotNull(tracker);
+ fTracker = tracker;
+ }
+
+ public void setParser(RegionParser newParser) {
+ internal_setParser(newParser);
+ }
+
+ /**
+ * @param positionManager
+ * The positionManager to set.
+ */
+ // TODO: make private is needed, else remove
+ void setPositionManager(GenericPositionManager positionManager) {
+ fPositionManager = positionManager;
+ }
+
+ /**
+ *
+ */
+ public void setReParser(IStructuredTextReParser newReParser) {
+ fReParser = newReParser;
+ if (fReParser != null) {
+ fReParser.setStructuredDocument(this);
+ }
+ }
+
+ /**
+ * One of the APIs to manipulate the IStructuredDocument in terms of text.
+ */
+ public StructuredDocumentEvent setText(Object requester, String theString) {
+ StructuredDocumentEvent result = null;
+ result = replaceText(requester, 0, getLength(), theString, getNextModificationStamp(), true);
+ return result;
+ }
+
+ /**
+ * Sets the document's text store. Must be called at the beginning of the
+ * constructor.
+ *
+ * @param store
+ * the document's text store
+ */
+ private void setTextStore(ITextStore store) {
+ Assert.isNotNull(store);
+ fStore = store;
+ }
+
+ public void setUndoManager(IStructuredTextUndoManager undoManager) {
+
+ // if the undo manager has already been set, then
+ // fail fast, since changing the undo manager will lead
+ // to unusual results (or at least loss of undo stack).
+ if (fUndoManager != null && fUndoManager != undoManager) {
+ throw new IllegalArgumentException("can not change undo manager once its been set"); //$NON-NLS-1$
+ }
+ else {
+ fUndoManager = undoManager;
+ }
+ }
+
+
+ /*
+ * {@inheritDoc}
+ */
+ public void startSequentialRewrite(boolean normalized) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing()
+ */
+ public void stopPostNotificationProcessing() {
+ ++fStoppedCount;
+ }
+
+
+ /*
+ * {@inheritDoc}
+ */
+ public void stopSequentialRewrite() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#subSequence(int, int)
+ */
+ public CharSequence subSequence(int arg0, int arg1) {
+ return get(arg0, arg1);
+ }
+
+ /**
+ * @param result
+ */
+ private void updateDeletedFields(StructuredDocumentRegionsReplacedEvent event) {
+ IStructuredDocumentRegionList oldRegions = event.getOldStructuredDocumentRegions();
+ for (int i = 0; i < oldRegions.getLength(); i++) {
+ IStructuredDocumentRegion structuredDocumentRegion = oldRegions.item(i);
+ structuredDocumentRegion.setDeleted(true);
+ }
+
+ }
+
+ /**
+ * Called by re-parser. Note: this method may be "public" but should only
+ * be called by re-parsers in the right circumstances.
+ */
+ public void updateDocumentData(int start, int lengthToReplace, String changes) {
+ stopPostNotificationProcessing();
+ getStore().replace(start, lengthToReplace, changes);
+ try {
+ getTracker().replace(start, lengthToReplace, changes);
+ }
+
+ catch (BadLocationException e) {
+ // should be impossible here, but will log for now
+ Logger.logException(e);
+ }
+ if (fPositionManager != null) {
+ fPositionManager.updatePositions(new DocumentEvent(this, start, lengthToReplace, changes));
+ }
+ fModificationStamp++;
+ fNextModificationStamp= Math.max(fModificationStamp, fNextModificationStamp);
+ resumePostNotificationProcessing();
+ }
+
+ private StructuredDocumentEvent updateModel(Object requester, int start, int lengthToReplace, String changes) {
+ StructuredDocumentEvent result = null;
+ IStructuredTextReParser reParser = getReParser();
+ // initialize the IStructuredTextReParser with the standard data
+ // that's
+ // always needed
+ reParser.initialize(requester, start, lengthToReplace, changes);
+ result = reParser.reparse();
+ // if result is null at this point, then there must be an error, since
+ // even if there
+ // was no change (either disallow due to readonly, or a person pasted
+ // the same thing
+ // they had selected) then a "NoChange" event should have been fired.
+ Assert.isNotNull(result, "no structuredDocument event was created in IStructuredDocument::updateStructuredDocument"); //$NON-NLS-1$
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument#getPreferredLineDelimiter()
+ */
+ public String getPreferredLineDelimiter() {
+ return getDefaultLineDelimiter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument#setPreferredLineDelimiter(java.lang.String)
+ */
+ public void setPreferredLineDelimiter(String probableLineDelimiter) {
+ setInitialLineDelimiter(probableLineDelimiter);
+
+ }
+
+
+ /**
+ * Class which implements the rewritable session for the SSE.
+ *
+ */
+ static class StructuredDocumentRewriteSession extends DocumentRewriteSession {
+
+ /**
+ * Creates a new session.
+ *
+ * @param sessionType
+ * the type of this session
+ */
+ protected StructuredDocumentRewriteSession(DocumentRewriteSessionType sessionType) {
+ super(sessionType);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#startRewriteSession(org.eclipse.jface.text.DocumentRewriteSessionType)
+ */
+ public DocumentRewriteSession startRewriteSession(DocumentRewriteSessionType sessionType) throws IllegalStateException {
+ // delegate to sub-class, so UI threading is handled correctly
+ return internalStartRewriteSession(sessionType);
+ }
+
+ /**
+ * NOT-API. Final protected so clients may call this method if needed, but
+ * cannot override.
+ *
+ * @param sessionType
+ * @return
+ * @throws IllegalStateException
+ */
+ final protected DocumentRewriteSession internalStartRewriteSession(DocumentRewriteSessionType sessionType) throws IllegalStateException {
+ if (getActiveRewriteSession() != null)
+ throw new IllegalStateException("already in a rewrite session");
+
+ DocumentRewriteSession session = new StructuredDocumentRewriteSession(sessionType);
+ DocumentRewriteSessionEvent event = new DocumentRewriteSessionEvent(this, session, DocumentRewriteSessionEvent.SESSION_START);
+ fireDocumentRewriteSessionEvent(event);
+
+ ILineTracker tracker = getTracker();
+ if (tracker instanceof ILineTrackerExtension) {
+ ILineTrackerExtension extension = (ILineTrackerExtension) tracker;
+ extension.startRewriteSession(session);
+ }
+
+ startRewriteSessionOnPartitioners(session);
+
+ if (DocumentRewriteSessionType.SEQUENTIAL == sessionType)
+ startSequentialRewrite(false);
+ else if (DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType)
+ startSequentialRewrite(true);
+
+ fActiveRewriteSession = session;
+ return session;
+ }
+
+ /**
+ * Starts the given rewrite session.
+ *
+ * @param session the rewrite session
+ * @since 2.0
+ */
+ final void startRewriteSessionOnPartitioners(DocumentRewriteSession session) {
+ if (fDocumentPartitioners != null) {
+ Iterator e= fDocumentPartitioners.values().iterator();
+ while (e.hasNext()) {
+ Object partitioner= e.next();
+ if (partitioner instanceof IDocumentPartitionerExtension3) {
+ IDocumentPartitionerExtension3 extension= (IDocumentPartitionerExtension3) partitioner;
+ extension.startRewriteSession(session);
+ }
+ }
+ }
+ }
+
+
+ public void stopRewriteSession(DocumentRewriteSession session) {
+ // delegate to sub-class, so UI threading is handled correctly
+ internalStopRewriteSession(session);
+ }
+
+ /**
+ * NOT-API. Final protected so clients may call this method if needed, but
+ * cannot override.
+ *
+ * @param session
+ */
+ final protected void internalStopRewriteSession(DocumentRewriteSession session) {
+ if (fActiveRewriteSession == session) {
+ DocumentRewriteSessionType sessionType = session.getSessionType();
+ if (DocumentRewriteSessionType.SEQUENTIAL == sessionType || DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType)
+ stopSequentialRewrite();
+
+ stopRewriteSessionOnPartitioners(session);
+
+ ILineTracker tracker = getTracker();
+ if (tracker instanceof ILineTrackerExtension) {
+ ILineTrackerExtension extension = (ILineTrackerExtension) tracker;
+ extension.stopRewriteSession(session, get());
+ }
+
+ fActiveRewriteSession = null;
+ DocumentRewriteSessionEvent event = new DocumentRewriteSessionEvent(this, session, DocumentRewriteSessionEvent.SESSION_STOP);
+ fireDocumentRewriteSessionEvent(event);
+ }
+ }
+
+ /**
+ * Stops the given rewrite session.
+ *
+ * @param session the rewrite session
+ * @since 2.0
+ */
+ final void stopRewriteSessionOnPartitioners(DocumentRewriteSession session) {
+ if (fDocumentPartitioners != null) {
+ DocumentPartitioningChangedEvent event= new DocumentPartitioningChangedEvent(this);
+ Iterator e= fDocumentPartitioners.keySet().iterator();
+ while (e.hasNext()) {
+ String partitioning= (String) e.next();
+ IDocumentPartitioner partitioner= (IDocumentPartitioner) fDocumentPartitioners.get(partitioning);
+ if (partitioner instanceof IDocumentPartitionerExtension3) {
+ IDocumentPartitionerExtension3 extension= (IDocumentPartitionerExtension3) partitioner;
+ extension.stopRewriteSession(session);
+ event.setPartitionChange(partitioning, 0, getLength());
+ }
+ }
+ if (!event.isEmpty())
+ fireDocumentPartitioningChanged(event);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#getActiveRewriteSession()
+ */
+ public DocumentRewriteSession getActiveRewriteSession() {
+ return fActiveRewriteSession;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#addDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
+ */
+ public void addDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) {
+ synchronized (listenerLock) {
+ Assert.isNotNull(listener);
+ if (fDocumentRewriteSessionListeners == null) {
+ fDocumentRewriteSessionListeners = new ArrayList(1);
+ }
+ if (!fDocumentRewriteSessionListeners.contains(listener))
+ fDocumentRewriteSessionListeners.add(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#removeDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
+ */
+ public void removeDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) {
+ synchronized (listenerLock) {
+
+ Assert.isNotNull(listener);
+ if (fDocumentRewriteSessionListeners != null)
+ fDocumentRewriteSessionListeners.remove(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#replace(int, int,
+ * java.lang.String, long)
+ */
+ public void replace(int offset, int length, String text, long modificationStamp) throws BadLocationException {
+ replaceText(this, offset, length, text, modificationStamp, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#set(java.lang.String,
+ * long)
+ */
+ public void set(String text, long modificationStamp) {
+ // bug 151069 - overwrite read only regions when setting entire document
+ replaceText(null, 0, getLength(), text, modificationStamp, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension4#getModificationStamp()
+ */
+ public long getModificationStamp() {
+ return fModificationStamp;
+ }
+
+ private long getNextModificationStamp() {
+ if (fNextModificationStamp == Long.MAX_VALUE || fNextModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
+ fNextModificationStamp= 0;
+ else
+ fNextModificationStamp= fNextModificationStamp + 1;
+
+ return fNextModificationStamp;
+ }
+
+ /**
+ * Fires an event, as specified, to the associated listeners.
+ *
+ * @param event
+ * The event to fire, either a start or stop event.
+ */
+ private void fireDocumentRewriteSessionEvent(final DocumentRewriteSessionEvent event) {
+ if (fDocumentRewriteSessionListeners == null || fDocumentRewriteSessionListeners.size() == 0)
+ return;
+
+ Object[] listeners = fDocumentRewriteSessionListeners.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ final IDocumentRewriteSessionListener l = (IDocumentRewriteSessionListener) listeners[i];
+ SafeRunner.run(new ISafeRunnable() {
+ public void run() throws Exception {
+ l.documentRewriteSessionChanged(event);
+ }
+ public void handleException(Throwable exception) {
+ // logged for us
+ }
+ });
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocumentRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocumentRegion.java
new file mode 100644
index 0000000000..7fba9b36ba
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/BasicStructuredDocumentRegion.java
@@ -0,0 +1,622 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * David Carver (Intalio) - bug 300430 - String concatenation
+ * David Carver (Intalio) - bug 300427 - Comparison of String Objects == or !=
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+import org.eclipse.wst.sse.core.internal.util.Debug;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+
+
+public class BasicStructuredDocumentRegion implements IStructuredDocumentRegion {
+ private static final String TEXT_STORE_NOT_ASSIGNED = "text store not assigned yet"; //$NON-NLS-1$
+ private static final String UNDEFINED = "org.eclipse.wst.sse.core.structuredDocument.UNDEFINED"; //$NON-NLS-1$
+
+ private ITextRegionList _regions;
+ /**
+ * has this region been removed from its document
+ */
+ private static final byte MASK_IS_DELETED = 1;
+ /**
+ * was this region terminated normally
+ */
+ private static final byte MASK_IS_ENDED = 1 << 1;
+
+ private byte fIsDeletedOrEnded = 0;
+
+ /**
+ * allow a pointer back to this nodes model
+ */
+ private IStructuredDocument fParentDocument;
+
+ protected int fLength;
+ private IStructuredDocumentRegion next = null;
+ private IStructuredDocumentRegion previous = null;
+ protected int start;
+
+ public BasicStructuredDocumentRegion() {
+ super();
+ _regions = new TextRegionListImpl();
+
+ }
+
+ /**
+ * Even inside-this class uses of 'regions' should use this method, as
+ * this is where (soft) memory management/reparsing, etc., will be
+ * centralized.
+ */
+ private ITextRegionList _getRegions() {
+
+ return _regions;
+ }
+
+ public void addRegion(ITextRegion aRegion) {
+ _getRegions().add(aRegion);
+ }
+
+ public void adjust(int i) {
+ start += i;
+ }
+
+ public void adjustLength(int i) {
+ fLength += i;
+ }
+
+ public void adjustStart(int i) {
+ start += i;
+ }
+
+ public void adjustTextLength(int i) {
+ // not supported
+
+ }
+
+ public boolean containsOffset(int i) {
+
+ return getStartOffset() <= i && i < getEndOffset();
+ }
+
+ public boolean containsOffset(ITextRegion containedRegion, int offset) {
+ return getStartOffset(containedRegion) <= offset && offset < getEndOffset(containedRegion);
+ }
+
+ public void equatePositions(ITextRegion region) {
+ start = region.getStart();
+ fLength = region.getLength();
+ }
+
+ /**
+ * getEnd and getEndOffset are the same only for
+ * IStructuredDocumentRegions
+ */
+ public int getEnd() {
+ return start + fLength;
+ }
+
+ /**
+ * getEnd and getEndOffset are the same only for
+ * IStructuredDocumentRegions
+ */
+ public int getEndOffset() {
+ return getEnd();
+ }
+
+ public int getEndOffset(ITextRegion containedRegion) {
+ return getStartOffset(containedRegion) + containedRegion.getLength();
+ }
+
+ public ITextRegion getFirstRegion() {
+ if (_getRegions() == null)
+ return null;
+ return _getRegions().get(0);
+ }
+
+ public String getFullText() {
+ String result = ""; //$NON-NLS-1$
+ try {
+ result = getParentDocument().get(start, fLength);
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ return result;
+ }
+
+ public String getFullText(ITextRegion aRegion) {
+ String result = ""; //$NON-NLS-1$
+ try {
+ int regionStart = aRegion.getStart();
+ int regionLength = aRegion.getLength();
+ result = fParentDocument.get(start + regionStart, regionLength);
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ return result;
+ }
+
+ public String getFullText(String context) {
+ // DMW: looping is faster than enumeration,
+ // so switched around 2/12/03
+ // Enumeration e = getRegions().elements();
+ ITextRegion region = null;
+ String result = ""; //$NON-NLS-1$
+ int length = getRegions().size();
+ StringBuffer sb = new StringBuffer(result);
+ for (int i = 0; i < length; i++) {
+ region = getRegions().get(i);
+ if (region.getType().equals(context))
+ sb.append(getFullText(region));
+ }
+ result = sb.toString();
+ return result;
+ }
+
+ public ITextRegion getLastRegion() {
+ if (_getRegions() == null)
+ return null;
+ return _getRegions().get(_getRegions().size() - 1);
+ }
+
+ public int getLength() {
+ return fLength;
+ }
+
+ public IStructuredDocumentRegion getNext() {
+ return next;
+ }
+
+ public int getNumberOfRegions() {
+ return _getRegions().size();
+ }
+
+ public IStructuredDocument getParentDocument() {
+
+ return fParentDocument;
+ }
+
+ public IStructuredDocumentRegion getPrevious() {
+ return previous;
+ }
+
+ /**
+ * The parameter offset refers to the overall offset in the document.
+ */
+ public ITextRegion getRegionAtCharacterOffset(int offset) {
+ if (_getRegions() != null) {
+ int thisStartOffset = getStartOffset();
+ if (offset < thisStartOffset)
+ return null;
+ int thisEndOffset = getStartOffset() + getLength();
+ if (offset > thisEndOffset)
+ return null;
+ // transform the requested offset to the "scale" that
+ // regions are stored in, which are all relative to the
+ // start point.
+ // int transformedOffset = offset - getStartOffset();
+ //
+ ITextRegionList regions = getRegions();
+ int length = regions.size();
+ int low = 0;
+ int high = length;
+ int mid = 0;
+ // Binary search for the region
+ while (low < high) {
+ mid = low + ((high - low) >> 1);
+ ITextRegion region = regions.get(mid);
+ if (Debug.debugStructuredDocument) {
+ System.out.println("region(s) in IStructuredDocumentRegion::getRegionAtCharacterOffset: " + region); //$NON-NLS-1$
+ System.out.println(" requested offset: " + offset); //$NON-NLS-1$
+ // System.out.println(" transformedOffset: " +
+ // transformedOffset); //$NON-NLS-1$
+ System.out.println(" region start: " + region.getStart()); //$NON-NLS-1$
+ System.out.println(" region end: " + region.getEnd()); //$NON-NLS-1$
+ System.out.println(" region type: " + region.getType()); //$NON-NLS-1$
+ System.out.println(" region class: " + region.getClass()); //$NON-NLS-1$
+
+ }
+ // Region is before this one
+ if (offset < region.getStart() + thisStartOffset)
+ high = mid;
+ else if (offset > (region.getEnd() + thisStartOffset - 1))
+ low = mid + 1;
+ else
+ return region;
+ }
+ }
+ return null;
+ }
+
+ public ITextRegionList getRegions() {
+ return _getRegions();
+ }
+
+ /**
+ * getStart and getStartOffset are the same only for
+ * IStrucutredDocumentRegions
+ */
+ public int getStart() {
+ return start;
+ }
+
+ /**
+ * getStart and getStartOffset are the same only for
+ * IStrucutredDocumentRegions
+ */
+ public int getStartOffset() {
+ return getStart();
+ }
+
+ public int getStartOffset(ITextRegion containedRegion) {
+ // assert: containedRegion can not be null
+ // (might be performance hit if literally put in assert call,
+ // but containedRegion can not be null). Needs to be checked
+ // by calling code.
+ return getStartOffset() + containedRegion.getStart();
+ }
+
+ public String getText() {
+ String result = null;
+ try {
+ if (fParentDocument == null) {
+ // likely to happen during inspecting
+ result = TEXT_STORE_NOT_ASSIGNED;
+ }
+ else {
+ result = fParentDocument.get(start, fLength);
+ }
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ return result;
+ }
+
+ public String getText(ITextRegion aRegion) {
+ // assert: aRegion can not be null
+ // (might be performance hit if literally put in assert call,
+ // but aRegion can not be null). Needs to be checked
+ // by calling code.
+ try {
+ return fParentDocument.get(this.getStartOffset(aRegion), aRegion.getTextLength());
+ }
+ catch (BadLocationException e) {
+ Logger.logException(e);
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the text of the first region with the matching context type
+ */
+ public String getText(String context) {
+ // DMW: looping is faster than enumeration,
+ // so switched around 2/12/03
+ // Enumeration e = getRegions().elements();
+ ITextRegion region = null;
+ String result = ""; //$NON-NLS-1$
+ int length = getRegions().size();
+ for (int i = 0; i < length; i++) {
+ region = getRegions().get(i);
+ if (region.getType().equals(context)) {
+ result = getText(region);
+ break;
+ }
+ }
+ return result;
+ }
+
+ public int getTextEnd() {
+ return start + fLength;
+ }
+
+ /**
+ * @return int
+ */
+ public int getTextEndOffset() {
+ ITextRegion region = _getRegions().get(_getRegions().size() - 1);
+ return getStartOffset() + region.getTextEnd();
+ }
+
+ public int getTextEndOffset(ITextRegion containedRegion) {
+ return getStartOffset(containedRegion) + containedRegion.getTextLength();
+ }
+
+ public int getTextLength() {
+ return fLength;
+ }
+
+ /**
+ * Provides the type of IStructuredDocumentRegion ... not to be confused
+ * with type of XML node! This is subclassed, if something other than type
+ * of first region is desired.
+ *
+ */
+ public String getType() {
+ String result = UNDEFINED;
+ ITextRegionList subregions = getRegions();
+ if (subregions != null && subregions.size() > 0) {
+ ITextRegion firstRegion = subregions.get(0);
+ if (firstRegion != null) {
+ result = firstRegion.getType();
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.text.IStructuredDocumentRegion#isDeleted()
+ */
+ public boolean isDeleted() {
+ return (fIsDeletedOrEnded & MASK_IS_DELETED) != 0 || (fParentDocument == null);
+ }
+
+ /**
+ *
+ * @return boolean
+ */
+ public boolean isEnded() {
+ return (fIsDeletedOrEnded & MASK_IS_ENDED) != 0;
+ }
+
+ public boolean sameAs(IStructuredDocumentRegion region, int shift) {
+ boolean result = false;
+ // if region == null, we return false;
+ if (region != null) {
+ // if the regions are the same instance, they are equal
+ if (this == region) {
+ result = true;
+ }
+ else {
+ // this is the non-trivial part
+ // note: we change for type first, then start offset and end
+ // offset,
+ // since that would decide many cases right away and avoid the
+ // text comparison
+ if (getType().equals(region.getType())) {
+ if (sameOffsetsAs(region, shift) && sameTextAs(region, shift)) {
+ result = true;
+ }
+ }
+
+ }
+ }
+ return result;
+ }
+
+ public boolean sameAs(ITextRegion oldRegion, IStructuredDocumentRegion newDocumentRegion, ITextRegion newRegion, int shift) {
+ boolean result = false;
+ // if any region is null, we return false (even if both are!)
+ if ((oldRegion != null) && (newRegion != null)) {
+ // if the regions are the same instance, they are equal
+ if (oldRegion == newRegion) {
+ result = true;
+ }
+ else {
+ // this is the non-trivial part
+ // note: we change for type first, then start offset and end
+ // offset,
+ // since that would decide many cases right away and avoid the
+ // text comparison
+ if (oldRegion.getType().equals(newRegion.getType())) {
+ if (sameOffsetsAs(oldRegion, newDocumentRegion, newRegion, shift)) {
+ if (sameTextAs(oldRegion, newDocumentRegion, newRegion, shift)) {
+ result = true;
+ }
+ }
+ }
+ }
+
+ }
+
+ return result;
+ }
+
+ private boolean sameOffsetsAs(IStructuredDocumentRegion region, int shift) {
+ if (getStartOffset() == region.getStartOffset() - shift) {
+ if (getEndOffset() == region.getEndOffset() - shift) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean sameOffsetsAs(ITextRegion oldRegion, IStructuredDocumentRegion documentRegion, ITextRegion newRegion, int shift) {
+ if (getStartOffset(oldRegion) == documentRegion.getStartOffset(newRegion) - shift) {
+ if (getEndOffset(oldRegion) == documentRegion.getEndOffset(newRegion) - shift) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean sameTextAs(IStructuredDocumentRegion region, int shift) {
+ boolean result = false;
+ try {
+ if (getText().equals(region.getText())) {
+ result = true;
+ }
+ }
+ // ISSUE: we should not need this
+ catch (StringIndexOutOfBoundsException e) {
+ result = false;
+ }
+
+ return result;
+ }
+
+ private boolean sameTextAs(ITextRegion oldRegion, IStructuredDocumentRegion documentRegion, ITextRegion newRegion, int shift) {
+ boolean result = false;
+
+ if (getText(oldRegion).equals(documentRegion.getText(newRegion))) {
+ result = true;
+ }
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.text.IStructuredDocumentRegion#setDelete(boolean)
+ */
+ public void setDeleted(boolean isDeleted) {
+ if (isDeleted)
+ fIsDeletedOrEnded |= MASK_IS_DELETED;
+ else
+ fIsDeletedOrEnded &= ~MASK_IS_DELETED;
+ }
+
+ /**
+ *
+ * @param newHasEnd
+ * boolean
+ */
+ public void setEnded(boolean newHasEnd) {
+ if (newHasEnd)
+ fIsDeletedOrEnded |= MASK_IS_ENDED;
+ else
+ fIsDeletedOrEnded &= ~MASK_IS_ENDED;
+ }
+
+ public void setLength(int newLength) {
+ // textLength = newLength;
+ fLength = newLength;
+ }
+
+ public void setNext(IStructuredDocumentRegion newNext) {
+ next = newNext;
+ }
+
+ public void setParentDocument(IStructuredDocument document) {
+ fParentDocument = document;
+
+ }
+
+ public void setPrevious(IStructuredDocumentRegion newPrevious) {
+ previous = newPrevious;
+ }
+
+ public void setRegions(ITextRegionList containedRegions) {
+ _regions = containedRegions;
+ }
+
+ public void setStart(int newStart) {
+ start = newStart;
+ }
+
+ public String toString() {
+ // NOTE: if the document held by any region has been updated and the
+ // region offsets have not
+ // yet been updated, the output from this method invalid.
+ // Also note, this method can not be changed, without "breaking"
+ // unit tests, since some of them compare current results to previous
+ // results.
+ String result = null;
+ result = "[" + getStart() + ", " + getEnd() + "] (" + getText() + ")"; //$NON-NLS-4$//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
+ return result;
+ }
+
+ private void updateDownStreamRegions(ITextRegion changedRegion, int lengthDifference) {
+ int listLength = _getRegions().size();
+ int startIndex = 0;
+ // first, loop through to find index of where to start
+ for (int i = 0; i < listLength; i++) {
+ ITextRegion region = _getRegions().get(i);
+ if (region == changedRegion) {
+ startIndex = i;
+ break;
+ }
+ }
+ // now, beginning one past the one that was changed, loop
+ // through to end of list, adjusting the start postions.
+ startIndex++;
+ for (int j = startIndex; j < listLength; j++) {
+ ITextRegion region = _getRegions().get(j);
+ region.adjustStart(lengthDifference);
+ }
+ }
+
+ public StructuredDocumentEvent updateRegion(Object requester, IStructuredDocumentRegion structuredDocumentRegion, String changes, int requestStart, int lengthToReplace) {
+ StructuredDocumentEvent result = null;
+ int lengthDifference = Utilities.calculateLengthDifference(changes, lengthToReplace);
+ // Get the region pointed to by the requestStart postion, and give
+ // that region a chance to effect
+ // the update.
+ ITextRegion region = getRegionAtCharacterOffset(requestStart);
+ // if there is no region, then the requested changes must come right
+ // after the
+ // node (and right after the last region). This happens, for example,
+ // when someone
+ // types something at the end of the document, or more commonly, when
+ // they are right
+ // at the beginning of one node, and the dirty start is therefore
+ // calculated to be the
+ // previous node.
+ // So, in this case, we'll give the last region a chance to see if it
+ // wants to
+ // swallow the requested changes -- but only for inserts -- deletes
+ // and "replaces"
+ // should be reparsed if they are in these border regions, and only if
+ // the
+ if ((region == null) && (lengthToReplace == 0)) {
+ region = _getRegions().get(_getRegions().size() - 1);
+ // make sure the region is contiguous
+ if (getEndOffset(region) == requestStart) {
+ result = region.updateRegion(requester, this, changes, requestStart, lengthToReplace);
+ }
+ }
+ else {
+ if (region != null) {
+ //
+ // If the requested change spans more than one region, then
+ // we don't give the region a chance to update.
+ if ((containsOffset(region, requestStart)) && (containsOffset(region, requestStart + lengthToReplace))) {
+ result = region.updateRegion(requester, this, changes, requestStart, lengthToReplace);
+ }
+ }
+ }
+ // if result is not null, then we need to update the start and end
+ // postions of the regions that follow this one
+ // if result is null, then apply the flatnode specific checks on what
+ // it can change
+ // (i.e. more than one region, but no change to the node itself)
+ if (result != null) {
+ // That is, a region decided it could handle the change and
+ // created
+ // a region changed event.
+ Assert.isTrue(result instanceof RegionChangedEvent, "Program Error"); //$NON-NLS-1$
+ updateDownStreamRegions(((RegionChangedEvent) result).getRegion(), lengthDifference);
+ // PLUS, we need to update our own node end point (length)
+ setLength(getLength() + lengthDifference);
+ }
+
+ return result;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CharSequenceReader.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CharSequenceReader.java
new file mode 100644
index 0000000000..6ab29b5549
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CharSequenceReader.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.text;
+
+import java.io.IOException;
+import java.io.Reader;
+
+public class CharSequenceReader extends Reader {
+ private int fCurrentPosition;
+ private int fMaximumReadOffset;
+
+ private CharSequence fOriginalSource;
+
+ /**
+ *
+ */
+ CharSequenceReader() {
+ super();
+ }
+
+
+ public CharSequenceReader(CharSequence originalSource, int offset, int length) {
+ // ISSUE: should we "fail fast" if requested length is more than there
+ // is?
+ fOriginalSource = originalSource;
+ int startOffset = offset;
+ int maxRequestedOffset = startOffset + length;
+ int maxPossibleOffset = 0 + originalSource.length();
+ fMaximumReadOffset = Math.min(maxRequestedOffset, maxPossibleOffset);
+
+ fCurrentPosition = startOffset;
+
+ }
+
+ /**
+ * @param lockObject
+ */
+ CharSequenceReader(Object lockObject) {
+ super(lockObject);
+ // for thread safety, may need to add back locking mechanism
+ // in our custom constructor. This constructor left here just
+ // for a reminder.
+ }
+
+ public void close() throws IOException {
+ // nothing to do when we close
+ // (may be to eventually "unlock" or null out some varibles
+ // just for hygene.
+ // or, perhaps if already closed once throw IOException? for
+ // consistency?
+ }
+
+ /**
+ * @return Returns the originalSource.
+ * @deprecated - only temporarily public, should be 'default' eventually
+ * or go away altogether.
+ */
+ public CharSequence getOriginalSource() {
+ return fOriginalSource;
+ }
+
+ public int read() {
+ int result = -1;
+ if (fCurrentPosition < fMaximumReadOffset) {
+ result = fOriginalSource.charAt(fCurrentPosition++);
+ }
+ return result;
+ }
+
+ /**
+ * Read characters into a portion of an array. This method will block
+ * until some input is available, an I/O error occurs, or the end of the
+ * stream is reached.
+ *
+ * @param cbuf
+ * Destination buffer
+ * @param off
+ * Offset at which to start storing characters
+ * @param len
+ * Maximum number of characters to read
+ *
+ * @return The number of characters read, or -1 if the end of the stream
+ * has been reached
+ *
+ * @exception IOException
+ * If an I/O error occurs
+ */
+
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ int charsToRead = -1;
+ // if already over max, just return -1
+ // remember, currentPosition is what is getting ready to be read
+ // (that is, its already been incremented in read()).
+ if (fCurrentPosition < fMaximumReadOffset) {
+
+
+ int buffMaxToRead = cbuf.length - off;
+ int minRequested = Math.min(buffMaxToRead, len);
+ int lengthRemaining = fMaximumReadOffset - fCurrentPosition;
+ charsToRead = Math.min(minRequested, lengthRemaining);
+
+
+ CharSequence seq = fOriginalSource.subSequence(fCurrentPosition, fCurrentPosition + charsToRead);
+ // for now, hard assumption that original is a String since source
+ // is assumed to be document, or text store
+ String seqString = (String) seq;
+ seqString.getChars(0, seqString.length(), cbuf, off);
+
+
+
+ fCurrentPosition = fCurrentPosition + charsToRead;
+
+
+ }
+ return charsToRead;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CoreNodeList.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CoreNodeList.java
new file mode 100644
index 0000000000..6acd927143
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/CoreNodeList.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+
+
+import java.util.Enumeration;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+
+
+public class CoreNodeList implements IStructuredDocumentRegionList {
+ int countedLength;
+ int currentIndex = -1;
+
+ IStructuredDocumentRegion[] flatNodes;
+ IStructuredDocumentRegion head;
+
+ /**
+ * CoreNodeList constructor comment.
+ */
+ public CoreNodeList() {
+ super();
+ // create an array, even if zero length
+ flatNodes = new IStructuredDocumentRegion[0];
+ }
+
+ public CoreNodeList(IStructuredDocumentRegion newHead) {
+ super();
+ // save head
+ head = newHead;
+ int count = 0;
+ IStructuredDocumentRegion countNode = newHead;
+ // we have to go through the list once, to get its
+ // length in order to create the array
+ while (countNode != null) {
+ count++;
+ countNode = countNode.getNext();
+ }
+ // create an array, even if zero length
+ flatNodes = new IStructuredDocumentRegion[count];
+ // start countNode over again, so to speak.
+ countNode = newHead;
+ count = 0;
+ while (countNode != null) {
+ flatNodes[count++] = countNode;
+ countNode = countNode.getNext();
+ }
+ if (count > 0) {
+ currentIndex = 0;
+ // else it stays at -1 initialized at object creation
+ //
+ // save length
+ countedLength = count;
+ }
+ }
+
+ public CoreNodeList(IStructuredDocumentRegion start, IStructuredDocumentRegion end) {
+ super();
+ // save head
+ head = start;
+ int count = 0;
+ IStructuredDocumentRegion countNode = start;
+ if ((start == null) || (end == null)) {
+ // error condition
+ //throw new IllegalArgumentException("Must provide start and end
+ // nodes to construct CoreNodeList");
+ } else {
+ count = 1;
+ while ((countNode != null) && (countNode != end)) {
+ count++;
+ countNode = countNode.getNext();
+ }
+ }
+ // if we ended because the last one was null,
+ // backup one.
+ if (countNode == null)
+ count--;
+ if (count < 0) {
+ count = 0;
+ }
+ flatNodes = new IStructuredDocumentRegion[count];
+ if (count > 0) {
+ flatNodes[0] = countNode = start;
+ for (int i = 1; i < count; i++) {
+ flatNodes[i] = flatNodes[i - 1].getNext();
+ }
+
+ }
+ currentIndex = 0;
+ countedLength = count;
+ }
+
+ public Enumeration elements() {
+ StructuredDocumentRegionEnumeration result = null;
+ if ((flatNodes != null) && (flatNodes.length > 0))
+ result = new StructuredDocumentRegionEnumeration(flatNodes[0], flatNodes[flatNodes.length - 1]);
+ else
+ result = new StructuredDocumentRegionEnumeration(null);
+ return result;
+ }
+
+ public int getLength() {
+ return flatNodes.length;
+ }
+
+ public boolean includes(Object o) {
+ if (flatNodes == null)
+ return false;
+ for (int i = 0; i < flatNodes.length; i++)
+ if (flatNodes[i] == o)
+ return true;
+ return false;
+ }
+
+ public IStructuredDocumentRegion item(int i) {
+ return flatNodes[i];
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/DeleteEqualPositionUpdater.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/DeleteEqualPositionUpdater.java
new file mode 100644
index 0000000000..e33783e542
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/DeleteEqualPositionUpdater.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+
+/**
+ * Follows the behavior of DefaultPositionUpdater except in addition to
+ * deleting/overwriting text which completely contains the position deletes
+ * the position, deleting text that equals the text in position also deletes
+ * the position.
+ *
+ * @see org.eclipse.jface.text.DefaultPositionUpdater
+ */
+public class DeleteEqualPositionUpdater extends DefaultPositionUpdater {
+
+ /**
+ * @param category
+ */
+ public DeleteEqualPositionUpdater(String category) {
+ super(category);
+ }
+
+ /**
+ * Determines whether the currently investigated position has been deleted
+ * by the replace operation specified in the current event. If so, it
+ * deletes the position and removes it from the document's position
+ * category.
+ *
+ * NOTE: position is deleted if current event completely overwrites
+ * position OR if current event deletes the area surrounding/including the
+ * position
+ *
+ * @return <code>true</code> if position has been deleted
+ */
+ protected boolean notDeleted() {
+ // position is deleted if current event completely overwrites position
+ // OR if
+ // current event deletes the area surrounding/including the position
+ if ((fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) || (fOffset <= fPosition.offset && (fPosition.offset + fPosition.length <= fOffset + fLength) && fReplaceLength == 0)) {
+
+ fPosition.delete();
+
+ try {
+ fDocument.removePosition(getCategory(), fPosition);
+ } catch (BadPositionCategoryException x) {
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/GenericPositionManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/GenericPositionManager.java
new file mode 100644
index 0000000000..0a422a3835
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/GenericPositionManager.java
@@ -0,0 +1,409 @@
+/*******************************************************************************
+ * 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.text;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.Position;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+
+/**
+ * Based on the Position management methods from
+ * org.eclipse.jface.text.AbstractDocument
+ */
+
+public class GenericPositionManager {
+ private CharSequence fCharSequence;
+
+
+
+ private Map fPositions;
+ /** All registered document position updaters */
+ private List fPositionUpdaters;
+
+ /**
+ * don't allow instantiation with out document pointer
+ *
+ */
+ private GenericPositionManager() {
+ super();
+ }
+
+ /**
+ *
+ */
+ public GenericPositionManager(CharSequence charSequence) {
+ this();
+ // we only use charSequence for "length", to
+ // made more generic than "document" even "text store"
+ fCharSequence = charSequence;
+ completeInitialization();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#addPosition(org.eclipse.jface.text.Position)
+ */
+ public void addPosition(Position position) throws BadLocationException {
+ try {
+ addPosition(IDocument.DEFAULT_CATEGORY, position);
+ }
+ catch (BadPositionCategoryException e) {
+ }
+ }
+
+
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#addPosition(java.lang.String,
+ * org.eclipse.jface.text.Position)
+ */
+ public synchronized void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException {
+
+ if ((0 > position.offset) || (0 > position.length) || (position.offset + position.length > getDocumentLength()))
+ throw new BadLocationException();
+
+ if (category == null)
+ throw new BadPositionCategoryException();
+
+ List list = (List) fPositions.get(category);
+ if (list == null)
+ throw new BadPositionCategoryException();
+
+ list.add(computeIndexInPositionList(list, position.offset), position);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#addPositionCategory(java.lang.String)
+ */
+ public void addPositionCategory(String category) {
+
+ if (category == null)
+ return;
+
+ if (!containsPositionCategory(category))
+ fPositions.put(category, new ArrayList());
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#addPositionUpdater(org.eclipse.jface.text.IPositionUpdater)
+ */
+ public void addPositionUpdater(IPositionUpdater updater) {
+ insertPositionUpdater(updater, fPositionUpdaters.size());
+ }
+
+
+ /**
+ * Initializes document listeners, positions, and position updaters. Must
+ * be called inside the constructor after the implementation plug-ins have
+ * been set.
+ */
+ protected void completeInitialization() {
+
+ fPositions = new HashMap();
+ fPositionUpdaters = new ArrayList();
+
+ addPositionCategory(IDocument.DEFAULT_CATEGORY);
+ addPositionUpdater(new DefaultPositionUpdater(IDocument.DEFAULT_CATEGORY));
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#computeIndexInCategory(java.lang.String,
+ * int)
+ */
+ public int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException {
+
+ if (0 > offset || offset > getDocumentLength())
+ throw new BadLocationException();
+
+ List c = (List) fPositions.get(category);
+ if (c == null)
+ throw new BadPositionCategoryException();
+
+ return computeIndexInPositionList(c, offset);
+ }
+
+
+ /**
+ * Computes the index in the list of positions at which a position with
+ * the given offset would be inserted. The position is supposed to become
+ * the first in this list of all positions with the same offset.
+ *
+ * @param positions
+ * the list in which the index is computed
+ * @param offset
+ * the offset for which the index is computed
+ * @return the computed index
+ *
+ * @see IDocument#computeIndexInCategory(String, int)
+ */
+ protected synchronized int computeIndexInPositionList(List positions, int offset) {
+
+ if (positions.size() == 0)
+ return 0;
+
+ int left = 0;
+ int right = positions.size() - 1;
+ int mid = 0;
+ Position p = null;
+
+ while (left < right) {
+
+ mid = (left + right) / 2;
+
+ p = (Position) positions.get(mid);
+ if (offset < p.getOffset()) {
+ if (left == mid)
+ right = left;
+ else
+ right = mid - 1;
+ }
+ else if (offset > p.getOffset()) {
+ if (right == mid)
+ left = right;
+ else
+ left = mid + 1;
+ }
+ else if (offset == p.getOffset()) {
+ left = right = mid;
+ }
+
+ }
+
+ int pos = left;
+ p = (Position) positions.get(pos);
+ if (offset > p.getOffset()) {
+ // append to the end
+ pos++;
+ }
+ else {
+ // entry will became the first of all entries with the same
+ // offset
+ do {
+ --pos;
+ if (pos < 0)
+ break;
+ p = (Position) positions.get(pos);
+ }
+ while (offset == p.getOffset());
+ ++pos;
+ }
+
+ Assert.isTrue(0 <= pos && pos <= positions.size());
+
+ return pos;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#containsPosition(java.lang.String,
+ * int, int)
+ */
+ public boolean containsPosition(String category, int offset, int length) {
+
+ if (category == null)
+ return false;
+
+ List list = (List) fPositions.get(category);
+ if (list == null)
+ return false;
+
+ int size = list.size();
+ if (size == 0)
+ return false;
+
+ int index = computeIndexInPositionList(list, offset);
+ if (index < size) {
+ Position p = (Position) list.get(index);
+ while (p != null && p.offset == offset) {
+ if (p.length == length)
+ return true;
+ ++index;
+ p = (index < size) ? (Position) list.get(index) : null;
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#containsPositionCategory(java.lang.String)
+ */
+ public boolean containsPositionCategory(String category) {
+ if (category != null)
+ return fPositions.containsKey(category);
+ return false;
+ }
+
+
+
+ public int getDocumentLength() {
+ return fCharSequence.length();
+ }
+
+ /**
+ * Returns all positions managed by the document grouped by category.
+ *
+ * @return the document's positions
+ */
+ protected Map getDocumentManagedPositions() {
+ return fPositions;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#getPositionCategories()
+ */
+ public String[] getPositionCategories() {
+ String[] categories = new String[fPositions.size()];
+ Iterator keys = fPositions.keySet().iterator();
+ for (int i = 0; i < categories.length; i++)
+ categories[i] = (String) keys.next();
+ return categories;
+ }
+
+
+ public Position[] getPositions(String category) throws BadPositionCategoryException {
+
+ if (category == null)
+ throw new BadPositionCategoryException();
+
+ List c = (List) fPositions.get(category);
+ if (c == null)
+ throw new BadPositionCategoryException();
+
+ Position[] positions = new Position[c.size()];
+ c.toArray(positions);
+ return positions;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#getPositionUpdaters()
+ */
+ public IPositionUpdater[] getPositionUpdaters() {
+ IPositionUpdater[] updaters = new IPositionUpdater[fPositionUpdaters.size()];
+ fPositionUpdaters.toArray(updaters);
+ return updaters;
+ }
+
+
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#insertPositionUpdater(org.eclipse.jface.text.IPositionUpdater,
+ * int)
+ */
+ public synchronized void insertPositionUpdater(IPositionUpdater updater, int index) {
+
+ for (int i = fPositionUpdaters.size() - 1; i >= 0; i--) {
+ if (fPositionUpdaters.get(i) == updater)
+ return;
+ }
+
+ if (index == fPositionUpdaters.size())
+ fPositionUpdaters.add(updater);
+ else
+ fPositionUpdaters.add(index, updater);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#removePosition(org.eclipse.jface.text.Position)
+ */
+ public void removePosition(Position position) {
+ try {
+ removePosition(IDocument.DEFAULT_CATEGORY, position);
+ }
+ catch (BadPositionCategoryException e) {
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#removePosition(java.lang.String,
+ * org.eclipse.jface.text.Position)
+ */
+ public synchronized void removePosition(String category, Position position) throws BadPositionCategoryException {
+
+ if (position == null)
+ return;
+
+ if (category == null)
+ throw new BadPositionCategoryException();
+
+ List c = (List) fPositions.get(category);
+ if (c == null)
+ throw new BadPositionCategoryException();
+
+ // remove based on identity not equality
+ int size = c.size();
+ for (int i = 0; i < size; i++) {
+ if (position == c.get(i)) {
+ c.remove(i);
+ return;
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#removePositionCategory(java.lang.String)
+ */
+ public void removePositionCategory(String category) throws BadPositionCategoryException {
+
+ if (category == null)
+ return;
+
+ if (!containsPositionCategory(category))
+ throw new BadPositionCategoryException();
+
+ fPositions.remove(category);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument#removePositionUpdater(org.eclipse.jface.text.IPositionUpdater)
+ */
+ public synchronized void removePositionUpdater(IPositionUpdater updater) {
+ for (int i = fPositionUpdaters.size() - 1; i >= 0; i--) {
+ if (fPositionUpdaters.get(i) == updater) {
+ fPositionUpdaters.remove(i);
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Updates all positions of all categories to the change described by the
+ * document event. All registered document updaters are called in the
+ * sequence they have been arranged. Uses a robust iterator.
+ *
+ * @param event
+ * the document event describing the change to which to adapt
+ * the positions
+ */
+ protected synchronized void updatePositions(DocumentEvent event) {
+ List list = new ArrayList(fPositionUpdaters);
+ Iterator e = list.iterator();
+ while (e.hasNext()) {
+ IPositionUpdater u = (IPositionUpdater) e.next();
+ u.update(event);
+ }
+ }
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IExecutionDelegatable.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IExecutionDelegatable.java
new file mode 100644
index 0000000000..03ed2675ef
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IExecutionDelegatable.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+import org.eclipse.wst.sse.core.internal.IExecutionDelegate;
+
+public interface IExecutionDelegatable {
+
+ void setExecutionDelegate(IExecutionDelegate executionDelegate);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IRegionComparible.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IRegionComparible.java
new file mode 100644
index 0000000000..3230439abb
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/IRegionComparible.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.text;
+
+public interface IRegionComparible {
+ boolean regionMatches(int offset, int length, String stringToCompare);
+
+ boolean regionMatchesIgnoreCase(int offset, int length, String stringToCompare);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/JobSafeStructuredDocument.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/JobSafeStructuredDocument.java
new file mode 100644
index 0000000000..502b5081b9
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/JobSafeStructuredDocument.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.text;
+
+import java.util.Stack;
+
+import org.eclipse.core.runtime.ISafeRunnable;
+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.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
+import org.eclipse.wst.sse.core.internal.IExecutionDelegate;
+import org.eclipse.wst.sse.core.internal.ILockable;
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+
+/**
+ * An IStructuredDocument that performs most of its computation and event
+ * notification through an IExecutionDelegate.
+ *
+ * If the delegate has not been set, we execute on current thread, like
+ * "normal". This is the case for normal non-editor use (which should still,
+ * ultimately, be protected by a scheduling rule). For every operation, a
+ * runnable is created, even if later (in the execution delegate instance) it
+ * is decided nothing special is needed (that is, in fact being called from an
+ * editor's display thread, in which case its just executed) in the UI.
+ */
+public class JobSafeStructuredDocument extends BasicStructuredDocument implements IExecutionDelegatable, ILockable {
+
+ private static abstract class JobSafeRunnable implements ISafeRunnable {
+ public void handleException(Throwable exception) {
+ // logged in SafeRunner
+ }
+ }
+
+ private Stack fExecutionDelegates = new Stack();
+ private ILock fLockable = Job.getJobManager().newLock();
+
+ public JobSafeStructuredDocument() {
+ super();
+ }
+
+
+ public JobSafeStructuredDocument(RegionParser parser) {
+ super(parser);
+ }
+
+
+ /**
+ *
+ */
+ protected final void acquireLock() {
+ getLockObject().acquire();
+ }
+
+ private IExecutionDelegate getExecutionDelegate() {
+ if (!fExecutionDelegates.isEmpty())
+ return (IExecutionDelegate) fExecutionDelegates.peek();
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.ILockable#getLock()
+ */
+
+ public ILock getLockObject() {
+ return fLockable;
+ }
+
+
+ /**
+ *
+ */
+ protected final void releaseLock() {
+ getLockObject().release();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocument.replace(int, int, String)
+ */
+ public void replace(final int offset, final int length, final String text) throws BadLocationException {
+ IExecutionDelegate delegate = getExecutionDelegate();
+ if (delegate == null) {
+ super.replace(offset, length, text);
+ }
+ else {
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ JobSafeStructuredDocument.super.replace(offset, length, text);
+ }
+ };
+ delegate.execute(runnable);
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentExtension4.replace(int, int, String, long)
+ */
+ public void replace(final int offset, final int length, final String text, final long modificationStamp) throws BadLocationException {
+ IExecutionDelegate delegate = getExecutionDelegate();
+ if (delegate == null) {
+ super.replace(offset, length, text, modificationStamp);
+ }
+ else {
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ JobSafeStructuredDocument.super.replace(offset, length, text, modificationStamp);
+ }
+ };
+ delegate.execute(runnable);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#replaceText(java.lang.Object, int, int, java.lang.String)
+ */
+ public StructuredDocumentEvent replaceText(final Object requester, final int start, final int replacementLength, final String changes) {
+ StructuredDocumentEvent event = null;
+ IExecutionDelegate delegate = getExecutionDelegate();
+ if (delegate == null) {
+ event = super.replaceText(requester, start, replacementLength, changes);
+ }
+ else {
+ final Object[] resultSlot = new Object[1];
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ resultSlot[0] = JobSafeStructuredDocument.super.replaceText(requester, start, replacementLength, changes);
+ }
+ };
+ delegate.execute(runnable);
+ event = (StructuredDocumentEvent) resultSlot[0];
+ }
+ return event;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#replaceText(java.lang.Object, int, int, java.lang.String, boolean)
+ */
+ public StructuredDocumentEvent replaceText(final Object requester, final int start, final int replacementLength, final String changes, final boolean ignoreReadOnlySettings) {
+ StructuredDocumentEvent event = null;
+ IExecutionDelegate delegate = getExecutionDelegate();
+ if (delegate == null) {
+ event = super.replaceText(requester, start, replacementLength, changes, ignoreReadOnlySettings);
+ }
+ else {
+ final Object[] resultSlot = new Object[1];
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ resultSlot[0] = JobSafeStructuredDocument.super.replaceText(requester, start, replacementLength, changes, ignoreReadOnlySettings);
+ }
+
+ public void handleException(Throwable exception) {
+ resultSlot[0] = new NoChangeEvent(JobSafeStructuredDocument.this, requester, changes, start, replacementLength);
+ super.handleException(exception);
+ }
+ };
+ delegate.execute(runnable);
+ event = (StructuredDocumentEvent) resultSlot[0];
+ }
+ return event;
+ }
+
+ public void setExecutionDelegate(IExecutionDelegate delegate) {
+ if (delegate != null)
+ fExecutionDelegates.push(delegate);
+ else if (!fExecutionDelegates.isEmpty())
+ fExecutionDelegates.pop();
+ }
+
+ public StructuredDocumentEvent setText(final Object requester, final String theString) {
+ StructuredDocumentEvent event = null;
+ IExecutionDelegate executionDelegate = getExecutionDelegate();
+ if (executionDelegate == null) {
+ event = super.setText(requester, theString);
+ }
+ else {
+ final Object[] resultSlot = new Object[1];
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ resultSlot[0] = JobSafeStructuredDocument.super.setText(requester, theString);
+ }
+ public void handleException(Throwable exception) {
+ resultSlot[0] = new NoChangeEvent(JobSafeStructuredDocument.this, requester, theString, 0, 0);
+ super.handleException(exception);
+ }
+ };
+ executionDelegate.execute(runnable);
+ event = (StructuredDocumentEvent) resultSlot[0];
+ }
+ return event;
+ }
+
+ public DocumentRewriteSession startRewriteSession(DocumentRewriteSessionType sessionType) throws IllegalStateException {
+ DocumentRewriteSession session = null;
+ IExecutionDelegate executionDelegate = getExecutionDelegate();
+ if (executionDelegate == null) {
+ session = internalStartRewriteSession(sessionType);
+ }
+ else {
+ final Object[] resultSlot = new Object[1];
+ final DocumentRewriteSessionType finalSessionType = sessionType;
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ resultSlot[0] = internalStartRewriteSession(finalSessionType);
+ }
+ };
+ executionDelegate.execute(runnable);
+ if (resultSlot[0] instanceof Throwable) {
+ throw new RuntimeException((Throwable) resultSlot[0]);
+ }
+ else {
+ session = (DocumentRewriteSession) resultSlot[0];
+ }
+ }
+ return session;
+ }
+
+ public void stopRewriteSession(DocumentRewriteSession session) {
+ IExecutionDelegate executionDelegate = getExecutionDelegate();
+ if (executionDelegate == null) {
+ internalStopRewriteSession(session);
+ }
+ else {
+ final DocumentRewriteSession finalSession = session;
+ JobSafeRunnable runnable = new JobSafeRunnable() {
+ public void run() throws Exception {
+ internalStopRewriteSession(finalSession);
+ }
+ };
+ executionDelegate.execute(runnable);
+ }
+ }
+
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/MinimalDocument.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/MinimalDocument.java
new file mode 100644
index 0000000000..de3ff23f39
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/MinimalDocument.java
@@ -0,0 +1,445 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.jface.text.IDocumentPartitioningListener;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.TypedRegion;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.NotImplementedException;
+import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
+import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
+import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
+import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager;
+
+
+/**
+ * Purely a dummy "marker" instance for StructuredDocumentRegions which are
+ * created temorarily in the course of re-parsing. Primarily a place holder,
+ * but can be needed to get text from.
+ */
+public class MinimalDocument implements IStructuredDocument {
+ private SubSetTextStore data;
+
+ /**
+ * Marked private to be sure never created without data being initialized.
+ *
+ */
+ private MinimalDocument() {
+ super();
+ }
+
+ public MinimalDocument(SubSetTextStore initialContents) {
+ this();
+ data = initialContents;
+ }
+
+ public void addDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addDocumentChangedListener(IStructuredDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addDocumentChangingListener(IStructuredDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addDocumentListener(IDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addDocumentPartitioningListener(IDocumentPartitioningListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addPosition(Position position) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addPositionCategory(String category) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addPositionUpdater(IPositionUpdater updater) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void addPrenotifiedDocumentListener(IDocumentListener documentAdapter) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void clearReadOnly(int startOffset, int length) {
+ // TODO: this is called from notifier loop inappropriately
+ // throw new NotImplementedException("intentionally not implemented");
+ }
+
+ public int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int computeNumberOfLines(String text) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public boolean containsPosition(String category, int offset, int length) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public boolean containsPositionCategory(String category) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public boolean containsReadOnly(int startOffset, int length) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void fireNewDocument(Object requester) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String get() {
+ String result = null;
+ result = data.get(0, data.getLength());
+ return result;
+ }
+
+ public String get(int offset, int length) throws BadLocationException {
+ String result = null;
+ try {
+ result = data.get(offset, length);
+ } catch (StringIndexOutOfBoundsException e) {
+ throw new BadLocationException("offset: " + offset + " length: " + length + "\ndocument length: " + data.getLength());
+ }
+ return result;
+ }
+
+ public Object getAdapter(Class adapter) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public char getChar(int offset) throws BadLocationException {
+ return data.get(offset);
+ }
+
+ public String getContentType(int offset) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IDocumentPartitioner getDocumentPartitioner() {
+ // temp fix
+ return null;
+ // throw new NotImplementedException("intentionally not implemented");
+ }
+
+ public EncodingMemento getEncodingMemento() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredDocumentRegion getFirstStructuredDocumentRegion() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredDocumentRegion getLastStructuredDocumentRegion() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String[] getLegalContentTypes() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String[] getLegalLineDelimiters() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getLength() {
+ return data.getLength();
+ }
+
+ public String getPreferedLineDelimiter() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String getLineDelimiter(int line) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IRegion getLineInformation(int line) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IRegion getLineInformationOfOffset(int offset) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getLineLength(int line) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getLineOffset(int line) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getLineOfOffset(int offset) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getNumberOfLines() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int getNumberOfLines(int offset, int length) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public RegionParser getParser() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public ITypedRegion getPartition(int offset) throws BadLocationException {
+ Logger.log(Logger.WARNING, "An instance of MinimalDocument was asked for its partition, sometime indicating a deleted region was being accessed."); //$NON-NLS-1$
+ return new TypedRegion(0,0, "undefined"); //$NON-NLS-1$
+ //throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String[] getPositionCategories() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public Position[] getPositions(String category) throws BadPositionCategoryException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IPositionUpdater[] getPositionUpdaters() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredDocumentRegion getRegionAtCharacterOffset(int offset) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredDocumentRegionList getRegionList() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredTextReParser getReParser() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String getText() {
+ return data.get(0, data.getLength());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.text.IStructuredDocument#getUndoManager()
+ */
+ public IStructuredTextUndoManager getUndoManager() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void insertPositionUpdater(IPositionUpdater updater, int index) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void makeReadOnly(int startOffset, int length) {
+ // TODO: this is called from notifier loop inappropriately
+ // throw new NotImplementedException("intentionally not implemented");
+ }
+
+ public IStructuredDocument newInstance() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#registerPostNotificationReplace(org.eclipse.jface.text.IDocumentListener,
+ * org.eclipse.jface.text.IDocumentExtension.IReplace)
+ */
+ public void registerPostNotificationReplace(IDocumentListener owner, IReplace replace) throws UnsupportedOperationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removeDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removeDocumentChangedListener(IStructuredDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removeDocumentChangingListener(IStructuredDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removeDocumentListener(IDocumentListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removeDocumentPartitioningListener(IDocumentPartitioningListener listener) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removePosition(Position position) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removePosition(String category, Position position) throws BadPositionCategoryException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removePositionCategory(String category) throws BadPositionCategoryException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removePositionUpdater(IPositionUpdater updater) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void removePrenotifiedDocumentListener(IDocumentListener documentAdapter) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void replace(int offset, int length, String text) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ // data.replace(offset, length, text);
+ }
+
+ public StructuredDocumentEvent replaceText(Object source, int oldStart, int replacementLength, String requestedChange) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.text.IStructuredDocument#replaceText(java.lang.Object,
+ * int, int, java.lang.String, boolean)
+ */
+ public StructuredDocumentEvent replaceText(Object source, int oldStart, int replacementLength, String requestedChange, boolean ignoreReadOnlySetting) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#resumePostNotificationProcessing()
+ */
+ public void resumePostNotificationProcessing() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public int search(int startOffset, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void set(String text) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ // data.set(text);
+ }
+
+ public void setDocumentPartitioner(IDocumentPartitioner partitioner) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void setEncodingMemento(EncodingMemento encodingMemento) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public void setPreferredLineDelimiter(String delimiter) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public StructuredDocumentEvent setText(Object requester, String allText) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.text.IStructuredDocument#setUndoManager(org.eclipse.wst.sse.core.undo.StructuredTextUndoManager)
+ */
+ public void setUndoManager(IStructuredTextUndoManager undoManager) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#startSequentialRewrite(boolean)
+ */
+ public void startSequentialRewrite(boolean normalize) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing()
+ */
+ public void stopPostNotificationProcessing() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.IDocumentExtension#stopSequentialRewrite()
+ */
+ public void stopSequentialRewrite() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public String getLineDelimiter() {
+ return null;
+ }
+
+ public String getPreferredLineDelimiter() {
+ return null;
+ }
+
+ public void setLineDelimiter(String delimiter) {
+
+ }
+
+ public IStructuredDocumentRegion[] getStructuredDocumentRegions() {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+
+ public IStructuredDocumentRegion[] getStructuredDocumentRegions(int start, int length) {
+ throw new NotImplementedException("intentionally not implemented"); //$NON-NLS-1$
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/ReadOnlyPosition.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/ReadOnlyPosition.java
new file mode 100644
index 0000000000..4c4bccfeb1
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/ReadOnlyPosition.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+import org.eclipse.jface.text.Position;
+
+class ReadOnlyPosition extends Position {
+ private boolean fIncludeStartOffset = false;
+
+ public ReadOnlyPosition(int newOffset, int newLength, boolean includeStart) {
+ super(newOffset, newLength);
+ fIncludeStartOffset = includeStart;
+ }
+
+ public boolean overlapsWith(int newOffset, int newLength) {
+ boolean overlapsWith = super.overlapsWith(newOffset, newLength);
+ if (overlapsWith) {
+ /*
+ * BUG157526 If at the start of the read only region and length =
+ * 0 most likely asking to insert and want to all inserting before
+ * read only region
+ */
+ if (fIncludeStartOffset && (newLength == 0) && (this.length != 0) && (newOffset == this.offset)) {
+ overlapsWith = false;
+ }
+ }
+ return overlapsWith;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentReParser.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentReParser.java
new file mode 100644
index 0000000000..1cad185b98
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentReParser.java
@@ -0,0 +1,1700 @@
+/*******************************************************************************
+ * 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
+ * David Carver (Intalio) - bug 300427 - Comparison of String Objects using == or !=
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.FindReplaceDocumentAdapter;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.ltk.parser.BlockTagParser;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.sse.core.internal.util.Debug;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+import org.eclipse.wst.sse.core.utils.StringUtils;
+
+
+/**
+ * This class provides a centralized place to put "reparsing" logic. This is
+ * the logic that reparses the text incrementally, as a user types in new
+ * characters, or DOM nodes are inserted or deleted. Note: it is not a thread
+ * safe class.
+ */
+public class StructuredDocumentReParser implements IStructuredTextReParser {
+ protected IStructuredDocumentRegion dirtyEnd = null;
+ protected IStructuredDocumentRegion dirtyStart = null;
+ final private String doubleQuote = new String(new char[]{'\"'});
+ protected final CoreNodeList EMPTY_LIST = new CoreNodeList();
+ protected String fChanges;
+ protected String fDeletedText;
+ protected boolean fIsEntireDocument;
+
+ private FindReplaceDocumentAdapter fFindReplaceDocumentAdapter = null;
+ protected int fLengthDifference;
+ protected int fLengthToReplace;
+ protected Object fRequester;
+ protected int fStart;
+ // note: this is the impl class of IStructuredDocument, not the interface
+ // FUTURE_TO_DO: I believe some of these can be made private now.?
+ protected BasicStructuredDocument fStructuredDocument;
+
+ /**
+ * variable used in anticiapation of multithreading
+ */
+ protected boolean isParsing;
+ final private String singleQuote = new String(new char[]{'\''});
+
+ public StructuredDocumentReParser() {
+ super();
+ }
+
+ public StructuredDocumentEvent _checkBlockNodeList(List blockTagList) {
+ StructuredDocumentEvent result = null;
+ if (blockTagList != null) {
+ for (int i = 0; i < blockTagList.size(); i++) {
+ org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker blockTag = (org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker) blockTagList.get(i);
+ String tagName = blockTag.getTagName();
+ final String tagStart = "<" + tagName; //$NON-NLS-1$
+ result = checkForCriticalName(tagStart); //$NON-NLS-1$
+ if (result != null)
+ break;
+ result = checkForCriticalName("</" + tagName); //$NON-NLS-1$
+ if (result != null)
+ break;
+ result = checkForSelfClosing(tagStart);
+ if (result != null)
+ break;
+ result = checkForTransitionToOpen(tagStart);
+ if (result != null)
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the start region has become self-closing. e.g., &lt;style&gt; -&gt; &lt;style/&gt;
+ */
+ private StructuredDocumentEvent checkForSelfClosing(String tagName) {
+ StructuredDocumentEvent result = null;
+ if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag
+ final int documentLength = fStructuredDocument.getLength();
+ int end = fStart + fLengthToReplace + fChanges.length() + 1;
+ if (end > documentLength)
+ end = documentLength - 1;
+ final String oldText = fStructuredDocument.get(fStart, 1);
+ final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace);
+ if ("/>".equals(peek)) { // Reparse afterwards if the tag became self-closing
+ result = reparse(dirtyStart.getStart(), documentLength - 1);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the start region has become self-closing. e.g., &lt;style/&gt; -&gt; &lt;style&gt;
+ */
+ private StructuredDocumentEvent checkForTransitionToOpen(String tagName) {
+ StructuredDocumentEvent result = null;
+ if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag
+ final int documentLength = fStructuredDocument.getLength();
+ int end = fStart + fLengthToReplace + fChanges.length() + 1;
+ if (end > documentLength)
+ end = documentLength - 1;
+ final String oldText = fStructuredDocument.get(fStart, 2);
+ final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace);
+ if ("/>".equals(oldText) && ">".equals(peek)) { // Reparse afterwards if the block tag went from self-closing to open
+ result = reparse(dirtyStart.getStart(), documentLength - 1);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Common utility for checking for critical word such as " <SCRIPT>"
+ */
+ private StructuredDocumentEvent _checkForCriticalWord(String criticalTarget, boolean checkEnd) {
+ StructuredDocumentEvent result = null;
+ int documentLength = fStructuredDocument.getLength();
+ int propLen = fLengthToReplace;
+ if (propLen > documentLength)
+ propLen = documentLength;
+ int startNeighborhood = fStart - criticalTarget.length();
+ int adjustInsert = 0;
+ if (startNeighborhood < 0) {
+ adjustInsert = 0 - startNeighborhood;
+ startNeighborhood = 0;
+ }
+ int endNeighborhood = fStart + fLengthToReplace + criticalTarget.length() - 1;
+ if (endNeighborhood > documentLength)
+ endNeighborhood = documentLength - 1;
+ int oldlen = endNeighborhood - startNeighborhood; // + 1;
+ if (oldlen + startNeighborhood > documentLength) {
+ oldlen = documentLength - startNeighborhood;
+ }
+ String oldText = fStructuredDocument.get(startNeighborhood, oldlen);
+ String peek = StringUtils.paste(oldText, fChanges, criticalTarget.length() - adjustInsert, fLengthToReplace);
+ boolean isCriticalString = checkTagNames(oldText, criticalTarget, checkEnd);
+ boolean toBeCriticalString = checkTagNames(peek, criticalTarget, checkEnd);
+ if ((isCriticalString != toBeCriticalString) || // OR if both are
+ // critical and there's
+ // a change in the end
+ // tag ('>')
+ ((isCriticalString && toBeCriticalString) && (changeInIsEndedState(oldText, peek)))) {
+ // if it involves a change of a critical string (making one where
+ // there wasn't, or removing
+ // one where there was one) then reparse everthing.
+ result = reparse(0, documentLength - 1);
+ }
+ return result;
+ }
+
+ private int _computeStartOfDifferences(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ int startOfDifferences = -1;
+ int newNodesLength = newNodes.getLength();
+ boolean foundDifference = false;
+ boolean done = false;
+ // we'll control our loop based on the old List length
+ int oldNodesLength = oldNodes.getLength();
+ // be sure to check 'done' first, so startOfDifferences isn't
+ // icremented if done is true
+ done : while ((!done) && (++startOfDifferences < oldNodesLength)) {
+ IStructuredDocumentRegion oldNode = oldNodes.item(startOfDifferences);
+ // this lessThanEffectedRegion is to check to be sure the node is
+ // infact a candidate
+ // to be considered as "old". This check is important for the case
+ // where some
+ // text is replaceing text that
+ // appears identical, but is a different instance. For example, if
+ // the text
+ // is <P><B></B></P> and <B></B> is inserted at postion 3,
+ // resulting in <P><B></B><B></B></P>
+ // we do not want the
+ // first <B> to be considered old ... it is the new one, the
+ // second
+ // <B> is the old one.
+ if (_lessThanEffectedRegion(oldNode)) {
+ // be sure to check that we have new nodes to compare against.
+ if (startOfDifferences > newNodesLength) {
+ foundDifference = false;
+ done = true;
+ continue done;
+ } else {
+ //
+ IStructuredDocumentRegion newNode = newNodes.item(startOfDifferences);
+ // note: shift is 0 while at beginning of list, before the
+ // insertion (or deletion) point. After that, it is
+ // fStart+fLengthDifference
+ if (!(oldNode.sameAs(newNode, 0))) {
+ foundDifference = true;
+ done = true;
+ continue done;
+ } else { // if they are equal, then we will be keeping the
+ // old one, so
+ // we need to be sure its parentDocument is set back
+ // to
+ // the right instance
+ oldNode.setParentDocument(fStructuredDocument);
+ }
+ }
+ } else {
+ // we didn't literally find a difference, but we count it as
+ // such by implication
+ foundDifference = true;
+ done = true;
+ continue done;
+ }
+ }
+ // if we literally found a difference, then all is ok and we can
+ // return
+ // it.
+ // if we did not literally find one, then we have to decide why.
+ if (!foundDifference) {
+ if (newNodesLength == oldNodesLength) { // then lists are
+ // identical
+ // (and may be of zero
+ // length)
+ startOfDifferences = -1;
+ } else {
+ if (newNodesLength > oldNodesLength) { // then lists are
+ // identical except for
+ // newNodes added
+ startOfDifferences = oldNodesLength;
+ } else {
+ if (newNodesLength < oldNodesLength) { // then lists are
+ // identical except
+ // for old Nodes
+ // deleted
+ startOfDifferences = newNodesLength;
+ }
+ }
+ }
+ }
+ return startOfDifferences;
+ }
+
+ private int _computeStartOfDifferences(IStructuredDocumentRegion oldNodeParam, ITextRegionList oldRegions, IStructuredDocumentRegion newNodeParam, ITextRegionList newRegions) {
+ int startOfDifferences = -1;
+ int newRegionsLength = newRegions.size();
+ boolean foundDifference = false;
+ boolean done = false;
+ // we'll control our loop based on the old List length
+ int oldRegionsLength = oldRegions.size();
+ // be sure to check 'done' first, so startOfDifferences isn't
+ // icremented if done is true
+ done : while ((!done) && (++startOfDifferences < oldRegionsLength)) {
+ ITextRegion oldRegion = oldRegions.get(startOfDifferences);
+ // this lessThanEffectedRegion is to check to be sure the node is
+ // infact a candidate
+ // to be considered as "old". This check is important for the case
+ // where some
+ // text is replaceing text that
+ // appears identical, but is a different instance. For example, if
+ // the text
+ // is <P><B></B></P> and <B></B> is inserted at postion 3,
+ // resulting in <P><B></B><B></B></P>
+ // we do not want the
+ // first <B> to be considered old ... it is the new one, the
+ // second
+ // <B> is the old one.
+ if (_lessThanEffectedRegion(oldNodeParam, oldRegion)) {
+ // be sure to check that we have new nodes to compare against.
+ if (startOfDifferences > newRegionsLength) {
+ foundDifference = false;
+ done = true;
+ continue done;
+ } else {
+ //
+ ITextRegion newRegion = newRegions.get(startOfDifferences);
+ // note: shift is 0 while at beginning of list, before the
+ // insertion (or deletion) point. After that, it is
+ // fStart+fLengthDifference
+ if (!(oldNodeParam.sameAs(oldRegion, newNodeParam, newRegion, 0))) {
+ foundDifference = true;
+ done = true;
+ continue done;
+ } else {
+ // if they are equal, then we will be keeping the old
+ // one.
+ // unlike the flatnode case, there is no reason to
+ // update
+ // the textstore, since its the same text store in
+ // either case
+ // (since its the same flatnode)
+ //oldRegion.setTextStore(fStructuredDocument.parentDocument);
+ }
+ }
+ } else {
+ // we didn't literally find a difference, but we count it as
+ // such by implication
+ foundDifference = true;
+ done = true;
+ continue done;
+ }
+ }
+ // if we literally found a difference, then all is ok and we can
+ // return
+ // it.
+ // if we did not literally find one, then we have to decide why.
+ if (!foundDifference) {
+ if (newRegionsLength == oldRegionsLength) { // then lists are
+ // identical (and may
+ // be of zero length)
+ startOfDifferences = -1;
+ } else {
+ if (newRegionsLength > oldRegionsLength) { // then lists are
+ // identical except
+ // for newRegions
+ // added
+ startOfDifferences = oldRegionsLength;
+ } else {
+ if (newRegionsLength < oldRegionsLength) { // then lists
+ // are identical
+ // except for
+ // old Nodes
+ // deleted
+ startOfDifferences = newRegionsLength;
+ }
+ }
+ }
+ }
+ return startOfDifferences;
+ }
+
+ /**
+ * Part 1 of 2 steps to do a core_reparse
+ *
+ * Parses a portion of the current text in the IStructuredDocument and
+ * returns the raw result
+ */
+ private IStructuredDocumentRegion _core_reparse_text(int rescanStart, int rescanEnd) {
+ fStructuredDocument.resetParser(rescanStart, rescanEnd);
+ return fStructuredDocument.getParser().getDocumentRegions();
+ }
+
+ /**
+ * Part 2 of 2 steps to do a core_reparse
+ *
+ * Integrates a list of StructuredDocumentRegions based on the current
+ * text contents of the IStructuredDocument into the IStructuredDocument
+ * data structure
+ */
+ private StructuredDocumentEvent _core_reparse_update_model(IStructuredDocumentRegion newNodesHead, int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) {
+ StructuredDocumentEvent result = null;
+ CoreNodeList newNodes = null;
+ // rescan
+ newNodes = new CoreNodeList(newNodesHead);
+ // adjust our newNode chain so the offset positions match
+ // our text store (not the simple string of text reparsed)
+ StructuredDocumentRegionIterator.adjustStart(newNodesHead, rescanStart);
+ // initialize the parentDocument variable of each instance in the new
+ // chain
+ StructuredDocumentRegionIterator.setParentDocument(newNodesHead, fStructuredDocument);
+ // initialize the structuredDocument variable of each instance in the
+ // new chain
+ //StructuredDocumentRegionIterator.setStructuredDocument(newNodesHead,
+ // fStructuredDocument);
+ //
+ if (firstTime) {
+ fStructuredDocument.setCachedDocumentRegion(newNodesHead);
+ fStructuredDocument.initializeFirstAndLastDocumentRegion();
+ // note: since we are inserting nodes, for the first time, there
+ // is
+ // no adjustments
+ // to downstream stuff necessary.
+ result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument);
+ } else {
+ // note: integrates changes into model as a side effect
+ result = minimumEvent(oldNodes, newNodes);
+ }
+ result.setDeletedText(fDeletedText);
+ return result;
+ }
+
+ private CoreNodeList _formMinimumList(CoreNodeList flatnodes, int startOfDifferences, int endOfDifferences) {
+ CoreNodeList minimalNodes = null;
+ // if startOfDifferces is still its initial value, then we have an
+ // empty document
+ if (startOfDifferences == -1) {
+ minimalNodes = EMPTY_LIST;
+ } else {
+ // if we do not have any flatnode in our flatnode list, then
+ // simply
+ // return our standard empty list
+ if (flatnodes.getLength() == 0) {
+ minimalNodes = EMPTY_LIST;
+ } else {
+ // if startOfDifferences is greater than endOfDifferences,
+ // then
+ // that means the calculations "crossed" each other, and
+ // hence,
+ // there really is no differences, so, again, return the empty
+ // list
+ if (startOfDifferences > endOfDifferences) {
+ minimalNodes = EMPTY_LIST;
+ } else {
+ // the last check be sure we have some differnces
+ if ((endOfDifferences > -1)) {
+ minimalNodes = new CoreNodeList(flatnodes.item(startOfDifferences), flatnodes.item(endOfDifferences));
+ } else {
+ // there were no differences, the list wasn't
+ // minimized, so simply return it.
+ minimalNodes = flatnodes;
+ }
+ }
+ }
+ }
+ return minimalNodes;
+ }
+
+ private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode) {
+ boolean result = false;
+ int nodeStart = oldNode.getStartOffset();
+ int changedRegionEnd = fStart + fLengthToReplace - 1;
+ result = nodeStart > changedRegionEnd;
+ return result;
+ }
+
+ private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) {
+ boolean result = false;
+ int regionStartOffset = oldNode.getStartOffset(oldRegion);
+ int effectedRegionEnd = fStart + fLengthToReplace - 1;
+ result = regionStartOffset > effectedRegionEnd;
+ return result;
+ }
+
+ private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode) {
+ boolean result = false;
+ int nodeEnd = oldNode.getEndOffset() - 1;
+ result = nodeEnd < fStart;
+ return result;
+ }
+
+ private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) {
+ boolean result = false;
+ int nodeEnd = oldNode.getEndOffset(oldRegion) - 1;
+ result = nodeEnd < fStart;
+ return result;
+ }
+
+ private boolean _regionsSameKind(ITextRegion newRegion, ITextRegion oldRegion) {
+ boolean result = false;
+ // if one region is a container region, and the other not, always
+ // return false
+ // else, just check their type.
+ // DW druing refactoring, looks like a "typo" here, using 'old' in
+ // both.
+ // if (isContainerRegion(oldRegion) != isContainerRegion(oldRegion))
+ if (isCollectionRegion(oldRegion) != isCollectionRegion(newRegion))
+ result = false;
+ else if (oldRegion.getType().equals(newRegion.getType()))
+ result = true;
+ return result;
+ }
+
+ // private boolean hasCollectionRegions(ITextRegion aRegion) {
+ // boolean result = false;
+ // if (aRegion instanceof ITextRegionCollection) {
+ // ITextRegionCollection regionContainter = (ITextRegionCollection)
+ // aRegion;
+ // ITextRegionList regions = regionContainter.getRegions();
+ // Iterator iterator = regions.iterator();
+ // while (iterator.hasNext()) {
+ // if (aRegion instanceof ITextRegionCollection) {
+ // result = true;
+ // break;
+ // }
+ // }
+ // }
+ // return result;
+ // }
+ /**
+ * This method is specifically to detect changes in 'isEnded' state,
+ * although it still does so with heuristics. If number of '>' changes,
+ * assume the isEnded state has changed.
+ */
+ private boolean changeInIsEndedState(String oldText, String newText) {
+ int nOld = StringUtils.occurrencesOf(oldText, '>');
+ int nNew = StringUtils.occurrencesOf(newText, '>');
+ return !(nOld == nNew);
+ }
+
+ private void checkAndAssignParent(IStructuredDocumentRegion oldNode, ITextRegion region) {
+ if (region instanceof ITextRegionContainer) {
+ ((ITextRegionContainer) region).setParent(oldNode);
+ return;
+ }
+ if (region instanceof ITextRegionCollection) {
+ ITextRegionCollection textRegionCollection = (ITextRegionCollection) region;
+ ITextRegionList regionList = textRegionCollection.getRegions();
+ for (int i = 0; i < regionList.size(); i++) {
+ ITextRegion innerRegion = regionList.get(i);
+ checkAndAssignParent(oldNode, innerRegion);
+ }
+ }
+ }
+
+ /**
+ * A change to a CDATA tag can result in all being reparsed.
+ */
+ private StructuredDocumentEvent checkForCDATA() {
+ StructuredDocumentEvent result = null;
+ result = checkForCriticalKey("<![CDATA["); //$NON-NLS-1$
+ if (result == null)
+ result = checkForCriticalKey("]]>"); //$NON-NLS-1$
+ return result;
+ }
+
+ /**
+ * If a comment start or end tag is being added or deleted, we'll rescan
+ * the whole document. The reason is that content that is revealed or
+ * commented out can effect the interpretation of the rest of the
+ * document. Note: for now this is very XML specific, can refactor/improve
+ * later.
+ */
+ protected StructuredDocumentEvent checkForComments() {
+ StructuredDocumentEvent result = null;
+ result = checkForCriticalKey("<!--"); //$NON-NLS-1$
+ if (result == null)
+ result = checkForCriticalKey("-->"); //$NON-NLS-1$
+ // we'll also check for these degenerate cases
+ if (result == null)
+ result = checkForCriticalKey("<!--->"); //$NON-NLS-1$
+ return result;
+ }
+
+ /**
+ * Common utility for checking for critical word such as " <SCRIPT>"
+ */
+ protected StructuredDocumentEvent checkForCriticalKey(String criticalTarget) {
+ return _checkForCriticalWord(criticalTarget, false);
+ }
+
+ /**
+ * Common utility for checking for critical word such as " <SCRIPT>"
+ */
+ private StructuredDocumentEvent checkForCriticalName(String criticalTarget) {
+ return _checkForCriticalWord(criticalTarget, true);
+ }
+
+ // /**
+ // * Currently this method is pretty specific to ?ML
+ // * @deprecated - not really deprecated, but plan to make
+ // * protected ... I'm not sure why its public or misspelled?
+ // */
+ protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionBoundryCases() {
+ StructuredDocumentEvent result = null;
+ // Case 1: See if the language's syntax requires that multiple
+ // StructuredDocumentRegions be rescanned
+ if (result == null) {
+ result = checkForCrossStructuredDocumentRegionSyntax();
+ }
+ // Case 2: "block tags" whose content is left unparsed
+ if (result == null) {
+ Object parser = fStructuredDocument.getParser();
+ if (parser instanceof BlockTagParser) {
+ List blockTags = ((BlockTagParser) parser).getBlockMarkers();
+ result = _checkBlockNodeList(blockTags);
+ }
+ }
+ // FUTURE_TO_DO: is there a better place to do this?
+ // or! do we already do it some other more central place?
+ if (result != null) {
+ result.setDeletedText(fDeletedText);
+ }
+ return result;
+ }
+
+ /**
+ * Allow a reparser to check for extra syntactic cases that require
+ * parsing beyond the flatNode boundary.
+ *
+ * This implementation is very XML-centric.
+ */
+ protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionSyntax() {
+ StructuredDocumentEvent result;
+ // Case 1: Quote characters are involved
+ result = checkForQuotes();
+ if (result == null) {
+ // Case 2: The input forms or undoes a comment beginning or
+ // comment
+ // end
+ result = checkForComments();
+ }
+ if (result == null) {
+ // Case 3: The input forms or undoes a processing instruction
+ result = checkForPI();
+ }
+ if (result == null) {
+ // Case 4: The input forms or undoes a CDATA section
+ result = checkForCDATA();
+ }
+ return result;
+ }
+
+ /**
+ * Checks to see if change request exactly matches the text it would be
+ * replacing. (In future, this, or similar method is where to check for
+ * "read only" attempted change.)
+ */
+ private StructuredDocumentEvent checkForNoChange() {
+ StructuredDocumentEvent result = null;
+ // don't check equals unless lengths match
+ // should be a tiny bit faster, since usually not
+ // of equal lengths (I'm surprised String's equals method
+ // doesn't do this.)
+ if ((fChanges != null) && (fDeletedText != null) && (fChanges.length() == fDeletedText.length()) && (fChanges.equals(fDeletedText))) {
+ result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
+ ((NoChangeEvent)result).reason = NoChangeEvent.NO_CONTENT_CHANGE;
+ }
+ return result;
+ }
+
+ /**
+ * A change to a PI tag can result in all being reparsed.
+ */
+ private StructuredDocumentEvent checkForPI() {
+ StructuredDocumentEvent result = null;
+ result = checkForCriticalKey("<?"); //$NON-NLS-1$
+ if (result == null)
+ result = checkForCriticalKey("?>"); //$NON-NLS-1$
+ return result;
+ }
+
+ /*
+ * For simplicity, if either text to be deleted, or text to be inserted
+ * contains at least one quote, we'll search for previous quote in
+ * document, if any, and use that document region as a dirty start, and we'll use
+ * end of document as dirty end. We need to assume either \" or \' is an
+ * acceptable quote. (NOTE: this is, loosely, an XML assumption -- other
+ * languages would differ, but we'll "hard code" for XML for now.
+ *
+ * future_TODO: this is a really bad heuristic ... we should be looking
+ * for odd number of quotes within a structuredDocumentRegion (or
+ * something!) This causes way too much reparsing on simple cases, like
+ * deleting a tag with a quoted attribute!
+ */
+ private StructuredDocumentEvent checkForQuotes() {
+ // routine is supported with null or empty string meaning the same
+ // thing: deletion
+ if (fChanges == null)
+ fChanges = ""; //$NON-NLS-1$
+ //
+ StructuredDocumentEvent result = null;
+ try {
+ int dirtyStartPos = -1;
+ String proposedDeletion = fStructuredDocument.get(fStart, fLengthToReplace);
+ if (fStart < fStructuredDocument.getLength()) {
+ if ((fChanges.indexOf(singleQuote) > -1) || (proposedDeletion.indexOf(singleQuote) > -1)) {
+ IRegion singleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, singleQuote, false, false, false, false);
+ if (singleQuoteRegion != null) {
+ dirtyStartPos = singleQuoteRegion.getOffset();
+ }
+ } else if ((fChanges.indexOf(doubleQuote) > -1) || (proposedDeletion.indexOf(doubleQuote) > -1)) {
+ IRegion doubleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, doubleQuote, false, false, false, false);
+ if (doubleQuoteRegion != null) {
+ dirtyStartPos = doubleQuoteRegion.getOffset();
+ }
+ }
+ }
+ if (dirtyStartPos > -1) {
+ // then we found one, do create new structuredDocument event
+ // based on the previous quote to end of document
+ // except, we need to be positive that the previous quote is
+ // in a "safe start" region (e.g. if in JSP content, we need
+ // to
+ // backup till we include the whole JSP region, in order for
+ // it
+ // to be correctly re-parsed. The backing up is done in the
+ // reparse/find dirty start from hint
+ // method.
+ result = reparse(dirtyStartPos, fStructuredDocument.getLength() - 1);
+ }
+ } catch (BadLocationException e) {
+ Logger.logException(e);
+ }
+ if (result != null) {
+ result.setDeletedText(fDeletedText);
+ }
+ return result;
+ }
+
+ private StructuredDocumentEvent checkHeuristics() {
+ StructuredDocumentEvent result = null;
+ result = checkForNoChange();
+ if (result == null) {
+ result = checkForCrossStructuredDocumentRegionBoundryCases();
+ if (result == null) {
+ result = quickCheck();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Takes into account "tag name" rules for comparisons; case-insensitive.
+ */
+ private boolean checkTagNames(String compareText, String criticalTarget, boolean checkEnd) {
+ boolean result = false;
+ if ((compareText == null) || (criticalTarget == null))
+ return false;
+ int posOfCriticalWord = compareText.toLowerCase().indexOf(criticalTarget.toLowerCase());
+ result = posOfCriticalWord > -1;
+ if (checkEnd && result) {
+ // instead of returning true right away, we'll only return true
+ // the
+ // potentially matched tag is indeed a tag, for example, if
+ // <SCRIPT
+ // becomes <SCRIPTS we don't want to say the latter is a critical
+ // tag
+ int lastPos = posOfCriticalWord + criticalTarget.length();
+ if (lastPos < compareText.length()) {
+ char lastChar = compareText.charAt(lastPos);
+ // Future: check formal definition of this java method, vs.
+ // XML
+ // parsing rules
+ result = (!Character.isLetterOrDigit(lastChar));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The core reparsing method ... after the dirty start and dirty end have
+ * been calculated elsewhere, and the text updated.
+ */
+ protected StructuredDocumentEvent core_reparse(int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) {
+ IStructuredDocumentRegion newNodesHead = null;
+ StructuredDocumentEvent result = null;
+ newNodesHead = _core_reparse_text(rescanStart, rescanEnd);
+ result = _core_reparse_update_model(newNodesHead, rescanStart, rescanEnd, oldNodes, firstTime);
+ return result;
+ }
+
+ /**
+ * Resets state to "not parsing"
+ */
+ private synchronized void endReParse() {
+ isParsing = false;
+ dirtyStart = null;
+ dirtyEnd = null;
+ fChanges = null;
+ fDeletedText = null;
+ fIsEntireDocument = false;
+ }
+
+ protected IStructuredDocumentRegion findDirtyEnd(int end) {
+ // Caution: here's one place we have to cast
+ IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(end);
+ // if not well formed, get one past, if there is something there
+ if ((result != null) && (!result.isEnded())) {
+ if (result.getNext() != null) {
+ result = result.getNext();
+ }
+ }
+ // also, get one past if exactly equal to the end (this was needed
+ // as a simple fix to when a whole exact region is deleted.
+ // there's probably a better way.
+ if ((result != null) && (end == result.getEnd())) {
+ if (result.getNext() != null) {
+ result = result.getNext();
+ }
+ }
+ // moved to subclass for quick transition
+ // 12/6/2001 - Since we've changed the parser/scanner to allow a lone
+ // '<' without
+ // always interpretting it as start of a tag name, we need to be a
+ // little fancier, in order
+ // to "skip" over any plain 'ol content between the lone '<' and any
+ // potential meating
+ // regions past plain 'ol content.
+ // if (isLoneOpenFollowedByContent(result) && (result.getNext() !=
+ // null)) {
+ // result = result.getNext();
+ // }
+ if (result != null)
+ fStructuredDocument.setCachedDocumentRegion(result);
+ dirtyEnd = result;
+ return dirtyEnd;
+ }
+
+ protected void findDirtyStart(int start) {
+ IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(start);
+ // heuristic: if the postion is exactly equal to the start, then
+ // go back one more, if it exists. This prevents problems with
+ // insertions
+ // of text that should be merged with the previous node instead of
+ // simply hung
+ // off of it as a separate node (ex.: XML content inserted right
+ // before
+ // an open
+ // bracket should become part of the previous content node)
+ if (result != null) {
+ IStructuredDocumentRegion previous = result.getPrevious();
+ if ((previous != null) && ((!(previous.isEnded())) || (start == result.getStart()))) {
+ result = previous;
+ }
+ // If we are now at the end of a "tag dependent" content area (or
+ // JSP area)
+ // then we need to back up all the way to the beginning of that.
+ IStructuredDocumentRegion potential = result;
+ // moved to subclass to speed transition
+ // while (isPartOfBlockRegion(potential)) {
+ // potential = potential.getPrevious();
+ // }
+ if (potential != null) {
+ result = potential;
+ fStructuredDocument.setCachedDocumentRegion(result);
+ }
+ }
+ dirtyStart = result;
+ }
+
+ protected CoreNodeList formOldNodes(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) {
+ CoreNodeList oldNodes = new CoreNodeList(dirtyStart, dirtyEnd);
+ // Now save the old text, that "goes with" the old nodes and regions.
+ // Notice we are getting it directly from the text store
+ String oldText = null;
+ int oldStart = -1;
+ int oldEnd = -1;
+ // make sure there is some text, if not, use empty string
+ // (if one node is not null, the other should ALWAYS be not null too,
+ // since it
+ // would at least be equal to it.)
+ if (dirtyStart != null) {
+ oldStart = dirtyStart.getStart();
+ oldEnd = dirtyEnd.getEnd();
+ oldText = fStructuredDocument.get(oldStart, oldEnd - oldStart);
+ } else {
+ oldStart = 0;
+ oldEnd = 0;
+ oldText = ""; //$NON-NLS-1$
+ }
+ // create a temporary text store for this text
+ SubSetTextStore subTextStore = new SubSetTextStore(oldText, oldStart, oldEnd, fStructuredDocument.getLength());
+ // Now update the text store of the oldNodes
+ StructuredDocumentRegionIterator.setParentDocument(oldNodes, new MinimalDocument(subTextStore));
+ return oldNodes;
+ }
+
+ /**
+ * @return Returns the findReplaceDocumentAdapter.
+ */
+ public FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
+ if (fFindReplaceDocumentAdapter == null) {
+ fFindReplaceDocumentAdapter = new FindReplaceDocumentAdapter(fStructuredDocument);
+ }
+ return fFindReplaceDocumentAdapter;
+ }
+
+ // Note: if thead safety is needed, this and all the other public methods
+ // of this class
+ // should be synchronized.
+ public void initialize(Object requester, int start, int lengthToReplace, String changes) {
+ isParsing = true;
+ fRequester = requester;
+ fStart = start;
+ fLengthToReplace = lengthToReplace;
+ fChanges = changes;
+ // notice this one is derived
+ fLengthDifference = Utilities.calculateLengthDifference(fChanges, fLengthToReplace);
+ fDeletedText = fStructuredDocument.get(fStart, fLengthToReplace);
+ int docLength = fStructuredDocument.getLength();
+ fIsEntireDocument = lengthToReplace >= docLength && docLength > 0;
+ }
+
+ protected void insertNodes(IStructuredDocumentRegion previousOldNode, IStructuredDocumentRegion nextOldNode, CoreNodeList newNodes) {
+ //
+ IStructuredDocumentRegion firstNew = null;
+ IStructuredDocumentRegion lastNew = null;
+ //
+ IStructuredDocumentRegion oldPrevious = previousOldNode;
+ IStructuredDocumentRegion oldNext = nextOldNode;
+ //
+ if (newNodes.getLength() > 0) {
+ // get pointers
+ firstNew = newNodes.item(0);
+ lastNew = newNodes.item(newNodes.getLength() - 1);
+ // switch surrounding StructuredDocumentRegions' references to
+ // lists
+ if (oldPrevious != null)
+ oldPrevious.setNext(firstNew);
+ if (oldNext != null) {
+ oldNext.setPrevious(lastNew);
+ } else {
+ // SIDE EFFECT
+ // if oldNext is null, that means we are replaceing the
+ // lastNode in the chain,
+ // so we need to update the structuredDocuments lastNode as
+ // the
+ // last of the new nodes.
+ fStructuredDocument.setLastDocumentRegion(newNodes.item(newNodes.getLength() - 1));
+ }
+ if (firstNew != null)
+ firstNew.setPrevious(oldPrevious);
+ if (lastNew != null)
+ lastNew.setNext(oldNext);
+ }
+ // else nothing to insert
+ }
+
+ /**
+ * @param oldRegion
+ */
+ private boolean isCollectionRegion(ITextRegion aRegion) {
+ return (aRegion instanceof ITextRegionCollection);
+ }
+
+ /**
+ * @return boolean
+ */
+ public boolean isParsing() {
+ return isParsing;
+ }
+
+ /**
+ * The minimization algorithm simply checks the old nodes to see if any of
+ * them "survived" the rescan and are unchanged. If so, the instance of
+ * the old node is used instead of the new node. Before the requested
+ * change, need to check type, offsets, and text to determine if the same.
+ * After the requested change, need to check type and text, but adjust the
+ * offsets to what ever the change was.
+ */
+ protected StructuredDocumentEvent minimumEvent(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ StructuredDocumentEvent event = null;
+ CoreNodeList minimalOldNodes = null;
+ CoreNodeList minimalNewNodes = null;
+ // To minimize nodes, we'll collect all those
+ // that are not equal into old and new lists
+ // Note: we assume that old and new nodes
+ // are basically contiguous -- and we force it to be so,
+ // by starting at the beginning to
+ // find first difference, and then starting at the end to find
+ // last difference. Everything in between we assume is different.
+ //
+ //
+ //
+ // startOfDifferences is the index into the core node list where the
+ // first difference
+ // occurs. But it may point into the old or the new list.
+ int startOfDifferences = _computeStartOfDifferences(oldNodes, newNodes);
+ int endOfDifferencesOld = -1;
+ int endOfDifferencesNew = -1;
+ // if one of the lists are shorter than where the differences start,
+ // then
+ // then some portion of the lists are identical
+ if ((startOfDifferences >= oldNodes.getLength()) || (startOfDifferences >= newNodes.getLength())) {
+ if (oldNodes.getLength() < newNodes.getLength()) {
+ // Then there are new regions to add
+ // these lengths will cause the vector of old ones to not
+ // have any elements, and the vector of new regions to have
+ // just the new ones not in common with the old ones
+ //startOfDifferences should equal oldNodes.getLength(),
+ // calculated above on _computeStartOfDifferences
+ minimalOldNodes = EMPTY_LIST;
+ endOfDifferencesNew = newNodes.getLength() - 1;
+ minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew);
+ } else {
+ if (oldNodes.getLength() > newNodes.getLength()) {
+ // delete old
+ // then there are old regions to delete
+ // these lengths will cause the vector of old regions to
+ // contain the ones to delete, and the vector of new
+ // regions
+ // not have any elements
+ //startOfDifferences should equal newNodes.getLength(),
+ // calculated above on _computeStartOfDifferences
+ endOfDifferencesOld = oldNodes.getLength() - 1;
+ minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld);
+ minimalNewNodes = EMPTY_LIST;
+ } else
+ // unlikely event
+ event = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
+ }
+ } else {
+ // We found a normal startOfDiffernces, but have not yet found the
+ // ends.
+ // We'll look for the end of differences by going backwards down
+ // the two lists.
+ // Here we need a seperate index for each array, since they may be
+ // (and
+ // probably are) of different lengths.
+ int indexOld = oldNodes.getLength() - 1;
+ int indexNew = newNodes.getLength() - 1;
+ // The greaterThanEffectedRegion is important to gaurd against
+ // incorrect counting
+ // when something identical is inserted to what's already there
+ // (see minimization test case 5)
+ // Note: the indexOld > startOfDifferences keeps indexOld from
+ // getting too small,
+ // so that the subsequent oldNodes.item(indexOld) is always valid.
+ while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNodes.item(indexOld)))) {
+ if (!(oldNodes.item(indexOld).sameAs(newNodes.item(indexNew), fLengthDifference))) {
+ break;
+ } else {
+ // if they are equal, then we will be keeping the old one,
+ // so
+ // we need to be sure its parentDocument is set back to
+ // the
+ // right instance
+ oldNodes.item(indexOld).setParentDocument(fStructuredDocument);
+ }
+ indexOld--;
+ indexNew--;
+ }
+ endOfDifferencesOld = indexOld;
+ endOfDifferencesNew = indexNew;
+ minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld);
+ minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew);
+ } /////////////////////////////////////////
+ //
+ IStructuredDocumentRegion firstDownStreamNode = null;
+ event = regionCheck(minimalOldNodes, minimalNewNodes);
+ if (event != null) {
+ firstDownStreamNode = minimalOldNodes.item(0).getNext();
+ if (firstDownStreamNode != null && fLengthDifference != 0) { // if
+ // firstDownStream
+ // is
+ // null,
+ // then
+ // we're
+ // at
+ // the
+ // end
+ // of
+ // the
+ // document
+ StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
+ } //
+ } else {
+ event = nodesReplacedCheck(minimalOldNodes, minimalNewNodes);
+ // now splice the new chain of nodes to where the old chain is (or
+ // was)
+ // the firstDownStreamNode (the first of those after the new
+ // nodes)
+ // is
+ // remembered as a tiny optimization.
+ if (minimalOldNodes.getLength() == 0 && minimalNewNodes.getLength() > 0) {
+ // if no old nodes are being deleted, then use the
+ // the newNodes offset (minus one) to find the point to
+ // update downstream nodes, and after updating downstream
+ // nodes postions, insert the new ones.
+ int insertOffset = minimalNewNodes.item(0).getStartOffset();
+ IStructuredDocumentRegion lastOldUnchangedNode = null;
+ if (insertOffset > 0) {
+ lastOldUnchangedNode = fStructuredDocument.getRegionAtCharacterOffset(insertOffset - 1);
+ firstDownStreamNode = lastOldUnchangedNode.getNext();
+ } else {
+ // we're inserting at very beginning
+ firstDownStreamNode = fStructuredDocument.getFirstStructuredDocumentRegion();
+ // SIDE EFFECT: change the firstNode pointer if we're
+ // inserting at beginning
+ fStructuredDocument.setFirstDocumentRegion(minimalNewNodes.item(0));
+ }
+ StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
+ insertNodes(lastOldUnchangedNode, firstDownStreamNode, minimalNewNodes);
+ // this (nodes replaced) is the only case where we need to
+ // update the cached Node
+ reSetCachedNode(minimalOldNodes, minimalNewNodes);
+ } else {
+ firstDownStreamNode = switchNodeLists(minimalOldNodes, minimalNewNodes);
+ // no need to adjust the length of the new nodes themselves,
+ // they
+ // are already correct, but we do need to
+ // adjust all "down stream" nodes with the length of the
+ // insertion or deletion
+ // --- adjustment moved to calling method.
+ if (firstDownStreamNode != null) {
+ // && event != null
+ StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
+ } //
+ // this (nodes replaced) is the only case where we need to
+ // update the cached Node
+ reSetCachedNode(minimalOldNodes, minimalNewNodes);
+ }
+ }
+ return event;
+ }
+
+ // TODO: This should be abstract.
+ public IStructuredTextReParser newInstance() {
+ return new StructuredDocumentReParser();
+ }
+
+ protected StructuredDocumentEvent nodesReplacedCheck(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ // actually, nothing to check here, since (and assuming) we've already
+ // minimized the number of nodes, and ruled out mere region changes
+ StructuredDocumentEvent result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument);
+ return result;
+ }
+
+ /**
+ * A method to allow any heuristic "quick checks" that might cover many
+ * many cases, before expending the time on a full reparse.
+ *
+ */
+ public StructuredDocumentEvent quickCheck() {
+ StructuredDocumentEvent result = null;
+ // if the dirty start is null, then we have an empty document.
+ // in which case we'll return null so everything can be
+ // reparsed "from scratch" . If its not null, we'll give the flatnode
+ // a
+ // chance
+ // to handle, but only if there is one flatnode involved.
+ if (dirtyStart != null && dirtyStart == dirtyEnd) {
+ IStructuredDocumentRegion targetNode = dirtyStart;
+ result = dirtyStart.updateRegion(fRequester, targetNode, fChanges, fStart, fLengthToReplace);
+ if (result != null) {
+ // at this point only, we need to update the text store and
+ // and downstream nodes.
+ // FUTURE_TO_DO: can this dependency on structuredDocument
+ // method be eliminated?
+ fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges);
+ IStructuredDocumentRegion firstDownStreamNode = targetNode.getNext();
+ // then flatnode must have been the last one, so need to
+ // update
+ // any downstream ones
+ if (firstDownStreamNode != null) {
+ StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
+ }
+ }
+ }
+ if (result != null) {
+ result.setDeletedText(fDeletedText);
+ }
+ return result;
+ }
+
+ /**
+ * If only one node is involved, sees how many regions are changed. If
+ * only one, then its a 'regionChanged' event ... if more than one, its a
+ * 'regionsReplaced' event.
+ */
+ protected StructuredDocumentEvent regionCheck(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ if (Debug.debugStructuredDocument)
+ System.out.println("IStructuredDocument::regionsReplacedCheck"); //$NON-NLS-1$
+ //$NON-NLS-1$
+ //$NON-NLS-1$
+ // the "regionsReplaced" event could only be true if and only if the
+ // nodelists
+ // are each only "1" in length.
+ StructuredDocumentEvent result = null;
+ int oldLength = oldNodes.getLength();
+ int newLength = newNodes.getLength();
+ if ((oldLength != 1) || (newLength != 1)) {
+ result = null;
+ } else {
+ IStructuredDocumentRegion oldNode = oldNodes.item(0);
+ IStructuredDocumentRegion newNode = newNodes.item(0);
+ result = regionCheck(oldNode, newNode);
+ }
+ return result;
+ }
+
+ /**
+ * If only one node is involved, sees how many regions are changed. If
+ * only one, then its a 'regionChanged' event ... if more than one, its a
+ * 'regionsReplaced' event.
+ */
+ protected StructuredDocumentEvent regionCheck(IStructuredDocumentRegion oldNode, IStructuredDocumentRegion newNode) {
+ //
+ StructuredDocumentEvent result = null;
+ ITextRegionList oldRegions = oldNode.getRegions();
+ ITextRegionList newRegions = newNode.getRegions();
+ ITextRegion[] oldRegionsArray = oldRegions.toArray();
+ ITextRegion[] newRegionsArray = newRegions.toArray();
+ //
+ // for the 'regionsReplaced' event, we don't care if
+ // the regions changed due to type, or text,
+ // we'll just collect all those that are not equal
+ // into the old and new region lists.
+ // Note: we, of course, assume that old and new regions
+ // are basically contiguous -- and we force it to be so,
+ // even if not literally so, by starting at beginning to
+ // find first difference, and then starting at end to find
+ // last difference. Everything in between we assume is different.
+ //
+ // going up is easy, we start at zero in each, and continue
+ // till regions are not the same.
+ int startOfDifferences = _computeStartOfDifferences(oldNode, oldRegions, newNode, newRegions);
+ int endOfDifferencesOld = -1;
+ int endOfDifferencesNew = -1;
+ //
+ //
+ // if one of the lists are shorter than where the differences start,
+ // then
+ // then some portion of the lists are identical
+ if ((startOfDifferences >= oldRegions.size()) || (startOfDifferences >= newRegions.size())) {
+ if (oldRegions.size() < newRegions.size()) {
+ // INSERT CASE
+ // then there are new regions to add
+ // these lengths will cause the vector of old ones to not
+ // have any elements, and the vector of new regions to have
+ // just the new ones.
+ startOfDifferences = oldRegionsArray.length;
+ endOfDifferencesOld = oldRegionsArray.length - 1;
+ endOfDifferencesNew = newRegionsArray.length - 1;
+ } else {
+ if (oldRegions.size() > newRegions.size()) {
+ // DELETE CASE
+ // then there are old regions to delete
+ // these lengths will cause the vector of old regions to
+ // contain the ones to delete, and the vector of new
+ // regions
+ // not have any elements
+ startOfDifferences = newRegionsArray.length;
+ endOfDifferencesOld = oldRegionsArray.length - 1;
+ endOfDifferencesNew = newRegionsArray.length - 1;
+ } else {
+ // else the lists are identical!
+ // unlikely event, probably error in current design, since
+ // we check for identity at the very beginning of
+ // reparsing.
+ result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
+ }
+ }
+ } else {
+ if ((startOfDifferences > -1) && (endOfDifferencesOld < 0) && (endOfDifferencesNew < 0)) {
+ // We found a normal startOfDiffernces, but have not yet found
+ // the ends.
+ // We'll look for the end of differences by going backwards
+ // down the two lists.
+ // Here we need a seperate index for each array, since they
+ // may
+ // be (and
+ // probably are) of different lengths.
+ int indexOld = oldRegionsArray.length - 1;
+ int indexNew = newRegionsArray.length - 1;
+ while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNode, oldRegionsArray[indexOld]))) {
+ if ((!(oldNode.sameAs(oldRegionsArray[indexOld], newNode, newRegionsArray[indexNew], fLengthDifference)))) {
+ //endOfDifferencesOld = indexOne;
+ //endOfDifferencesNew = indexTwo;
+ break;
+ }
+ indexOld--;
+ indexNew--;
+ }
+ endOfDifferencesOld = indexOld;
+ endOfDifferencesNew = indexNew;
+ }
+ }
+ //
+ // result != null means the impossible case above occurred
+ if (result == null) {
+ // Now form the two vectors of different regions
+ ITextRegionList holdOldRegions = new TextRegionListImpl();
+ ITextRegionList holdNewRegions = new TextRegionListImpl();
+ if (startOfDifferences > -1 && endOfDifferencesOld > -1) {
+ for (int i = startOfDifferences; i <= endOfDifferencesOld; i++) {
+ holdOldRegions.add(oldRegionsArray[i]);
+ }
+ }
+ if (startOfDifferences > -1 && endOfDifferencesNew > -1) {
+ for (int i = startOfDifferences; i <= endOfDifferencesNew; i++) {
+ holdNewRegions.add(newRegionsArray[i]);
+ }
+ }
+ if (holdOldRegions.size() == 0 && holdNewRegions.size() == 0) {
+ // then this means the regions were identical, which means
+ // someone
+ // pasted exactly the same thing they had selected, or !!!
+ // someone deleted the end bracket of the tag. !!!?
+ result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
+ } else {
+ //If both holdOldRegions and holdNewRegions are of length 1,
+ // then its
+ // a "region changed" event, else a "regions replaced" event.
+ // so we want the new instance of region to become part of the
+ // old instance of old node
+ if ((holdOldRegions.size() == 1) && (holdNewRegions.size() == 1) && _regionsSameKind((holdNewRegions.get(0)), (holdOldRegions.get(0)))) {
+ ITextRegion newOldRegion = swapNewForOldRegion(oldNode, holdOldRegions.get(0), newNode, holdNewRegions.get(0));
+ // -- need to update any down stream regions, within this
+ // 'oldNode'
+ updateDownStreamRegions(oldNode, newOldRegion);
+ result = new RegionChangedEvent(fStructuredDocument, fRequester, oldNode, newOldRegion, fChanges, fStart, fLengthToReplace);
+ } else {
+ replaceRegions(oldNode, holdOldRegions, newNode, holdNewRegions);
+ // -- need to update any down stream regions, within this
+ // 'oldNode'
+ // don't need with the way replaceRegions is implemented.
+ // It handles.
+ //if(holdNewRegions.size() > 0)
+ //updateDownStreamRegions(oldNode, (ITextRegion)
+ // holdNewRegions.lastElement());
+ result = new RegionsReplacedEvent(fStructuredDocument, fRequester, oldNode, holdOldRegions, holdNewRegions, fChanges, fStart, fLengthToReplace);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * An entry point for reparsing. It calculates the dirty start and dirty
+ * end flatnodes based on the start point and length of the changes.
+ *
+ */
+ public StructuredDocumentEvent reparse() {
+ StructuredDocumentEvent result = null;
+ // if we do not have a cachedNode, then the document
+ // must be empty, so simply use 'null' as the dirtyStart and dirtyEnd
+ // otherwise, find them.
+ if (fStructuredDocument.getCachedDocumentRegion() != null) {
+ findDirtyStart(fStart);
+ int end = fStart + fLengthToReplace;
+ findDirtyEnd(end);
+ }
+ if (fStructuredDocument.getCachedDocumentRegion() != null) {
+ result = checkHeuristics();
+ }
+ if (result == null) {
+ result = reparse(dirtyStart, dirtyEnd);
+ }
+ endReParse();
+ return result;
+ }
+
+ /**
+ * An entry point for reparsing. It calculates the dirty start and dirty
+ * end flatnodes based on suggested positions to begin and end. This is
+ * needed for cases where parsing must go beyond the immediate node and
+ * its direct neighbors.
+ *
+ */
+ protected StructuredDocumentEvent reparse(int reScanStartHint, int reScanEndHint) {
+ StructuredDocumentEvent result = null;
+ // if we do not have a cachedNode, then the document
+ // must be empty, so simply use 'null' as the dirtyStart and dirtyEnd
+ if (fStructuredDocument.getCachedDocumentRegion() != null) {
+ findDirtyStart(reScanStartHint);
+ findDirtyEnd(reScanEndHint);
+ }
+ result = reparse(dirtyStart, dirtyEnd);
+ isParsing = false;
+ // debug
+ //verifyStructured(result);
+ return result;
+ }
+
+ /**
+ * The core reparsing method ... after the dirty start and dirty end have
+ * been calculated elsewhere.
+ */
+ protected StructuredDocumentEvent reparse(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) {
+ StructuredDocumentEvent result = null;
+ int rescanStart = -1;
+ int rescanEnd = -1;
+ boolean firstTime = false;
+ //
+ // "save" the oldNodes (that may be replaced) in a list
+ CoreNodeList oldNodes = formOldNodes(dirtyStart, dirtyEnd);
+ if (dirtyStart == null || dirtyEnd == null) {
+ // dirtyStart or dirty end are null, then that means we didn't
+ // have
+ // a
+ // cached node, which means we have an empty document, so we
+ // just need to rescan the changes
+ rescanStart = 0;
+ rescanEnd = fChanges.length();
+ firstTime = true;
+ } else {
+ // set the start of the text to rescan
+ rescanStart = dirtyStart.getStart();
+ //
+ // set the end of the text to rescan
+ // notice we use the same rationale as for the rescanStart,
+ // with the added caveat that length has to be added to it,
+ // to compensate for the new text which has been added or deleted.
+ // If changes has zero length, then "length" will be negative,
+ // since
+ // we are deleting text. Otherwise, use the difference between
+ // what's selected to be replaced and the length of the new text.
+ rescanEnd = dirtyEnd.getEnd() + fLengthDifference;
+ }
+ // now that we have the old stuff "saved" away, update the document
+ // with the changes.
+ // FUTURE_TO_DO -- don't fire "document changed" event till later
+ fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges);
+ // ------------------ now the real work
+ result = core_reparse(rescanStart, rescanEnd, oldNodes, firstTime);
+ //
+ // event is returned to the caller, incase there is
+ // some opitmization they can do
+ return result;
+ }
+
+ protected void replaceRegions(IStructuredDocumentRegion oldNode, ITextRegionList oldRegions, IStructuredDocumentRegion newNode, ITextRegionList newRegions) {
+ int insertPos = -1;
+ ITextRegionList regions = oldNode.getRegions();
+ // make a fake flatnode to be new parent of oldRegions, so their text
+ // will be right.
+ //IStructuredDocumentRegion holdOldStructuredDocumentRegion = new
+ // BasicStructuredDocumentRegion(oldNode);
+ //
+ // need to reset the parent of the new to-be-inserted regions to be
+ // the
+ // same oldNode that is the one having its regions changed
+ // DW, 4/16/2003, removed since ITextRegion no longer has parent.
+ // ITextRegionContainer oldParent = oldNode;
+ // for (int i = 0; i < newRegions.size(); i++) {
+ // AbstractRegion region = (AbstractRegion) newRegions.elementAt(i);
+ // region.setParent(oldParent);
+ // }
+ // if there are no old regions, insert the new regions according to
+ // offset
+ if (oldRegions.size() == 0) {
+ ITextRegion firstNewRegion = newRegions.get(0);
+ int firstOffset = newNode.getStartOffset(firstNewRegion);
+ // if at beginning, insert there
+ if (firstOffset == 0) {
+ insertPos = 0;
+ } else {
+ //
+ ITextRegion regionAtOffset = oldNode.getRegionAtCharacterOffset(firstOffset);
+ if (regionAtOffset == null)
+ insertPos = regions.size();
+ else
+ insertPos = regions.indexOf(regionAtOffset);
+ }
+ } else {
+ // else, delete old ones before inserting new ones in their place
+ ITextRegion firstOldRegion = oldRegions.get(0);
+ insertPos = regions.indexOf(firstOldRegion);
+ regions.removeAll(oldRegions);
+ }
+ regions.addAll(insertPos, newRegions);
+ // now regions vector of each node should be of equal length,
+ // so go through each, and make sure the old regions
+ // offsets matches the new regions offsets
+ // (we'll just assign them all, but could be slightly more effiecient)
+ ITextRegionList allNewRegions = newNode.getRegions();
+ for (int i = 0; i < regions.size(); i++) {
+ ITextRegion nextOldishRegion = regions.get(i);
+ ITextRegion nextNewRegion = allNewRegions.get(i);
+ nextOldishRegion.equatePositions(nextNewRegion);
+ checkAndAssignParent(oldNode, nextOldishRegion);
+ }
+ oldNode.setLength(newNode.getLength());
+ oldNode.setEnded(newNode.isEnded());
+ oldNode.setParentDocument(newNode.getParentDocument());
+ // removed concept of part of these regions, so no longer need to do.
+ // for (int i = 0; i < oldRegions.size(); i++) {
+ // ITextRegion region = (ITextRegion) oldRegions.elementAt(i);
+ // region.setParent(holdOldStructuredDocumentRegion);
+ // }
+ }
+
+ private void reSetCachedNode(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ // use the last newNode as the new cachedNode postion, unless its null
+ // (e.g. when nodes are deleted) in which case, assign
+ // it to a "safe" node so we don't lose reference to the
+ // structuredDocument!
+ if (newNodes.getLength() > 0) {
+ // use last new node as the cache
+ fStructuredDocument.setCachedDocumentRegion(newNodes.item(newNodes.getLength() - 1));
+ } else {
+ // if cachedNode is an old node, then we're in trouble:
+ // we can't leave it as the cached node! and its already
+ // been disconnected from the model, so we can't do getNext
+ // or getPrevious, so we'll get one that is right before
+ // (or right after) the offset of the old nodes that are being
+ // deleted.
+ //
+ // if newNodesHead and cachedNode are both null, then
+ // it means we were asked to insert an empty string into
+ // an empty document. So we have nothing to do here
+ // (that is, we have no node to cache)
+ // similarly if there are no new nodes and no old nodes then
+ // nothing to do (but that should never happen ... we shouldn't
+ // get there if there is no event to generate).
+ if ((fStructuredDocument.getCachedDocumentRegion() != null) && (oldNodes.getLength() > 0)) {
+ // note: we can't simple use nodeAtCharacterOffset, since it
+ // depends on cachedNode.
+ if (oldNodes.includes(fStructuredDocument.getCachedDocumentRegion()))
+ fStructuredDocument.setCachedDocumentRegion(fStructuredDocument.getFirstStructuredDocumentRegion());
+ }
+ if ((fStructuredDocument.getCachedDocumentRegion() == null) && (Debug.displayWarnings)) {
+ // this will happen now legitamately when all text is deleted
+ // from a document
+ System.out.println("Warning: StructuredDocumentReParser::reSetCachedNode: could not find a node to cache! (its ok if all text deleted)"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ public void setStructuredDocument(IStructuredDocument newStructuredDocument) {
+ // NOTE: this method (and class) depend on being able to
+ // do the following cast (i.e. references some fields directly)
+ fStructuredDocument = (BasicStructuredDocument) newStructuredDocument;
+ fFindReplaceDocumentAdapter = null;
+ }
+
+ private IStructuredDocumentRegion splice(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ //
+ IStructuredDocumentRegion firstOld = null;
+ IStructuredDocumentRegion firstNew = null;
+ IStructuredDocumentRegion lastOld = null;
+ IStructuredDocumentRegion lastNew = null;
+ //
+ IStructuredDocumentRegion oldPrevious = null;
+ IStructuredDocumentRegion oldNext = null;
+ IStructuredDocumentRegion newPrevious = null;
+ IStructuredDocumentRegion newNext = null;
+ //
+ // if called with both arguments empty lists, we can disregard.
+ // this happens, for example, when some text is replaced with the
+ // identical text.
+ if ((oldNodes.getLength() == 0) && (newNodes.getLength() == 0)) {
+ return null;
+ }
+ // get pointers
+ if (newNodes.getLength() > 0) {
+ firstNew = newNodes.item(0);
+ lastNew = newNodes.item(newNodes.getLength() - 1);
+ }
+ //
+ if (oldNodes.getLength() > 0) {
+ firstOld = oldNodes.item(0);
+ lastOld = oldNodes.item(oldNodes.getLength() - 1);
+ if (firstOld != null)
+ oldPrevious = firstOld.getPrevious();
+ if (lastOld != null)
+ oldNext = lastOld.getNext();
+ }
+ // handle switch
+ if (newNodes.getLength() > 0) {
+ // switch surrounding StructuredDocumentRegions' references to
+ // lists
+ if (oldPrevious != null)
+ oldPrevious.setNext(firstNew);
+ if (newPrevious != null)
+ newPrevious.setNext(firstOld);
+ if (oldNext != null)
+ oldNext.setPrevious(lastNew);
+ if (newNext != null)
+ newNext.setPrevious(lastOld);
+ // switch list pointers to surrounding StructuredDocumentRegions
+ if (firstOld != null)
+ firstOld.setPrevious(newPrevious);
+ if (lastOld != null)
+ lastOld.setNext(newNext);
+ if (firstNew != null)
+ firstNew.setPrevious(oldPrevious);
+ if (lastNew != null)
+ lastNew.setNext(oldNext);
+ } else {
+ // short circuit when there are no new nodes
+ if (oldPrevious != null)
+ oldPrevious.setNext(oldNext);
+ if (oldNext != null)
+ oldNext.setPrevious(oldPrevious);
+ }
+ //
+ // SIDE EFFECTs
+ // if we have oldNodes, and if oldNext or oldPrevious is null,
+ // that means we are replacing
+ // the lastNode or firstNode the structuredDocuments's chain of nodes,
+ // so we need to update the structuredDocuments last or first Node
+ // as the last or first of the new nodes.
+ // (and sometimes even these will be null! such as when deleting all
+ // text in a document).
+ if ((oldNext == null) && (oldNodes.getLength() > 0)) {
+ if (newNodes.getLength() > 0) {
+ fStructuredDocument.setLastDocumentRegion(lastNew);
+ } else {
+ // in this case, the last node is being deleted, but not
+ // replaced
+ // with anything. In this case, we can just back up one
+ // from the first old node
+ fStructuredDocument.setLastDocumentRegion(firstOld.getPrevious());
+ }
+ }
+ if ((oldPrevious == null) && (oldNodes.getLength() > 0)) {
+ if (newNodes.getLength() > 0) {
+ fStructuredDocument.setFirstDocumentRegion(firstNew);
+ } else {
+ // in this case the first node is being deleted, but not
+ // replaced
+ // with anything. So, we just go one forward past the last old
+ // node.
+ fStructuredDocument.setFirstDocumentRegion(lastOld.getNext());
+ }
+ }
+ // as a tiny optimization, we return the first of the downstream
+ // nodes,
+ // if any
+ return oldNext;
+ }
+
+ /**
+ * The purpose of this method is to "reuse" the old container region, when
+ * found to be same (so same instance doesn't change). The goal is to
+ * "transform" the old region, so its equivelent to the newly parsed one.
+ *
+ */
+ private ITextRegion swapNewForOldRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion, IStructuredDocumentRegion newNode, ITextRegion newRegion) {
+ // makes the old region instance the correct size.
+ oldRegion.equatePositions(newRegion);
+ // adjusts old node instance appropriately
+ oldNode.setLength(newNode.getLength());
+ oldNode.setEnded(newNode.isEnded());
+ // we do have to set the parent document, since the oldNode's
+ // were set to a temporary one, then newNode's have the
+ // right one.
+ oldNode.setParentDocument(newNode.getParentDocument());
+ // if we're transforming a container region, we need to be sure to
+ // transfer the new embedded regions, to the old parent
+ // Note: if oldRegion hasEmbeddedRegions, then we know the
+ // newRegion does too, since we got here because they were the
+ // same type.
+ if (isCollectionRegion(oldRegion)) { // ||
+ // hasContainerRegions(oldRegion))
+ // {
+ transferEmbeddedRegions(oldNode, (ITextRegionContainer) oldRegion, (ITextRegionContainer) newRegion);
+ }
+ return oldRegion;
+ }
+
+ private IStructuredDocumentRegion switchNodeLists(CoreNodeList oldNodes, CoreNodeList newNodes) {
+ IStructuredDocumentRegion result = splice(oldNodes, newNodes);
+ // ensure that the old nodes hold no references to the existing model
+ if (oldNodes.getLength() > 0) {
+ IStructuredDocumentRegion firstItem = oldNodes.item(0);
+ firstItem.setPrevious(null);
+ IStructuredDocumentRegion lastItem = oldNodes.item(oldNodes.getLength() - 1);
+ lastItem.setNext(null);
+ }
+ return result;
+ }
+
+ /**
+ * The purpose of this method is to "reuse" the old container region, when
+ * found to be same (so same instance doesn't change). The goal is to
+ * "transform" the old region, so its equivelent to the newly parsed one.
+ *
+ */
+ private void transferEmbeddedRegions(IStructuredDocumentRegion oldNode, ITextRegionContainer oldRegion, ITextRegionContainer newRegion) {
+ // the oldRegion should already have the right parent, since
+ // we got here because all's equivelent except the region
+ // postions have changed.
+ //oldRegion.setParent(newRegion.getParent());
+ // but we should check if there's "nested" embedded regions, and if
+ // so, we can just move them over. setting their parent as this old
+ // region.
+ ITextRegionList newRegionsToTransfer = newRegion.getRegions();
+ oldRegion.setRegions(newRegionsToTransfer);
+ Iterator newRegionsInOldOne = newRegionsToTransfer.iterator();
+ while (newRegionsInOldOne.hasNext()) {
+ ITextRegion newOne = (ITextRegion) newRegionsInOldOne.next();
+ if (isCollectionRegion(newOne)) { // ||
+ // hasContainerRegions(newOne)) {
+ //((ITextRegionContainer) newOne).setParent(oldRegion);
+ oldRegion.setRegions(newRegion.getRegions());
+ }
+ }
+ }
+
+ private void updateDownStreamRegions(IStructuredDocumentRegion flatNode, ITextRegion lastKnownRegion) {
+ // so all regions after the last known region (last known to be ok)
+ // have to have their start and end values adjusted.
+ ITextRegionList regions = flatNode.getRegions();
+ int listLength = regions.size();
+ int startIndex = 0;
+ // first, loop through to find where to start
+ for (int i = 0; i < listLength; i++) {
+ ITextRegion region = regions.get(i);
+ if (region == lastKnownRegion) {
+ startIndex = i;
+ break;
+ }
+ }
+ // now, beginning one past the last known one, loop
+ // through to end of list, adjusting the start and end postions.
+ startIndex++;
+ for (int j = startIndex; j < listLength; j++) {
+ ITextRegion region = regions.get(j);
+ region.adjustStart(fLengthDifference);
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionEnumeration.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionEnumeration.java
new file mode 100644
index 0000000000..11911984e7
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionEnumeration.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.util.Debug;
+
+
+public class StructuredDocumentRegionEnumeration implements Enumeration {
+
+ private int count;
+ private IStructuredDocumentRegion head;
+ private IStructuredDocumentRegion oldHead;
+
+ /**
+ * StructuredDocumentRegionEnumeration constructor comment.
+ */
+ public StructuredDocumentRegionEnumeration(IStructuredDocumentRegion newHead) {
+ super();
+ IStructuredDocumentRegion countNode = head = newHead;
+ while (countNode != null) {
+ count++;
+ countNode = countNode.getNext();
+ }
+ if (Debug.DEBUG > 5) {
+ System.out.println("N Nodes in StructuredDocumentRegionEnumeration Contructor: " + count); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * StructuredDocumentRegionEnumeration constructor comment.
+ */
+ public StructuredDocumentRegionEnumeration(IStructuredDocumentRegion start, IStructuredDocumentRegion end) {
+ super();
+ IStructuredDocumentRegion countNode = head = start;
+ if ((start == null) || (end == null)) {
+ // error condition
+ count = 0;
+ return;
+ }
+ //If both nodes are non-null, we assume there is always at least one
+ // item
+ count = 1;
+ while (countNode != end) {
+ count++;
+ countNode = countNode.getNext();
+ }
+ if (org.eclipse.wst.sse.core.internal.util.Debug.DEBUG > 5) {
+ System.out.println("N Nodes in StructuredDocumentRegionEnumeration Contructor: " + count); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * hasMoreElements method comment.
+ */
+ public synchronized boolean hasMoreElements() {
+ return count > 0;
+ }
+
+ /**
+ * nextElement method comment.
+ */
+ public synchronized Object nextElement() {
+ if (count > 0) {
+ count--;
+ oldHead = head;
+ head = head.getNext();
+ return oldHead;
+ }
+ throw new NoSuchElementException("StructuredDocumentRegionEnumeration"); //$NON-NLS-1$
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionIterator.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionIterator.java
new file mode 100644
index 0000000000..c121b31ad0
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentRegionIterator.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text;
+
+
+
+import java.util.Vector;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+import org.eclipse.wst.sse.core.internal.util.Assert;
+
+
+public class StructuredDocumentRegionIterator {
+
+ public final static IStructuredDocumentRegion adjustStart(IStructuredDocumentRegion headNode, int adjustment) {
+ IStructuredDocumentRegion aNode = headNode;
+ while (aNode != null) {
+ aNode.adjustStart(adjustment);
+ aNode = aNode.getNext();
+ }
+ return headNode;
+ }
+
+ public final static int countRegions(IStructuredDocumentRegionList flatNodes) {
+ int result = 0;
+ if (flatNodes != null) {
+ int length = flatNodes.getLength();
+ for (int i = 0; i < length; i++) {
+ IStructuredDocumentRegion node = flatNodes.item(i);
+ // don't know why, but we're getting null pointer exceptions
+ // in this method
+ if (node != null) {
+ result = result + node.getNumberOfRegions();
+ }
+ }
+ }
+ return result;
+ }
+
+ public final static String getText(CoreNodeList flatNodes) {
+ String result = null;
+ if (flatNodes == null) {
+ result = ""; //$NON-NLS-1$
+ } else {
+ StringBuffer buff = new StringBuffer();
+ //IStructuredDocumentRegion aNode = null;
+ int length = flatNodes.getLength();
+ for (int i = 0; i < length; i++) {
+ buff.append(flatNodes.item(i).getText());
+ }
+ result = buff.toString();
+ }
+ return result;
+ }
+
+ public final static CoreNodeList setParentDocument(CoreNodeList nodelist, IStructuredDocument textStore) {
+ Assert.isNotNull(nodelist, "nodelist was null in CoreNodeList::setTextStore(CoreNodeList, StructuredTextStore)"); //$NON-NLS-1$
+ int len = nodelist.getLength();
+ for (int i = 0; i < len; i++) {
+ IStructuredDocumentRegion node = nodelist.item(i);
+ //Assert.isNotNull(node, "who's putting null in the node list? in
+ // CoreNodeList::setTextStore(CoreNodeList,
+ // StructuredTextStore)"); //$NON-NLS-1$
+ node.setParentDocument(textStore);
+ }
+ return nodelist;
+ }
+
+ // public final static IStructuredDocumentRegion
+ // setStructuredDocument(IStructuredDocumentRegion headNode,
+ // BasicStructuredDocument structuredDocument) {
+ // IStructuredDocumentRegion aNode = headNode;
+ // while (aNode != null) {
+ // aNode.setParentDocument(structuredDocument);
+ // aNode = (IStructuredDocumentRegion) aNode.getNext();
+ // }
+ // return headNode;
+ // }
+ public final static IStructuredDocumentRegion setParentDocument(IStructuredDocumentRegion headNode, IStructuredDocument document) {
+ IStructuredDocumentRegion aNode = headNode;
+ while (aNode != null) {
+ aNode.setParentDocument(document);
+ aNode = aNode.getNext();
+ }
+ return headNode;
+ }
+
+ public final static Vector toVector(IStructuredDocumentRegion headNode) {
+ IStructuredDocumentRegion aNode = headNode;
+ Vector v = new Vector();
+ while (aNode != null) {
+ v.addElement(aNode);
+ aNode = aNode.getNext();
+ }
+ return v;
+ }
+
+ /**
+ *
+ */
+ private StructuredDocumentRegionIterator() {
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentSequentialRewriteTextStore.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentSequentialRewriteTextStore.java
new file mode 100644
index 0000000000..5c31aa8094
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentSequentialRewriteTextStore.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.text;
+
+import org.eclipse.jface.text.ITextStore;
+import org.eclipse.jface.text.SequentialRewriteTextStore;
+
+public class StructuredDocumentSequentialRewriteTextStore extends SequentialRewriteTextStore implements CharSequence, IRegionComparible {
+
+ /**
+ * @param source
+ */
+ public StructuredDocumentSequentialRewriteTextStore(ITextStore source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#charAt(int)
+ */
+ public char charAt(int index) {
+ return get(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#length()
+ */
+ public int length() {
+ return getLength();
+ }
+
+ /**
+ * @param c
+ * @param d
+ * @return
+ */
+ private boolean matchesIgnoreCase(char c1, char c2) {
+ // we check both case conversions to handle those few cases,
+ // in languages such as Turkish, which have some characters
+ // which sort of have 3 cases.
+ boolean result = false;
+ if (Character.toUpperCase(c1) == Character.toUpperCase(c2))
+ result = true;
+ else if (Character.toLowerCase(c1) == Character.toLowerCase(c2))
+ result = true;
+ return result;
+ }
+
+ public boolean regionMatches(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ int compareLength = stringToCompare.length();
+ if (compareLength == length) {
+ int endOffset = offset + length;
+ if (endOffset <= length()) {
+ result = regionMatches(offset, stringToCompare);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * This method assumes all lengths have been checked and fall withint
+ * exceptable limits
+ *
+ * @param offset
+ * @param stringToCompare
+ * @return
+ */
+ private boolean regionMatches(int offset, String stringToCompare) {
+ boolean result = true;
+ int stringOffset = 0;
+ int len = stringToCompare.length();
+ for (int i = offset; i < len; i++) {
+ if (charAt(i) != stringToCompare.charAt(stringOffset++)) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ public boolean regionMatchesIgnoreCase(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ int compareLength = stringToCompare.length();
+ if (compareLength == length) {
+ int endOffset = offset + length;
+ if (endOffset <= length()) {
+ result = regionMatchesIgnoreCase(offset, stringToCompare);
+ }
+ }
+
+ return result;
+ }
+
+ private boolean regionMatchesIgnoreCase(int offset, String stringToCompare) {
+ boolean result = true;
+ int stringOffset = 0;
+ int len = stringToCompare.length();
+ for (int i = offset; i < len; i++) {
+ if (!matchesIgnoreCase(charAt(i), stringToCompare.charAt(stringOffset++))) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#subSequence(int, int)
+ */
+ public CharSequence subSequence(int start, int end) {
+
+ return get(start, end - start);
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentTextStore.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentTextStore.java
new file mode 100644
index 0000000000..e825e72054
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/StructuredDocumentTextStore.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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
+ * Viacheslav Kabanovich/Exadel 97817 Wrong algoritm in class StructuredDocumentTextStore
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=97817
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+import org.eclipse.jface.text.GapTextStore;
+import org.eclipse.jface.text.ITextStore;
+
+public class StructuredDocumentTextStore implements ITextStore, CharSequence, IRegionComparible {
+
+ private GapTextStore fInternalStore;
+
+ /**
+ *
+ */
+ public StructuredDocumentTextStore() {
+ this(50, 300);
+ }
+
+ /**
+ * @param lowWatermark
+ * @param highWatermark
+ */
+ public StructuredDocumentTextStore(int lowWatermark, int highWatermark) {
+ super();
+ fInternalStore = new GapTextStore(lowWatermark, highWatermark);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#charAt(int)
+ */
+ public char charAt(int index) {
+ return fInternalStore.get(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.ITextStore#get(int)
+ */
+ public char get(int offset) {
+
+ return fInternalStore.get(offset);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.ITextStore#get(int, int)
+ */
+ public String get(int offset, int length) {
+
+ return fInternalStore.get(offset, length);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.ITextStore#getLength()
+ */
+ public int getLength() {
+
+ return fInternalStore.getLength();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#length()
+ */
+ public int length() {
+
+ return fInternalStore.getLength();
+ }
+
+ private boolean matchesIgnoreCase(char c1, char c2) {
+ // we check both case conversions to handle those few cases,
+ // in languages such as Turkish, which have some characters
+ // which sort of have 3 cases.
+ boolean result = false;
+ if (Character.toUpperCase(c1) == Character.toUpperCase(c2))
+ result = true;
+ else if (Character.toLowerCase(c1) == Character.toLowerCase(c2))
+ result = true;
+ return result;
+ }
+
+ public boolean regionMatches(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ int compareLength = stringToCompare.length();
+ if (compareLength == length) {
+ int endOffset = offset + length;
+ if (endOffset <= length()) {
+ result = regionMatches(offset, stringToCompare);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * This method assumes all lengths have been checked and fall withint
+ * exceptable limits
+ *
+ * @param offset
+ * @param stringToCompare
+ * @return
+ */
+ private boolean regionMatches(int offset, String stringToCompare) {
+ boolean result = true;
+ int stringOffset = 0;
+ int end = offset + stringToCompare.length();
+ for (int i = offset; i < end; i++) {
+ if (charAt(i) != stringToCompare.charAt(stringOffset++)) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ public boolean regionMatchesIgnoreCase(int offset, int length, String stringToCompare) {
+ boolean result = false;
+ int compareLength = stringToCompare.length();
+ if (compareLength == length) {
+ int endOffset = offset + length;
+ if (endOffset <= length()) {
+ result = regionMatchesIgnoreCase(offset, stringToCompare);
+ }
+ }
+
+ return result;
+ }
+
+ private boolean regionMatchesIgnoreCase(int offset, String stringToCompare) {
+ boolean result = true;
+ int stringOffset = 0;
+ int end = offset + stringToCompare.length();
+ for (int i = offset; i < end; i++) {
+ if (!matchesIgnoreCase(charAt(i), stringToCompare.charAt(stringOffset++))) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.ITextStore#replace(int, int,
+ * java.lang.String)
+ */
+ public void replace(int offset, int length, String text) {
+ fInternalStore.replace(offset, length, text);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.text.ITextStore#set(java.lang.String)
+ */
+ public void set(String text) {
+ fInternalStore.set(text);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.CharSequence#subSequence(int, int)
+ */
+ public CharSequence subSequence(int start, int end) {
+ // convert 'end' to 'length'
+ return fInternalStore.get(start, end - start);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/SubSetTextStore.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/SubSetTextStore.java
new file mode 100644
index 0000000000..caca8b9284
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/SubSetTextStore.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.text;
+
+/**
+ * This is a convience or utility class that allows you to make a copy of a
+ * part of a larger text store, but have the copy behave as if it was the
+ * larger text store.
+ *
+ * In other words, it provides a subset of a larger document, that "looks like"
+ * the orginal document. That is, "looks like" in terms of offsets and lengths.
+ * Valid access can only be made to data between the orignal offsets, even
+ * though those offsets are in the same units at the original, and even though
+ * the length appears to be long.
+ *
+ * For example, if a subsettext store is created for the def part of abcdefgh,
+ * then get(3,5) is valid, getLength is 8. Any other access, such as
+ * getChar(2), would be invalid.
+ */
+import org.eclipse.jface.text.ITextStore;
+
+/**
+ * Similar to basics of IDocument, but the offsets are mapped from coordinates
+ * of underlying storage to a "virtual" document.
+ */
+public class SubSetTextStore implements ITextStore {
+ private int pseudoBeginOffset; // maps to "zero" postion of new text
+ //private int pseudoEndOffset;
+ private int pseudoLength; // length of old/original document
+ private StringBuffer stringBuffer = new StringBuffer();
+
+ /**
+ * SubSetTextStore constructor comment.
+ *
+ * @param initialContent
+ * java.lang.String
+ */
+ public SubSetTextStore(String initialContent, int beginOffset, int endOffset, int originalDocumentLength) {
+ super();
+ pseudoBeginOffset = beginOffset;
+ //pseudoEndOffset = endOffset;
+ // used to be originalDocument.getLength ... not sure if used, or
+ // which
+ // is right
+ pseudoLength = originalDocumentLength;
+ stringBuffer = new StringBuffer(initialContent);
+ //set(initialContent);
+ }
+
+ // this is our "private" get, which methods in this class should
+ // use to get using "real" coordinates of underlying representation.
+ private String _get(int begin, int length) {
+ char[] chars = new char[length];
+ int srcEnd = begin + length;
+ stringBuffer.getChars(begin, srcEnd, chars, 0);
+ return new String(chars);
+ }
+
+ public char get(int offset) {
+ return stringBuffer.charAt(offset - pseudoBeginOffset);
+ }
+
+ /**
+ * @return java.lang.String
+ * @param begin
+ * int
+ * @param end
+ * int
+ */
+ public String get(int begin, int length) {
+ // remap the begin and end to "appear" to be in the
+ // same coordinates of the original parentDocument
+ return _get(begin - pseudoBeginOffset, length);
+ }
+
+ /**
+ * @return java.lang.String
+ * @param begin
+ * int
+ * @param end
+ * int
+ */
+ public char getChar(int pos) {
+ // remap the begin and end to "appear" to be in the
+ // same coordinates of the original parentDocument
+ return get(pos - pseudoBeginOffset);
+ }
+
+ /**
+ * We redefine getLength so its not the true length of this sub-set
+ * document, but the length of the original. This is needed, as a simple
+ * example, if you want to see if the pseudo end is equal the last
+ * position of the original document.
+ */
+ public int getLength() {
+ return pseudoLength;
+ }
+
+ /**
+ * Returns the length as if considered a true, standalone document
+ */
+ public int getTrueLength() {
+ return stringBuffer.length();
+ }
+
+ public void replace(int begin, int length, String changes) {
+ // remap the begin and end to "appear" to be in the
+ // same coordinates of the original parentDocument
+ int end = begin + length;
+ stringBuffer.replace(begin - pseudoBeginOffset, end, changes);
+ }
+
+ public void set(String text) {
+ stringBuffer.setLength(0);
+ stringBuffer.append(text);
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/TextRegionListImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/TextRegionListImpl.java
new file mode 100644
index 0000000000..9f62b78323
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/TextRegionListImpl.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * David Carver (Intalio) - bug 300434 - Make inner classes static where possible
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+
+
+public class TextRegionListImpl implements ITextRegionList {
+
+ static private class NullIterator implements Iterator {
+ public NullIterator() {
+ }
+
+ public boolean hasNext() {
+ return false;
+ }
+
+ public Object next() {
+ throw new NoSuchElementException();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("can not remove regions via iterator"); //$NON-NLS-1$
+
+ }
+
+ }
+
+ private static class RegionIterator implements Iterator {
+ private ITextRegion[] fIteratorRegions;
+ private int index = -1;
+ private int maxindex = -1;
+
+ public RegionIterator(ITextRegion[] regions) {
+ fIteratorRegions = regions;
+ maxindex = fIteratorRegions.length - 1;
+ }
+
+ public boolean hasNext() {
+ return index < maxindex;
+ }
+
+ public Object next() {
+ if (!(index < maxindex))
+ throw new NoSuchElementException();
+ return fIteratorRegions[++index];
+ }
+
+ public void remove() {
+ if (index < 0) {
+ // next() has never been called
+ throw new IllegalStateException("can not remove regions without prior invocation of next()"); //$NON-NLS-1$
+ }
+ throw new UnsupportedOperationException("can not remove regions via iterator"); //$NON-NLS-1$
+ }
+
+ }
+
+ private final static int growthConstant = 2;
+
+ private ITextRegion[] fRegions;
+ private int fRegionsCount = 0;
+
+ public TextRegionListImpl() {
+ super();
+ }
+
+ public TextRegionListImpl(ITextRegionList regionList) {
+ this();
+ fRegions = (ITextRegion[]) regionList.toArray().clone();
+ fRegionsCount = fRegions.length;
+ }
+
+ public boolean add(ITextRegion region) {
+ if (region == null)
+ return false;
+ ensureCapacity(fRegionsCount + 1);
+ fRegions[fRegionsCount++] = region;
+ return true;
+ }
+
+ public boolean addAll(int insertPos, ITextRegionList newRegionList) {
+ // beginning of list is 0 to insertPos-1
+ // remainder of list is insertPos to fRegionsCount
+ // resulting total will be be fRegionsCount + newRegions.size()
+ if (insertPos < 0 || insertPos > fRegionsCount) {
+ throw new ArrayIndexOutOfBoundsException(insertPos);
+ }
+
+ int newRegionListSize = newRegionList.size();
+
+ ensureCapacity(fRegionsCount + newRegionListSize);
+
+ int numMoved = fRegionsCount - insertPos;
+ if (numMoved > 0)
+ System.arraycopy(fRegions, insertPos, fRegions, insertPos + newRegionListSize, numMoved);
+
+ if (newRegionList instanceof TextRegionListImpl && ((TextRegionListImpl) newRegionList).fRegions != null) {
+ System.arraycopy(((TextRegionListImpl) newRegionList).fRegions, 0, fRegions, insertPos, newRegionListSize);
+ }
+ else {
+ for (int i = 0; i < newRegionListSize; i++) {
+ fRegions[insertPos++] = newRegionList.get(i);
+ }
+ }
+ fRegionsCount += newRegionListSize;
+ return newRegionListSize != 0;
+ }
+
+ public void clear() {
+ // note: size of array is not reduced!
+ fRegionsCount = 0;
+ }
+
+ private void ensureCapacity(int needed) {
+ if (fRegions == null) {
+ // first time
+ fRegions = new ITextRegion[needed];
+ return;
+ }
+ int oldLength = fRegions.length;
+ if (oldLength < needed) {
+ ITextRegion[] oldAdapters = fRegions;
+ ITextRegion[] newAdapters = new ITextRegion[needed + growthConstant];
+ System.arraycopy(oldAdapters, 0, newAdapters, 0, fRegionsCount);
+ fRegions = newAdapters;
+ }
+ }
+
+ public ITextRegion get(int index) {
+ // fRegionCount may not equal fRegions.length
+ if (index < 0 || index >= fRegionsCount) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return fRegions[index];
+ }
+
+ public int indexOf(ITextRegion region) {
+ int result = -1;
+ if (region != null) {
+ if (fRegions != null) {
+ for (int i = 0; i < fRegions.length; i++) {
+ if (region.equals(fRegions[i])) {
+ result = i;
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public boolean isEmpty() {
+ return fRegionsCount == 0;
+ }
+
+ public Iterator iterator() {
+ if (size() == 0) {
+ return new NullIterator();
+ } else {
+ return new RegionIterator(toArray());
+ }
+ }
+
+ public ITextRegion remove(int index) {
+ // much more efficient ways to implement this, but
+ // I doubt if called often
+ ITextRegion oneToRemove = get(index);
+ remove(oneToRemove);
+ return oneToRemove;
+ }
+
+ public void remove(ITextRegion a) {
+ if (fRegions == null || a == null)
+ return;
+ int newIndex = 0;
+ ITextRegion[] newRegions = new ITextRegion[fRegionsCount];
+ int oldRegionCount = fRegionsCount;
+ boolean found = false;
+ for (int oldIndex = 0; oldIndex < oldRegionCount; oldIndex++) {
+ ITextRegion candidate = fRegions[oldIndex];
+ if (a == candidate) {
+ fRegionsCount--;
+ found = true;
+ } else
+ newRegions[newIndex++] = fRegions[oldIndex];
+ }
+ if (found)
+ fRegions = newRegions;
+ }
+
+ public void removeAll(ITextRegionList regionList) {
+ // much more efficient ways to implement this, but
+ // I doubt if called often
+ if (regionList != null) {
+ for (int i = 0; i < regionList.size(); i++) {
+ this.remove(regionList.get(i));
+ }
+ }
+
+ }
+
+ public int size() {
+ return fRegionsCount;
+ }
+
+ public ITextRegion[] toArray() {
+ // return "clone" of internal array
+ ITextRegion[] newArray = new ITextRegion[fRegionsCount];
+ System.arraycopy(fRegions, 0, newArray, 0, fRegionsCount);
+ return newArray;
+ }
+
+ public void trimToSize() {
+ if (fRegions.length > fRegionsCount) {
+ ITextRegion[] newRegions = new ITextRegion[fRegionsCount];
+ System.arraycopy(fRegions, 0, newRegions, 0, fRegionsCount);
+ fRegions = newRegions;
+ }
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredRegion.java
new file mode 100644
index 0000000000..f12b76ab49
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredRegion.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text.rules;
+
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Like super class except allows length and offset to be modified. This is
+ * convenient for some algorithms, and allows region objects to be reused.
+ * Note: There MIGHT be some code that assumes regions are immutable. This
+ * class would not be appropriate for those uses.
+ */
+public interface IStructuredRegion extends IRegion {
+ void setLength(int length);
+
+ void setOffset(int offset);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredTypedRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredTypedRegion.java
new file mode 100644
index 0000000000..1ea21a9851
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/IStructuredTypedRegion.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text.rules;
+
+import org.eclipse.jface.text.ITypedRegion;
+
+/**
+ * Similar to extended interface, except it allows the length, offset, and
+ * type to be set. This is useful when iterating through a number of "small"
+ * regions, that all map to the the same partion regions.
+ */
+public interface IStructuredTypedRegion extends IStructuredRegion, ITypedRegion {
+ void setType(String partitionType);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredRegion.java
new file mode 100644
index 0000000000..4ca1ded47b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredRegion.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text.rules;
+
+import org.eclipse.jface.text.IRegion;
+
+
+
+/**
+ * Similar to jface region except we wanted a setting on length
+ */
+public class SimpleStructuredRegion implements IStructuredRegion {
+ /** The region length */
+ private int fLength;
+
+ /** The region offset */
+ private int fOffset;
+
+ /**
+ * Create a new region.
+ *
+ * @param offset
+ * the offset of the region
+ * @param length
+ * the length of the region
+ */
+ public SimpleStructuredRegion(int offset, int length) {
+ fOffset = offset;
+ fLength = length;
+ }
+
+ /**
+ * Two regions are equal if they have the same offset and length.
+ *
+ * @see Object#equals
+ */
+ public boolean equals(Object o) {
+ if (o instanceof IRegion) {
+ IRegion r = (IRegion) o;
+ return r.getOffset() == fOffset && r.getLength() == fLength;
+ }
+ return false;
+ }
+
+ /*
+ * @see IRegion#getLength
+ */
+ public int getLength() {
+ return fLength;
+ }
+
+ /*
+ * @see IRegion#getOffset
+ */
+ public int getOffset() {
+ return fOffset;
+ }
+
+ /**
+ * @see Object#hashCode hascode is overridden since we provide our own
+ * equals.
+ */
+ public int hashCode() {
+ return (fOffset << 24) | (fLength << 16);
+ }
+
+ /**
+ * Sets the length.
+ *
+ * @param length
+ * The length to set
+ */
+ public void setLength(int length) {
+ fLength = length;
+ }
+
+ public void setOffset(int offset) {
+ fOffset = offset;
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredTypedRegion.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredTypedRegion.java
new file mode 100644
index 0000000000..1822a8a4d8
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/SimpleStructuredTypedRegion.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.text.rules;
+
+
+
+
+/**
+ * Similar jace TypedRegion, but had to subclass our version which allowed
+ * length to be set.
+ */
+public class SimpleStructuredTypedRegion extends SimpleStructuredRegion implements IStructuredTypedRegion {
+
+ /** The region's type */
+ private String fType;
+
+ /**
+ * Creates a typed region based on the given specification.
+ *
+ * @param offset
+ * the region's offset
+ * @param length
+ * the region's length
+ * @param type
+ * the region's type
+ */
+ public SimpleStructuredTypedRegion(int offset, int length, String type) {
+ super(offset, length);
+ fType = type;
+ }
+
+ /**
+ * Two typed positions are equal if they have the same offset, length, and
+ * type.
+ *
+ * @see Object#equals
+ */
+ public boolean equals(Object o) {
+ if (o instanceof SimpleStructuredTypedRegion) {
+ SimpleStructuredTypedRegion r = (SimpleStructuredTypedRegion) o;
+ return super.equals(r) && ((fType == null && r.getType() == null) || fType.equals(r.getType()));
+ }
+ return false;
+ }
+
+ /*
+ * @see ITypedRegion#getType()
+ */
+ public String getType() {
+ return fType;
+ }
+
+ /*
+ * @see Object#hashCode
+ */
+ public int hashCode() {
+ int type = fType == null ? 0 : fType.hashCode();
+ return super.hashCode() | type;
+ }
+
+ public void setType(String type) {
+ fType = type;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ s.append(getOffset());
+ s.append(":"); //$NON-NLS-1$
+ s.append(getLength());
+ s.append(" - "); //$NON-NLS-1$
+ s.append(getType());
+ return s.toString();
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/StructuredTextPartitioner.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/StructuredTextPartitioner.java
new file mode 100644
index 0000000000..445e5f27f5
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/text/rules/StructuredTextPartitioner.java
@@ -0,0 +1,655 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.text.rules;
+
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentPartitioner;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.wst.sse.core.internal.ltk.parser.IBlockedStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.parser.ForeignRegion;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextPartitioner;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.sse.core.text.IStructuredPartitions;
+
+
+/**
+ * Base Document partitioner for StructuredDocuments. BLOCK_TEXT ITextRegions
+ * have a partition type of BLOCK or BLOCK:TAGNAME if a surrounding tagname
+ * was recorded.
+ *
+ * Subclasses should synchronize access to <code>internalReusedTempInstance</code> using the lock
+ * <code>PARTITION_LOCK</code>.
+ */
+public class StructuredTextPartitioner implements IDocumentPartitioner, IStructuredTextPartitioner {
+
+ static class CachedComputedPartitions {
+ int fLength;
+ int fOffset;
+ ITypedRegion[] fPartitions;
+ boolean isInValid;
+
+ CachedComputedPartitions(int offset, int length, ITypedRegion[] partitions) {
+ fOffset = offset;
+ fLength = length;
+ fPartitions = partitions;
+ isInValid = true;
+ }
+ }
+
+ private CachedComputedPartitions cachedPartitions = new CachedComputedPartitions(-1, -1, null);
+ protected String[] fSupportedTypes = null;
+ protected IStructuredTypedRegion internalReusedTempInstance = new SimpleStructuredTypedRegion(0, 0, IStructuredPartitions.DEFAULT_PARTITION);
+ protected IStructuredDocument fStructuredDocument;
+
+ protected final Object PARTITION_LOCK = new Object();
+
+ /**
+ * StructuredTextPartitioner constructor comment.
+ */
+ public StructuredTextPartitioner() {
+ super();
+ }
+
+ /**
+ * Returns the partitioning of the given range of the connected document.
+ * There must be a document connected to this partitioner.
+ *
+ * Note: this shouldn't be called directly by clients, unless they control
+ * the threading that includes modifications to the document. Otherwise
+ * the document could be modified while partitions are being computed. We
+ * advise that clients use the computePartitions API directly from the
+ * document, so they won't have to worry about that.
+ *
+ * @param offset
+ * the offset of the range of interest
+ * @param length
+ * the length of the range of interest
+ * @return the partitioning of the range
+ */
+ public ITypedRegion[] computePartitioning(int offset, int length) {
+ if (fStructuredDocument == null) {
+ throw new IllegalStateException("document partitioner is not connected"); //$NON-NLS-1$
+ }
+ ITypedRegion[] results = null;
+
+ synchronized (cachedPartitions) {
+ if ((!cachedPartitions.isInValid) && (offset == cachedPartitions.fOffset) && (length == cachedPartitions.fLength))
+ results = cachedPartitions.fPartitions;
+ }
+
+ if (results == null) {
+ if (length == 0) {
+ results = new ITypedRegion[]{getPartition(offset)};
+ } else {
+ List list = new ArrayList();
+ int endPos = offset + length;
+ if (endPos > fStructuredDocument.getLength()) {
+ // This can occur if the model instance is being
+ // changed
+ // and everyone's not yet up to date
+ return new ITypedRegion[]{createPartition(offset, length, getUnknown())};
+ }
+ int currentPos = offset;
+ IStructuredTypedRegion previousPartition = null;
+ while (currentPos < endPos) {
+ IStructuredTypedRegion partition = null;
+ synchronized (PARTITION_LOCK) {
+ internalGetPartition(currentPos, false);
+ currentPos += internalReusedTempInstance.getLength();
+
+ // check if this partition just continues last one
+ // (type is the same),
+ // if so, just extend length of last one, not need to
+ // create new
+ // instance.
+ if (previousPartition != null && internalReusedTempInstance.getType().equals(previousPartition.getType())) {
+ // same partition type
+ previousPartition.setLength(previousPartition.getLength() + internalReusedTempInstance.getLength());
+ }
+ else {
+ // not the same, so add to list
+ partition = createNewPartitionInstance();
+ }
+ }
+ if (partition != null) {
+ list.add(partition);
+ // and make current, previous
+ previousPartition = partition;
+ }
+ }
+ results = new ITypedRegion[list.size()];
+ list.toArray(results);
+ }
+ if (results.length > 0) {
+ // truncate returned results to requested range
+ if (results[0].getOffset() < offset && results[0] instanceof IStructuredRegion) {
+ ((IStructuredRegion) results[0]).setOffset(offset);
+ }
+ int lastEnd = results[results.length - 1].getOffset() + results[results.length - 1].getLength();
+ if (lastEnd > offset + length && results[results.length - 1] instanceof IStructuredRegion) {
+ ((IStructuredRegion) results[results.length - 1]).setLength(offset + length - results[results.length - 1].getOffset());
+ }
+ }
+ synchronized (cachedPartitions) {
+ cachedPartitions.fLength = length;
+ cachedPartitions.fOffset = offset;
+ cachedPartitions.fPartitions = results;
+ cachedPartitions.isInValid = false;
+ }
+ }
+ return results;
+ }
+
+ private void invalidatePartitionCache() {
+ synchronized (cachedPartitions) {
+ cachedPartitions.isInValid = true;
+ }
+ }
+
+ /**
+ * Connects the document to the partitioner, i.e. indicates the begin of
+ * the usage of the receiver as partitioner of the given document.
+ */
+ public synchronized void connect(IDocument document) {
+ if (document instanceof IStructuredDocument) {
+ invalidatePartitionCache();
+ this.fStructuredDocument = (IStructuredDocument) document;
+ } else {
+ throw new IllegalArgumentException("This class and API are for Structured Documents only"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Determines if the given ITextRegionContainer itself contains another
+ * ITextRegionContainer
+ *
+ * @param ITextRegionContainer
+ * @return boolean
+ */
+ protected boolean containsEmbeddedRegion(IStructuredDocumentRegion container) {
+ boolean containsEmbeddedRegion = false;
+
+ ITextRegionList regions = container.getRegions();
+ for (int i = 0; i < regions.size(); i++) {
+ ITextRegion region = regions.get(i);
+ if (region instanceof ITextRegionContainer) {
+ containsEmbeddedRegion = true;
+ break;
+ }
+ }
+ return containsEmbeddedRegion;
+ }
+
+ private IStructuredTypedRegion createNewPartitionInstance() {
+ synchronized (PARTITION_LOCK) {
+ return new SimpleStructuredTypedRegion(internalReusedTempInstance.getOffset(), internalReusedTempInstance.getLength(), internalReusedTempInstance.getType());
+ }
+ }
+
+ /**
+ * Creates the concrete partition from the given values. Returns a new
+ * instance for each call.
+ *
+ * Subclasses may override.
+ *
+ * @param offset
+ * @param length
+ * @param type
+ * @return ITypedRegion
+ *
+ * TODO: should be protected
+ */
+ public IStructuredTypedRegion createPartition(int offset, int length, String type) {
+ return new SimpleStructuredTypedRegion(offset, length, type);
+ }
+
+ /**
+ * Disconnects the document from the partitioner, i.e. indicates the end
+ * of the usage of the receiver as partitioner of the given document.
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
+ */
+ public synchronized void disconnect() {
+ invalidatePartitionCache();
+ this.fStructuredDocument = null;
+ }
+
+ /**
+ * Informs about a forthcoming document change.
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
+ */
+ public void documentAboutToBeChanged(DocumentEvent event) {
+ invalidatePartitionCache();
+ }
+
+ /**
+ * The document has been changed. The partitioner updates the set of
+ * regions and returns whether the structure of the document partitioning
+ * has been changed, i.e. whether partitions have been added or removed.
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
+ */
+ public boolean documentChanged(DocumentEvent event) {
+ boolean result = false;
+ if (event instanceof StructuredDocumentRegionsReplacedEvent) {
+ // partitions don't always change while document regions do,
+ // but that's the only "quick check" we have.
+ // I'm not sure if something more sophisticated will be needed
+ // in the future. (dmw, 02/18/04).
+ result = true;
+ }
+ return result;
+ }
+
+ protected boolean doParserSpecificCheck(int offset, boolean partitionFound, IStructuredDocumentRegion sdRegion, IStructuredDocumentRegion previousStructuredDocumentRegion, ITextRegion next, ITextRegion previousStart) {
+ // this (conceptually) abstract method is not concerned with
+ // specific region types
+ return false;
+ }
+
+ protected IStructuredDocumentRegion getParserSpecificPreviousRegion(IStructuredDocumentRegion currentRegion) {
+ return currentRegion != null ? currentRegion.getPrevious() : null;
+ }
+
+ /**
+ * Returns the content type of the partition containing the given
+ * character position of the given document. The document has previously
+ * been connected to the partitioner.
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
+ */
+ public String getContentType(int offset) {
+ return getPartition(offset).getType();
+ }
+
+ /**
+ * To be used by default!
+ */
+ public String getDefaultPartitionType() {
+
+ return IStructuredPartitions.DEFAULT_PARTITION;
+ }
+
+ /**
+ * Returns the set of all possible content types the partitioner supports.
+ * I.e. Any result delivered by this partitioner may not contain a content
+ * type which would not be included in this method's result.
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
+ */
+ public java.lang.String[] getLegalContentTypes() {
+ if (fSupportedTypes == null) {
+ initLegalContentTypes();
+ }
+ return fSupportedTypes;
+ }
+
+ /**
+ * Returns the partition containing the given character position of the
+ * given document. The document has previously been connected to the
+ * partitioner.
+ *
+ * Note: this shouldn't be called directly by clients, unless they control
+ * the threading that includes modifications to the document. Otherwise
+ * the document could be modified while partitions are being computed. We
+ * advise that clients use the getPartition API directly from the
+ * document, so they won't have to worry about that.
+ *
+ *
+ *
+ * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
+ */
+ public ITypedRegion getPartition(int offset) {
+ internalGetPartition(offset, true);
+ return createNewPartitionInstance();
+ }
+
+ protected String getPartitionFromBlockedText(ITextRegion region, int offset, String result) {
+ // parser sensitive code was moved to subclass for quick transition
+ // this (conceptually) abstract version isn't concerned with blocked
+ // text
+
+ return result;
+ }
+
+ protected String getPartitionType(ForeignRegion region, int offset) {
+ String tagname = region.getSurroundingTag();
+ String result = null;
+ if (tagname != null) {
+ result = "BLOCK:" + tagname.toUpperCase(Locale.ENGLISH); //$NON-NLS-1$
+ } else {
+ result = "BLOCK"; //$NON-NLS-1$
+ }
+ return result;
+ }
+
+
+ protected String getPartitionType(IBlockedStructuredDocumentRegion blockedStructuredDocumentRegion, int offset) {
+ String result = null;
+ ITextRegionList regions = blockedStructuredDocumentRegion.getRegions();
+
+ // regions should never be null, or hold zero regions, but just in
+ // case...
+ if (regions != null && regions.size() > 0) {
+ if (regions.size() == 1) {
+ // if only one, then its a "pure" blocked note.
+ // if more than one, then must contain some embedded region
+ // container
+ ITextRegion blockedRegion = regions.get(0);
+ // double check for code safefy, though should always be true
+ if (blockedRegion instanceof ForeignRegion) {
+ result = getPartitionType((ForeignRegion) blockedRegion, offset);
+ }
+ } else {
+ // must have some embedded region container, so we'll make
+ // sure we'll get the appropriate one
+ result = getReleventRegionType(blockedStructuredDocumentRegion, offset);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Method getPartitionType.
+ *
+ * @param region
+ * @return String
+ */
+ private String getPartitionType(ITextRegion region) {
+ // if it get's to this "raw" level, then
+ // must be default.
+ return getDefaultPartitionType();
+ }
+
+ /**
+ * Returns the partition based on region type. This basically maps from
+ * one region-type space to another, higher level, region-type space.
+ *
+ * @param region
+ * @param offset
+ * @return String
+ */
+ public String getPartitionType(ITextRegion region, int offset) {
+ String result = getDefaultPartitionType();
+ // if (region instanceof ContextRegionContainer) {
+ // result = getPartitionType((ITextRegionContainer) region, offset);
+ // } else {
+ if (region instanceof ITextRegionContainer) {
+ result = getPartitionType((ITextRegionContainer) region, offset);
+ }
+
+ result = getPartitionFromBlockedText(region, offset, result);
+
+ return result;
+
+ }
+
+ /**
+ * Similar to method with 'ITextRegion' as argument, except for
+ * RegionContainers, if it has embedded regions, then we need to drill
+ * down and return DocumentPartition based on "lowest level" region type.
+ * For example, in <body id=" <%= object.getID() %>" > The text between
+ * <%= and %> would be a "java region" not an "HTML region".
+ */
+ protected String getPartitionType(ITextRegionContainer region, int offset) {
+ // TODO this method needs to be 'cleaned up' after refactoring
+ // its instanceof logic seems messed up now.
+ String result = null;
+ if (region != null) {
+ ITextRegion coreRegion = region;
+ if (coreRegion instanceof ITextRegionContainer) {
+ result = getPartitionType((ITextRegionContainer) coreRegion, ((ITextRegionContainer) coreRegion).getRegions(), offset);
+ } else {
+ result = getPartitionType(region);
+ }
+ } else {
+ result = getPartitionType((ITextRegion) region, offset);
+ }
+
+ return result;
+ }
+
+ private String getPartitionType(ITextRegionContainer coreRegion, ITextRegionList regions, int offset) {
+ String result = null;
+ for (int i = 0; i < regions.size(); i++) {
+ ITextRegion region = regions.get(i);
+ if (coreRegion.containsOffset(region, offset)) {
+ result = getPartitionType(region, offset);
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Computes the partition type for the zero-length partition between a
+ * start tag and end tag with the given name regions.
+ *
+ * @param previousStartTagNameRegion
+ * @param nextEndTagNameRegion
+ * @return String
+ */
+ public String getPartitionTypeBetween(IStructuredDocumentRegion previousNode, IStructuredDocumentRegion nextNode) {
+ return getDefaultPartitionType();
+ }
+
+ /**
+ * Return the ITextRegion at the given offset. For most cases, this will
+ * be the flatNode itself. Should it contain an embedded
+ * ITextRegionContainer, will return the internal region at the offset
+ *
+ *
+ * @param flatNode
+ * @param offset
+ * @return ITextRegion
+ */
+ private String getReleventRegionType(IStructuredDocumentRegion flatNode, int offset) {
+ // * Note: the original form of this method -- which returned "deep"
+ // region, isn't that
+ // * useful, after doing parent elimination refactoring,
+ // * since once the deep region is returned, its hard to get its text
+ // or offset without
+ // * proper parent.
+ ITextRegion resultRegion = null;
+ if (containsEmbeddedRegion(flatNode)) {
+ resultRegion = flatNode.getRegionAtCharacterOffset(offset);
+ if (resultRegion instanceof ITextRegionContainer) {
+ resultRegion = flatNode.getRegionAtCharacterOffset(offset);
+ ITextRegionList regions = ((ITextRegionContainer) resultRegion).getRegions();
+ for (int i = 0; i < regions.size(); i++) {
+ ITextRegion region = regions.get(i);
+ if (flatNode.getStartOffset(region) <= offset && offset < flatNode.getEndOffset(region)) {
+ resultRegion = region;
+ break;
+ }
+ }
+ }
+ } else {
+ resultRegion = flatNode;
+ }
+ return resultRegion.getType();
+ }
+
+ /**
+ * To be used, instead of default, when there is some thing surprising
+ * about are attempt to partition
+ */
+ protected String getUnknown() {
+ return IStructuredPartitions.UNKNOWN_PARTITION;
+ }
+
+ /**
+ * to be abstract eventually
+ */
+ protected void initLegalContentTypes() {
+ fSupportedTypes = new String[]{IStructuredPartitions.DEFAULT_PARTITION, IStructuredPartitions.UNKNOWN_PARTITION};
+ }
+
+ /**
+ * Returns the partition containing the given character position of the
+ * given document. The document has previously been connected to the
+ * partitioner. If the checkBetween parameter is true, an offset between a
+ * start and end tag will return a zero-length region.
+ */
+ private void internalGetPartition(int offset, boolean checkBetween) {
+ if (fStructuredDocument == null) {
+ throw new IllegalStateException("document partitioner is not connected"); //$NON-NLS-1$
+ }
+
+ boolean partitionFound = false;
+ int docLength = fStructuredDocument.getLength();
+ // get document region type and map to partition type :
+ // Note: a partion can be smaller than a flatnode, if that flatnode
+ // contains a region container.
+ // That's why we need to get "relevent region".
+ IStructuredDocumentRegion structuredDocumentRegion = fStructuredDocument.getRegionAtCharacterOffset(offset);
+ // flatNode is null if empty document
+ // this is king of a "normal case" for empty document
+ if (structuredDocumentRegion == null) {
+ if (docLength == 0) {
+ /*
+ * In order to prevent infinite error loops, this partition
+ * must never have a zero length unless the document is also
+ * zero length
+ */
+ setInternalPartition(offset, 0, getDefaultPartitionType());
+ partitionFound = true;
+ }
+ else {
+ /*
+ * This case is "unusual". When would region be null, and
+ * document longer than 0. I think this means something's "out
+ * of sync". And we may want to "flag" that fact and just
+ * return one big region of 'unknown', instead of one
+ * character at a time.
+ */
+ setInternalPartition(offset, 1, getUnknown());
+ partitionFound = true;
+ }
+ }
+ else if (checkBetween) {
+ // dmw: minimizes out to the first if test above
+ // if (structuredDocumentRegion == null && docLength == 0) {
+ // // known special case for an empty document
+ // setInternalPartition(offset, 0, getDefault());
+ // partitionFound = true;
+ // }
+ // else
+ if (structuredDocumentRegion.getStartOffset() == offset) {
+ IStructuredDocumentRegion previousStructuredDocumentRegion = getParserSpecificPreviousRegion(structuredDocumentRegion);
+ if (previousStructuredDocumentRegion != null) {
+ ITextRegion next = structuredDocumentRegion.getRegionAtCharacterOffset(offset);
+ ITextRegion previousStart = previousStructuredDocumentRegion.getRegionAtCharacterOffset(previousStructuredDocumentRegion.getStartOffset());
+ partitionFound = doParserSpecificCheck(offset, partitionFound, structuredDocumentRegion, previousStructuredDocumentRegion, next, previousStart);
+ }
+ }
+ }
+
+ if (!partitionFound && structuredDocumentRegion != null) {
+ /* We want the actual ITextRegion and not a possible ITextRegionCollection that
+ * could be returned by IStructuredDocumentRegion#getRegionAtCharacterOffset
+ * This allows for correct syntax highlighting and content assist.
+ */
+ DeepRegion resultRegion = getDeepRegionAtCharacterOffset(structuredDocumentRegion, offset);
+ partitionFound = isDocumentRegionBasedPartition(structuredDocumentRegion, resultRegion.region, offset);
+ if (!partitionFound) {
+ if (resultRegion.region != null) {
+ String type = getPartitionType(resultRegion.region, offset);
+ setInternalPartition(offset, resultRegion.end - offset, type);
+ } else {
+ // can happen at EOF
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=224886
+ // The unknown type was causing problems with content assist in JSP documents
+ setInternalPartition(offset, 1, getDefaultPartitionType());
+ }
+ }
+ }
+ }
+
+ private static class DeepRegion {
+ int end;
+ ITextRegion region;
+ DeepRegion(ITextRegion r, int e) {
+ region = r;
+ end = e;
+ }
+ }
+
+ /**
+ * <p>Unlike {@link IStructuredDocumentRegion#getRegionAtCharacterOffset(int)} this will dig
+ * into <code>ITextRegionCollection</code> to find the region containing the given offset</p>
+ *
+ * @param region the containing region of the given <code>offset</code>
+ * @param offset to the overall offset in the document.
+ * @return the <code>ITextRegion</code> containing the given <code>offset</code>, will never be
+ * a <code>ITextRegionCollextion</code>
+ */
+ private DeepRegion getDeepRegionAtCharacterOffset(IStructuredDocumentRegion region, int offset) {
+ ITextRegion text = region.getRegionAtCharacterOffset(offset);
+ int end = region.getStartOffset();
+ if (text != null)
+ end += text.getStart();
+ while (text instanceof ITextRegionCollection) {
+ text = ((ITextRegionCollection) text).getRegionAtCharacterOffset(offset);
+ end += text.getStart();
+ }
+ if (text != null)
+ end += text.getLength();
+ return new DeepRegion(text, end);
+ }
+
+ /**
+ * Provides for a per-StructuredDocumentRegion override selecting the
+ * partition type using more than just a single ITextRegion.
+ *
+ * @param structuredDocumentRegion
+ * the StructuredDocumentRegion
+ * @param containedChildRegion
+ * an ITextRegion within the given StructuredDocumentRegion
+ * that would normally determine the partition type by itself
+ * @param offset
+ * the document offset
+ * @return true if the partition type will be overridden, false to
+ * continue normal processing
+ */
+ protected boolean isDocumentRegionBasedPartition(IStructuredDocumentRegion structuredDocumentRegion, ITextRegion containedChildRegion, int offset) {
+ return false;
+ }
+
+ public IDocumentPartitioner newInstance() {
+ return new StructuredTextPartitioner();
+ }
+
+ protected void setInternalPartition(int offset, int length, String type) {
+ synchronized (PARTITION_LOCK) {
+ internalReusedTempInstance.setOffset(offset);
+ internalReusedTempInstance.setLength(length);
+ internalReusedTempInstance.setType(type);
+ }
+ }
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/CommandCursorPosition.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/CommandCursorPosition.java
new file mode 100644
index 0000000000..439cf5264e
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/CommandCursorPosition.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+
+
+public interface CommandCursorPosition {
+
+ /**
+ * Returns the cursor position to be set to after this command is redone.
+ *
+ * @return int
+ */
+ int getRedoCursorPosition();
+
+ /**
+ * Returns the length of text to be selected after this command is redone.
+ *
+ * @return int
+ */
+ int getRedoSelectionLength();
+
+ /**
+ * Returns the cursor position to be set to after this command is undone.
+ *
+ * @return int
+ */
+ int getUndoCursorPosition();
+
+ /**
+ * Returns the length of text to be selected after this command is undone.
+ *
+ * @return int
+ */
+ int getUndoSelectionLength();
+
+ /**
+ * Sets the cursor position to be used after this command is redone.
+ */
+ void setRedoCursorPosition(int cursorPosition);
+
+ /**
+ * Sets the length of text to be selected after this command is redone.
+ */
+ void setRedoSelectionLength(int selectionLength);
+
+ /**
+ * Sets the cursor position to be used after this command is undone.
+ */
+ void setUndoCursorPosition(int cursorPosition);
+
+ /**
+ * Sets the length of text to be selected after this command is undone.
+ */
+ void setUndoSelectionLength(int selectionLength);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IDocumentSelectionMediator.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IDocumentSelectionMediator.java
new file mode 100644
index 0000000000..56328844b0
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IDocumentSelectionMediator.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+import org.eclipse.jface.text.IDocument;
+
+
+public interface IDocumentSelectionMediator {
+ /**
+ * Returns the document selection mediator's input document.
+ *
+ * @return the document selection mediator's input document
+ */
+ IDocument getDocument();
+
+ /**
+ * Sets a new selection in the document as a result of an undo operation.
+ *
+ * UndoDocumentEvent contains the requester of the undo operation, and the
+ * offset and length of the new selection. Implementation of
+ * IDocumentSelectionMediator can check if it's the requester that caused
+ * the new selection, and decide if the new selection should be applied.
+ */
+ void undoOperationSelectionChanged(UndoDocumentEvent event);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IStructuredTextUndoManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IStructuredTextUndoManager.java
new file mode 100644
index 0000000000..80351ba5e5
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/IStructuredTextUndoManager.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CommandStack;
+
+public interface IStructuredTextUndoManager {
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, int cursorPosition, int selectionLength);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, int cursorPosition, int selectionLength);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, String description);
+
+ /**
+ * Begin recording undo transactions.
+ */
+ void beginRecording(Object requester, String label, String description, int cursorPosition, int selectionLength);
+
+ /**
+ * Connect the mediator to the undo manager.
+ */
+ void connect(IDocumentSelectionMediator mediator);
+
+ /**
+ * Disable undo management.
+ */
+ void disableUndoManagement();
+
+ /**
+ * Disconnect the mediator from the undo manager.
+ */
+ void disconnect(IDocumentSelectionMediator mediator);
+
+ /**
+ * Enable undo management.
+ */
+ void enableUndoManagement();
+
+ /**
+ * End recording undo transactions.
+ */
+ void endRecording(Object requester);
+
+ /**
+ * End recording undo transactions.
+ */
+ void endRecording(Object requester, int cursorPosition, int selectionLength);
+
+ /**
+ * <p>
+ * Normally, the undo manager can figure out the best times when to end a
+ * pending command and begin a new one ... to the structure of a structued
+ * document. There are times, however, when clients may wish to override
+ * those algorithms and end one earlier than normal. The one known case is
+ * for multipage editors. If a user is on one page, and type '123' as
+ * attribute value, then click around to other parts of page, or different
+ * pages, then return to '123|' and type 456, then "undo" they typically
+ * expect the undo to just undo what they just typed, the 456, not the
+ * whole attribute value.
+ * <p>
+ * If there is no pending command, the request is ignored.
+ */
+ public void forceEndOfPendingCommand(Object requester, int currentPosition, int length);
+
+ /**
+ * Some clients need to do complicated things with undo stack. Plus, in
+ * some cases, if clients setCommandStack temporarily, they have
+ * reponsibility to set back to original one when finished.
+ */
+ public CommandStack getCommandStack();
+
+ /**
+ * Get the redo command even if it's not committed yet.
+ */
+ Command getRedoCommand();
+
+ /**
+ * Get the undo command even if it's not committed yet.
+ */
+ Command getUndoCommand();
+
+ /**
+ * Redo the last command in the undo manager.
+ */
+ void redo();
+
+ /**
+ * Redo the last command in the undo manager and notify the requester
+ * about the new selection.
+ */
+ void redo(IDocumentSelectionMediator requester);
+
+ /**
+ * Returns whether at least one text change can be repeated. A text change
+ * can be repeated only if it was executed and rolled back.
+ *
+ * @return <code>true</code> if at least on text change can be repeated
+ */
+ boolean redoable();
+
+ /**
+ * Set the command stack.
+ */
+ void setCommandStack(CommandStack commandStack);
+
+ /**
+ * Undo the last command in the undo manager.
+ */
+ void undo();
+
+ /**
+ * Undo the last command in the undo manager and notify the requester
+ * about the new selection.
+ */
+ void undo(IDocumentSelectionMediator requester);
+
+ /**
+ * Returns whether at least one text change can be rolled back.
+ *
+ * @return <code>true</code> if at least one text change can be rolled
+ * back
+ */
+ boolean undoable();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommand.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommand.java
new file mode 100644
index 0000000000..b085f5bc8b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommand.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+
+
+public interface StructuredTextCommand {
+
+ String getTextDeleted();
+
+ int getTextEnd();
+
+ String getTextInserted();
+
+ int getTextStart();
+
+ void setTextDeleted(String textDeleted);
+
+ void setTextEnd(int textEnd);
+
+ void setTextInserted(String textInserted);
+
+ void setTextStart(int textStart);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommandImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommandImpl.java
new file mode 100644
index 0000000000..70f2bc2d32
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCommandImpl.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+
+
+import org.eclipse.emf.common.command.AbstractCommand;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+
+public class StructuredTextCommandImpl extends AbstractCommand implements StructuredTextCommand {
+
+ protected IDocument fDocument = null; // needed for updating the text
+ protected String fTextDeleted = null;
+ protected int fTextEnd = -1;
+ protected String fTextInserted = null;
+ protected int fTextStart = -1;
+
+ /**
+ * We have no-arg constructor non-public to force document to be specfied.
+ *
+ */
+ protected StructuredTextCommandImpl() {
+ super();
+ }
+
+ public StructuredTextCommandImpl(IDocument document) {
+ this();
+ fDocument = document; // needed for updating the text
+ }
+
+ public void execute() {
+ }
+
+ /**
+ * getTextDeleted method comment.
+ */
+ public java.lang.String getTextDeleted() {
+ return fTextDeleted;
+ }
+
+ /**
+ * textEnd is the same as (textStart + textInserted.length())
+ */
+ public int getTextEnd() {
+ return fTextEnd;
+ }
+
+ /**
+ * getTextInserted method comment.
+ */
+ public java.lang.String getTextInserted() {
+ return fTextInserted;
+ }
+
+ /**
+ * getTextStart method comment.
+ */
+ public int getTextStart() {
+ return fTextStart;
+ }
+
+ protected boolean prepare() {
+ return true;
+ }
+
+ public void redo() {
+ if (fDocument instanceof IStructuredDocument) {
+ // note: one of the few places we programatically ignore read-only
+ // settings
+ ((IStructuredDocument) fDocument).replaceText(this, fTextStart, fTextDeleted.length(), fTextInserted, true);
+ } else {
+ try {
+ fDocument.replace(fTextStart, fTextDeleted.length(), fTextInserted);
+ } catch (BadLocationException e) {
+ // assumed impossible, for now
+ Logger.logException(e);
+ }
+ }
+ }
+
+ /**
+ * setTextDeleted method comment.
+ */
+ public void setTextDeleted(java.lang.String textDeleted) {
+ fTextDeleted = textDeleted;
+ }
+
+ /**
+ * setTextEnd method comment.
+ */
+ public void setTextEnd(int textEnd) {
+ fTextEnd = textEnd;
+ }
+
+ /**
+ * setTextInserted method comment.
+ */
+ public void setTextInserted(java.lang.String textInserted) {
+ fTextInserted = textInserted;
+ }
+
+ /**
+ * setTextStart method comment.
+ */
+ public void setTextStart(int textStart) {
+ fTextStart = textStart;
+ }
+
+ public void undo() {
+ if (fDocument instanceof IStructuredDocument) {
+ // note: one of the few places we programatically ignore read-only
+ // settings
+ ((IStructuredDocument) fDocument).replaceText(this, fTextStart, fTextInserted.length(), fTextDeleted, true);
+ } else {
+ try {
+ fDocument.replace(fTextStart, fTextInserted.length(), fTextDeleted);
+ } catch (BadLocationException e) {
+ // assumed impossible, for now
+ Logger.logException(e);
+ }
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCompoundCommandImpl.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCompoundCommandImpl.java
new file mode 100644
index 0000000000..ddf76362b2
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextCompoundCommandImpl.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+
+
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CompoundCommand;
+
+
+
+public class StructuredTextCompoundCommandImpl extends CompoundCommand implements CommandCursorPosition {
+ protected int fRedoCursorPosition = -1;
+ protected int fRedoSelectionLength = 0;
+
+ protected int fUndoCursorPosition = -1;
+ protected int fUndoSelectionLength = 0;
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ */
+ public StructuredTextCompoundCommandImpl() {
+ super();
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex) {
+ super(resultIndex);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex, java.util.List commandList) {
+ super(resultIndex, commandList);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ * @param label
+ * java.lang.String
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex, String label) {
+ super(resultIndex, label);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ * @param label
+ * java.lang.String
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex, String label, java.util.List commandList) {
+ super(resultIndex, label, commandList);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ * @param label
+ * java.lang.String
+ * @param description
+ * java.lang.String
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex, String label, String description) {
+ super(resultIndex, label, description);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param resultIndex
+ * int
+ * @param label
+ * java.lang.String
+ * @param description
+ * java.lang.String
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(int resultIndex, String label, String description, java.util.List commandList) {
+ super(resultIndex, label, description, commandList);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(java.util.List commandList) {
+ super(commandList);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param label
+ * java.lang.String
+ */
+ public StructuredTextCompoundCommandImpl(String label) {
+ super(label);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param label
+ * java.lang.String
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(String label, java.util.List commandList) {
+ super(label, commandList);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param label
+ * java.lang.String
+ * @param description
+ * java.lang.String
+ */
+ public StructuredTextCompoundCommandImpl(String label, String description) {
+ super(label, description);
+ }
+
+ /**
+ * StructuredTextCompoundCommandImpl constructor comment.
+ *
+ * @param label
+ * java.lang.String
+ * @param description
+ * java.lang.String
+ * @param commandList
+ * java.util.List
+ */
+ public StructuredTextCompoundCommandImpl(String label, String description, java.util.List commandList) {
+ super(label, description, commandList);
+ }
+
+ /**
+ * Returns the cursor position to be set to after this command is redone.
+ *
+ * @return int
+ */
+ public int getRedoCursorPosition() {
+ int cursorPosition = -1;
+
+ if (fRedoCursorPosition != -1)
+ cursorPosition = fRedoCursorPosition;
+ else if (!commandList.isEmpty()) {
+ int commandListSize = commandList.size();
+ Command lastCommand = (Command) commandList.get(commandListSize - 1);
+
+ if (lastCommand instanceof CommandCursorPosition)
+ cursorPosition = ((CommandCursorPosition) lastCommand).getRedoCursorPosition();
+ }
+
+ return cursorPosition;
+ }
+
+ /**
+ * Returns the length of text to be selected after this command is redone.
+ *
+ * @return int
+ */
+ public int getRedoSelectionLength() {
+ return fRedoSelectionLength;
+ }
+
+ /**
+ * Returns the cursor position to be set to after this command is undone.
+ *
+ * @return int
+ */
+ public int getUndoCursorPosition() {
+ int cursorPosition = -1;
+
+ if (fUndoCursorPosition != -1)
+ cursorPosition = fUndoCursorPosition;
+ else if (!commandList.isEmpty()) {
+ // never used
+ //int commandListSize = commandList.size();
+ Command firstCommand = (Command) commandList.get(0);
+
+ if (firstCommand instanceof CommandCursorPosition)
+ cursorPosition = ((CommandCursorPosition) firstCommand).getUndoCursorPosition();
+ }
+
+ return cursorPosition;
+ }
+
+ /**
+ * Returns the length of text to be selected after this command is undone.
+ *
+ * @return int
+ */
+ public int getUndoSelectionLength() {
+ return fUndoSelectionLength;
+ }
+
+ /**
+ * Sets the cursor position to be used after this command is redone.
+ */
+ public void setRedoCursorPosition(int cursorPosition) {
+ fRedoCursorPosition = cursorPosition;
+ }
+
+ /**
+ * Sets the length of text to be selected after this command is redone.
+ */
+ public void setRedoSelectionLength(int selectionLength) {
+ fRedoSelectionLength = selectionLength;
+ }
+
+ /**
+ * Sets the cursor position to be used after this command is undone.
+ */
+ public void setUndoCursorPosition(int cursorPosition) {
+ fUndoCursorPosition = cursorPosition;
+ }
+
+ /**
+ * Sets the length of text to be selected after this command is undone.
+ */
+ public void setUndoSelectionLength(int selectionLength) {
+ fUndoSelectionLength = selectionLength;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextUndoManager.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextUndoManager.java
new file mode 100644
index 0000000000..93a9b91b52
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/StructuredTextUndoManager.java
@@ -0,0 +1,650 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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
+ * Jesper Steen Møller - initial IDocumentExtension4 support - #102822
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.undo;
+
+import java.util.EventObject;
+
+import org.eclipse.emf.common.command.BasicCommandStack;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CommandStack;
+import org.eclipse.emf.common.command.CommandStackListener;
+import org.eclipse.emf.common.command.CompoundCommand;
+import org.eclipse.jface.text.DocumentRewriteSession;
+import org.eclipse.jface.text.DocumentRewriteSessionType;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension4;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.SSECoreMessages;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+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.Utilities;
+
+public class StructuredTextUndoManager implements IStructuredTextUndoManager {
+
+ class InternalCommandStackListener implements CommandStackListener {
+ public void commandStackChanged(EventObject event) {
+ resetInternalCommands();
+ }
+ }
+
+ class InternalStructuredDocumentListener implements IStructuredDocumentListener {
+
+ public void newModel(NewDocumentEvent structuredDocumentEvent) {
+ // Do nothing. Do not push the new model's structuredDocument
+ // changes
+ // onto the undo command stack, or else the user may be able to
+ // undo
+ // an existing file to an empty file.
+ }
+
+ public void noChange(NoChangeEvent structuredDocumentEvent) {
+ // Since "no change", do nothing.
+ }
+
+ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) {
+ processStructuredDocumentEvent(structuredDocumentEvent);
+ }
+
+ private void processStructuredDocumentEvent(String textDeleted, String textInserted, int textStart, int textEnd) {
+ if (fTextCommand != null && textStart == fTextCommand.getTextEnd()) {
+ // append to the text command
+ fTextCommand.setTextDeleted(fTextCommand.getTextDeleted().concat(textDeleted));
+ fTextCommand.setTextInserted(fTextCommand.getTextInserted().concat(textInserted));
+ fTextCommand.setTextEnd(textEnd);
+ }
+ else if (fTextCommand != null && textStart == fTextCommand.getTextStart() - 1 && textEnd <= fTextCommand.getTextEnd() - 1 && textDeleted.length() == 1 && textInserted.length() == 0 && fTextCommand.getTextDeleted().length() > 0) {
+ // backspace pressed
+ // erase a character in the file
+ fTextCommand.setTextDeleted(textDeleted.concat(fTextCommand.getTextDeleted()));
+ fTextCommand.setTextStart(textStart);
+ }
+ else {
+ createNewTextCommand(textDeleted, textInserted, textStart, textEnd);
+ }
+
+ // save cursor position
+ fCursorPosition = textEnd;
+ }
+
+ private void processStructuredDocumentEvent(StructuredDocumentEvent structuredDocumentEvent) {
+ // Note: fListening tells us if we should listen to the
+ // StructuredDocumentEvent.
+ // fListening is set to false right before the undo/redo process
+ // and
+ // then set to true again
+ // right after the undo/redo process to block out and ignore all
+ // StructuredDocumentEvents generated
+ // by the undo/redo process.
+
+ // Process StructuredDocumentEvent if fListening is true.
+ //
+ // We are executing a command from the command stack if the
+ // requester
+ // is a command (for example, undo/redo).
+ // We should not process the flat model event when we are
+ // executing a
+ // command from the command stack.
+ if (fUndoManagementEnabled && !(structuredDocumentEvent.getOriginalRequester() instanceof Command)) {
+ // check requester if not recording
+ if (!fRecording)
+ checkRequester(structuredDocumentEvent.getOriginalRequester());
+
+ // process the structuredDocumentEvent
+ String textDeleted = structuredDocumentEvent.getDeletedText();
+ String textInserted = structuredDocumentEvent.getText();
+ int textStart = structuredDocumentEvent.getOffset();
+ int textEnd = textStart + textInserted.length();
+ processStructuredDocumentEvent(textDeleted, textInserted, textStart, textEnd);
+ }
+ }
+
+ public void regionChanged(RegionChangedEvent structuredDocumentEvent) {
+ processStructuredDocumentEvent(structuredDocumentEvent);
+ }
+
+ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) {
+ processStructuredDocumentEvent(structuredDocumentEvent);
+ }
+
+ }
+
+ private static final String TEXT_CHANGE_TEXT = SSECoreMessages.Text_Change_UI_; //$NON-NLS-1$
+ private CommandStack fCommandStack = null;
+ private StructuredTextCompoundCommandImpl fCompoundCommand = null;
+ private String fCompoundCommandDescription = null;
+ private String fCompoundCommandLabel = null;
+ int fCursorPosition = 0;
+ // private IStructuredModel fStructuredModel = null;
+ private IDocument fDocument;
+ private InternalCommandStackListener fInternalCommandStackListener;
+ // private Map fTextViewerToListenerMap = new HashMap();
+ private IStructuredDocumentListener fInternalStructuredDocumentListener;
+ private IDocumentSelectionMediator[] fMediators = null;
+ private boolean fRecording = false;
+ private int fRecordingCount = 0;
+ private Object fRequester;
+ StructuredTextCommandImpl fTextCommand = null;
+ private int fUndoCursorPosition = -1;
+ boolean fUndoManagementEnabled = true;
+ private int fUndoSelectionLength = 0;
+
+ public StructuredTextUndoManager() {
+ this(new BasicCommandStack());
+ }
+
+ public StructuredTextUndoManager(CommandStack commandStack) {
+ setCommandStack(commandStack);
+ }
+
+ private void addDocumentSelectionMediator(IDocumentSelectionMediator mediator) {
+ if (!Utilities.contains(fMediators, mediator)) {
+ int oldSize = 0;
+
+ if (fMediators != null) {
+ // normally won't be null, but we need to be sure, for first
+ // time through
+ oldSize = fMediators.length;
+ }
+
+ int newSize = oldSize + 1;
+ IDocumentSelectionMediator[] newMediators = new IDocumentSelectionMediator[newSize];
+ if (fMediators != null) {
+ System.arraycopy(fMediators, 0, newMediators, 0, oldSize);
+ }
+
+ // add the new undo mediator to last position
+ newMediators[newSize - 1] = mediator;
+
+ // now switch new for old
+ fMediators = newMediators;
+ }
+ else {
+ removeDocumentSelectionMediator(mediator);
+ addDocumentSelectionMediator(mediator);
+ }
+ }
+
+ public void beginRecording(Object requester) {
+ beginRecording(requester, null, null);
+ }
+
+ public void beginRecording(Object requester, int cursorPosition, int selectionLength) {
+ beginRecording(requester, null, null);
+
+ fUndoCursorPosition = cursorPosition;
+ fUndoSelectionLength = selectionLength;
+ }
+
+ public void beginRecording(Object requester, String label) {
+ beginRecording(requester, label, null);
+ }
+
+ public void beginRecording(Object requester, String label, int cursorPosition, int selectionLength) {
+ beginRecording(requester, label, null);
+
+ fUndoCursorPosition = cursorPosition;
+ fUndoSelectionLength = selectionLength;
+ }
+
+ public void beginRecording(Object requester, String label, String description) {
+ // save the requester
+ fRequester = requester;
+
+ // update label and desc only on the first level when recording is
+ // nested
+ if (fRecordingCount == 0) {
+ fCompoundCommandLabel = label;
+ if (fCompoundCommandLabel == null)
+ fCompoundCommandLabel = TEXT_CHANGE_TEXT;
+
+ fCompoundCommandDescription = description;
+ if (fCompoundCommandDescription == null)
+ fCompoundCommandDescription = TEXT_CHANGE_TEXT;
+
+ // clear commands
+ fTextCommand = null;
+ fCompoundCommand = null;
+ }
+
+ // update counter and flag
+ fRecordingCount++;
+ fRecording = true;
+
+ // no undo cursor position and undo selection length specified
+ // reset undo cursor position and undo selection length
+ fUndoCursorPosition = -1;
+ fUndoSelectionLength = 0;
+ }
+
+ public void beginRecording(Object requester, String label, String description, int cursorPosition, int selectionLength) {
+ beginRecording(requester, label, description);
+
+ fUndoCursorPosition = cursorPosition;
+ fUndoSelectionLength = selectionLength;
+ }
+
+ void checkRequester(Object requester) {
+ if (fRequester != null && !fRequester.equals(requester)) {
+ // Force restart of recording so the last compound command is
+ // closed.
+ //
+ // However, we should not force restart of recording when the
+ // request came from StructuredDocumentToTextAdapter or
+ // XMLModelImpl
+ // because cut/paste requests and character inserts to the
+ // textViewer are from StructuredDocumentToTextAdapter,
+ // and requests to delete a node in the XMLTableTreeViewer are
+ // from XMLModelImpl (which implements IStructuredModel).
+
+ if (!(requester instanceof IStructuredModel || requester instanceof IStructuredDocument)) {
+ resetInternalCommands();
+ }
+ }
+ }
+
+
+
+ public void connect(IDocumentSelectionMediator mediator) {
+ Assert.isNotNull(mediator);
+ if (fDocument == null) {
+ // add this undo manager as structured document listener
+ fDocument = mediator.getDocument();
+ // future_TODO: eventually we want to refactor or allow either
+ // type of document, but for now, we'll do instanceof check, and
+ // fail
+ // if not right type
+ if (fDocument instanceof IStructuredDocument) {
+ ((IStructuredDocument) fDocument).addDocumentChangedListener(getInternalStructuredDocumentListener());
+ }
+ else {
+ throw new IllegalArgumentException("only meditator with structured documents currently handled"); //$NON-NLS-1$
+ }
+ }
+ else {
+ // if we've already had our document set, we'll just do this fail
+ // fast integrity check
+ if (!fDocument.equals(mediator.getDocument()))
+ throw new IllegalStateException("Connection to undo manager failed. Document for document selection mediator inconistent with undo manager."); //$NON-NLS-1$
+ }
+
+ addDocumentSelectionMediator(mediator);
+ }
+
+ void createNewTextCommand(String textDeleted, String textInserted, int textStart, int textEnd) {
+ StructuredTextCommandImpl textCommand = new StructuredTextCommandImpl(fDocument);
+ textCommand.setLabel(TEXT_CHANGE_TEXT);
+ textCommand.setDescription(TEXT_CHANGE_TEXT);
+ textCommand.setTextStart(textStart);
+ textCommand.setTextEnd(textEnd);
+ textCommand.setTextDeleted(textDeleted);
+ textCommand.setTextInserted(textInserted);
+
+ if (fRecording) {
+ if (fCompoundCommand == null) {
+ StructuredTextCompoundCommandImpl compoundCommand = new StructuredTextCompoundCommandImpl();
+ compoundCommand.setUndoCursorPosition(fUndoCursorPosition);
+ compoundCommand.setUndoSelectionLength(fUndoSelectionLength);
+
+ compoundCommand.setLabel(fCompoundCommandLabel);
+ compoundCommand.setDescription(fCompoundCommandDescription);
+ compoundCommand.append(textCommand);
+
+ fCompoundCommand = compoundCommand;
+ }
+ else {
+ fCompoundCommand.append(textCommand);
+ }
+ }
+ else {
+ fCommandStack.execute(textCommand);
+ }
+
+ fTextCommand = textCommand;
+ }
+
+ /**
+ * Disable undo management.
+ */
+ public void disableUndoManagement() {
+ fUndoManagementEnabled = false;
+ }
+
+ public void disconnect(IDocumentSelectionMediator mediator) {
+ removeDocumentSelectionMediator(mediator);
+
+ if (fMediators != null && fMediators.length == 0 && fDocument != null) {
+ // remove this undo manager as structured document listener
+ // future_TODO: eventually we want to refactor or allow either
+ // type of document, but for now, we'll do instanceof check, and
+ // fail
+ // if not right type
+ if (fDocument instanceof IStructuredDocument) {
+ ((IStructuredDocument) fDocument).removeDocumentChangedListener(getInternalStructuredDocumentListener());
+ }
+ else {
+ throw new IllegalArgumentException("only meditator with structured documents currently handled"); //$NON-NLS-1$
+ }
+ // if no longer listening to document, then dont even track it
+ // anymore
+ // (this allows connect to reconnect to document again)
+ fDocument = null;
+ }
+ }
+
+ public void enableUndoManagement() {
+ fUndoManagementEnabled = true;
+ }
+
+ public void endRecording(Object requester) {
+ int cursorPosition = (fTextCommand != null) ? fTextCommand.getTextEnd() : -1;
+ int selectionLength = 0;
+
+ endRecording(requester, cursorPosition, selectionLength);
+ }
+
+ public void endRecording(Object requester, int cursorPosition, int selectionLength) {
+ // Recording could be stopped by forceEndOfPendingCommand(). Make sure
+ // we are still recording before proceeding, or else fRecordingCount
+ // may not be balanced.
+ if (fRecording) {
+ if (fCompoundCommand != null) {
+ fCompoundCommand.setRedoCursorPosition(cursorPosition);
+ fCompoundCommand.setRedoSelectionLength(selectionLength);
+ }
+
+ // end recording is a logical stopping point for text command,
+ // even when fRecordingCount > 0 (in nested beginRecording)
+ fTextCommand = null;
+
+ // update counter and flag
+ if (fRecordingCount > 0)
+ fRecordingCount--;
+ if (fRecordingCount == 0) {
+
+ // Finally execute the commands accumulated in the compound command.
+
+ if (fCompoundCommand != null) {
+ fCommandStack.execute(fCompoundCommand);
+ }
+
+ fRecording = false;
+
+ // reset compound command only when fRecordingCount ==
+ // 0
+ fCompoundCommand = null;
+ fCompoundCommandLabel = null;
+ fCompoundCommandDescription = null;
+
+ // Also reset fRequester
+ fRequester = null;
+ }
+ }
+ }
+
+ /**
+ * Utility method to find model given document
+ */
+ private IStructuredModel findStructuredModel(IDocument document) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel structuredModel = modelManager.getExistingModelForRead(document);
+ return structuredModel;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.undo.IStructuredTextUndoManager#forceEndOfPendingCommand(java.lang.Object,
+ * int, int)
+ */
+ public void forceEndOfPendingCommand(Object requester, int currentPosition, int length) {
+ if (fRecording)
+ endRecording(requester, currentPosition, length);
+ else
+ resetInternalCommands();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.wst.sse.core.undo.IStructuredTextUndoManager#getCommandStack()
+ */
+ public CommandStack getCommandStack() {
+ return fCommandStack;
+ }
+
+ /**
+ * @return
+ */
+ private CommandStackListener getInternalCommandStackListener() {
+ if (fInternalCommandStackListener == null) {
+ fInternalCommandStackListener = new InternalCommandStackListener();
+ }
+ return fInternalCommandStackListener;
+ }
+
+ /**
+ * @return
+ */
+ private IStructuredDocumentListener getInternalStructuredDocumentListener() {
+ if (fInternalStructuredDocumentListener == null) {
+ fInternalStructuredDocumentListener = new InternalStructuredDocumentListener();
+ }
+ return fInternalStructuredDocumentListener;
+ }
+
+ public Command getRedoCommand() {
+ return fCommandStack.getRedoCommand();
+ }
+
+ public Command getUndoCommand() {
+ return fCommandStack.getUndoCommand();
+ }
+
+ public void redo() {
+ redo(null);
+ }
+
+ public void redo(IDocumentSelectionMediator requester) {
+ IStructuredModel model = findStructuredModel(fDocument);
+
+ if (redoable()) {
+ IDocumentExtension4 docExt4 = null;
+ DocumentRewriteSession rewriteSession = null;
+ try {
+ if (model != null)
+ model.aboutToChangeModel();
+
+ Command redoCommand = getRedoCommand();
+ if (redoCommand instanceof CompoundCommand &&
+ model.getStructuredDocument() instanceof IDocumentExtension4) {
+ docExt4 = (IDocumentExtension4)model.getStructuredDocument();
+ }
+ rewriteSession = (docExt4 == null) ? null :
+ docExt4.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
+
+ // make sure to redo before setting document selection
+ fCommandStack.redo();
+
+ // set document selection
+ setRedoDocumentSelection(requester, redoCommand);
+ }
+ finally {
+ if (docExt4 != null && rewriteSession != null)
+ docExt4.stopRewriteSession(rewriteSession);
+ if (model != null) {
+ model.changedModel();
+ model.releaseFromRead();
+ }
+ }
+ }
+ }
+
+ public boolean redoable() {
+ return fCommandStack.canRedo();
+ }
+
+ private void removeDocumentSelectionMediator(IDocumentSelectionMediator mediator) {
+ if (fMediators != null && mediator != null) {
+ // if its not in the array, we'll ignore the request
+ if (Utilities.contains(fMediators, mediator)) {
+ int oldSize = fMediators.length;
+ int newSize = oldSize - 1;
+ IDocumentSelectionMediator[] newMediators = new IDocumentSelectionMediator[newSize];
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fMediators[i] == mediator) { // ignore
+ }
+ else {
+ // copy old to new if its not the one we are removing
+ newMediators[index++] = fMediators[i];
+ }
+ }
+ // now that we have a new array, let's switch it for the old
+ // one
+ fMediators = newMediators;
+ }
+ }
+ }
+
+ void resetInternalCommands() {
+ // Either the requester of the structured document change event is
+ // changed, or the command stack is changed. Need to reset internal
+ // commands so we won't continue to append changes.
+ fCompoundCommand = null;
+ fTextCommand = null;
+
+ // Also reset fRequester
+ fRequester = null;
+ }
+
+ public void setCommandStack(CommandStack commandStack) {
+ if (fCommandStack != null)
+ fCommandStack.removeCommandStackListener(getInternalCommandStackListener());
+
+ fCommandStack = commandStack;
+
+ if (fCommandStack != null)
+ fCommandStack.addCommandStackListener(getInternalCommandStackListener());
+ }
+
+ private void setRedoDocumentSelection(IDocumentSelectionMediator requester, Command command) {
+ int cursorPosition = -1;
+ int selectionLength = 0;
+
+ if (command instanceof CommandCursorPosition) {
+ CommandCursorPosition commandCursorPosition = (CommandCursorPosition) command;
+ cursorPosition = commandCursorPosition.getRedoCursorPosition();
+ selectionLength = commandCursorPosition.getRedoSelectionLength();
+ }
+ else if (command instanceof StructuredTextCommand) {
+ StructuredTextCommand structuredTextCommand = (StructuredTextCommand) command;
+ cursorPosition = structuredTextCommand.getTextStart();
+ selectionLength = structuredTextCommand.getTextInserted().length();
+ }
+
+ if (cursorPosition > -1 && fMediators != null && fMediators.length > 0) {
+ for (int i = 0; i < fMediators.length; i++) {
+ IDocument document = fMediators[i].getDocument();
+ fMediators[i].undoOperationSelectionChanged(new UndoDocumentEvent(requester, document, cursorPosition, selectionLength));
+ }
+ }
+ }
+
+ private void setUndoDocumentSelection(IDocumentSelectionMediator requester, Command command) {
+ int cursorPosition = -1;
+ int selectionLength = 0;
+
+ if (command instanceof CommandCursorPosition) {
+ CommandCursorPosition commandCursorPosition = (CommandCursorPosition) command;
+ cursorPosition = commandCursorPosition.getUndoCursorPosition();
+ selectionLength = commandCursorPosition.getUndoSelectionLength();
+ }
+ else if (command instanceof StructuredTextCommand) {
+ StructuredTextCommand structuredTextCommand = (StructuredTextCommand) command;
+ cursorPosition = structuredTextCommand.getTextStart();
+ selectionLength = structuredTextCommand.getTextDeleted().length();
+ }
+
+ if (cursorPosition > -1 && fMediators != null && fMediators.length > 0) {
+ for (int i = 0; i < fMediators.length; i++) {
+ IDocument document = fMediators[i].getDocument();
+ fMediators[i].undoOperationSelectionChanged(new UndoDocumentEvent(requester, document, cursorPosition, selectionLength));
+ }
+ }
+ }
+
+ public void undo() {
+ undo(null);
+ }
+
+ public void undo(IDocumentSelectionMediator requester) {
+ // Force an endRecording before undo.
+ //
+ // For example, recording was turned on on the Design Page of
+ // PageDesigner.
+ // Then undo is invoked on the Source Page. Recording should be
+ // stopped before we undo.
+ // Note that redo should not be available when we switch to the Source
+ // Page.
+ // Therefore, this force ending of recording is not needed in redo.
+ if (fRecording)
+ endRecording(this);
+
+ if (undoable()) {
+ IStructuredModel model = findStructuredModel(fDocument);
+ IDocumentExtension4 docExt4 = null;
+ DocumentRewriteSession rewriteSession = null;
+
+ try {
+ if (model != null)
+ model.aboutToChangeModel();
+
+ Command undoCommand = getUndoCommand();
+ if (undoCommand instanceof CompoundCommand &&
+ model.getStructuredDocument() instanceof IDocumentExtension4) {
+ docExt4 = (IDocumentExtension4)model.getStructuredDocument();
+ }
+ rewriteSession = (docExt4 == null) ? null :
+ docExt4.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
+
+ // make sure to undo before setting document selection
+ fCommandStack.undo();
+
+ // set document selection
+ setUndoDocumentSelection(requester, undoCommand);
+ }
+ finally {
+ if (docExt4 != null && rewriteSession != null)
+ docExt4.stopRewriteSession(rewriteSession);
+ if (model != null) {
+ model.changedModel();
+ model.releaseFromRead();
+ }
+ }
+ }
+ }
+
+ public boolean undoable() {
+ return fCommandStack.canUndo();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/UndoDocumentEvent.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/UndoDocumentEvent.java
new file mode 100644
index 0000000000..0fee467220
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/undo/UndoDocumentEvent.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.undo;
+
+import org.eclipse.jface.text.IDocument;
+
+public class UndoDocumentEvent {
+ private IDocument fDocument;
+ private int fLength;
+ private int fOffset;
+ private IDocumentSelectionMediator fRequester;
+
+ public UndoDocumentEvent(IDocumentSelectionMediator requester, IDocument document, int offset, int length) {
+ fRequester = requester;
+ fDocument = document;
+ fOffset = offset;
+ fLength = length;
+ }
+
+ public IDocument getDocument() {
+ return fDocument;
+ }
+
+ public int getLength() {
+ return fLength;
+ }
+
+ public int getOffset() {
+ return fOffset;
+ }
+
+ public IDocumentSelectionMediator getRequester() {
+ return fRequester;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java
new file mode 100644
index 0000000000..46c60a85e4
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.SSECorePlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+/**
+ * This responds to memory events.
+ *
+ * Create an instance of a child of this class with the events you are interested in.
+ * Then call connect() to start listening. To stop listening call disconnect();
+ */
+public abstract class AbstractMemoryListener implements EventHandler {
+ /**
+ * The event that indicates that memory is running low at the lowest severity.
+ * Listeners are requested to release caches that can easily be recomputed.
+ * The Java VM is not seriously in trouble, but process size is getting higher than
+ * is deemed acceptable.
+ */
+ public static final String SEV_NORMAL = "org/eclipse/equinox/events/MemoryEvent/NORMAL"; //$NON-NLS-1$
+
+ /**
+ * The event that indicates that memory is running low at medium severity.
+ * Listeners are requested to release intermediate build results, complex models, etc.
+ * Memory is getting low and may cause operating system level stress, such as swapping.
+ */
+ public static final String SEV_SERIOUS = "org/eclipse/equinox/events/MemoryEvent/SERIOUS"; //$NON-NLS-1$
+
+ /**
+ * The event that indicates that memory is running low at highest severity.
+ * Listeners are requested to do things like close editors and perspectives, close database connections, etc.
+ * Restoring these resources and caches constitutes lots of work, but memory is so low that
+ * drastic measures are required.
+ */
+ public static final String SEV_CRITICAL = "org/eclipse/equinox/events/MemoryEvent/CRITICAL"; //$NON-NLS-1$
+
+ /**
+ * All of the valid memory severities
+ */
+ public static final String[] SEV_ALL = { SEV_NORMAL, SEV_SERIOUS, SEV_CRITICAL };
+
+ /**
+ * Used to register the {@link EventAdmin} listener
+ */
+ private static BundleContext CONTEXT =
+ (SSECorePlugin.getDefault() != null) ?
+ SSECorePlugin.getDefault().getBundle().getBundleContext() : null;
+
+ /**
+ * the severities that will be reacted to
+ */
+ private final List fSeverities;
+
+ /**
+ * service used to register this listener
+ */
+ private ServiceRegistration fRegisterService;
+
+ /**
+ * Will listen to all memory events
+ */
+ public AbstractMemoryListener() {
+ this(AbstractMemoryListener.SEV_ALL);
+ }
+
+ /**
+ * Will listen to memory events of the given <code>severity</code>
+ *
+ * @param severity listen for memory events of this severity
+ */
+ public AbstractMemoryListener(String severity) {
+ Assert.isNotNull(severity, "Severity can not be null"); //$NON-NLS-1$
+
+ List severities = new ArrayList(1);
+ severities.add(severity);
+ fSeverities = severities;
+ }
+
+ /**
+ * Will listen to memory events of the given <code>severities</code>
+ *
+ * @param severities listen for memory events for any of these severities
+ */
+ public AbstractMemoryListener(String[] severities) {
+ Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$
+ Assert.isLegal(severities.length > 0, "Severities must specify at least one severity"); //$NON-NLS-1$
+
+ fSeverities = Arrays.asList(severities);
+ }
+
+ /**
+ * Will listen to memory events of the given <code>severities</code>
+ *
+ * @param severities listen for memory events for any of these severities
+ */
+ public AbstractMemoryListener(List severities) {
+ Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$
+ Assert.isLegal(!severities.isEmpty(), "Severities must specify at least one severity"); //$NON-NLS-1$
+ fSeverities = severities;
+ }
+
+ /**
+ * Connect this listener to the {@link EventAdmin}
+ */
+ public final void connect() {
+ if (CONTEXT != null) {
+ // NOTE: This is TEMPORARY CODE needed to load the plugin
+ // until its done automatically by the product
+ // TODO: Remove me
+ Bundle b = Platform.getBundle("org.eclipse.equinox.event"); //$NON-NLS-1$
+ if (b != null && b.getState() == Bundle.RESOLVED) {
+ try {
+ b.start(Bundle.START_TRANSIENT);
+ }
+ catch (BundleException e) {
+ e.printStackTrace();
+ }
+ }
+ // end remove me
+
+ //register this handler
+ String[] severities = (String[])fSeverities.toArray(new String[fSeverities.size()]);
+ Hashtable prop = new Hashtable(1);
+ prop.put(EventConstants.EVENT_TOPIC, severities);
+ fRegisterService = CONTEXT.registerService(EventHandler.class.getName(), this, prop);
+
+ //call any implementer specific connect code
+ doConnect();
+ } else {
+ Logger.log(Logger.WARNING, "Error accessing bundle context. Is Platform running? Not tracking memory events. "); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Disconnect this listener to the {@link EventAdmin}
+ */
+ public final void disconnect() {
+ if (fRegisterService != null) {
+ fRegisterService.unregister();
+ fRegisterService = null;
+ }
+
+ //call any implementer specific disconnect code
+ doDisconnect();
+ }
+
+ /**
+ * <p>Filter out any events that are not of the type that this listener handles</p>
+ *
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ public final void handleEvent(Event event) {
+ if (fSeverities.contains(event.getTopic())) {
+ handleMemoryEvent(event);
+ }
+ }
+
+ /**
+ * Implementing child classes may assume that only {@link Event}s of the types
+ * given to the constructor will be given to this method.
+ *
+ * @param event the {@link Event} with a topic equal to one of the memory
+ * severities that this listener is listening for
+ */
+ protected abstract void handleMemoryEvent(Event event);
+
+ /**
+ * Implementers may overrun this method to do setup after connection of this listener
+ */
+ protected void doConnect() {
+ //do nothing by default
+ }
+
+ /**
+ * Implementers may overrun this method to do tear down after disconnection of this listener
+ */
+ protected void doDisconnect() {
+ //do nothing by default
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Assert.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Assert.java
new file mode 100644
index 0000000000..f3d1ee04ab
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Assert.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util;
+
+
+/**
+ * <code>Assert</code> is useful for for embedding runtime sanity checks in
+ * code. The predicate methods all test a condition and throw some type of
+ * unchecked exception if the condition does not hold.
+ * <p>
+ * Assertion failure exceptions, like most runtime exceptions, are thrown when
+ * something is misbehaving. Assertion failures are invariably unspecified
+ * behavior; consequently, clients should never rely on these being thrown
+ * (and certainly should not being catching them specifically).
+ * </p>
+ */
+public final class Assert {
+
+ /**
+ * <code>AssertionFailedException</code> is a runtime exception thrown
+ * by some of the methods in <code>Assert</code>.
+ * <p>
+ * This class is not declared public to prevent some misuses; programs
+ * that catch or otherwise depend on assertion failures are susceptible to
+ * unexpected breakage when assertions in the code are added or removed.
+ * </p>
+ */
+ class AssertionFailedException extends RuntimeException {
+ /**
+ * Comment for <code>serialVersionUID</code>
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception.
+ */
+ public AssertionFailedException() {
+ super();
+ }
+
+ /**
+ * Constructs a new exception with the given message.
+ */
+ public AssertionFailedException(String detail) {
+ super(detail);
+ }
+ }
+
+ /**
+ * Asserts that an argument is legal. If the given boolean is not
+ * <code>true</code>, an <code>IllegalArgumentException</code> is
+ * thrown.
+ *
+ * @param expression
+ * the outcode of the check
+ * @return <code>true</code> if the check passes (does not return if the
+ * check fails)
+ * @exception IllegalArgumentException
+ * if the legality test failed
+ */
+ public static boolean isLegal(boolean expression) {
+ return isLegal(expression, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * Asserts that an argument is legal. If the given boolean is not
+ * <code>true</code>, an <code>IllegalArgumentException</code> is
+ * thrown. The given message is included in that exception, to aid
+ * debugging.
+ *
+ * @param expression
+ * the outcode of the check
+ * @param message
+ * the message to include in the exception
+ * @return <code>true</code> if the check passes (does not return if the
+ * check fails)
+ * @exception IllegalArgumentException
+ * if the legality test failed
+ */
+ public static boolean isLegal(boolean expression, String message) {
+ if (!expression)
+ throw new IllegalArgumentException();
+ return expression;
+ }
+
+ /**
+ * Asserts that the given object is not <code>null</code>. If this is
+ * not the case, some kind of unchecked exception is thrown.
+ *
+ * @param object
+ * the value to test
+ * @exception IllegalArgumentException
+ * if the object is <code>null</code>
+ */
+ public static void isNotNull(Object object) {
+ isNotNull(object, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * Asserts that the given object is not <code>null</code>. If this is
+ * not the case, some kind of unchecked exception is thrown. The given
+ * message is included in that exception, to aid debugging.
+ *
+ * @param object
+ * the value to test
+ * @param message
+ * the message to include in the exception
+ * @exception IllegalArgumentException
+ * if the object is <code>null</code>
+ */
+ public static void isNotNull(Object object, String message) {
+ if (object == null) {
+ //Logger.log(Logger.ERROR, "null_argument: " + message); //$NON-NLS-1$
+ throw new Assert().new AssertionFailedException(message);
+ }
+ }
+
+ /**
+ * Asserts that the given boolean is <code>true</code>. If this is not
+ * the case, some kind of unchecked exception is thrown.
+ *
+ * @param expression
+ * the outcode of the check
+ * @return <code>true</code> if the check passes (does not return if the
+ * check fails)
+ */
+ public static boolean isTrue(boolean expression) {
+ return isTrue(expression, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * Asserts that the given boolean is <code>true</code>. If this is not
+ * the case, some kind of unchecked exception is thrown. The given message
+ * is included in that exception, to aid debugging.
+ *
+ * @param expression
+ * the outcode of the check
+ * @param message
+ * the message to include in the exception
+ * @return <code>true</code> if the check passes (does not return if the
+ * check fails)
+ */
+ public static boolean isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new Assert().new AssertionFailedException(message);
+ }
+ return expression;
+ }
+
+ /* This class is not intended to be instantiated. */
+ private Assert() {
+ super();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Debug.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Debug.java
new file mode 100644
index 0000000000..3df89548da
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Debug.java
@@ -0,0 +1,208 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * David Carver (Intalio) - bug 300430 - String concatenation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+
+
+import java.util.Enumeration;
+
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+
+
+public final class Debug {
+ public static final boolean checkForMemoryLeaks = false;
+
+ public static final boolean collectStats = false;
+
+ public static final int DEBUG = 0;
+
+ public static final boolean DEBUG_THREADLOCAL = false;
+
+ public static final boolean debugBreakpoints = false;
+ public static final boolean debugCaretMediator = false;
+ public static final boolean debugDisplayTreePositions = false;
+ //
+ public static final boolean debugMediator = false;
+ //
+ public static final boolean debugNotification = false;
+ public static final boolean debugNotificationAndEvents = false;
+
+ public static final boolean debugNotifyDeferred = false;
+ public static final boolean debugReconciling = false;
+ //
+ public static final boolean debugRtfFormatProvider = false;
+ //
+ public static final boolean debugStructuredDocument = false;
+ public static final boolean debugTaglibs = false;
+ //
+ public static final boolean debugTokenizer = false;
+ //
+ public static final boolean debugTreeModel = false;
+ public static final boolean debugUpdateTreePositions = false;
+ public static final boolean displayInfo = false;
+
+ /** effects output of Logger */
+ public static final boolean displayToConsole = true;
+ public static final boolean displayWarnings = false;
+ //
+ public static final boolean headParsing = false;
+ public static final boolean jsDebugContextAssist = false;
+ //
+ public static final boolean jsDebugSyntaxColoring = false;
+
+ public static final boolean LOCKS = false;
+ //
+ public static final boolean perfTest = false;
+ public static final boolean perfTestAdapterClassLoading = false;
+ public static final boolean perfTestFormat = false;
+ public static final boolean perfTestRawStructuredDocumentOnly = false;
+ public static final boolean perfTestStructuredDocumentEventOnly = false;
+ public static final boolean perfTestStructuredDocumentOnly = false;
+
+ //
+ public static final boolean syntaxHighlighting = false;
+ //
+ public static final boolean useStandardEolInWidget = false;
+
+ /**
+ * For tests and debug only
+ */
+
+ public static final void dump(IStructuredDocument structuredDocument) {
+ dump(structuredDocument, false);
+ }
+
+ public static final void dump(IStructuredDocument structuredDocument, boolean verbose) {
+ ITextRegionCollection flatNode = null;
+ System.out.println("Dump of structuredDocument:"); //$NON-NLS-1$
+ IStructuredDocumentRegionList flatNodes = structuredDocument.getRegionList();
+ Enumeration structuredDocumentRegions = flatNodes.elements();
+ while (structuredDocumentRegions.hasMoreElements()) {
+ flatNode = (ITextRegionCollection) structuredDocumentRegions.nextElement();
+ if (!verbose) {
+ String outString = flatNode.toString();
+ outString = org.eclipse.wst.sse.core.utils.StringUtils.escape(outString);
+ System.out.println(outString);
+ } else {
+ dump(flatNode, verbose);
+ }
+ }
+ System.out.println();
+ System.out.println("= = = = = ="); //$NON-NLS-1$
+ System.out.println();
+ }
+
+ /**
+ * @param flatNode
+ * @param verbose
+ */
+ public static final void dump(ITextRegionCollection region, boolean verbose) {
+ if (region == null)
+ return;
+ if (verbose) {
+ printParent(region);
+ }
+ printChildRegions(region, 0);
+ }
+
+ private static void printChildRegions(ITextRegionCollection region, int depth) {
+ if (region != null) {
+ // ==> // ITextRegionCollection regionCollection = region;
+ System.out.println(region);
+ ITextRegionList regionList = region.getRegions();
+ for (int i = 0; i < regionList.size(); i++) {
+ ITextRegion r = regionList.get(i);
+ if (r instanceof ITextRegionCollection) {
+ ITextRegionCollection rc = (ITextRegionCollection) r;
+ printChildRegions(rc, depth++);
+ } else {
+ System.out.println(space(depth) + r);
+ depth--;
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple utility to make sure println's are some what in order
+ */
+ public static final synchronized void println(String msg) {
+ System.out.println(System.currentTimeMillis() + "\t" + msg); //$NON-NLS-1$
+ }
+
+ private static void printParent(IStructuredDocumentRegion region) {
+ System.out.println(" [parent document: " + toStringUtil(region.getParentDocument()) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private static void printParent(ITextRegionCollection region) {
+ if (region instanceof IStructuredDocumentRegion) {
+ printParent((IStructuredDocumentRegion) region);
+ } else if (region instanceof ITextRegionContainer) {
+ printParent((ITextRegionContainer) region);
+ } else
+ System.out.println(" [parent document: " + "(na)" + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ private static void printParent(ITextRegionContainer region) {
+ System.out.println(" [parent document: " + toStringUtil(region.getParent()) + "]"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * @param depth
+ * @return
+ */
+ private static String space(int depth) {
+ String result = " "; //$NON-NLS-1$
+ StringBuffer sb = new StringBuffer(result);
+ for (int i = 0; i < depth; i++) {
+ sb.append(" "); //$NON-NLS-1$
+ }
+ result = sb.toString();
+ return result;
+ }
+
+ public static final String toStringUtil(IStructuredDocument object) {
+ String className = object.getClass().getName();
+ String shortClassName = className.substring(className.lastIndexOf(".") + 1); //$NON-NLS-1$
+ String result = shortClassName;
+ // NOTE: if the document held by any region has been updated and the
+ // region offsets have not
+ // yet been updated, the output from this method invalid.
+ return result;
+
+ }
+
+ public static final String toStringUtil(ITextRegionCollection object) {
+ String className = object.getClass().getName();
+ String shortClassName = className.substring(className.lastIndexOf(".") + 1); //$NON-NLS-1$
+ String result = shortClassName;
+ // NOTE: if the document held by any region has been updated and the
+ // region offsets have not
+ // yet been updated, the output from this method invalid.
+ return result;
+
+ }
+
+ /**
+ * Debug constructor comment.
+ */
+ public Debug() {
+ super();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/DocumentInputStream.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/DocumentInputStream.java
new file mode 100644
index 0000000000..fad095102a
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/DocumentInputStream.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+
+public class DocumentInputStream extends InputStream {
+ private IDocument fDocument;
+ private int fMark = -1;
+ private int fPosition = 0;
+
+ public DocumentInputStream(IDocument source) {
+ super();
+ fDocument = source;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#available()
+ */
+ public int available() throws IOException {
+ return fDocument.getLength() - fPosition;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#close()
+ */
+ public void close() throws IOException {
+ this.fDocument = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#mark(int)
+ */
+ public synchronized void mark(int readlimit) {
+ fMark = fPosition;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#markSupported()
+ */
+ public boolean markSupported() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#read()
+ */
+ public int read() throws IOException {
+ try {
+ if (fPosition < fDocument.getLength())
+ return fDocument.getChar(fPosition++);
+ else
+ return -1;
+ } catch (BadLocationException e) {
+ throw new IOException(e.getMessage());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#reset()
+ */
+ public synchronized void reset() throws IOException {
+ fPosition = fMark;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.io.InputStream#skip(long)
+ */
+ public long skip(long n) throws IOException {
+ long skipped = n;
+ if (n < fDocument.getLength() - fPosition) {
+ skipped = n;
+ fPosition += skipped;
+ } else {
+ skipped = fDocument.getLength() - fPosition;
+ fPosition = fDocument.getLength();
+ }
+ return skipped;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/JarUtilities.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/JarUtilities.java
new file mode 100644
index 0000000000..d2bedbb921
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/JarUtilities.java
@@ -0,0 +1,394 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.wst.sse.core.internal.Logger;
+
+
+public class JarUtilities {
+
+ /**
+ * @see http://java.sun.com/products/jsp/errata_1_1_a_042800.html, Issues
+ * 8 & 9
+ *
+ * "There are two cases. In both cases the TLD_URI is to be
+ * interpreted relative to the root of the Web Application. In the
+ * first case the TLD_URI refers to a TLD file directly. In the
+ * second case, the TLD_URI refers to a JAR file. If so, that JAR
+ * file should have a TLD at location META-INF/taglib.tld."
+ */
+ public static final String JSP11_TAGLIB = "META-INF/taglib.tld"; //$NON-NLS-1$
+
+ public static void closeJarFile(ZipFile file) {
+ if (file == null)
+ return;
+ try {
+ file.close();
+ }
+ catch (IOException ioe) {
+ // no cleanup can be done
+ }
+ }
+
+ /**
+ * Provides a stream to a local copy of the input or null if not possible
+ */
+ protected static InputStream getCachedInputStream(String jarFilename, String entryName) {
+ File testFile = new File(jarFilename);
+ if (!testFile.exists())
+ return getInputStream(ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(jarFilename)), entryName);
+
+ InputStream cache = null;
+ ZipFile jarfile = null;
+ try {
+ jarfile = new ZipFile(jarFilename);
+ }
+ catch (IOException ioExc) {
+ closeJarFile(jarfile);
+ }
+
+ if (jarfile != null) {
+ try {
+ ZipEntry zentry = jarfile.getEntry(entryName);
+ if (zentry != null) {
+ InputStream entryInputStream = null;
+ try {
+ entryInputStream = jarfile.getInputStream(zentry);
+ }
+ catch (IOException ioExc) {
+ // no cleanup can be done
+ }
+
+ if (entryInputStream != null) {
+ int c;
+ ByteArrayOutputStream buffer = null;
+ if (zentry.getSize() > 0) {
+ buffer = new ByteArrayOutputStream((int) zentry.getSize());
+ }
+ else {
+ buffer = new ByteArrayOutputStream();
+ }
+ // array dim restriction?
+ byte bytes[] = new byte[2048];
+ try {
+ while ((c = entryInputStream.read(bytes)) >= 0) {
+ buffer.write(bytes, 0, c);
+ }
+ cache = new ByteArrayInputStream(buffer.toByteArray());
+ closeJarFile(jarfile);
+ }
+ catch (IOException ioe) {
+ // no cleanup can be done
+ }
+ finally {
+ try {
+ entryInputStream.close();
+ }
+ catch (IOException e) {
+ // no cleanup can be done
+ }
+ }
+ }
+ }
+ }
+ finally {
+ closeJarFile(jarfile);
+ }
+ }
+ return cache;
+ }
+
+ private static InputStream copyAndCloseStream(InputStream original) {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ InputStream cachedCopy = null;
+
+ if (original != null) {
+ int c;
+ // array dim restriction?
+ byte bytes[] = new byte[2048];
+ try {
+ while ((c = original.read(bytes)) >= 0) {
+ buffer.write(bytes, 0, c);
+ }
+ cachedCopy = new ByteArrayInputStream(buffer.toByteArray());
+ closeStream(original);
+ }
+ catch (IOException ioe) {
+ // no cleanup can be done
+ }
+ }
+ return cachedCopy;
+ }
+
+ /**
+ * @param jarResource
+ * the zip file
+ * @return a string array containing the entry paths to every file in this
+ * zip resource, excluding directories
+ */
+ public static String[] getEntryNames(IResource jarResource) {
+ if (jarResource == null || jarResource.getType() != IResource.FILE || !jarResource.isAccessible())
+ return new String[0];
+
+ try {
+ return getEntryNames(jarResource.getFullPath().toString(), new ZipInputStream(((IFile) jarResource).getContents()), true);
+ }
+ catch (CoreException e) {
+ // no cleanup can be done
+ }
+
+ IPath location = jarResource.getLocation();
+ if (location != null)
+ return getEntryNames(location.toString());
+ return new String[0];
+ }
+
+ /**
+ * @param jarFilename
+ * the location of the zip file
+ * @return a string array containing the entry paths to every file in the
+ * zip file at this location, excluding directories
+ */
+ public static String[] getEntryNames(String jarFilename) {
+ return getEntryNames(jarFilename, true);
+ }
+
+ private static String[] getEntryNames(String filename, ZipInputStream jarInputStream, boolean excludeDirectories) {
+ List entryNames = new ArrayList();
+ try {
+ ZipEntry z = jarInputStream.getNextEntry();
+ while (z != null) {
+ if (!(z.isDirectory() && excludeDirectories))
+ entryNames.add(z.getName());
+ z = jarInputStream.getNextEntry();
+ }
+ }
+ catch (ZipException zExc) {
+ Logger.log(Logger.WARNING_DEBUG, "JarUtilities ZipException: (stream) " + filename, zExc); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ catch (IOException ioExc) {
+ // no cleanup can be done
+ }
+ finally {
+ closeStream(jarInputStream);
+ }
+ String[] names = (String[]) entryNames.toArray(new String[0]);
+ return names;
+ }
+
+ private static void closeStream(InputStream inputStream) {
+ try {
+ inputStream.close();
+ }
+ catch (IOException e) {
+ // no cleanup can be done
+ }
+ }
+
+ /**
+ * @param jarFilename
+ * the location of the zip file
+ * @param excludeDirectories
+ * whether to not include directories in the results
+ * @return a string array containing the entry paths to every file in the
+ * zip file at this location, excluding directories if indicated
+ */
+ public static String[] getEntryNames(String jarFilename, boolean excludeDirectories) {
+ ZipFile jarfile = null;
+ List entryNames = new ArrayList();
+ File f = new File(jarFilename);
+ if (f.exists() && f.canRead()) {
+ try {
+ jarfile = new ZipFile(f);
+ Enumeration entries = jarfile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry z = (ZipEntry) entries.nextElement();
+ if (!(z.isDirectory() && excludeDirectories))
+ entryNames.add(z.getName());
+ }
+ }
+ catch (ZipException zExc) {
+ Logger.log(Logger.WARNING_DEBUG, "JarUtilities ZipException: " + jarFilename + " " + zExc.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ catch (IOException ioExc) {
+ // no cleanup can be done
+ }
+ finally {
+ closeJarFile(jarfile);
+ }
+ }
+ String[] names = (String[]) entryNames.toArray(new String[0]);
+ return names;
+ }
+
+ /**
+ * @param jarResource
+ * the zip file
+ * @param entryName
+ * the entry's path in the zip file
+ * @return an InputStream to the contents of the given entry or null if
+ * not possible
+ */
+ public static InputStream getInputStream(IResource jarResource, String entryName) {
+ if (jarResource == null || jarResource.getType() != IResource.FILE || !jarResource.isAccessible())
+ return null;
+
+ try {
+ InputStream zipStream = ((IFile) jarResource).getContents();
+ return getInputStream(jarResource.getFullPath().toString(), new ZipInputStream(zipStream), entryName);
+ }
+ catch (CoreException e) {
+ // no cleanup can be done, probably out of sync
+ }
+
+ IPath location = jarResource.getLocation();
+ if (location != null) {
+ return getInputStream(location.toString(), entryName);
+ }
+ return null;
+ }
+
+ private static InputStream getInputStream(String filename, ZipInputStream zip, String entryName) {
+ InputStream result = null;
+ try {
+ ZipEntry z = zip.getNextEntry();
+ while (z != null && !z.getName().equals(entryName)) {
+ z = zip.getNextEntry();
+ }
+ if (z != null) {
+ result = copyAndCloseStream(zip);
+ }
+ }
+ catch (ZipException zExc) {
+ Logger.log(Logger.WARNING_DEBUG, "JarUtilities ZipException: (stream) " + filename, zExc); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ catch (IOException ioExc) {
+ // no cleanup can be done
+ }
+ finally {
+ closeStream(zip);
+ }
+ return result;
+ }
+
+ /**
+ * @param jarFilename
+ * the location of the zip file
+ * @param entryName
+ * the entry's path in the zip file
+ * @return an InputStream to the contents of the given entry or null if
+ * not possible
+ */
+ public static InputStream getInputStream(String jarFilename, String entryName) {
+ // check sanity
+ if (jarFilename == null || jarFilename.length() < 1 || entryName == null || entryName.length() < 1)
+ return null;
+
+ // JAR files are not allowed to have leading '/' in member names
+ String internalName = null;
+ if (entryName.startsWith("/")) //$NON-NLS-1$
+ internalName = entryName.substring(1);
+ else
+ internalName = entryName;
+
+ return getCachedInputStream(jarFilename, internalName);
+ }
+
+ /**
+ * @param url
+ * a URL pointint to a zip file
+ * @return a cached copy of the contents at this URL, opening it as a file
+ * if it is a jar:file: URL, and using a URLConnection otherwise,
+ * or null if it could not be read. All sockets and file handles
+ * are closed as quickly as possible.
+ */
+ public static InputStream getInputStream(URL url) {
+ String urlString = url.toString();
+ if (urlString.length() > 12 && urlString.startsWith("jar:file:") && urlString.indexOf("!/") > 9) { //$NON-NLS-1$ //$NON-NLS-2$
+ int fileIndex = urlString.indexOf("!/"); //$NON-NLS-1$
+ String jarFileName = urlString.substring(9, fileIndex);
+ if (fileIndex < urlString.length()) {
+ String jarPath = urlString.substring(fileIndex + 1);
+ return getInputStream(jarFileName, jarPath);
+ }
+ }
+
+ InputStream input = null;
+ JarURLConnection jarUrlConnection = null;
+ try {
+ URLConnection openConnection = url.openConnection();
+ openConnection.setDefaultUseCaches(false);
+ openConnection.setUseCaches(false);
+ if (openConnection instanceof JarURLConnection) {
+ jarUrlConnection = (JarURLConnection) openConnection;
+ JarFile jarFile = jarUrlConnection.getJarFile();
+ input = jarFile.getInputStream(jarUrlConnection.getJarEntry());
+ }
+ else {
+ input = openConnection.getInputStream();
+ }
+ if (input != null) {
+ return copyAndCloseStream(input);
+ }
+ }
+ catch (FileNotFoundException e) {
+ // May be a file URL connection, do not log
+ }
+ catch (IOException e) {
+ Logger.logException(e);
+ }
+ finally {
+ if (jarUrlConnection != null) {
+ try {
+ jarUrlConnection.getJarFile().close();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+ catch (IllegalStateException e) {
+ /*
+ * ignore. Can happen in case the stream.close() did close
+ * the jar file see
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=140750
+ */
+ }
+
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/PathHelper.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/PathHelper.java
new file mode 100644
index 0000000000..b83508559c
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/PathHelper.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * David Carver (Intalio) - bug 300430 - String concatenation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+
+
+import java.io.File;
+import com.ibm.icu.util.StringTokenizer;
+
+/**
+ * Collection of helper methods to manage and convert links Originally part of
+ * the LinksManager
+ */
+public class PathHelper {
+ public static final String BACKWARD_SLASH = "\\";//$NON-NLS-1$
+
+ public static final String FORWARD_SLASH = "/";//$NON-NLS-1$
+ public static final String RELATIVE_PATH_SIG = "../";//$NON-NLS-1$
+
+ /**
+ * adjust relative path isside the absolute path
+ */
+ public static String adjustPath(String path) {
+ int i = 0;
+ while ((i = path.indexOf(RELATIVE_PATH_SIG)) > 0) {
+ // split the string into two
+ String part1 = path.substring(0, i - 1);
+ String part2 = path.substring(i + RELATIVE_PATH_SIG.length() - 1);
+ // strip one path seg from part1
+ int j = part1.lastIndexOf(FORWARD_SLASH);
+ if (j == -1) {
+ // can't resolve. passed path is like
+ // E:/eclipseproject/../../sample.css.
+ return "";//$NON-NLS-1$
+ }
+ part1 = part1.substring(0, j);
+ path = part1 + part2;
+ }
+ return path;
+ }
+
+ /**
+ * Append trailing url slash if needed
+ */
+ public static String appendTrailingURLSlash(String input) {
+ // check to see already a slash
+ if (!input.endsWith(FORWARD_SLASH)) {
+ input += FORWARD_SLASH;
+ }
+ return input;
+ }
+
+ /**
+ * Convert to relative url based on base
+ */
+ public static String convertToRelative(String input, String base) {
+ // tokenize the strings
+ StringTokenizer inputTokenizer = new StringTokenizer(input, FORWARD_SLASH);
+ StringTokenizer baseTokenizer = new StringTokenizer(base, FORWARD_SLASH);
+ String token1 = "", token2 = "";//$NON-NLS-2$//$NON-NLS-1$
+ //
+ // Go through until equls
+ while (true) {
+ if (!inputTokenizer.hasMoreTokens() || !baseTokenizer.hasMoreTokens())
+ break;
+ token1 = baseTokenizer.nextToken();
+ token2 = inputTokenizer.nextToken();
+ if (!token1.equals(token2))
+ break;
+ }
+ // now generate the backs
+ String output = "";//$NON-NLS-1$
+ StringBuffer sb = new StringBuffer(output);
+ while (baseTokenizer.hasMoreTokens()) {
+ baseTokenizer.nextToken();
+ sb.append("../"); //$NON-NLS-1$
+ }
+ sb.append(token2);
+ // generate the rest
+ while (inputTokenizer.hasMoreTokens()) {
+ sb.append(FORWARD_SLASH);
+ sb.append(inputTokenizer.nextToken());
+ }
+ output = sb.toString();
+ return output;
+ }
+
+ /**
+ * Return the containing folder path. Will handle both url and file path
+ */
+ public static String getContainingFolderPath(String path) {
+ String retValue = path;
+
+ int urlSlashIndex = path.lastIndexOf(FORWARD_SLASH);
+ int filePathSlashIndex = path.lastIndexOf(File.separator);
+ int index = filePathSlashIndex;
+ if (urlSlashIndex > filePathSlashIndex)
+ index = urlSlashIndex;
+ if (index >= 0)
+ retValue = path.substring(0, index);
+ return retValue;
+ }
+
+ /**
+ * Remove leading path separator
+ */
+ public static String removeLeadingPathSeparator(String path) {
+ if (path.startsWith(File.separator))
+ path = path.substring(File.separator.length());
+ return path;
+ }
+
+ /**
+ * Remove leading path separator
+ */
+ public static String removeLeadingSeparator(String path) {
+ if (path.startsWith(File.separator))
+ path = path.substring(File.separator.length());
+ else if (path.startsWith(FORWARD_SLASH) || path.startsWith(BACKWARD_SLASH))
+ path = path.substring(FORWARD_SLASH.length());
+ return path;
+ }
+
+ /**
+ * Switch to file path slashes
+ */
+ public static String switchToFilePathSlashes(String path) {
+ path = path.replace(FORWARD_SLASH.charAt(0), File.separatorChar);
+ path = path.replace(BACKWARD_SLASH.charAt(0), File.separatorChar);
+ return path;
+ }
+
+ /**
+ * Switch to file path slashes
+ */
+ public static String switchToForwardSlashes(String path) {
+ path = path.replace(File.separatorChar, FORWARD_SLASH.charAt(0));
+ path = path.replace(BACKWARD_SLASH.charAt(0), FORWARD_SLASH.charAt(0));
+ return path;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ProjectResolver.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ProjectResolver.java
new file mode 100644
index 0000000000..0a21f95409
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ProjectResolver.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2007 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.util;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.wst.common.uriresolver.internal.util.URIHelper;
+
+import com.ibm.icu.util.StringTokenizer;
+
+/**
+ * @deprecated The URIResolver interface is deprecated. Use the resolver from
+ * org.eclipse.wst.common.uriresolver.
+ */
+public class ProjectResolver implements URIResolver {
+ private String fFileBaseLocation = null;
+ private IProject fProject = null;
+
+ /**
+ * It is strongly recommended that clients use
+ * project.getAdapter(URIResolver.class) to obtain a URIResolver aware of
+ * the Project's special requirements. Note that a URIResolver may not be
+ * returned at all so manually creating this object may still be required.
+ */
+ public ProjectResolver(IProject project) {
+ super();
+ fProject = project;
+ }
+
+ public String getFileBaseLocation() {
+ return fFileBaseLocation;
+ }
+
+ public String getLocationByURI(String uri) {
+ return getLocationByURI(uri, getFileBaseLocation());
+ }
+
+ // defect 244817 end
+ /**
+ * Resolve the (possibly relative) URI acording to RFC1808 using the
+ * default file base location. Resolves resource references into absolute
+ * resource locations without ensuring that the resource actually exists.
+ *
+ * Note: currently resolveCrossProjectLinks is ignored in this
+ * implementation.
+ */
+ public String getLocationByURI(String uri, boolean resolveCrossProjectLinks) {
+ return getLocationByURI(uri, getFileBaseLocation(), resolveCrossProjectLinks);
+ }
+
+ public String getLocationByURI(String uri, String baseReference) {
+ if (uri == null)
+ return null;
+ /*
+ * defect 244817 try { URL aURL = new URL(uri);
+ */
+ /**
+ * An actual URL was given, but only the "file:///" protocol is
+ * supported. Resolve the URI by finding the file to which it points.
+ */
+ /*
+ * defect 244817 if (!aURL.getProtocol().equals("platform")) {
+ * //$NON-NLS-1$ if (aURL.getProtocol().equals("file") &&
+ * (aURL.getHost().equals("localhost") || aURL.getHost().length() ==
+ * 0)) { //$NON-NLS-2$//$NON-NLS-1$ return aURL.getFile(); } return
+ * uri; } } catch (MalformedURLException mfuExc) { }
+ */
+ // defect 244817 start
+ if (isFileURL(uri)) {
+ try {
+ URL url = new URL(uri);
+ return getPath(url);
+ }
+ catch (MalformedURLException e) {
+ }
+ }
+ // defect 244817 end
+
+ // which of the serveral are we suppose to use here?
+ //
+
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=71223
+ // Workaround for problem in URIHelper; uris starting with '/' are
+ // returned as-is.
+ String location = null;
+ if (uri.startsWith("/")) { //$NON-NLS-1$
+ IProject p = getProject();
+ if (p != null && p.isAccessible()) {
+ IFile file = p.getFile(uri);
+
+ if (file.getLocation() != null) {
+ location = file.getLocation().toString();
+ }
+ if (location == null && file.getLocationURI() != null) {
+ location = file.getLocationURI().toString();
+ }
+ if (location == null) {
+ location = file.getFullPath().toString();
+ }
+ }
+ }
+ if(location == null) {
+ location = URIHelper.normalize(uri, baseReference, getRootLocationString());
+ }
+ return location;
+ }
+
+ /**
+ * Perform the getLocationByURI action using the baseReference as the
+ * point of reference instead of the default for this resolver
+ *
+ * Note: currently resolveCrossProjectLinks is ignored in this
+ * implementation.
+ */
+ public String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks) {
+ return getLocationByURI(uri, baseReference);
+ }
+
+ /**
+ *
+ * @param path
+ * @param host
+ * @return String
+ */
+ private String getPath(IPath path, String host) {
+ IPath newPath = path;
+ // They are potentially for only Windows operating system.
+ // a.) if path has a device, and if it begins with IPath.SEPARATOR,
+ // remove it
+ final String device = path.getDevice();
+ if ((device != null) && (device.length() > 0)) {
+ if (device.charAt(0) == IPath.SEPARATOR) {
+ final String newDevice = device.substring(1);
+ newPath = path.setDevice(newDevice);
+ }
+ }
+ // b.) if it has a hostname, it is UNC name... Any java or eclipse api
+ // helps it ??
+ if (path != null && host != null && host.length() != 0) {
+ IPath uncPath = new Path(host);
+ uncPath = uncPath.append(path);
+ newPath = uncPath.makeUNC(true);
+ }
+ return newPath.toString();
+ }
+
+ /**
+ *
+ * @param url
+ * @return String
+ */
+ private String getPath(URL url) {
+ String ref = url.getRef() == null ? "" : "#" + url.getRef(); //$NON-NLS-1$ //$NON-NLS-2$
+ String strPath = url.getFile() + ref;
+ IPath path;
+ if (strPath.length() == 0) {
+ path = Path.ROOT;
+ }
+ else {
+ path = new Path(strPath);
+ String query = null;
+ StringTokenizer parser = new StringTokenizer(strPath, "?"); //$NON-NLS-1$
+ int tokenCount = parser.countTokens();
+ if (tokenCount == 2) {
+ path = new Path((String) parser.nextElement());
+ query = (String) parser.nextElement();
+ }
+ if (query == null) {
+ parser = new StringTokenizer(path.toString(), "#"); //$NON-NLS-1$
+ tokenCount = parser.countTokens();
+ if (tokenCount == 2) {
+ path = new Path((String) parser.nextElement());
+ }
+ }
+ }
+ return getPath(path, url.getHost());
+ }
+
+ public org.eclipse.core.resources.IProject getProject() {
+ return fProject;
+ }
+
+ public org.eclipse.core.resources.IContainer getRootLocation() {
+ return fProject;
+ }
+
+ protected String getRootLocationString() {
+ String location = null;
+ if (fProject == null)
+ return null;
+ if (fProject.getLocation() != null) {
+ location = fProject.getLocation().toString();
+ }
+ if (location == null && fProject.getLocationURI() != null) {
+ location = fProject.getLocationURI().toString();
+ }
+ if (location == null) {
+ location = fProject.getFullPath().toString();
+ }
+ return location;
+ }
+
+ public InputStream getURIStream(String uri) {
+ return null;
+ }
+
+ // defect 244817 start
+ /**
+ *
+ * @param passedSpec
+ * @return boolean
+ */
+ private boolean isFileURL(String passedSpec) {
+ if (passedSpec == null) {
+ return false;
+ }
+ final String spec = passedSpec.trim();
+ if (spec.length() == 0) {
+ return false;
+ }
+ final int limit = spec.length();
+ String newProtocol = null;
+ for (int index = 0; index < limit; index++) {
+ final char p = spec.charAt(index);
+ if (p == '/') { //$NON-NLS-1$
+ break;
+ }
+ if (p == ':') { //$NON-NLS-1$
+ newProtocol = spec.substring(0, index);
+ break;
+ }
+ }
+ return (newProtocol != null && newProtocol.compareToIgnoreCase("file") == 0); //$NON-NLS-1$
+ }
+
+ public void setFileBaseLocation(String newFileBaseLocation) {
+ fFileBaseLocation = newFileBaseLocation;
+ }
+
+ public void setProject(IProject newProject) {
+ fProject = newProject;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ResourceUtil.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ResourceUtil.java
new file mode 100644
index 0000000000..ab59ecbbc5
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ResourceUtil.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+
+
+/**
+ * @deprecated - makes assumptions on behalf of the requester
+ */
+public class ResourceUtil {
+
+ /**
+ * Obtains the IFile for a model
+ *
+ * @param model
+ * the model to use
+ * @return the IFile used to create the model, if it came from an IFile,
+ * null otherwise
+ */
+ public static IFile getFileFor(IStructuredModel model) {
+ if (model == null)
+ return null;
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IFile file = null;
+ IPath location = new Path(model.getBaseLocation());
+ // if the path is not a path in the file system and there are at least
+ // 2 segments, it might be in the workspace
+ IFile[] files = root.findFilesForLocation(location);
+ if (files.length > 0) {
+ file = files[0];
+ }
+ else if (location.segmentCount() > 1) {
+ // remember, this IFile isn't guaranteed to exist
+ file = root.getFile(location);
+ }
+ return file;
+ }
+
+ /**
+ * Obtain IFiles from IStructuredModel (includes linkedResources)
+ *
+ * @return the corresponding files in the workspace, or an empty array if
+ * none
+ */
+ public static IFile[] getFilesFor(IStructuredModel model) {
+ if (model == null)
+ return null;
+
+ IFile[] files = null;
+ IPath location = new Path(model.getBaseLocation());
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ // if the path is not a path in the file system and there are at least
+ // 2 segments, it might be in the workspace
+ if (!location.toFile().exists() && location.segmentCount() > 1) {
+ // remember, this IFile isn't guaranteed to exist
+ files = new IFile[]{root.getFile(location)};
+ }
+ else {
+ files = root.findFilesForLocation(location);
+ }
+ return files;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ScriptLanguageKeys.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ScriptLanguageKeys.java
new file mode 100644
index 0000000000..d886ba2ffe
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/ScriptLanguageKeys.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+/**
+ * Contains list of script languages and mime types
+ */
+public interface ScriptLanguageKeys {
+
+ public static final String JAVA = "java"; //$NON-NLS-1$
+
+ public static final String[] JAVA_LANGUAGE_KEYS = new String[]{"java"}; //$NON-NLS-1$
+
+ public static final String JAVASCRIPT = "javascript"; //$NON-NLS-1$
+ public static final String[] JAVASCRIPT_LANGUAGE_KEYS = {"javascript", //$NON-NLS-1$
+ "ecmascript", //$NON-NLS-1$
+ "javascript1.0", //$NON-NLS-1$
+ "javascript1.1", //$NON-NLS-1$
+ "javascript1.2", //$NON-NLS-1$
+ "javascript1.3", //$NON-NLS-1$
+ "javascript1.4", //$NON-NLS-1$
+ "javascript1.5", //$NON-NLS-1$
+ "javascript1.6", //$NON-NLS-1$
+ "jscript", //$NON-NLS-1$
+ "sashscript"}; //$NON-NLS-1$
+
+ public static final String[] JAVASCRIPT_MIME_TYPE_KEYS = {"text/javascript", //$NON-NLS-1$
+ "application/ecmascript", //$NON-NLS-1$
+ "application/javascript", //$NON-NLS-1$
+ "application/x-ecmascript", //$NON-NLS-1$
+ "application/x-javascript", //$NON-NLS-1$
+ "text/ecmascript", //$NON-NLS-1$
+ "text/javascript1.0", //$NON-NLS-1$
+ "text/javascript1.1", //$NON-NLS-1$
+ "text/javascript1.2", //$NON-NLS-1$
+ "text/javascript1.3", //$NON-NLS-1$
+ "text/javascript1.4", //$NON-NLS-1$
+ "text/javascript1.5", //$NON-NLS-1$
+ "text/jscript", //$NON-NLS-1$
+ "text/livescript", //$NON-NLS-1$
+ "text/x-ecmascript", //$NON-NLS-1$
+ "text/x-javascript", //$NON-NLS-1$
+ "text/sashscript"}; //$NON-NLS-1$
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Sorter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Sorter.java
new file mode 100644
index 0000000000..41cdf33265
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Sorter.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2009 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.util;
+
+
+
+/**
+ * The SortOperation takes a collection of objects and returns a sorted
+ * collection of these objects. Concrete instances of this class provide the
+ * criteria for the sorting of the objects based on the type of the objects.
+ */
+public abstract class Sorter {
+
+ /**
+ * Returns true iff elementTwo is 'greater than' elementOne. This is the
+ * 'ordering' method of the sort operation. Each subclass overrides this
+ * method with the particular implementation of the 'greater than' concept
+ * for the objects being sorted. If elementOne and elementTwo are
+ * equivalent in terms of their sorting order, this method must return
+ * 'false'.
+ */
+ public abstract boolean compare(Object elementOne, Object elementTwo);
+
+ /**
+ * Sort the objects in the array and return the array.
+ */
+ private Object[] quickSort(Object[] array, int left, int right) {
+ int originalLeft = left;
+ int originalRight = right;
+ Object mid = array[(left + right) / 2];
+
+ do {
+ while (compare(array[left], mid))
+ left++;
+ while (compare(mid, array[right]))
+ right--;
+ if (left <= right) {
+ Object tmp = array[left];
+ array[left] = array[right];
+ array[right] = tmp;
+ left++;
+ right--;
+ }
+ } while (left <= right);
+
+ if (originalLeft < right)
+ array = quickSort(array, originalLeft, right);
+ if (left < originalRight)
+ array = quickSort(array, left, originalRight);
+
+ return array;
+ }
+
+ /**
+ * Return a new (quick)sorted array from this unsorted array. The original
+ * array is not modified.
+ */
+ public Object[] sort(Object[] unSortedCollection) {
+ int size = unSortedCollection.length;
+ Object[] sortedCollection = new Object[size];
+
+ //copy the array so can return a new sorted collection
+ System.arraycopy(unSortedCollection, 0, sortedCollection, 0, size);
+ if (size > 1)
+ quickSort(sortedCollection, 0, size - 1);
+
+ return sortedCollection;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/TextUtilities.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/TextUtilities.java
new file mode 100644
index 0000000000..423fa49d4f
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/TextUtilities.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util;
+
+
+
+/**
+ * Collection of text functions.
+ * @deprecated - the JFace class is public in 3.1
+ */
+// This class was originally copied from org.eclipse.jface.text, and made
+// public
+public class TextUtilities {
+ /**
+ * Returns whether the text ends with one of the given search strings.
+ */
+ public static boolean endsWith(String[] searchStrings, String text) {
+ for (int i = 0; i < searchStrings.length; i++) {
+ if (text.endsWith(searchStrings[i]))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the position in the string greater than offset of the longest
+ * matching search string.
+ */
+ public static int[] indexOf(String[] searchStrings, String text, int offset) {
+
+ int[] result = {-1, -1};
+
+ for (int i = 0; i < searchStrings.length; i++) {
+ int index = text.indexOf(searchStrings[i], offset);
+ if (index >= 0) {
+
+ if (result[0] == -1) {
+ result[0] = index;
+ result[1] = i;
+ } else if (index < result[0]) {
+ result[0] = index;
+ result[1] = i;
+ } else if (index == result[0] && searchStrings[i].length() > searchStrings[result[1]].length()) {
+ result[0] = index;
+ result[1] = i;
+ }
+ }
+ }
+
+ return result;
+
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolver.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolver.java
new file mode 100644
index 0000000000..e2259eb4fd
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolver.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.util;
+
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
+
+
+/**
+ * @deprecated
+ *
+ * Should use extensible URIResolver from org.eclipse.wst.common.uriresolver
+ * instead.
+ */
+
+public interface URIResolver {
+
+ String getFileBaseLocation();
+
+ /**
+ * Resolve the (possibly relative) URI acording to RFC1808 using the
+ * default file base location. Resolves resource references into absolute
+ * resource locations without ensuring that the resource actually exists.
+ */
+ String getLocationByURI(String uri);
+
+ /**
+ * Resolve the (possibly relative) URI acording to RFC1808 using the
+ * default file base location. Resolves resource references into absolute
+ * resource locations without ensuring that the resource actually exists.
+ *
+ * If resolveCrossProjectLinks is set to true, then this method will
+ * properly resolve the URI if it is a valid URI to another (appropriate)
+ * project.
+ */
+ String getLocationByURI(String uri, boolean resolveCrossProjectLinks);
+
+ /**
+ * Perform the getLocationByURI action using the baseReference as the
+ * point of reference instead of the default for this resolver
+ */
+ String getLocationByURI(String uri, String baseReference);
+
+ /**
+ * Perform the getLocationByURI action using the baseReference as the
+ * point of reference instead of the default for this resolver
+ *
+ * If resolveCrossProjectLinks is set to true, then this method will
+ * properly resolve the URI if it is a valid URI to another (appropriate)
+ * project.
+ */
+ String getLocationByURI(String uri, String baseReference, boolean resolveCrossProjectLinks);
+
+ IProject getProject();
+
+ IContainer getRootLocation();
+
+ /**
+ * Attempts to return a direct inputstream to the given URI which must be
+ * relative to the default point of reference.
+ *
+ */
+ InputStream getURIStream(String uri);
+
+ void setFileBaseLocation(String newLocation);
+
+ void setProject(IProject newProject);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolverExtension.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolverExtension.java
new file mode 100644
index 0000000000..23fcfa2239
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/URIResolverExtension.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+/**
+ * Extension to the {@link URIResolver} interface. Implementing this interface
+ * allows for a new copy of the URIResolver to be created
+ *
+ */
+public interface URIResolverExtension {
+ /**
+ * Creates a new instance of the implementing {@link URIResolver}
+ *
+ * @return a new instance of the {@link URIResolver}
+ */
+ URIResolver newInstance();
+} \ No newline at end of file
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Utilities.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Utilities.java
new file mode 100644
index 0000000000..911c5e921c
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/Utilities.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.util;
+
+
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+
+import org.eclipse.wst.sse.core.internal.encoding.CodedIO;
+import org.eclipse.wst.sse.core.internal.encoding.util.BufferedLimitedStream;
+
+
+
+public class Utilities {
+
+ /**
+ * a common calculation in some of the parsing methods (e.g. in
+ * ContextRegion and IStructuredDocumentRegion)
+ */
+ public static int calculateLengthDifference(String changes, int lengthToReplace) {
+ // determine the length by the text itself, or, if there is no text to
+ // insert (i.e. we are doing a delete) then calculate the length as
+ // a negative number to denote the amount to delete.
+ // For a straight insert, the selection Length will be zero.
+ int lengthDifference = 0;
+ if (changes == null) {
+ // the delete case
+ lengthDifference = 0 - lengthToReplace;
+ }
+ else {
+ lengthDifference = changes.length() - lengthToReplace;
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("lengthDifference: " + lengthDifference);//$NON-NLS-1$
+ }
+ return lengthDifference;
+ }
+
+ /**
+ * Returns true iff both parameters are not null and the object is within
+ * the array. Careful, this uses identity. Not good for basic strings.
+ */
+ public static boolean contains(Object[] objectArray, Object object) {
+ boolean result = false;
+ // if object or objectArray is null, return false
+ if ((objectArray != null) && (object != null)) {
+ for (int i = 0; i < objectArray.length; i++) {
+ if (objectArray[i] == object) {
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static boolean containsString(String[] objectArray, String object) {
+ boolean result = false;
+ // if object or objectArray is null, return false
+ if ((objectArray != null) && (object != null)) {
+ for (int i = 0; i < objectArray.length; i++) {
+ if (objectArray[i].equals(object)) {
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Ensures that an InputStream has mark/reset support, is readlimit is
+ * set, and that the stream is "limitable" (that is, reports "end of
+ * input" rather than allow going past mark). This is very specialized
+ * stream introduced to overcome
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=67211. See also
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=68565
+ */
+ public static InputStream getLimitedStream(InputStream original) {
+ if (original == null)
+ return null;
+ if (original instanceof BufferedLimitedStream)
+ return original;
+ return new BufferedLimitedStream(original, CodedIO.MAX_BUF_SIZE);
+ }
+
+ /**
+ * <p>
+ * Ensures that an InputStream has mark/reset support.
+ * </p>
+ * <p>
+ * It's vital that a BufferedInputStream <b>not</b> be wrapped in another
+ * BufferedInputStream as each can preemptively consume <i>n</i> bytes
+ * (e.g. 2048) from the parent stream before any requests are made. The
+ * cascading effect is that the second/inner BufferedInputStream can never
+ * rewind itself to the first <i>n</i> bytes since they were already
+ * consumed by its parent.
+ * </p>
+ */
+ public static InputStream getMarkSupportedStream(InputStream original) {
+ if (original == null)
+ return null;
+ if (original.markSupported())
+ return original;
+ InputStream buffered = new BufferedInputStream(original, CodedIO.MAX_BUF_SIZE);
+ buffered.mark(CodedIO.MAX_MARK_SIZE);
+ return buffered;
+ }
+
+ /**
+ * Used for log/trace messages. Id is assumed to be some form of a
+ * filename. See IModelManager.
+ */
+ public static String makeShortId(Object id) {
+ if (id == null)
+ id = "NOID";//$NON-NLS-1$
+ String whole = id.toString();
+ String part = whole.substring(whole.lastIndexOf("/") + 1); //$NON-NLS-1$
+ return "..." + part; //$NON-NLS-1$
+ }
+
+
+ /**
+ * Utilities constructor comment.
+ */
+ public Utilities() {
+ super();
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ErrorInfo.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ErrorInfo.java
new file mode 100644
index 0000000000..d36b92cb9b
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ErrorInfo.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.internal.validate;
+
+
+
+public interface ErrorInfo {
+
+ public String getHint();
+
+ public int getLength();
+
+ public int getOffset();
+
+ public int getState();
+
+ public short getTargetType();
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationAdapter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationAdapter.java
new file mode 100644
index 0000000000..336020ee36
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationAdapter.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.validate;
+
+
+
+import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+
+/**
+ */
+public interface ValidationAdapter extends INodeAdapter {
+
+ /**
+ */
+ void setReporter(ValidationReporter reporter);
+
+ /**
+ */
+ void validate(IndexedRegion node);
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationMessage.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationMessage.java
new file mode 100644
index 0000000000..5f2f6057a4
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationMessage.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.validate;
+
+
+
+/**
+ */
+public class ValidationMessage {
+ public static final int IGNORE = -1;
+ public static final int ERROR = 1;
+ public static final int INFORMATION = 3;
+ public static final int WARNING = 2;
+ private int length;
+
+ private String message;
+ private int offset;
+ private int severity;
+
+ /**
+ */
+ public ValidationMessage(String message, int offset, int severity) {
+ this(message, offset, 0, severity);
+ }
+
+ /**
+ */
+ public ValidationMessage(String message, int offset, int length, int severity) {
+ super();
+
+ this.message = message;
+ this.offset = offset;
+ this.length = length;
+ this.severity = severity;
+ }
+
+ /**
+ */
+ public int getLength() {
+ return this.length;
+ }
+
+ /**
+ */
+ public String getMessage() {
+ return this.message;
+ }
+
+ /**
+ */
+ public int getOffset() {
+ return this.offset;
+ }
+
+ /**
+ */
+ public int getSeverity() {
+ return this.severity;
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationReporter.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationReporter.java
new file mode 100644
index 0000000000..82a06b576d
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidationReporter.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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.validate;
+
+
+
+
+/**
+ */
+public interface ValidationReporter {
+
+ /**
+ */
+ void report(ValidationMessage message);
+
+ void report(ErrorInfo info);
+
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidatorGroupListener.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidatorGroupListener.java
new file mode 100644
index 0000000000..24473cb6c4
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/validate/ValidatorGroupListener.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2013 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.wst.sse.core.internal.validate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.Logger;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.validation.IValidatorGroupListener;
+import org.eclipse.wst.validation.ValidationState;
+
+public class ValidatorGroupListener implements IValidatorGroupListener {
+
+ Map fDiagnosticMap = new HashMap();
+ private static final Object LOCK = new Object();
+ private static final boolean _debug = false;
+
+ public ValidatorGroupListener() {
+ }
+
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (fDiagnosticMap != null && !fDiagnosticMap.isEmpty()) {
+ Object[] paths = fDiagnosticMap.keySet().toArray();
+ for (int i = 0; i < paths.length; i++) {
+ Logger.log(Logger.ERROR, "Leaked model: " + paths[i]);
+ validationFinishing(ResourcesPlugin.getWorkspace().getRoot().getFile((IPath) paths[i]), new NullProgressMonitor(), null);
+ }
+ }
+ }
+
+ public void validationFinishing(IResource resource, IProgressMonitor monitor, ValidationState state) {
+ if (_debug)
+ System.out.println("Finishing:" + resource.getFullPath());
+ if (resource.getType() != IResource.FILE)
+ return;
+
+ synchronized (LOCK) {
+ final IPath path = resource.getFullPath();
+ final ValidationModelReference ref = (ValidationModelReference) fDiagnosticMap.get(path);
+ if (ref != null) {
+ if (--ref.count == 0) {
+ // The model is no longer being tracked
+ fDiagnosticMap.remove(path);
+ if (ref.model != null) {
+ ref.model.releaseFromRead();
+ }
+ }
+ }
+ }
+ }
+
+ public void validationStarting(IResource resource, IProgressMonitor monitor, ValidationState state) {
+ if (_debug)
+ System.out.println("Starting: " + resource.getFullPath());
+ try {
+ if (monitor != null && !monitor.isCanceled()) {
+ if (resource.getType() != IResource.FILE)
+ return;
+
+ synchronized (LOCK) {
+ final IPath path = resource.getFullPath();
+ final ValidationModelReference ref = (ValidationModelReference) fDiagnosticMap.get(path);
+ if (ref != null) {
+ // The model is already being tracked
+ ++ref.count;
+ }
+ else {
+ // The model has not been obtained as part of the validation group yet
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ // possible when shutting down
+ if (modelManager != null) {
+ IStructuredModel model = modelManager.getModelForRead((IFile) resource);
+ if (model != null) {
+ fDiagnosticMap.put(resource.getFullPath(), new ValidationModelReference(model));
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ Logger.logException(e);
+ }
+ }
+
+ private class ValidationModelReference {
+ IStructuredModel model;
+ int count;
+ public ValidationModelReference(IStructuredModel model) {
+ this.model = model;
+ count = 1;
+ }
+ }
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/text/IStructuredPartitions.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/text/IStructuredPartitions.java
new file mode 100644
index 0000000000..e1cfc2461c
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/text/IStructuredPartitions.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.text;
+
+/**
+ * This interface is not intended to be implemented.
+ * It defines the partitioning for StructuredDocuments.
+ * Clients should reference the partition type Strings defined here directly.
+ *
+ * @since 1.1
+ */
+public interface IStructuredPartitions {
+
+ String DEFAULT_PARTITION = "org.eclipse.wst.sse.ST_DEFAULT"; //$NON-NLS-1$
+ String UNKNOWN_PARTITION = "org.eclipse.wst.sse.UNKNOWN_PARTITION_TYPE"; //$NON-NLS-1$
+}
diff --git a/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/utils/StringUtils.java b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/utils/StringUtils.java
new file mode 100644
index 0000000000..72dca7d6ac
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/utils/StringUtils.java
@@ -0,0 +1,751 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2010 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
+ * David Carver (Intalio) - bug 300430 - String concatenation
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.core.utils;
+
+
+
+import java.util.ArrayList;
+import java.util.List;
+import com.ibm.icu.util.StringTokenizer;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.wst.sse.core.internal.Logger;
+
+
+public class StringUtils {
+ protected static final String AMPERSTAND = "&"; //$NON-NLS-1$
+ protected static final String AMPERSTAND_ENTITY = "&&;"; //$NON-NLS-1$
+ protected static final String CARRIAGE_RETURN = "\r"; //$NON-NLS-1$
+ protected static final String CARRIAGE_RETURN_ENTITY = "\\r"; //$NON-NLS-1$
+ protected static final String CR = "\r"; //$NON-NLS-1$
+ protected static final String CRLF = "\r\n"; //$NON-NLS-1$
+ protected static final String DELIMITERS = " \t\n\r\f"; //$NON-NLS-1$
+ protected static final String DOUBLE_QUOTE = "\""; //$NON-NLS-1$
+ protected static final char DOUBLE_QUOTE_CHAR = '\"'; //$NON-NLS-1$
+ protected static final String DOUBLE_QUOTE_ENTITY = "&quot;"; //$NON-NLS-1$
+
+ protected static final String EQUAL_SIGN = "="; //$NON-NLS-1$
+ protected static final String EQUAL_SIGN_ENTITY = "&#61;"; //$NON-NLS-1$
+ private static final String FALSE = "false"; //$NON-NLS-1$
+ protected static final String GREATER_THAN = ">"; //$NON-NLS-1$
+ protected static final String GREATER_THAN_ENTITY = "&gt;"; //$NON-NLS-1$
+ protected static final String LESS_THAN = "<"; //$NON-NLS-1$
+ protected static final String LESS_THAN_ENTITY = "&lt;"; //$NON-NLS-1$
+ protected static final String LF = "\n"; //$NON-NLS-1$
+ protected static final String LINE_FEED = "\n"; //$NON-NLS-1$
+ protected static final String LINE_FEED_ENTITY = "\\n"; //$NON-NLS-1$
+ protected static final String LINE_FEED_TAG = "<dl>"; //$NON-NLS-1$
+ protected static final String LINE_TAB = "\t"; //$NON-NLS-1$
+ protected static final String LINE_TAB_ENTITY = "\\t"; //$NON-NLS-1$
+ protected static final String LINE_TAB_TAG = "<dd>"; //$NON-NLS-1$
+ protected static final String SINGLE_QUOTE = "'"; //$NON-NLS-1$
+ protected static final char SINGLE_QUOTE_CHAR = '\''; //$NON-NLS-1$
+ protected static final String SINGLE_QUOTE_ENTITY = "&#039;"; //$NON-NLS-1$
+ protected static final String SPACE = " "; //$NON-NLS-1$
+ protected static final String SPACE_ENTITY = "&nbsp;"; //$NON-NLS-1$
+ private static final String TRUE = "true"; //$NON-NLS-1$
+
+ /**
+ * Append appendString to the end of aString only if aString does not end
+ * with the insertString.
+ */
+ public static String appendIfNotEndWith(String aString, String appendString) {
+ if ((aString != null) && (appendString != null))
+ if (aString.endsWith(appendString))
+ return aString;
+ else
+ return aString + appendString;
+ else
+ return aString;
+ }
+
+ /**
+ * Breaks out space-separated words into an array of words. For example:
+ * <code>"no comment"</code> into an array <code>a[0]="no"</code> and
+ * <code>a[1]= "comment"</code>.
+ *
+ * @param value
+ * the string to be converted
+ * @return the list of words
+ */
+ public static String[] asArray(String value) {
+ ArrayList list = new ArrayList();
+ StringTokenizer stok = new StringTokenizer(value);
+ while (stok.hasMoreTokens()) {
+ list.add(stok.nextToken());
+ }
+ String result[] = new String[list.size()];
+ list.toArray(result);
+ return result;
+ }
+
+ /**
+ * Breaks out delim-separated words into an array of words. For example:
+ * <code>"no comment"</code> into an array <code>a[0]="no"</code> and
+ * <code>a[1]= "comment"</code>.
+ *
+ * @param value
+ * the string to be converted
+ * @return the list of words
+ */
+ public static String[] asArray(String value, String delim) {
+ return asArray(value, delim, false);
+ }
+
+ /**
+ * Breaks out delim-separated words into an array of words. For example:
+ * <code>"no comment"</code> into an array <code>a[0]="no"</code> and
+ * <code>a[1]= "comment"</code>.
+ *
+ * @param value
+ * the string to be converted
+ * @return the list of words
+ */
+ public static String[] asArray(String value, String delim, boolean returnTokens) {
+ ArrayList list = new ArrayList();
+ StringTokenizer stok = new StringTokenizer(value, delim, returnTokens);
+ while (stok.hasMoreTokens()) {
+ list.add(stok.nextToken());
+ }
+ String result[] = new String[list.size()];
+ list.toArray(result);
+ return result;
+ }
+
+ /**
+ * Breaks out delim-separated words into an array of words. For example:
+ * <code>"abc,,def"</code> into an array <code>a[0]="abc"</code>,
+ * <code>a[1]=null</code>, and <code>a[2]= "def"</code> where "," is
+ * the delim.
+ *
+ * @param value
+ * the string to be converted
+ * @return the list of words
+ */
+ public static String[] asFixedArray(String value, String delim) {
+ String array[] = asArray(value, delim, true);
+ int arrayLength = array.length;
+ boolean stringFound = false;
+ ArrayList list = new ArrayList();
+
+ for (int i = 0; i < arrayLength; i++) {
+ String token = array[i];
+ if (token.compareTo(delim) == 0) {
+ if (!stringFound)
+ list.add(null);
+ stringFound = false;
+ }
+ else {
+ list.add(token);
+ stringFound = true;
+ }
+ }
+ // add one more null if last token is the delim
+ if (!stringFound)
+ list.add(null);
+
+ String result[] = new String[list.size()];
+ list.toArray(result);
+ return result;
+ }
+
+ public static String chop(String source) {
+ return chop(source, "/"); //$NON-NLS-1$
+ }
+
+ public static String chop(String source, String delimiter) {
+ return source.substring(0, source.lastIndexOf(delimiter));
+ }
+
+ public static boolean contains(String[] arrayOfStrings, String needle, boolean caseSensitive) {
+ boolean result = false;
+ if (needle == null)
+ return false;
+ if (arrayOfStrings == null)
+ return false;
+
+ if (caseSensitive) {
+ for (int i = 0; i < arrayOfStrings.length; i++) {
+ if (needle.equals(arrayOfStrings[i])) {
+ result = true;
+ break;
+ }
+ }
+ }
+ else {
+ for (int i = 0; i < arrayOfStrings.length; i++) {
+ if (needle.equalsIgnoreCase(arrayOfStrings[i])) {
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static boolean containsLetters(String fullValue) {
+
+ if (fullValue == null || fullValue.length() == 0)
+ return false;
+
+ char[] chars = fullValue.toCharArray();
+ for (int i = 0; i < fullValue.length(); i++)
+ if (Character.isLetter(chars[i]))
+ return true;
+
+ return false;
+ }
+
+ public static boolean containsLineDelimiter(String aString) {
+ return indexOfLineDelimiter(aString) != -1;
+ }
+
+ public static String convertLineDelimiters(String allText, String lineDelimiterToUse) {
+ IDocument tempDoc = new Document(allText);
+
+ if (lineDelimiterToUse == null)
+ lineDelimiterToUse = System.getProperty("line.separator"); //$NON-NLS-1$
+
+ String newText = ""; //$NON-NLS-1$
+ int lineCount = tempDoc.getNumberOfLines();
+ StringBuffer sb = new StringBuffer(newText);
+ for (int i = 0; i < lineCount; i++) {
+ try {
+ org.eclipse.jface.text.IRegion lineInfo = tempDoc.getLineInformation(i);
+ int lineStartOffset = lineInfo.getOffset();
+ int lineLength = lineInfo.getLength();
+ int lineEndOffset = lineStartOffset + lineLength;
+ sb.append(allText.substring(lineStartOffset, lineEndOffset));
+
+ if ((i < lineCount - 1) && (tempDoc.getLineDelimiter(i) != null)) {
+ sb.append(lineDelimiterToUse);
+ }
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ }
+ newText = sb.toString();
+
+ return newText;
+ }
+
+ /**
+ * Replaces all instances of special HTML characters with the appropriate
+ * HTML entity equivalent. WARNING only use this method for strings that
+ * dont already have HTML-specific items such as tags and entities.
+ *
+ * @param String
+ * content String to convert
+ *
+ * @return String the converted string
+ * @see HTMLPrinter#convertToHTMLContent(String content)
+ */
+ public static String convertToHTMLContent(String content) {
+ content = replace(content, AMPERSTAND, AMPERSTAND_ENTITY);
+ content = replace(content, LESS_THAN, LESS_THAN_ENTITY);
+ content = replace(content, GREATER_THAN, GREATER_THAN_ENTITY);
+ content = replace(content, LINE_FEED, LINE_FEED_TAG);
+ content = replace(content, LINE_TAB, LINE_TAB_TAG);
+ content = replace(content, SINGLE_QUOTE, SINGLE_QUOTE_ENTITY);
+ content = replace(content, DOUBLE_QUOTE, DOUBLE_QUOTE_ENTITY);
+ content = replace(content, SPACE + SPACE, SPACE_ENTITY + SPACE_ENTITY); // replacing
+ // every
+ // space
+ // would
+ // be
+ // too
+ // much
+ return content;
+ }
+
+ /**
+ * Converts a string into a form that will not conflict with saving it
+ * into an INI file
+ */
+ public static String escape(String normalString) {
+ if (normalString == null)
+ return null;
+ StringBuffer escapedBuffer = new StringBuffer();
+ StringTokenizer toker = new StringTokenizer(normalString, EQUAL_SIGN + LINE_FEED + CARRIAGE_RETURN + LINE_TAB, true);
+ String chunk = null;
+ while (toker.hasMoreTokens()) {
+ chunk = toker.nextToken();
+ if (chunk.equals(EQUAL_SIGN)) {
+ escapedBuffer.append(EQUAL_SIGN_ENTITY);
+ }
+ else if (chunk.equals(LINE_FEED)) {
+ escapedBuffer.append(LINE_FEED_ENTITY);
+ }
+ else if (chunk.equals(CARRIAGE_RETURN)) {
+ escapedBuffer.append(CARRIAGE_RETURN_ENTITY);
+ }
+ else if (chunk.equals(LINE_TAB)) {
+ escapedBuffer.append(LINE_TAB_ENTITY);
+ }
+ else {
+ escapedBuffer.append(chunk);
+ }
+ }
+ return escapedBuffer.toString();
+ }
+
+ /**
+ * Returns the first line of the given text without a trailing delimiter
+ *
+ * @param text
+ * @return
+ */
+ public static String firstLineOf(String text) {
+ if (text == null || text.length() < 1) {
+ return text;
+ }
+ IDocument doc = new Document(text);
+ try {
+ int lineNumber = doc.getLineOfOffset(0);
+ IRegion line = doc.getLineInformation(lineNumber);
+ return doc.get(line.getOffset(), line.getLength());
+ }
+ catch (BadLocationException e) {
+ // do nothing
+ }
+ return text;
+ }
+
+ public static int indexOfLastLineDelimiter(String aString) {
+ return indexOfLastLineDelimiter(aString, aString.length());
+ }
+
+ public static int indexOfLastLineDelimiter(String aString, int offset) {
+ int index = -1;
+
+ if (aString != null && aString.length() > 0) {
+ index = aString.lastIndexOf(CRLF, offset);
+ if (index == -1) {
+ index = aString.lastIndexOf(CR, offset);
+ if (index == -1)
+ index = aString.lastIndexOf(LF, offset);
+ }
+ }
+
+ return index;
+ }
+
+ public static int indexOfLineDelimiter(String aString) {
+ return indexOfLineDelimiter(aString, 0);
+ }
+
+ public static int indexOfLineDelimiter(String aString, int offset) {
+ int index = -1;
+
+ if (aString != null && aString.length() > 0) {
+ index = aString.indexOf(CRLF, offset);
+ if (index == -1) {
+ index = aString.indexOf(CR, offset);
+ if (index == -1)
+ index = aString.indexOf(LF, offset);
+ }
+ }
+
+ return index;
+ }
+
+ public static int indexOfNonblank(String aString) {
+ return indexOfNonblank(aString, 0);
+ }
+
+ public static int indexOfNonblank(String aString, int offset) {
+ int index = -1;
+
+ if (aString != null && aString.length() > 0) {
+ for (int i = offset; i < aString.length(); i++) {
+ if (DELIMITERS.indexOf(aString.substring(i, i + 1)) == -1) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ /**
+ * Insert insertString to the beginning of aString only if aString does
+ * not start with the insertString.
+ */
+ public static String insertIfNotStartWith(String aString, String insertString) {
+ if ((aString != null) && (insertString != null))
+ if (aString.startsWith(insertString))
+ return aString;
+ else
+ return insertString + aString;
+ else
+ return aString;
+ }
+
+ public static boolean isQuoted(String string) {
+ if ((string == null) || (string.length() < 2))
+ return false;
+
+ int lastIndex = string.length() - 1;
+ char firstChar = string.charAt(0);
+ char lastChar = string.charAt(lastIndex);
+
+ return (((firstChar == SINGLE_QUOTE_CHAR) && (lastChar == SINGLE_QUOTE_CHAR)) || ((firstChar == DOUBLE_QUOTE_CHAR) && (lastChar == DOUBLE_QUOTE_CHAR)));
+ }
+
+ /**
+ * Unit tests.
+ *
+ * @param args
+ * java.lang.String[]
+ */
+ public static void main(String[] args) {
+ // testPaste();
+ testStripNonLetterDigits();
+ }
+
+ /*
+ * Returns the merged form of both strings
+ */
+ public static String merge(String newStart, String newEnd) {
+ String[] regions = overlapRegions(newStart, newEnd);
+ return regions[0] + regions[1] + regions[2];
+ }
+
+ public static int occurrencesOf(String searchString, char targetChar) {
+ int result = 0;
+ int len = searchString.length();
+ for (int i = 0; i < len; i++) {
+ if (targetChar == searchString.charAt(i))
+ result++;
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @return java.lang.String[]
+ * @param start
+ * java.lang.String
+ * @param end
+ * java.lang.String
+ *
+ * Returns a 3 String array containing unique text from the start,
+ * duplicated text that overlaps the start and end, and the unique text
+ * from the end.
+ */
+ private static String[] overlapRegions(String start, String end) {
+ String[] results = null;
+ if (start != null && end == null) {
+ results = new String[]{start, "", ""}; //$NON-NLS-2$//$NON-NLS-1$
+ }
+ else if (start == null && end != null) {
+ results = new String[]{"", "", end}; //$NON-NLS-2$//$NON-NLS-1$
+ }
+ else if (start == null && end == null) {
+ results = new String[]{"", "", ""}; //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
+ }
+ else if (start != null && end != null) {
+
+ int startLength = start.length();
+ int endLength = end.length();
+
+ if (startLength == 0 || endLength == 0) {
+ results = new String[]{"", "", ""}; //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
+ }
+ else {
+ results = new String[3];
+ String testStart = ""; //$NON-NLS-1$
+ String testEnd = ""; //$NON-NLS-1$
+ int mergeLength = Math.min(startLength, endLength);
+ boolean finished = false;
+ while (mergeLength > 0 && !finished) {
+ testStart = start.substring(startLength - mergeLength);
+ testEnd = end.substring(0, mergeLength);
+ // case sensitive
+ if (testStart.equals(testEnd)) {
+ finished = true;
+ results[0] = start.substring(0, startLength - mergeLength);
+ results[1] = start.substring(startLength - mergeLength);
+ results[2] = end.substring(mergeLength);
+ }
+ mergeLength--;
+ }
+ if (!finished) {
+ results[0] = start;
+ results[1] = ""; //$NON-NLS-1$
+ results[2] = end;
+ }
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Packs an array of Strings into a single comma delimited String.
+ *
+ * @param strings
+ * @return
+ * @todo Generated comment
+ */
+ public static String pack(String[] strings) {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < strings.length; i++) {
+ buf.append(StringUtils.replace(strings[i], ",", "&comma;")); //$NON-NLS-1$ //$NON-NLS-2$
+ if (i < strings.length - 1)
+ buf.append(","); //$NON-NLS-1$
+ }
+ return buf.toString();
+ }
+
+ /*
+ * Pastes the new text into the old at the start position, replacing text
+ * implied by length.
+ */
+ public static String paste(String oldText, String newText, int start, int length) {
+ String result = null;
+ StringBuffer sb = new StringBuffer();
+ int startIndex = start;
+ int endIndex = start + length;
+ if (startIndex > oldText.length()) {
+ startIndex = oldText.length();
+ }
+ sb.append(oldText.substring(0, startIndex));
+ // null or empty new text accompliches a delete
+ if (newText != null) {
+ sb.append(newText);
+ }
+ if (endIndex < oldText.length()) {
+
+ sb.append(oldText.substring(endIndex));
+ }
+ result = sb.toString();
+ return result;
+ }
+
+ /**
+ * Replace matching literal portions of a string with another string
+ */
+ public static String replace(String aString, String source, String target) {
+ if (aString == null)
+ return null;
+ String normalString = ""; //$NON-NLS-1$
+ int length = aString.length();
+ int position = 0;
+ int previous = 0;
+ int spacer = source.length();
+ StringBuffer sb = new StringBuffer(normalString);
+ while (position + spacer - 1 < length && aString.indexOf(source, position) > -1) {
+ position = aString.indexOf(source, previous);
+ sb.append(normalString);
+ sb.append(aString.substring(previous, position));
+ sb.append(target);
+ position += spacer;
+ previous = position;
+ }
+ sb.append(aString.substring(position, aString.length()));
+ normalString = sb.toString();
+
+ return normalString;
+ }
+
+ /**
+ * Restore the entity references for markup delimiters in text where they
+ * have been replaced by the proper Unicode values through a DOM text
+ * parser.
+ */
+ public static String restoreMarkers(String text) {
+ String content = text;
+ content = replace(content, AMPERSTAND, AMPERSTAND_ENTITY);
+ content = replace(content, LESS_THAN, LESS_THAN_ENTITY);
+ content = replace(content, GREATER_THAN, GREATER_THAN_ENTITY);
+ return content;
+ }
+
+ /**
+ * Removes extra whitespace characters and quotes
+ */
+ public static String strip(String quotedString) {
+ if (quotedString == null || quotedString.length() == 0)
+ return quotedString;
+ String trimmed = quotedString.trim();
+ if (trimmed.length() < 2)
+ return quotedString;
+
+ char first = trimmed.charAt(0);
+ char nextToLast = trimmed.charAt(trimmed.length() - 2);
+ char last = trimmed.charAt(trimmed.length() - 1);
+
+ if ((first == '\"' && last == '\"' && nextToLast != '\\') || (first == '\'' && last == '\'' && nextToLast != '\\')) {
+ return trimmed.substring(1, trimmed.length() - 1);
+ }
+ return trimmed;
+ }
+
+ /**
+ * This method strips anything from the beginning and end of a string that
+ * is not a letter or digit. It is used by some encoding detectors to come
+ * up with the encoding name from illformed input (e.g in <?xml
+ * encoding="abc?> -- where final quote is left off, the '>' is returned
+ * with the rest of the attribute value 'abc').
+ */
+ public static String stripNonLetterDigits(String fullValue) {
+ if (fullValue == null || fullValue.length() == 0)
+ return fullValue;
+ int fullValueLength = fullValue.length();
+ int firstPos = 0;
+ while (firstPos < fullValueLength && !Character.isLetterOrDigit(fullValue.charAt(firstPos))) {
+ firstPos++;
+ }
+ int lastPos = fullValueLength - 1;
+ while (lastPos > firstPos && !Character.isLetterOrDigit(fullValue.charAt(lastPos))) {
+ lastPos--;
+ }
+ String result = fullValue;
+ if (firstPos != 0 || lastPos != fullValueLength) {
+ result = fullValue.substring(firstPos, lastPos + 1);
+ }
+ return result;
+ }
+
+ /**
+ * Similar to strip, except quotes don't need to match such as "UTF' is
+ * still stripped of both quotes. (Plus, this one does not detect escaped
+ * quotes)
+ */
+ public static String stripQuotes(String quotedValue) {
+ if (quotedValue == null)
+ return null;
+ // normally will never have leading or trailing blanks,
+ // but if it does, we'll do lenient interpretation
+ return stripQuotesLeaveInsideSpace(quotedValue).trim();
+ }
+
+ /**
+ * Like strip quotes, except leaves the start and end space inside the
+ * quotes
+ *
+ * @param quotedValue
+ * @return
+ */
+ public static String stripQuotesLeaveInsideSpace(String quotedValue) {
+ if (quotedValue == null)
+ return null;
+ // nomally will never have leading or trailing blanks ... but just in
+ // case.
+ String result = quotedValue.trim();
+ int len = result.length();
+ if (len > 0) {
+ char firstChar = result.charAt(0);
+ if ((firstChar == SINGLE_QUOTE_CHAR) || (firstChar == DOUBLE_QUOTE_CHAR)) {
+ result = result.substring(1, len);
+ }
+ len = result.length();
+ if (len > 0) {
+ char lastChar = result.charAt(len - 1);
+ if ((lastChar == SINGLE_QUOTE_CHAR) || (lastChar == DOUBLE_QUOTE_CHAR)) {
+ result = result.substring(0, len - 1);
+ }
+ }
+ }
+ return result;
+ }
+
+ public static void testPaste() {
+ String testString = "The quick brown fox ..."; //$NON-NLS-1$
+ System.out.println(paste(testString, null, 4, 5));
+ System.out.println(paste(testString, null, 4, 6));
+ System.out.println(paste(testString, "", 4, 6)); //$NON-NLS-1$
+ System.out.println(paste(testString, "fast", 4, 6)); //$NON-NLS-1$
+ System.out.println(paste(testString, "fast ", 4, 6)); //$NON-NLS-1$
+ System.out.println(paste(testString, "But ", 0, 0)); //$NON-NLS-1$
+ System.out.println(paste("", "burp", 4, 6)); //$NON-NLS-2$//$NON-NLS-1$
+ }
+
+ public static void testStripNonLetterDigits() {
+ String testString = "abc"; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = ""; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = "\"abc\""; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = "\"ab-c1?"; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = "+++"; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = "abc="; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+ testString = "abc "; //$NON-NLS-1$
+ System.out.println(testString + " -->" + stripNonLetterDigits(testString) + "<--"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ }
+
+ public static String toString(boolean booleanValue) {
+ if (booleanValue)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ /**
+ * Remove "escaped" chars from a string.
+ */
+ public static String unescape(String aString) {
+ if (aString == null)
+ return null;
+ String normalString = replace(aString, EQUAL_SIGN_ENTITY, EQUAL_SIGN);
+ normalString = replace(normalString, LINE_FEED_ENTITY, LINE_FEED);
+ normalString = replace(normalString, CARRIAGE_RETURN_ENTITY, CARRIAGE_RETURN);
+ normalString = replace(normalString, LINE_TAB_ENTITY, LINE_TAB);
+ return normalString;
+ }
+
+ public static String uniqueEndOf(String newStart, String newEnd) {
+ String[] regions = overlapRegions(newStart, newEnd);
+ return regions[2];
+ }
+
+ /**
+ * Unpacks a comma delimited String into an array of Strings
+ *
+ * @param s
+ * @return
+ * @todo Generated comment
+ */
+ public static String[] unpack(String s) {
+ if (s == null)
+ return new String[0];
+ StringTokenizer toker = new StringTokenizer(s, ","); //$NON-NLS-1$
+ List list = new ArrayList();
+ while (toker.hasMoreTokens()) {
+ // since we're separating the values with ',', escape ',' in the
+ // values
+ list.add(StringUtils.replace(toker.nextToken(), "&comma;", ",").trim()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return (String[]) list.toArray(new String[0]);
+ }
+
+ /**
+ * StringUtils constructor comment.
+ */
+ private StringUtils() {
+ super();
+ }
+
+}

Back to the top