added class fields to outline
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / folding / DefaultJavaFoldingStructureProvider.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text.folding;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18
19 import net.sourceforge.phpdt.core.ElementChangedEvent;
20 import net.sourceforge.phpdt.core.IElementChangedListener;
21 import net.sourceforge.phpdt.core.IJavaElement;
22 import net.sourceforge.phpdt.core.IJavaElementDelta;
23 import net.sourceforge.phpdt.core.IParent;
24 import net.sourceforge.phpdt.core.ISourceRange;
25 import net.sourceforge.phpdt.core.ISourceReference;
26 import net.sourceforge.phpdt.core.IType;
27 import net.sourceforge.phpdt.core.JavaCore;
28 import net.sourceforge.phpdt.core.JavaModelException;
29 import net.sourceforge.phpdt.core.ToolFactory;
30 import net.sourceforge.phpdt.core.compiler.IScanner;
31 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
32 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
33 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
34 import net.sourceforge.phpdt.ui.PreferenceConstants;
35 import net.sourceforge.phpdt.ui.text.folding.IJavaFoldingStructureProvider;
36 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
37 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
38 import net.sourceforge.phpeclipse.phpeditor.PHPUnitEditor;
39
40 import org.eclipse.jface.preference.IPreferenceStore;
41 import org.eclipse.jface.text.BadLocationException;
42 import org.eclipse.jface.text.IDocument;
43 import org.eclipse.jface.text.IRegion;
44 import org.eclipse.jface.text.Position;
45 import org.eclipse.jface.text.Region;
46 import org.eclipse.jface.text.source.Annotation;
47 import org.eclipse.jface.text.source.IAnnotationModel;
48 import org.eclipse.jface.text.source.projection.IProjectionListener;
49 import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
50 import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
51 import org.eclipse.jface.text.source.projection.ProjectionViewer;
52 import org.eclipse.ui.texteditor.IDocumentProvider;
53 import org.eclipse.ui.texteditor.ITextEditor;
54
55
56 /**
57  * Updates the projection model of a class file or compilation unit.
58  *
59  * @since 3.0
60  */
61 public class DefaultJavaFoldingStructureProvider implements IProjectionListener, IJavaFoldingStructureProvider {
62         
63         private static class JavaProjectionAnnotation extends ProjectionAnnotation {
64                 
65                 private IJavaElement fJavaElement;
66                 private boolean fIsComment;
67                 
68                 public JavaProjectionAnnotation(IJavaElement element, boolean isCollapsed, boolean isComment) {
69                         super(isCollapsed);
70                         fJavaElement= element;
71                         fIsComment= isComment;
72                 }
73                 
74                 public IJavaElement getElement() {
75                         return fJavaElement;
76                 }
77                 
78                 public void setElement(IJavaElement element) {
79                         fJavaElement= element;
80                 }
81                 
82                 public boolean isComment() {
83                         return fIsComment;
84                 }
85                 
86                 public void setIsComment(boolean isComment) {
87                         fIsComment= isComment;
88                 }
89         }
90         
91         private class ElementChangedListener implements IElementChangedListener {
92                 
93                 /*
94                  * @see net.sourceforge.phpdt.core.IElementChangedListener#elementChanged(net.sourceforge.phpdt.core.ElementChangedEvent)
95                  */
96                 public void elementChanged(ElementChangedEvent e) {
97                         IJavaElementDelta delta= findElement(fInput, e.getDelta());
98                         if (delta != null)
99                                 processDelta(delta);
100                 }
101                 
102                 private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {
103                         
104                         if (delta == null || target == null)
105                                 return null;
106                         
107                         IJavaElement element= delta.getElement();
108                         
109                         if (element.getElementType() > IJavaElement.CLASS_FILE)
110                                 return null;
111                         
112                         if (target.equals(element))
113                                 return delta;                           
114                         
115                         IJavaElementDelta[] children= delta.getAffectedChildren();
116                         if (children == null || children.length == 0)
117                                 return null;
118                                 
119                         for (int i= 0; i < children.length; i++) {
120                                 IJavaElementDelta d= findElement(target, children[i]);
121                                 if (d != null)
122                                         return d;
123                         }
124                         
125                         return null;
126                 }               
127         }
128         
129         
130         private IDocument fCachedDocument;
131         
132         private ITextEditor fEditor;
133         private ProjectionViewer fViewer;
134         private IJavaElement fInput;
135         private IElementChangedListener fElementListener;
136         
137         private boolean fAllowCollapsing= false;
138         private boolean fCollapseJavadoc= false;
139         private boolean fCollapseImportContainer= true;
140         private boolean fCollapseInnerTypes= true;
141         private boolean fCollapseMethods= false;
142         
143         public DefaultJavaFoldingStructureProvider() {
144         }
145         
146         public void install(ITextEditor editor, ProjectionViewer viewer) {
147                 if (editor instanceof PHPEditor) {
148                         fEditor= editor;
149                         fViewer= viewer;
150                         fViewer.addProjectionListener(this);
151                 }
152         }
153         
154         public void uninstall() {
155                 if (isInstalled()) {
156                         projectionDisabled();
157                         fViewer.removeProjectionListener(this);
158                         fViewer= null;
159                         fEditor= null;
160                 }
161         }
162         
163         protected boolean isInstalled() {
164                 return fEditor != null;
165         }
166                 
167         /*
168          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
169          */
170         public void projectionEnabled() {
171                 if (fEditor instanceof PHPEditor) {
172                         initialize();
173                         fElementListener= new ElementChangedListener();
174                         JavaCore.addElementChangedListener(fElementListener);
175                 }
176         }
177         
178         /*
179          * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
180          */
181         public void projectionDisabled() {
182                 fCachedDocument= null;
183                 if (fElementListener != null) {
184                         JavaCore.removeElementChangedListener(fElementListener);
185                         fElementListener= null;
186                 }
187         }
188                 
189         public void initialize() {
190                 
191                 if (!isInstalled())
192                         return;
193                 
194                 initializePreferences();
195                 
196                 try {
197                         
198                         IDocumentProvider provider= fEditor.getDocumentProvider();
199                         fCachedDocument= provider.getDocument(fEditor.getEditorInput());
200                         fAllowCollapsing= true;
201                         
202                         if (fEditor instanceof PHPUnitEditor) {
203                                 IWorkingCopyManager manager= PHPeclipsePlugin.getDefault().getWorkingCopyManager();
204                                 fInput= manager.getWorkingCopy(fEditor.getEditorInput());
205                         } 
206 //                      else if (fEditor instanceof ClassFileEditor) {
207 //                              IClassFileEditorInput editorInput= (IClassFileEditorInput) fEditor.getEditorInput();
208 //                              fInput= editorInput.getClassFile();
209 //                      }
210                         
211                         if (fInput != null) {
212                                 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
213                                 if (model != null) {
214                                         Map additions= computeAdditions((IParent) fInput);
215                                         model.removeAllAnnotations();
216                                         model.replaceAnnotations(null, additions);
217                                 }
218                         }
219                         
220                 } finally {
221                         fCachedDocument= null;
222                         fAllowCollapsing= false;
223                 }
224         }
225
226         private void initializePreferences() {
227                 IPreferenceStore store= PHPeclipsePlugin.getDefault().getPreferenceStore();
228                 fCollapseInnerTypes= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
229                 fCollapseImportContainer= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
230                 fCollapseJavadoc= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
231                 fCollapseMethods= store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
232         }
233
234         private Map computeAdditions(IParent parent) {
235                 Map map= new HashMap();
236                 try {
237                         computeAdditions(parent.getChildren(), map);
238                 } catch (JavaModelException x) {
239                 }
240                 return map;
241         }
242
243         private void computeAdditions(IJavaElement[] elements, Map map) throws JavaModelException {
244                 for (int i= 0; i < elements.length; i++) {
245                         IJavaElement element= elements[i];
246                         
247                         computeAdditions(element, map);
248                         
249                         if (element instanceof IParent) {
250                                 IParent parent= (IParent) element;
251                                 computeAdditions(parent.getChildren(), map);
252                         }
253                 }
254         }
255
256         private void computeAdditions(IJavaElement element, Map map) {
257                 
258                 boolean createProjection= false;
259                 
260                 boolean collapse= false;
261                 switch (element.getElementType()) {
262                         
263                         case IJavaElement.IMPORT_CONTAINER:
264                                 collapse= fAllowCollapsing && fCollapseImportContainer;
265                                 createProjection= true;
266                                 break;
267                         case IJavaElement.TYPE:
268                                 collapse= fAllowCollapsing && fCollapseInnerTypes && isInnerType((IType) element);
269                                 createProjection= true;
270                                 break;
271                         case IJavaElement.METHOD:
272                                 collapse= fAllowCollapsing && fCollapseMethods;
273                                 createProjection= true;
274                                 break;
275                 }
276                 
277                 if (createProjection) {
278                         IRegion[] regions= computeProjectionRanges(element);
279                         if (regions != null) {
280                                 // comments
281                                 for (int i= 0; i < regions.length - 1; i++) {
282                                         Position position= createProjectionPosition(regions[i]);
283                                         if (position != null)
284                                                 map.put(new JavaProjectionAnnotation(element, fAllowCollapsing && fCollapseJavadoc, true), position);
285                                 }
286                                 // code
287                                 Position position= createProjectionPosition(regions[regions.length - 1]);
288                                 if (position != null)
289                                         map.put(new JavaProjectionAnnotation(element, collapse, false), position);
290                         }
291                 }
292         }
293
294         private boolean isInnerType(IType type) {
295                 
296                 try {
297                         return type.isMember();
298                 } catch (JavaModelException x) {
299                         IJavaElement parent= type.getParent();
300                         if (parent != null) {
301                                 int parentType= parent.getElementType();
302                                 return (parentType != IJavaElement.COMPILATION_UNIT && parentType != IJavaElement.CLASS_FILE);
303                         }
304                 }
305                 
306                 return false;           
307         }
308
309         private IRegion[] computeProjectionRanges(IJavaElement element) {
310                 
311                 try {
312                         if (element instanceof ISourceReference) {
313                                 ISourceReference reference= (ISourceReference) element;
314                                 ISourceRange range= reference.getSourceRange();
315                                 String contents= reference.getSource();
316                                 if (contents == null)
317                                         return null;
318                                 
319                                 IScanner scanner= ToolFactory.createScanner(true/*tokenizeComments*/, false, false, true /* phpMode */ );
320                                 scanner.setSource(contents.toCharArray());
321                                 
322                                 List regions= new ArrayList();
323                                 int shift= range.getOffset();
324                                 int start= shift;
325                                 while (true) {
326                                         
327                                         int token= scanner.getNextToken();
328                                         start= shift + scanner.getCurrentTokenStartPosition();
329                                         
330                                         switch (token) {
331                                                 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC: //  COMMENT_JAVADOC:
332                                                 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
333                                                         int end= shift + scanner.getCurrentTokenEndPosition() + 1;
334                                                         regions.add(new Region(start, end - start));
335                                                 }
336                                                 case ITerminalSymbols.TokenNameCOMMENT_LINE:
337                                                         continue;
338                                         }
339                                         
340                                         break;
341                                 }
342                                 
343                                 regions.add(new Region(start, range.getOffset() + range.getLength() - start));
344                                 
345                                 if (regions.size() > 0) {
346                                         IRegion[] result= new IRegion[regions.size()];
347                                         regions.toArray(result);
348                                         return result;
349                                 }
350                         }
351                 } catch (JavaModelException e) {
352                 } catch (InvalidInputException e) {
353                 }
354                 
355                 return null;
356         }
357         
358         private Position createProjectionPosition(IRegion region) {
359                 
360                 if (fCachedDocument == null)
361                         return null;
362                 
363                 try {
364                         
365                         int start= fCachedDocument.getLineOfOffset(region.getOffset());
366                         int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
367                         if (start != end) {
368                                 int offset= fCachedDocument.getLineOffset(start);
369                                 int endOffset= fCachedDocument.getLineOffset(end + 1);
370                                 return new Position(offset, endOffset - offset);
371                         }
372                         
373                 } catch (BadLocationException x) {
374                 }
375                 
376                 return null;
377         }
378                 
379         protected void processDelta(IJavaElementDelta delta) {
380                 
381                 if (!isInstalled())
382                         return;
383                 
384                 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
385                 if (model == null)
386                         return;
387                 
388                 try {
389                         
390                         IDocumentProvider provider= fEditor.getDocumentProvider();
391                         fCachedDocument= provider.getDocument(fEditor.getEditorInput());
392                         fAllowCollapsing= false;
393                         
394                         Map additions= new HashMap();
395                         List deletions= new ArrayList();
396                         List updates= new ArrayList();
397                         
398                         Map updated= computeAdditions((IParent) fInput);
399                         Map previous= createAnnotationMap(model);
400                         
401                         
402                         Iterator e= updated.keySet().iterator();
403                         while (e.hasNext()) {
404                                 JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) e.next();
405                                 IJavaElement element= annotation.getElement();
406                                 Position position= (Position) updated.get(annotation);
407                                 
408                                 List annotations= (List) previous.get(element);
409                                 if (annotations == null) {
410                                         
411                                         additions.put(annotation, position);
412                                         
413                                 } else {
414                                         
415                                         Iterator x= annotations.iterator();
416                                         while (x.hasNext()) {
417                                                 JavaProjectionAnnotation a= (JavaProjectionAnnotation) x.next();
418                                                 if (annotation.isComment() == a.isComment()) {
419                                                         Position p= model.getPosition(a);
420                                                         if (p != null && !position.equals(p)) {
421                                                                 p.setOffset(position.getOffset());
422                                                                 p.setLength(position.getLength());
423                                                                 updates.add(a);
424                                                         }
425                                                         x.remove();
426                                                         break;
427                                                 }
428                                         }
429                                                                                 
430                                         if (annotations.isEmpty())
431                                                 previous.remove(element);
432                                 }
433                         }
434                         
435                         e= previous.values().iterator();
436                         while (e.hasNext()) {
437                                 List list= (List) e.next();
438                                 int size= list.size();
439                                 for (int i= 0; i < size; i++)
440                                         deletions.add(list.get(i));
441                         }
442                         
443                         match(model, deletions, additions, updates);
444                         
445                         Annotation[] removals= new Annotation[deletions.size()];
446                         deletions.toArray(removals);
447                         Annotation[] changes= new Annotation[updates.size()];
448                         updates.toArray(changes);
449                         model.modifyAnnotations(removals, additions, changes);
450                         
451                 } finally {
452                         fCachedDocument= null;
453                         fAllowCollapsing= true;
454                 }
455         }
456         
457         private void match(ProjectionAnnotationModel model, List deletions, Map additions, List changes) {
458                 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
459                         return;
460                 
461                 List newDeletions= new ArrayList();
462                 List newChanges= new ArrayList();
463                 
464                 Iterator deletionIterator= deletions.iterator();
465                 outer: while (deletionIterator.hasNext()) {
466                         JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next();
467                         Position deletedPosition= model.getPosition(deleted);
468                         if (deletedPosition == null)
469                                 continue;
470                         
471                         Iterator changesIterator= changes.iterator();
472                         while (changesIterator.hasNext()) {
473                                 JavaProjectionAnnotation changed= (JavaProjectionAnnotation) changesIterator.next();
474                                 if (deleted.isComment() == changed.isComment()) {
475                                         Position changedPosition= model.getPosition(changed);
476                                         if (changedPosition == null)
477                                                 continue;
478                                         
479                                         if (deletedPosition.getOffset() == changedPosition.getOffset()) {
480                                                 
481                                                 deletedPosition.setLength(changedPosition.getLength());
482                                                 deleted.setElement(changed.getElement());
483                                                 
484                                                 deletionIterator.remove();
485                                                 newChanges.add(deleted);
486                                                 
487                                                 changesIterator.remove();
488                                                 newDeletions.add(changed);
489                                                 
490                                                 continue outer;
491                                         }
492                                 }
493                         }
494                         
495                         Iterator additionsIterator= additions.keySet().iterator();
496                         while (additionsIterator.hasNext()) {
497                                 JavaProjectionAnnotation added= (JavaProjectionAnnotation) additionsIterator.next();
498                                 if (deleted.isComment() == added.isComment()) {
499                                         Position addedPosition= (Position) additions.get(added);
500                                         
501                                         if (deletedPosition.getOffset() == addedPosition.getOffset()) {
502                                                 
503                                                 deletedPosition.setLength(addedPosition.getLength());
504                                                 deleted.setElement(added.getElement());
505                                                 
506                                                 deletionIterator.remove();
507                                                 newChanges.add(deleted);
508                                                 
509                                                 additionsIterator.remove();
510                                                 
511                                                 break;
512                                         }
513                                 }
514                         }
515                 }
516                 
517                 deletions.addAll(newDeletions);
518                 changes.addAll(newChanges);
519         }
520
521         private Map createAnnotationMap(IAnnotationModel model) {
522                 Map map= new HashMap();
523                 Iterator e= model.getAnnotationIterator();
524                 while (e.hasNext()) {
525                         Object annotation= e.next();
526                         if (annotation instanceof JavaProjectionAnnotation) {
527                                 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation;
528                                 List list= (List) map.get(java.getElement());
529                                 if (list == null) {
530                                         list= new ArrayList(2);
531                                         map.put(java.getElement(), list);
532                                 }
533                                 list.add(java);
534                         }
535                 }
536                 return map;
537         }
538 }