3m9 compatible;
[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, false, false);//, false);
320                                 scanner.setSource(contents.toCharArray());
321                                 List regions= new ArrayList();
322                                 int shift= range.getOffset();
323                                 int start= shift;
324                                 while (true) {
325                                         
326                                         int token= scanner.getNextToken();
327                                         start= shift + scanner.getCurrentTokenStartPosition();
328                                         
329                                         switch (token) {
330                                                 case ITerminalSymbols.TokenNameCOMMENT_PHPDOC: //  COMMENT_JAVADOC:
331                                                 case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
332                                                         int end= shift + scanner.getCurrentTokenEndPosition() + 1;
333                                                         regions.add(new Region(start, end - start));
334                                                 }
335                                                 case ITerminalSymbols.TokenNameCOMMENT_LINE:
336                                                         continue;
337                                         }
338                                         
339                                         break;
340                                 }
341                                 
342                                 regions.add(new Region(start, range.getOffset() + range.getLength() - start));
343                                 
344                                 if (regions.size() > 0) {
345                                         IRegion[] result= new IRegion[regions.size()];
346                                         regions.toArray(result);
347                                         return result;
348                                 }
349                         }
350                 } catch (JavaModelException e) {
351                 } catch (InvalidInputException e) {
352                 }
353                 
354                 return null;
355         }
356         
357         private Position createProjectionPosition(IRegion region) {
358                 
359                 if (fCachedDocument == null)
360                         return null;
361                 
362                 try {
363                         
364                         int start= fCachedDocument.getLineOfOffset(region.getOffset());
365                         int end= fCachedDocument.getLineOfOffset(region.getOffset() + region.getLength());
366                         if (start != end) {
367                                 int offset= fCachedDocument.getLineOffset(start);
368                                 int endOffset= fCachedDocument.getLineOffset(end + 1);
369                                 return new Position(offset, endOffset - offset);
370                         }
371                         
372                 } catch (BadLocationException x) {
373                 }
374                 
375                 return null;
376         }
377                 
378         protected void processDelta(IJavaElementDelta delta) {
379                 
380                 if (!isInstalled())
381                         return;
382                 
383                 ProjectionAnnotationModel model= (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
384                 if (model == null)
385                         return;
386                 
387                 try {
388                         
389                         IDocumentProvider provider= fEditor.getDocumentProvider();
390                         fCachedDocument= provider.getDocument(fEditor.getEditorInput());
391                         fAllowCollapsing= false;
392                         
393                         Map additions= new HashMap();
394                         List deletions= new ArrayList();
395                         List updates= new ArrayList();
396                         
397                         Map updated= computeAdditions((IParent) fInput);
398                         Map previous= createAnnotationMap(model);
399                         
400                         
401                         Iterator e= updated.keySet().iterator();
402                         while (e.hasNext()) {
403                                 JavaProjectionAnnotation annotation= (JavaProjectionAnnotation) e.next();
404                                 IJavaElement element= annotation.getElement();
405                                 Position position= (Position) updated.get(annotation);
406                                 
407                                 List annotations= (List) previous.get(element);
408                                 if (annotations == null) {
409                                         
410                                         additions.put(annotation, position);
411                                         
412                                 } else {
413                                         
414                                         Iterator x= annotations.iterator();
415                                         while (x.hasNext()) {
416                                                 JavaProjectionAnnotation a= (JavaProjectionAnnotation) x.next();
417                                                 if (annotation.isComment() == a.isComment()) {
418                                                         Position p= model.getPosition(a);
419                                                         if (p != null && !position.equals(p)) {
420                                                                 p.setOffset(position.getOffset());
421                                                                 p.setLength(position.getLength());
422                                                                 updates.add(a);
423                                                         }
424                                                         x.remove();
425                                                         break;
426                                                 }
427                                         }
428                                                                                 
429                                         if (annotations.isEmpty())
430                                                 previous.remove(element);
431                                 }
432                         }
433                         
434                         e= previous.values().iterator();
435                         while (e.hasNext()) {
436                                 List list= (List) e.next();
437                                 int size= list.size();
438                                 for (int i= 0; i < size; i++)
439                                         deletions.add(list.get(i));
440                         }
441                         
442                         match(model, deletions, additions, updates);
443                         
444                         Annotation[] removals= new Annotation[deletions.size()];
445                         deletions.toArray(removals);
446                         Annotation[] changes= new Annotation[updates.size()];
447                         updates.toArray(changes);
448                         model.modifyAnnotations(removals, additions, changes);
449                         
450                 } finally {
451                         fCachedDocument= null;
452                         fAllowCollapsing= true;
453                 }
454         }
455         
456         private void match(ProjectionAnnotationModel model, List deletions, Map additions, List changes) {
457                 if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
458                         return;
459                 
460                 List newDeletions= new ArrayList();
461                 List newChanges= new ArrayList();
462                 
463                 Iterator deletionIterator= deletions.iterator();
464                 outer: while (deletionIterator.hasNext()) {
465                         JavaProjectionAnnotation deleted= (JavaProjectionAnnotation) deletionIterator.next();
466                         Position deletedPosition= model.getPosition(deleted);
467                         if (deletedPosition == null)
468                                 continue;
469                         
470                         Iterator changesIterator= changes.iterator();
471                         while (changesIterator.hasNext()) {
472                                 JavaProjectionAnnotation changed= (JavaProjectionAnnotation) changesIterator.next();
473                                 if (deleted.isComment() == changed.isComment()) {
474                                         Position changedPosition= model.getPosition(changed);
475                                         if (changedPosition == null)
476                                                 continue;
477                                         
478                                         if (deletedPosition.getOffset() == changedPosition.getOffset()) {
479                                                 
480                                                 deletedPosition.setLength(changedPosition.getLength());
481                                                 deleted.setElement(changed.getElement());
482                                                 
483                                                 deletionIterator.remove();
484                                                 newChanges.add(deleted);
485                                                 
486                                                 changesIterator.remove();
487                                                 newDeletions.add(changed);
488                                                 
489                                                 continue outer;
490                                         }
491                                 }
492                         }
493                         
494                         Iterator additionsIterator= additions.keySet().iterator();
495                         while (additionsIterator.hasNext()) {
496                                 JavaProjectionAnnotation added= (JavaProjectionAnnotation) additionsIterator.next();
497                                 if (deleted.isComment() == added.isComment()) {
498                                         Position addedPosition= (Position) additions.get(added);
499                                         
500                                         if (deletedPosition.getOffset() == addedPosition.getOffset()) {
501                                                 
502                                                 deletedPosition.setLength(addedPosition.getLength());
503                                                 deleted.setElement(added.getElement());
504                                                 
505                                                 deletionIterator.remove();
506                                                 newChanges.add(deleted);
507                                                 
508                                                 additionsIterator.remove();
509                                                 
510                                                 break;
511                                         }
512                                 }
513                         }
514                 }
515                 
516                 deletions.addAll(newDeletions);
517                 changes.addAll(newChanges);
518         }
519
520         private Map createAnnotationMap(IAnnotationModel model) {
521                 Map map= new HashMap();
522                 Iterator e= model.getAnnotationIterator();
523                 while (e.hasNext()) {
524                         Object annotation= e.next();
525                         if (annotation instanceof JavaProjectionAnnotation) {
526                                 JavaProjectionAnnotation java= (JavaProjectionAnnotation) annotation;
527                                 List list= (List) map.get(java.getElement());
528                                 if (list == null) {
529                                         list= new ArrayList(2);
530                                         map.put(java.getElement(), list);
531                                 }
532                                 list.add(java);
533                         }
534                 }
535                 return map;
536         }
537 }