Changes:
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / PHPDocumentProvider.java
1 package net.sourceforge.phpeclipse.phpeditor;
2
3 /**********************************************************************
4 Copyright (c) 2000, 2002 IBM Corp. and others.
5 All rights reserved. This program and the accompanying materials
6 are made available under the terms of the Common Public License v1.0
7 which accompanies this distribution, and is available at
8 http://www.eclipse.org/legal/cpl-v10.html
9
10 Contributors:
11     IBM Corporation - Initial implementation
12     Klaus Hartlage - www.eclipseproject.de
13 **********************************************************************/
14
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18
19 import net.sourceforge.phpdt.core.IProblemRequestor;
20 import net.sourceforge.phpdt.core.compiler.IProblem;
21 import net.sourceforge.phpdt.internal.ui.text.java.IProblemRequestorExtension;
22 import net.sourceforge.phpeclipse.phpeditor.php.IPHPPartitionScannerConstants;
23 import net.sourceforge.phpeclipse.phpeditor.php.PHPPartitionScanner;
24
25 import org.eclipse.core.resources.IFile;
26 import org.eclipse.core.resources.IMarker;
27 import org.eclipse.core.resources.IMarkerDelta;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.jface.text.DefaultLineTracker;
31 import org.eclipse.jface.text.IDocument;
32 import org.eclipse.jface.text.IDocumentPartitioner;
33 import org.eclipse.jface.text.ILineTracker;
34 import org.eclipse.jface.text.Position;
35 import org.eclipse.jface.text.rules.DefaultPartitioner;
36 import org.eclipse.jface.text.source.Annotation;
37 import org.eclipse.jface.text.source.AnnotationModelEvent;
38 import org.eclipse.jface.text.source.IAnnotationModel;
39 import org.eclipse.jface.text.source.IAnnotationModelListener;
40 import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
41 import org.eclipse.jface.util.ListenerList;
42 import org.eclipse.ui.IFileEditorInput;
43 import org.eclipse.ui.editors.text.FileDocumentProvider;
44 import org.eclipse.ui.part.FileEditorInput;
45 import org.eclipse.ui.texteditor.MarkerAnnotation;
46 import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
47
48 /** 
49  * The PHPDocumentProvider provides the IDocuments used by java editors.
50  */
51
52 public class PHPDocumentProvider extends FileDocumentProvider {
53
54   // private final static String[] TYPES= new String[] { PHPPartitionScanner.PHP, PHPPartitionScanner.JAVA_DOC, PHPPartitionScanner.JAVA_MULTILINE_COMMENT };
55   private final static String[] TYPES =
56     new String[] {
57       IPHPPartitionScannerConstants.PHP,
58       IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT,
59       IPHPPartitionScannerConstants.HTML,
60       IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT,
61       IPHPPartitionScannerConstants.JAVASCRIPT,
62       IPHPPartitionScannerConstants.CSS,
63                         IPHPPartitionScannerConstants.SMARTY,
64                         IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT };
65
66   private static PHPPartitionScanner PHP_PARTITION_SCANNER = null;
67   private static PHPPartitionScanner HTML_PARTITION_SCANNER = null;
68   private static PHPPartitionScanner XML_PARTITION_SCANNER = null;
69   private static PHPPartitionScanner SMARTY_PARTITION_SCANNER = null;
70
71   /** annotation model listener added to all created CU annotation models */
72   //            private GlobalAnnotationModelListener fGlobalAnnotationModelListener;   
73
74   /**
75                  * Internal structure for mapping positions to some value. 
76                  * The reason for this specific structure is that positions can
77                  * change over time. Thus a lookup is based on value and not
78                  * on hash value.
79                  */
80   protected static class ReverseMap {
81
82     static class Entry {
83       Position fPosition;
84       Object fValue;
85     };
86
87     private List fList = new ArrayList(2);
88     private int fAnchor = 0;
89
90     public ReverseMap() {
91     }
92
93     public Object get(Position position) {
94
95       Entry entry;
96
97       // behind anchor
98       int length = fList.size();
99       for (int i = fAnchor; i < length; i++) {
100         entry = (Entry) fList.get(i);
101         if (entry.fPosition.equals(position)) {
102           fAnchor = i;
103           return entry.fValue;
104         }
105       }
106
107       // before anchor
108       for (int i = 0; i < fAnchor; i++) {
109         entry = (Entry) fList.get(i);
110         if (entry.fPosition.equals(position)) {
111           fAnchor = i;
112           return entry.fValue;
113         }
114       }
115
116       return null;
117     }
118
119     private int getIndex(Position position) {
120       Entry entry;
121       int length = fList.size();
122       for (int i = 0; i < length; i++) {
123         entry = (Entry) fList.get(i);
124         if (entry.fPosition.equals(position))
125           return i;
126       }
127       return -1;
128     }
129
130     public void put(Position position, Object value) {
131       int index = getIndex(position);
132       if (index == -1) {
133         Entry entry = new Entry();
134         entry.fPosition = position;
135         entry.fValue = value;
136         fList.add(entry);
137       } else {
138         Entry entry = (Entry) fList.get(index);
139         entry.fValue = value;
140       }
141     }
142
143     public void remove(Position position) {
144       int index = getIndex(position);
145       if (index > -1)
146         fList.remove(index);
147     }
148
149     public void clear() {
150       fList.clear();
151     }
152   };
153
154   /**
155                  * Annotation model dealing with java marker annotations and temporary problems.
156                  * Also acts as problem requestor for its compilation unit. Initialiy inactive. Must explicitly be
157                  * activated.
158                  */
159   protected class CompilationUnitAnnotationModel
160     extends ResourceMarkerAnnotationModel
161     implements IProblemRequestor, IProblemRequestorExtension {
162
163     private IFileEditorInput fInput;
164     private List fCollectedProblems;
165     private List fGeneratedAnnotations;
166     private IProgressMonitor fProgressMonitor;
167     private boolean fIsActive = false;
168
169     private ReverseMap fReverseMap = new ReverseMap();
170     private List fPreviouslyOverlaid = null;
171     private List fCurrentlyOverlaid = new ArrayList();
172
173     public CompilationUnitAnnotationModel(IFileEditorInput input) {
174       super(input.getFile());
175       fInput = input;
176     }
177
178     protected MarkerAnnotation createMarkerAnnotation(IMarker marker) {
179       return new JavaMarkerAnnotation(marker);
180     }
181
182     protected Position createPositionFromProblem(IProblem problem) {
183       int start = problem.getSourceStart();
184       if (start < 0)
185         return null;
186
187       int length = problem.getSourceEnd() - problem.getSourceStart() + 1;
188       if (length < 0)
189         return null;
190
191       return new Position(start, length);
192     }
193
194     protected void update(IMarkerDelta[] markerDeltas) {
195
196       super.update(markerDeltas);
197
198       //                                        if (markerDeltas != null && markerDeltas.length > 0) {
199       //                                                try {
200       //                                                        ICompilationUnit workingCopy = getWorkingCopy(fInput);
201       //                                                        if (workingCopy != null)
202       //                                                                workingCopy.reconcile(true, null);
203       //                                                } catch (JavaModelException ex) {
204       //                                                        handleCoreException(ex, ex.getMessage());
205       //                                                }
206       //                                        }
207     }
208
209     /*
210      * @see IProblemRequestor#beginReporting()
211      */
212     public void beginReporting() {
213       //                                        ICompilationUnit unit= getWorkingCopy(fInput);
214       //                                        if (unit != null && unit.getJavaProject().isOnClasspath(unit))
215       //                                                fCollectedProblems= new ArrayList();
216       //                                        else
217       fCollectedProblems = null;
218     }
219
220     /*
221      * @see IProblemRequestor#acceptProblem(IProblem)
222      */
223     public void acceptProblem(IProblem problem) {
224       if (isActive())
225         fCollectedProblems.add(problem);
226     }
227
228     /*
229      * @see IProblemRequestor#endReporting()
230      */
231     public void endReporting() {
232       if (!isActive())
233         return;
234
235       if (fProgressMonitor != null && fProgressMonitor.isCanceled())
236         return;
237
238       boolean isCanceled = false;
239       boolean temporaryProblemsChanged = false;
240       fPreviouslyOverlaid = fCurrentlyOverlaid;
241       fCurrentlyOverlaid = new ArrayList();
242
243       synchronized (fAnnotations) {
244
245         if (fGeneratedAnnotations.size() > 0) {
246           temporaryProblemsChanged = true;
247           removeAnnotations(fGeneratedAnnotations, false, true);
248           fGeneratedAnnotations.clear();
249         }
250
251         if (fCollectedProblems != null && fCollectedProblems.size() > 0) {
252
253           Iterator e = fCollectedProblems.iterator();
254           while (e.hasNext()) {
255
256             IProblem problem = (IProblem) e.next();
257
258             if (fProgressMonitor != null && fProgressMonitor.isCanceled()) {
259               isCanceled = true;
260               break;
261             }
262
263             Position position = createPositionFromProblem(problem);
264             if (position != null) {
265
266               //                                                                        ProblemAnnotation annotation= new ProblemAnnotation(problem);
267               //                                                                        overlayMarkers(position, annotation);                                                           
268               //                                                                        fGeneratedAnnotations.add(annotation);
269               //                                                                        addAnnotation(annotation, position, false);
270
271               temporaryProblemsChanged = true;
272             }
273           }
274
275           fCollectedProblems.clear();
276         }
277
278         removeMarkerOverlays(isCanceled);
279         fPreviouslyOverlaid.clear();
280         fPreviouslyOverlaid = null;
281       }
282
283       //                                        if (temporaryProblemsChanged)
284       //                                                fireModelChanged(new CompilationUnitAnnotationModelEvent(this, getResource(), false));
285     }
286
287     private void removeMarkerOverlays(boolean isCanceled) {
288       if (isCanceled) {
289         fCurrentlyOverlaid.addAll(fPreviouslyOverlaid);
290       } else if (fPreviouslyOverlaid != null) {
291         Iterator e = fPreviouslyOverlaid.iterator();
292         while (e.hasNext()) {
293           JavaMarkerAnnotation annotation = (JavaMarkerAnnotation) e.next();
294           annotation.setOverlay(null);
295         }
296       }
297     }
298
299     /**
300      * Overlays value with problem annotation.
301      * @param problemAnnotation
302      */
303     //                          private void setOverlay(Object value, ProblemAnnotation problemAnnotation) {
304     //                                  if (value instanceof  JavaMarkerAnnotation) {
305     //                                          JavaMarkerAnnotation annotation= (JavaMarkerAnnotation) value;
306     //                                          if (annotation.isProblem()) {
307     //                                                  annotation.setOverlay(problemAnnotation);
308     //                                                  fPreviouslyOverlaid.remove(annotation);
309     //                                                  fCurrentlyOverlaid.add(annotation);
310     //                                          }
311     //                                  }
312     //                          }
313
314     //                          private void  overlayMarkers(Position position, ProblemAnnotation problemAnnotation) {
315     //                                  Object value= getAnnotations(position);
316     //                                  if (value instanceof List) {
317     //                                          List list= (List) value;
318     //                                          for (Iterator e = list.iterator(); e.hasNext();)
319     //                                                  setOverlay(e.next(), problemAnnotation);
320     //                                  } else {
321     //                                          setOverlay(value, problemAnnotation);
322     //                                  }
323     //                          }
324
325     /**
326      * Tells this annotation model to collect temporary problems from now on.
327      */
328     private void startCollectingProblems() {
329       fCollectedProblems = new ArrayList();
330       fGeneratedAnnotations = new ArrayList();
331     }
332
333     /**
334      * Tells this annotation model to no longer collect temporary problems.
335      */
336     private void stopCollectingProblems() {
337       if (fGeneratedAnnotations != null) {
338         removeAnnotations(fGeneratedAnnotations, true, true);
339         fGeneratedAnnotations.clear();
340       }
341       fCollectedProblems = null;
342       fGeneratedAnnotations = null;
343     }
344
345     /*
346      * @see AnnotationModel#fireModelChanged()
347      */
348     //                          protected void fireModelChanged() {
349     //                                  fireModelChanged(new CompilationUnitAnnotationModelEvent(this, getResource(), true));
350     //                          }
351
352     /*
353      * @see IProblemRequestor#isActive()
354      */
355     public boolean isActive() {
356       return fIsActive && (fCollectedProblems != null);
357     }
358
359     /*
360      * @see IProblemRequestorExtension#setProgressMonitor(IProgressMonitor)
361      */
362     public void setProgressMonitor(IProgressMonitor monitor) {
363       fProgressMonitor = monitor;
364     }
365
366     /*
367      * @see IProblemRequestorExtension#setIsActive(boolean)
368      */
369     public void setIsActive(boolean isActive) {
370       if (fIsActive != isActive) {
371         fIsActive = isActive;
372         if (fIsActive)
373           startCollectingProblems();
374         else
375           stopCollectingProblems();
376       }
377     }
378
379     private Object getAnnotations(Position position) {
380       return fReverseMap.get(position);
381     }
382
383     /*
384      * @see AnnotationModel#addAnnotation(Annotation, Position, boolean)
385      */
386     protected void addAnnotation(Annotation annotation, Position position, boolean fireModelChanged) {
387       super.addAnnotation(annotation, position, fireModelChanged);
388
389       Object cached = fReverseMap.get(position);
390       if (cached == null)
391         fReverseMap.put(position, annotation);
392       else if (cached instanceof List) {
393         List list = (List) cached;
394         list.add(annotation);
395       } else if (cached instanceof Annotation) {
396         List list = new ArrayList(2);
397         list.add(cached);
398         list.add(annotation);
399         fReverseMap.put(position, list);
400       }
401     }
402
403     /*
404      * @see AnnotationModel#removeAllAnnotations(boolean)
405      */
406     protected void removeAllAnnotations(boolean fireModelChanged) {
407       super.removeAllAnnotations(fireModelChanged);
408       fReverseMap.clear();
409     }
410
411     /*
412      * @see AnnotationModel#removeAnnotation(Annotation, boolean)
413      */
414     protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) {
415       Position position = getPosition(annotation);
416       Object cached = fReverseMap.get(position);
417       if (cached instanceof List) {
418         List list = (List) cached;
419         list.remove(annotation);
420         if (list.size() == 1) {
421           fReverseMap.put(position, list.get(0));
422           list.clear();
423         }
424       } else if (cached instanceof Annotation) {
425         fReverseMap.remove(position);
426       }
427
428       super.removeAnnotation(annotation, fireModelChanged);
429     }
430   };
431
432   protected static class GlobalAnnotationModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension {
433
434     private ListenerList fListenerList;
435
436     public GlobalAnnotationModelListener() {
437       fListenerList = new ListenerList();
438     }
439
440     /**
441      * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
442      */
443     public void modelChanged(IAnnotationModel model) {
444       Object[] listeners = fListenerList.getListeners();
445       for (int i = 0; i < listeners.length; i++) {
446         ((IAnnotationModelListener) listeners[i]).modelChanged(model);
447       }
448     }
449
450     /**
451      * @see IAnnotationModelListenerExtension#modelChanged(AnnotationModelEvent)
452      */
453     public void modelChanged(AnnotationModelEvent event) {
454       Object[] listeners = fListenerList.getListeners();
455       for (int i = 0; i < listeners.length; i++) {
456         Object curr = listeners[i];
457         if (curr instanceof IAnnotationModelListenerExtension) {
458           ((IAnnotationModelListenerExtension) curr).modelChanged(event);
459         }
460       }
461     }
462
463     public void addListener(IAnnotationModelListener listener) {
464       fListenerList.add(listener);
465     }
466
467     public void removeListener(IAnnotationModelListener listener) {
468       fListenerList.remove(listener);
469     }
470   };
471
472   public PHPDocumentProvider() {
473     super();
474
475     //          fGlobalAnnotationModelListener= new GlobalAnnotationModelListener();
476
477   }
478
479   /* (non-Javadoc)
480    * Method declared on AbstractDocumentProvider
481    */
482   protected IDocument createDocument(Object element) throws CoreException {
483     IDocument document = super.createDocument(element);
484     if (document != null) {
485       //  int fileType = 0; // PHP 
486       IDocumentPartitioner partitioner = null;
487       if (element instanceof FileEditorInput) {
488         IFile file = (IFile) ((FileEditorInput) element).getAdapter(IFile.class);
489         String filename = file.getLocation().toString();
490         String extension = filename.substring(filename.lastIndexOf("."), filename.length());
491         //   System.out.println(extension);
492         if (extension.equalsIgnoreCase(".html") || extension.equalsIgnoreCase(".htm")) {
493           // html
494           partitioner = createHTMLPartitioner();
495         } else if (extension.equalsIgnoreCase(".xml")) {
496           // xml
497           partitioner = createXMLPartitioner();
498         } else if (extension.equalsIgnoreCase(".js")) {
499           // javascript
500           partitioner = createJavaScriptPartitioner();
501         } else if (extension.equalsIgnoreCase(".css")) {
502           // cascading style sheets
503           partitioner = createCSSPartitioner();
504         } else if (extension.equalsIgnoreCase(".tpl")) {
505           // smarty ?
506           partitioner = createSmartyPartitioner();
507         } else if (extension.equalsIgnoreCase(".inc")) {
508           // php include files ?
509           partitioner = createIncludePartitioner();
510         }
511       }
512
513       if (partitioner == null) {
514         partitioner = createPHPPartitioner();
515       }
516       document.setDocumentPartitioner(partitioner);
517       partitioner.connect(document);
518     }
519     return document;
520   }
521
522   /**
523    * Return a partitioner for .php files.
524    */
525   private IDocumentPartitioner createPHPPartitioner() {
526     return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
527   }
528
529   /**
530    * Return a partitioner for .html files.
531    */
532   private IDocumentPartitioner createHTMLPartitioner() {
533     return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
534   }
535
536   private IDocumentPartitioner createXMLPartitioner() {
537     return new DefaultPartitioner(getXMLPartitionScanner(), TYPES);
538   }
539
540   private IDocumentPartitioner createJavaScriptPartitioner() {
541     return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
542   }
543
544   private IDocumentPartitioner createCSSPartitioner() {
545     return new DefaultPartitioner(getHTMLPartitionScanner(), TYPES);
546   }
547
548   private IDocumentPartitioner createSmartyPartitioner() {
549     return new DefaultPartitioner(getSmartyPartitionScanner(), TYPES);
550   }
551
552   private IDocumentPartitioner createIncludePartitioner() {
553     return new DefaultPartitioner(getPHPPartitionScanner(), TYPES);
554   }
555   /**
556    * Return a scanner for creating php partitions.
557    */
558   private PHPPartitionScanner getPHPPartitionScanner() {
559     if (PHP_PARTITION_SCANNER == null)
560       PHP_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.PHP_FILE);
561     return PHP_PARTITION_SCANNER;
562   }
563
564   /**
565    * Return a scanner for creating html partitions.
566    */
567   private PHPPartitionScanner getHTMLPartitionScanner() {
568     if (HTML_PARTITION_SCANNER == null)
569       HTML_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.HTML_FILE);
570     return HTML_PARTITION_SCANNER;
571   }
572
573   /**
574    * Return a scanner for creating xml partitions.
575    */
576   private PHPPartitionScanner getXMLPartitionScanner() {
577     if (XML_PARTITION_SCANNER == null)
578       XML_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.XML_FILE);
579     return XML_PARTITION_SCANNER;
580   }
581
582   /**
583    * Return a scanner for creating smarty partitions.
584    */
585   private PHPPartitionScanner getSmartyPartitionScanner() {
586     if (SMARTY_PARTITION_SCANNER == null)
587       SMARTY_PARTITION_SCANNER = new PHPPartitionScanner(IPHPPartitionScannerConstants.SMARTY_FILE);
588     return SMARTY_PARTITION_SCANNER;
589   }
590
591   /**
592    * Creates a line tracker working with the same line delimiters as the document
593    * of the given element. Assumes the element to be managed by this document provider.
594    * 
595    * @param element the element serving as blue print
596    * @return a line tracker based on the same line delimiters as the element's document
597    */
598   public ILineTracker createLineTracker(Object element) {
599     return new DefaultLineTracker();
600   }
601 }