KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > compare > JavaStructureCreator


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jdt.internal.ui.compare;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.HashMap JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18
19 import org.eclipse.core.runtime.CoreException;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.IStatus;
22
23 import org.eclipse.core.resources.IResource;
24
25 import org.eclipse.swt.widgets.Shell;
26
27 import org.eclipse.jface.text.Document;
28 import org.eclipse.jface.text.IDocument;
29 import org.eclipse.jface.text.IDocumentPartitioner;
30
31 import org.eclipse.ui.services.IDisposable;
32
33 import org.eclipse.compare.CompareUI;
34 import org.eclipse.compare.IEditableContent;
35 import org.eclipse.compare.IEditableContentExtension;
36 import org.eclipse.compare.IResourceProvider;
37 import org.eclipse.compare.ISharedDocumentAdapter;
38 import org.eclipse.compare.IStreamContentAccessor;
39 import org.eclipse.compare.structuremergeviewer.DiffNode;
40 import org.eclipse.compare.structuremergeviewer.Differencer;
41 import org.eclipse.compare.structuremergeviewer.DocumentRangeNode;
42 import org.eclipse.compare.structuremergeviewer.ICompareInput;
43 import org.eclipse.compare.structuremergeviewer.IDiffContainer;
44 import org.eclipse.compare.structuremergeviewer.IDiffElement;
45 import org.eclipse.compare.structuremergeviewer.IStructureComparator;
46 import org.eclipse.compare.structuremergeviewer.StructureCreator;
47 import org.eclipse.compare.structuremergeviewer.StructureRootNode;
48
49 import org.eclipse.jdt.core.ICompilationUnit;
50 import org.eclipse.jdt.core.IJavaElement;
51 import org.eclipse.jdt.core.IJavaProject;
52 import org.eclipse.jdt.core.JavaCore;
53 import org.eclipse.jdt.core.ToolFactory;
54 import org.eclipse.jdt.core.compiler.IScanner;
55 import org.eclipse.jdt.core.compiler.ITerminalSymbols;
56 import org.eclipse.jdt.core.compiler.InvalidInputException;
57 import org.eclipse.jdt.core.dom.AST;
58 import org.eclipse.jdt.core.dom.ASTParser;
59 import org.eclipse.jdt.core.dom.CompilationUnit;
60
61 import org.eclipse.jdt.ui.text.IJavaPartitions;
62
63 import org.eclipse.jdt.internal.ui.JavaPlugin;
64
65
66 public class JavaStructureCreator extends StructureCreator {
67     
68     private Map JavaDoc fDefaultCompilerOptions;
69     
70     /**
71      * A root node for the structure. It is similar to {@link StructureRootNode} but needed
72      * to be a subclass of {@link JavaNode} because of the code used to build the structure.
73      */

74     private final class RootJavaNode extends JavaNode implements IDisposable {
75         
76         private final Object JavaDoc fInput;
77         private final boolean fEditable;
78         private final ISharedDocumentAdapter fAdapter;
79
80         private RootJavaNode(IDocument document, boolean editable, Object JavaDoc input, ISharedDocumentAdapter adapter) {
81             super(document);
82             this.fEditable = editable;
83             fInput= input;
84             fAdapter= adapter;
85         }
86
87         /* (non-Javadoc)
88          * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#isEditable()
89          */

90         public boolean isEditable() {
91             return fEditable;
92         }
93         
94         /* (non-Javadoc)
95          * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#nodeChanged(org.eclipse.compare.structuremergeviewer.DocumentRangeNode)
96          */

97         protected void nodeChanged(DocumentRangeNode node) {
98             save(this, fInput);
99         }
100
101         /* (non-Javadoc)
102          * @see org.eclipse.ui.services.IDisposable#dispose()
103          */

104         public void dispose() {
105             if (fAdapter != null) {
106                 fAdapter.disconnect(fInput);
107             }
108         }
109
110         /* (non-Javadoc)
111          * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#getAdapter(java.lang.Class)
112          */

113         public Object JavaDoc getAdapter(Class JavaDoc adapter) {
114             if (adapter == ISharedDocumentAdapter.class) {
115                 return fAdapter;
116             }
117             return super.getAdapter(adapter);
118         }
119         
120         
121         /* (non-Javadoc)
122          * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#isReadOnly()
123          */

124         public boolean isReadOnly() {
125             if (fInput instanceof IEditableContentExtension) {
126                 IEditableContentExtension ext = (IEditableContentExtension) fInput;
127                 return ext.isReadOnly();
128             }
129             return super.isReadOnly();
130         }
131         
132         /* (non-Javadoc)
133          * @see org.eclipse.compare.structuremergeviewer.DocumentRangeNode#validateEdit(org.eclipse.swt.widgets.Shell)
134          */

135         public IStatus validateEdit(Shell shell) {
136             if (fInput instanceof IEditableContentExtension) {
137                 IEditableContentExtension ext = (IEditableContentExtension) fInput;
138                 return ext.validateEdit(shell);
139             }
140             return super.validateEdit(shell);
141         }
142     }
143
144     /**
145      * RewriteInfos are used temporarily when rewriting the diff tree
146      * in order to combine similar diff nodes ("smart folding").
147      */

148     static class RewriteInfo {
149         
150         boolean fIsOut= false;
151         
152         JavaNode fAncestor= null;
153         JavaNode fLeft= null;
154         JavaNode fRight= null;
155         
156         ArrayList JavaDoc fChildren= new ArrayList JavaDoc();
157         
158         void add(IDiffElement diff) {
159             fChildren.add(diff);
160         }
161         
162         void setDiff(ICompareInput diff) {
163             if (fIsOut)
164                 return;
165             
166             fIsOut= true;
167             
168             JavaNode a= (JavaNode) diff.getAncestor();
169             JavaNode y= (JavaNode) diff.getLeft();
170             JavaNode m= (JavaNode) diff.getRight();
171             
172             if (a != null) {
173                 if (fAncestor != null)
174                     return;
175                 fAncestor= a;
176             }
177             if (y != null) {
178                 if (fLeft != null)
179                     return;
180                 fLeft= y;
181             }
182             if (m != null) {
183                 if (fRight != null)
184                     return;
185                 fRight= m;
186             }
187             
188             fIsOut= false;
189         }
190                 
191         /**
192          * @return true if some nodes could be successfully combined into one
193          */

194         boolean matches() {
195             return !fIsOut && fAncestor != null && fLeft != null && fRight != null;
196         }
197     }
198     
199     public JavaStructureCreator() {
200     }
201     
202     void setDefaultCompilerOptions(Map JavaDoc compilerSettings) {
203         fDefaultCompilerOptions= compilerSettings;
204     }
205     
206     /**
207      * @return the name that appears in the enclosing pane title bar
208      */

209     public String JavaDoc getName() {
210         return CompareMessages.JavaStructureViewer_title;
211     }
212     
213     /**
214      * @param input implement the IStreamContentAccessor interface
215      * @return a tree of JavaNodes for the given input.
216      * In case of error null is returned.
217      */

218     public IStructureComparator getStructure(final Object JavaDoc input) {
219         String JavaDoc contents= null;
220         char[] buffer= null;
221         IDocument doc= CompareUI.getDocument(input);
222         if (doc == null) {
223             if (input instanceof IStreamContentAccessor) {
224                 IStreamContentAccessor sca= (IStreamContentAccessor) input;
225                 try {
226                     contents= JavaCompareUtilities.readString(sca);
227                 } catch (CoreException ex) {
228                     // return null indicates the error.
229
return null;
230                 }
231             }
232             
233             if (contents != null) {
234                 int n= contents.length();
235                 buffer= new char[n];
236                 contents.getChars(0, n, buffer, 0);
237                 
238                 doc= new Document(contents);
239                 setupDocument(doc);
240             }
241         }
242         
243         return createStructureComparator(input, buffer, doc, null, null);
244     }
245
246     /* (non-Javadoc)
247      * @see org.eclipse.compare.structuremergeviewer.StructureCreator#createStructureComparator(java.lang.Object, org.eclipse.jface.text.IDocument, org.eclipse.compare.ISharedDocumentAdapter, org.eclipse.core.runtime.IProgressMonitor)
248      */

249     protected IStructureComparator createStructureComparator(Object JavaDoc element,
250             IDocument document, ISharedDocumentAdapter sharedDocumentAdapter,
251             IProgressMonitor monitor) throws CoreException {
252         return createStructureComparator(element, null, document, sharedDocumentAdapter, monitor);
253     }
254     
255     private IStructureComparator createStructureComparator(final Object JavaDoc input, char[] buffer, IDocument doc, ISharedDocumentAdapter adapter, IProgressMonitor monitor) {
256         String JavaDoc contents;
257         Map JavaDoc compilerOptions= null;
258         
259         if (input instanceof IResourceProvider) {
260             IResource resource= ((IResourceProvider) input).getResource();
261             if (resource != null) {
262                 IJavaElement element= JavaCore.create(resource);
263                 if (element != null) {
264                     IJavaProject javaProject= element.getJavaProject();
265                     if (javaProject != null)
266                         compilerOptions= javaProject.getOptions(true);
267                 }
268             }
269         }
270         if (compilerOptions == null)
271             compilerOptions= fDefaultCompilerOptions;
272         
273         if (doc != null) {
274             boolean isEditable= false;
275             if (input instanceof IEditableContent)
276                 isEditable= ((IEditableContent) input).isEditable();
277             
278             // we hook into the root node to intercept all node changes
279
JavaNode root= new RootJavaNode(doc, isEditable, input, adapter);
280             
281             if (buffer == null) {
282                 contents= doc.get();
283                 int n= contents.length();
284                 buffer= new char[n];
285                 contents.getChars(0, n, buffer, 0);
286             }
287                         
288             ASTParser parser= ASTParser.newParser(AST.JLS3);
289             if (compilerOptions != null)
290                 parser.setCompilerOptions(compilerOptions);
291             parser.setSource(buffer);
292             parser.setFocalPosition(0);
293             CompilationUnit cu= (CompilationUnit) parser.createAST(monitor);
294             cu.accept(new JavaParseTreeBuilder(root, buffer, true));
295             
296             return root;
297         }
298         return null;
299     }
300     
301     /**
302      * Returns the contents of the given node as a string.
303      * This string is used to test the content of a Java element
304      * for equality. Is is never shown in the UI, so any string representing
305      * the content will do.
306      * @param node must implement the IStreamContentAccessor interface
307      * @param ignoreWhiteSpace if true all Java white space (including comments) is removed from the contents.
308      * @return contents for equality test
309      */

310     public String JavaDoc getContents(Object JavaDoc node, boolean ignoreWhiteSpace) {
311         
312         if (! (node instanceof IStreamContentAccessor))
313             return null;
314             
315         IStreamContentAccessor sca= (IStreamContentAccessor) node;
316         String JavaDoc content= null;
317         try {
318             content= JavaCompareUtilities.readString(sca);
319         } catch (CoreException ex) {
320             JavaPlugin.log(ex);
321             return null;
322         }
323                 
324         if (ignoreWhiteSpace) { // we return everything but Java whitespace
325

326             // replace comments and whitespace by a single blank
327
StringBuffer JavaDoc buf= new StringBuffer JavaDoc();
328             char[] b= content.toCharArray();
329             
330             // to avoid the trouble when dealing with Unicode
331
// we use the Java scanner to extract non-whitespace and non-comment tokens
332
IScanner scanner= ToolFactory.createScanner(true, true, false, false); // however we request Whitespace and Comments
333
scanner.setSource(b);
334             try {
335                 int token;
336                 while ((token= scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) {
337                     switch (token) {
338                     case ITerminalSymbols.TokenNameWHITESPACE:
339                         int l= buf.length();
340                         if (l > 0 && buf.charAt(l-1) != ' ')
341                             buf.append(' ');
342                         break;
343                     default:
344                         buf.append(scanner.getCurrentTokenSource());
345                         buf.append(' ');
346                         break;
347                     }
348                 }
349                 content= buf.toString(); // success!
350
} catch (InvalidInputException ex) {
351                 // NeedWork
352
}
353         }
354         return content;
355     }
356     
357     /**
358      * @return true since this IStructureCreator can rewrite the diff tree
359      * in order to fold certain combinations of additions and deletions.
360      */

361     public boolean canRewriteTree() {
362         return true;
363     }
364     
365     /**
366      * Tries to detect certain combinations of additions and deletions
367      * as renames or signature changes and folders them into a single node.
368      * @param differencer
369      * @param root
370      */

371     public void rewriteTree(Differencer differencer, IDiffContainer root) {
372         
373         HashMap JavaDoc map= new HashMap JavaDoc(10);
374                 
375         Object JavaDoc[] children= root.getChildren();
376         for (int i= 0; i < children.length; i++) {
377             DiffNode diff= (DiffNode) children[i];
378             JavaNode jn= (JavaNode) diff.getId();
379             
380             if (jn == null)
381                 continue;
382             int type= jn.getTypeCode();
383             
384             // we can only combine methods or constructors
385
if (type == JavaNode.METHOD || type == JavaNode.CONSTRUCTOR) {
386                 
387                 // find or create a RewriteInfo for all methods with the same name
388
String JavaDoc name= jn.extractMethodName();
389                 RewriteInfo nameInfo= (RewriteInfo) map.get(name);
390                 if (nameInfo == null) {
391                     nameInfo= new RewriteInfo();
392                     map.put(name, nameInfo);
393                 }
394                 nameInfo.add(diff);
395                 
396                 // find or create a RewriteInfo for all methods with the same
397
// (non-empty) argument list
398
String JavaDoc argList= jn.extractArgumentList();
399                 RewriteInfo argInfo= null;
400                 if (argList != null && !argList.equals("()")) { //$NON-NLS-1$
401
argInfo= (RewriteInfo) map.get(argList);
402                     if (argInfo == null) {
403                         argInfo= new RewriteInfo();
404                         map.put(argList, argInfo);
405                     }
406                     argInfo.add(diff);
407                 }
408                 
409                 switch (diff.getKind() & Differencer.CHANGE_TYPE_MASK) {
410                 case Differencer.ADDITION:
411                 case Differencer.DELETION:
412                     // we only consider addition and deletions
413
// since a rename or argument list change looks
414
// like a pair of addition and deletions
415
if (type != JavaNode.CONSTRUCTOR)
416                         nameInfo.setDiff(diff);
417                     
418                     if (argInfo != null)
419                         argInfo.setDiff(diff);
420                     break;
421                 default:
422                     break;
423                 }
424             }
425             
426             // recurse
427
rewriteTree(differencer, diff);
428         }
429         
430         // now we have to rebuild the diff tree according to the combined
431
// changes
432
Iterator JavaDoc it= map.keySet().iterator();
433         while (it.hasNext()) {
434             String JavaDoc name= (String JavaDoc) it.next();
435             RewriteInfo i= (RewriteInfo) map.get(name);
436             if (i.matches()) { // we found a RewriteInfo that could be successfully combined
437

438                 // we have to find the differences of the newly combined node
439
// (because in the first pass we only got a deletion and an addition)
440
DiffNode d= (DiffNode) differencer.findDifferences(true, null, root, i.fAncestor, i.fLeft, i.fRight);
441                 if (d != null) {// there better should be a difference
442
d.setDontExpand(true);
443                     Iterator JavaDoc it2= i.fChildren.iterator();
444                     while (it2.hasNext()) {
445                         IDiffElement rd= (IDiffElement) it2.next();
446                         root.removeToRoot(rd);
447                         d.add(rd);
448                     }
449                 }
450             }
451         }
452     }
453
454     /**
455      * The JavaHistoryAction uses this function to determine whether
456      * a selected Java element can be replaced by some piece of
457      * code from the local history.
458      * @param je Java element
459      * @return true if the given IJavaElement maps to a JavaNode
460      */

461     static boolean hasEdition(IJavaElement je) {
462         return JavaElementHistoryPageSource.hasEdition(je);
463     }
464
465     /* (non-Javadoc)
466      * @see org.eclipse.compare.structuremergeviewer.StructureCreator#getDocumentPartitioner()
467      */

468     protected IDocumentPartitioner getDocumentPartitioner() {
469         return JavaCompareUtilities.createJavaPartitioner();
470     }
471     
472     /* (non-Javadoc)
473      * @see org.eclipse.compare.structuremergeviewer.StructureCreator#getDocumentPartitioning()
474      */

475     protected String JavaDoc getDocumentPartitioning() {
476         return IJavaPartitions.JAVA_PARTITIONING;
477     }
478     
479     /* (non-Javadoc)
480      * @see org.eclipse.compare.structuremergeviewer.StructureCreator#getPath(java.lang.Object, java.lang.Object)
481      */

482     protected String JavaDoc[] getPath(Object JavaDoc element, Object JavaDoc input) {
483         if (element instanceof IJavaElement) {
484             IJavaElement je = (IJavaElement) element;
485             // build a path starting at the given Java element and walk
486
// up the parent chain until we reach a IWorkingCopy or ICompilationUnit
487
List JavaDoc args= new ArrayList JavaDoc();
488             while (je != null) {
489                 // each path component has a name that uses the same
490
// conventions as a JavaNode name
491
String JavaDoc name= JavaCompareUtilities.getJavaElementID(je);
492                 if (name == null)
493                     return null;
494                 args.add(name);
495                 if (je instanceof ICompilationUnit)
496                     break;
497                 je= je.getParent();
498             }
499             
500             // revert the path
501
int n= args.size();
502             String JavaDoc[] path= new String JavaDoc[n];
503             for (int i= 0; i < n; i++)
504                 path[i]= (String JavaDoc) args.get(n-1-i);
505                 
506             return path;
507         }
508         return null;
509     }
510 }
511
Popular Tags