blob: 4cdcc0638f368214d372c193bdc6e595b5bbaa74 [file] [log] [blame]
paverydc8b3062005-10-24 20:43:42 +00001package org.eclipse.wst.sse.ui.internal.reconcile;
2
3import java.io.ByteArrayInputStream;
4import java.io.IOException;
5import java.util.ArrayList;
6import java.util.HashMap;
7import java.util.Iterator;
8import java.util.List;
9
10import org.eclipse.core.runtime.Platform;
11import org.eclipse.core.runtime.content.IContentDescription;
12import org.eclipse.core.runtime.content.IContentType;
13import org.eclipse.jface.text.DocumentEvent;
14import org.eclipse.jface.text.IDocument;
15import org.eclipse.jface.text.IDocumentListener;
16import org.eclipse.jface.text.ITextInputListener;
17import org.eclipse.jface.text.ITextViewer;
18import org.eclipse.jface.text.ITypedRegion;
19import org.eclipse.jface.text.reconciler.DirtyRegion;
20import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
21import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
22import org.eclipse.jface.text.source.ISourceViewer;
23import org.eclipse.wst.sse.ui.internal.IReleasable;
24import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorBuilder;
25import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorMetaData;
26import org.eclipse.wst.sse.ui.internal.reconcile.validator.ValidatorStrategy;
27
28/**
29 * Adds to DirtyRegionProcessor Job:
30 *
31 * - IDocumentListener
32 * - ValidatorStrategy
33 * - DefaultStrategy
34 * - Text viewer(dispose, input changed) listeners.
35 * - default and validator strategies
36 * - DirtyRegion processing logic.
37 */
38public class DocumentRegionProcessor extends DirtyRegionProcessor implements IDocumentListener{
39
40 /**
41 * Reconclies the entire document when the document in the viewer is
42 * changed. This happens when the document is initially opened, as well as
43 * after a save-as.
44 *
45 * Also see processPostModelEvent(...) for similar behavior when document
46 * for the model is changed.
47 */
48 private class SourceTextInputListener implements ITextInputListener {
49
50 public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
51 // do nothing
52 }
53
54 public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
55 handleInputDocumentChanged(oldInput, newInput);
56 }
57 }
58
59 /** for initital reconcile when document is opened */
60 private SourceTextInputListener fTextInputListener = null;
61
62
63 /** strategy called for unmapped partitions */
64 private IReconcilingStrategy fDefaultStrategy;
65
66 /**
67 * The strategy that runs validators contributed via
68 * <code>org.eclipse.wst.sse.ui.extensions.sourcevalidation</code>
69 * extension point
70 */
71 private ValidatorStrategy fValidatorStrategy;
72
73 private final String SSE_EDITOR_ID = "org.eclipse.wst.sse.ui"; //$NON-NLS-1$
74
75 private String[] fLastPartitions;
76
77 /**
78 * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#install(org.eclipse.jface.text.ITextViewer)
79 */
80 public void install(ITextViewer textViewer) {
81
82 super.install(textViewer);
83 fTextInputListener = new SourceTextInputListener();
84 textViewer.addTextInputListener(fTextInputListener);
85 }
86
87 /**
88 * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#uninstall()
89 */
90 public void uninstall() {
91 if (isInstalled()) {
92
93 getLocalProgressMonitor().setCanceled(true);
94 cancel();
95
96 // removes document listeners
97 reconcilerDocumentChanged(null);
98
99 // removes widget listener
100 getTextViewer().removeTextInputListener(fTextInputListener);
101 // getTextViewer().getTextWidget().removeDisposeListener(fDisposeListener);
102
103 // release all strategies
104 List strategyTypes = getStrategyTypes();
105 if (!strategyTypes.isEmpty()) {
106 Iterator it = strategyTypes.iterator();
107 IReconcilingStrategy strategy = null;
108 while (it.hasNext()) {
109 strategy = getReconcilingStrategy((String) it.next());
110 if (strategy instanceof IReleasable) {
111 ((IReleasable) strategy).release();
112 strategy = null;
113 }
114 }
115 }
116
117 IReconcilingStrategy defaultStrategy = getDefaultStrategy();
118 IReconcilingStrategy validatorStrategy = getValidatorStrategy();
119
120 if(defaultStrategy != null) {
121 if(defaultStrategy instanceof IReleasable)
122 ((IReleasable)defaultStrategy).release();
123 }
124
125 if(validatorStrategy != null) {
126 if(validatorStrategy instanceof IReleasable)
127 ((IReleasable)validatorStrategy).release();
128 }
129 }
130 super.uninstall();
131 }
132
133 /**
134 *
135 * @param oldInput
136 * @param newInput
137 */
138 public void handleInputDocumentChanged(IDocument oldInput, IDocument newInput) {
139 // don't bother if reconciler not installed
140 if (isInstalled()) {
141
142 reconcilerDocumentChanged(newInput);
143
144 setDocument(newInput);
145 setDocumentOnAllStrategies(newInput);
146 setEntireDocumentDirty(newInput);
147 }
148 }
149
150 /**
151 * Reinitializes listeners and sets new document onall strategies.
152 *
153 * @see org.eclipse.jface.text.reconciler.AbstractReconciler#reconcilerDocumentChanged(IDocument)
154 */
155 protected void reconcilerDocumentChanged(IDocument newDocument) {
156
157 IDocument currentDoc = getDocument();
158
159 // unhook old document listener
160 if (currentDoc != null)
161 currentDoc.removeDocumentListener(this);
162 // hook up new document listener
163 if (newDocument != null)
164 newDocument.addDocumentListener(this);
165
166 // sets document on all strategies
167 super.reconcilerDocumentChanged(newDocument);
168 }
169
170 /**
171 * @param dirtyRegion
172 */
173 protected void process(DirtyRegion dirtyRegion) {
174 if (!isInstalled())
175 return;
176
177 ITypedRegion[] unfiltered = computePartitioning(dirtyRegion);
178
179 // remove duplicate typed regions
180 // that are handled by the same "total scope" strategy
181 ITypedRegion[] filtered = filterTotalScopeRegions(unfiltered);
182
183 IReconcilingStrategy s;
184 DirtyRegion dirty = null;
185 for (int i = 0; i < filtered.length; i++) {
186
187 dirty = createDirtyRegion(filtered[i], DirtyRegion.INSERT);
188 s = getReconcilingStrategy(filtered[i].getType());
189 if (s != null && dirty != null) {
190 s.reconcile(dirty, dirty);
191 }
192
193 // validator for this partition
194 if (fValidatorStrategy != null)
195 fValidatorStrategy.reconcile(filtered[i], dirty);
196 }
197 }
198 /**
199 * Removes multiple "total-scope" regions (and leaves one) for a each
200 * partitionType. This improves performance by preventing unnecessary full
201 * document validations.
202 *
203 * @param unfiltered
204 * @return
205 */
206 private ITypedRegion[] filterTotalScopeRegions(ITypedRegion[] unfiltered) {
207 IReconcilingStrategy s = null;
208 // ensure there is only one typed region in the list
209 // for regions handled by "total scope" strategies
210 HashMap totalScopeRegions = new HashMap();
211 HashMap partialScopeRegions = new HashMap();
212 List allRegions = new ArrayList();
213 for (int i = 0; i < unfiltered.length; i++) {
214
215 String partitionType = unfiltered[i].getType();
216
217 // short circuit loop
218 if (totalScopeRegions.containsKey(partitionType) || partialScopeRegions.containsKey(partitionType))
219 continue;
220
221 s = getReconcilingStrategy(partitionType);
222
223 // might be the validator strategy
224 if (s == null) {
225 ValidatorStrategy validatorStrategy = getValidatorStrategy();
226 if (validatorStrategy != null) {
227 if (validatorStrategy.canValidatePartition(partitionType))
228 s = validatorStrategy;
229 }
230 }
231
232 if (s instanceof AbstractStructuredTextReconcilingStrategy) {
233 // only allow one dirty region for a strategy
234 // that has "total scope"
235 if (((AbstractStructuredTextReconcilingStrategy) s).isTotalScope())
236 totalScopeRegions.put(partitionType, unfiltered[i]);
237 else
238 partialScopeRegions.put(partitionType, unfiltered[i]);
239 }
240 else
241 partialScopeRegions.put(partitionType, unfiltered[i]);
242 }
243 allRegions.addAll(totalScopeRegions.values());
244 allRegions.addAll(partialScopeRegions.values());
245 ITypedRegion[] filtered = (ITypedRegion[]) allRegions.toArray(new ITypedRegion[allRegions.size()]);
246
247 if (DEBUG)
248 System.out.println("filtered out this many 'total-scope' regions: " + (unfiltered.length - filtered.length)); //$NON-NLS-1$
249
250 return filtered;
251 }
252
253 /**
254 * @param defaultStrategy
255 * The fDefaultStrategy to set.
256 */
257 public void setDefaultStrategy(IReconcilingStrategy defaultStrategy) {
258 fDefaultStrategy = defaultStrategy;
259 if (fDefaultStrategy != null) {
260 fDefaultStrategy.setDocument(getDocument());
261 if (fDefaultStrategy instanceof IReconcilingStrategyExtension)
262 ((IReconcilingStrategyExtension) fDefaultStrategy).setProgressMonitor(getLocalProgressMonitor());
263 }
264 }
265 /**
266 * @return Returns the fDefaultStrategy.
267 */
268 public IReconcilingStrategy getDefaultStrategy() {
269 return fDefaultStrategy;
270 }
271
272 /**
273 * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#getAppropriateStrategy(org.eclipse.jface.text.reconciler.DirtyRegion)
274 */
275 protected IReconcilingStrategy getStrategy(DirtyRegion dirtyRegion) {
276 IReconcilingStrategy strategy = super.getStrategy(dirtyRegion);
277 if (strategy == null)
278 strategy = getDefaultStrategy();
279 return strategy;
280 }
281
282 /**
283 * @return Returns the fValidatorStrategy.
284 */
285 public ValidatorStrategy getValidatorStrategy() {
286 if (fValidatorStrategy == null) {
287 ValidatorStrategy validatorStrategy = null;
288
289 if (getTextViewer() instanceof ISourceViewer) {
290 ISourceViewer viewer = (ISourceViewer)getTextViewer();
291 String contentTypeId = null;
292
293 IDocument doc = viewer.getDocument();
294 contentTypeId = getContentType(doc);
295
296 if (contentTypeId != null) {
297
298 validatorStrategy = new ValidatorStrategy(viewer, contentTypeId);
299 ValidatorBuilder vBuilder = new ValidatorBuilder();
300 ValidatorMetaData[] vmds = vBuilder.getValidatorMetaData(SSE_EDITOR_ID);
301 for (int i = 0; i < vmds.length; i++) {
302 if (vmds[i].canHandleContentType(contentTypeId))
303 validatorStrategy.addValidatorMetaData(vmds[i]);
304 }
305 }
306 }
307 fValidatorStrategy = validatorStrategy;
308 }
309 return fValidatorStrategy;
310 }
311
312 protected String getContentType(IDocument doc) {
313
314 if(doc == null)
315 return null;
316
317 String contentTypeId = null;
318
319 // pa_TODO it would be nice to be able to get filename from IDocument...
320 // because it seems that getting content type from input stream
321 // isn't all that accurate (eg. w/ a javascript file)
322
323 //IContentType ct = Platform.getContentTypeManager().findContentTypeFor("test.js");
324 IContentType ct = null;
325 try {
326 IContentDescription desc = Platform.getContentTypeManager().getDescriptionFor(new ByteArrayInputStream(doc.get().getBytes()), null, IContentDescription.ALL);
327 if(desc != null) {
328 ct = desc.getContentType();
329 if(ct != null)
330 contentTypeId = ct.getId();
331 }
332 }
333 catch (IOException e) {
334 // just bail
335 }
336 return contentTypeId;
337 }
338 /**
339 * @see org.eclipse.wst.sse.ui.internal.reconcile.DirtyRegionProcessor#setDocumentOnAllStrategies(org.eclipse.jface.text.IDocument)
340 */
341 protected void setDocumentOnAllStrategies(IDocument document) {
342
343 super.setDocumentOnAllStrategies(document);
344
345 IReconcilingStrategy defaultStrategy = getDefaultStrategy();
346 IReconcilingStrategy validatorStrategy = getValidatorStrategy();
347
348 // default strategies
349 if (defaultStrategy != null)
350 defaultStrategy.setDocument(document);
351
352 // external validator strategy
353 if (validatorStrategy != null)
354 validatorStrategy.setDocument(document);
355 }
356
357 public void documentAboutToBeChanged(DocumentEvent event) {
358 // save partition type (to see if it changes in documentChanged())
359 fLastPartitions = getPartitions(event.getOffset(), event.getLength());
360 }
361
362 public void documentChanged(DocumentEvent event) {
363 if(partitionsChanged(event)) {
364 // pa_TODO
365 // this is a simple way to ensure old
366 // annotations are removed when partition changes
367
368 // it might be a performance hit though
369 setEntireDocumentDirty(getDocument());
370 }
371 else {
372 DirtyRegion dr = null;
373 if(event.getLength() == 0) {
374 // it's an insert
375 // we use text length though
376 // so that the new region gets validated...
377 dr = createDirtyRegion(event.getOffset(), 0, DirtyRegion.INSERT);
378 }
379 else {
380 if(event.getText().equals("")) {
381 // it's a delete
382 dr =createDirtyRegion(event.getOffset(), event.getLength(), DirtyRegion.REMOVE);
383 }
384 else {
385 // it's a replace
386 dr = createDirtyRegion(event.getOffset(), event.getLength(), DirtyRegion.INSERT);
387 }
388 }
389 processDirtyRegion(dr);
390 }
391 }
392
393 /**
394 * Checks previous partitions from the span of the event
395 * w/ the new partitions from the span of the event.
396 * If partitions changed, return true, else return false
397 *
398 * @param event
399 * @return
400 */
401 private boolean partitionsChanged(DocumentEvent event) {
402 boolean changed = false;
403 String[] newPartitions = getPartitions(event.getOffset(), event.getLength());
404 if(fLastPartitions != null) {
405 if(fLastPartitions.length != newPartitions.length) {
406 changed = true;
407 }
408 else {
409 for(int i=0; i<fLastPartitions.length; i++) {
410 if(!fLastPartitions[i].equals(newPartitions[i])) {
411 changed = true;
412 break;
413 }
414 }
415 }
416 }
417 return changed;
418 }
419
420 /**
421 * Basically means process the entire document.
422 * @param document
423 */
424 protected void setEntireDocumentDirty(IDocument document) {
425
426 // make the entire document dirty
427 // this also happens on a "save as"
428 if (document != null && isInstalled()) {
429
430 // since we're marking the entire doc dirty
431 getDirtyRegionQueue().clear();
432 DirtyRegion entireDocument = createDirtyRegion(0, document.getLength(), DirtyRegion.INSERT);
433 processDirtyRegion(entireDocument);
434 }
435 }
436}