KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > compare > structuremergeviewer > DocumentRangeNode


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.compare.structuremergeviewer;
12
13 import java.io.ByteArrayInputStream JavaDoc;
14 import java.io.InputStream JavaDoc;
15 import java.util.ArrayList JavaDoc;
16
17 import org.eclipse.compare.*;
18 import org.eclipse.compare.contentmergeviewer.IDocumentRange;
19 import org.eclipse.compare.internal.CompareUIPlugin;
20 import org.eclipse.compare.internal.Utilities;
21 import org.eclipse.core.runtime.*;
22 import org.eclipse.jface.text.*;
23 import org.eclipse.swt.widgets.Shell;
24
25
26 /**
27  * A document range node represents a structural element
28  * when performing a structure compare of documents.
29  * <code>DocumentRangeNodes</code> are created while parsing the document and represent
30  * a semantic entity (e.g. a Java class or method).
31  * As a consequence of the parsing a <code>DocumentRangeNode</code> maps to a range
32  * of characters in the document.
33  * <p>
34  * Since a <code>DocumentRangeNode</code> implements the <code>IStructureComparator</code>
35  * and <code>IStreamContentAccessor</code> interfaces it can be used as input to the
36  * differencing engine. This makes it possible to perform
37  * a structural diff on a document and have the nodes and leaves of the compare easily map
38  * to character ranges within the document.
39  * <p>
40  * Clients need to be aware that this node registers position updaters with the document
41  * using {@link IDocument#addPosition(String, Position)} with the category set to
42  * {@link IDocumentRange#RANGE_CATEGORY}. The {@link StructureDiffViewer} will
43  * remove the category when the nodes are no longer being used. Other clients
44  * must do the same.
45  * <p>
46  * Subclasses may add additional state collected while parsing the document.
47  * </p>
48  * @see Differencer
49  */

50 public class DocumentRangeNode
51         implements IDocumentRange, IStructureComparator, IEditableContent, IEncodedStreamContentAccessor, IAdaptable, IEditableContentExtension {
52
53     private static final String JavaDoc UTF_16= "UTF-16"; //$NON-NLS-1$
54

55     private IDocument fBaseDocument;
56     private Position fRange; // the range in the base document
57
private int fTypeCode;
58     private String JavaDoc fID;
59     private Position fAppendPosition; // a position where to insert a child textually
60
private ArrayList JavaDoc fChildren;
61     private final DocumentRangeNode fParent;
62
63     /**
64      * Creates a new <code>DocumentRangeNode</code> for the given range within the specified
65      * document. The <code>typeCode</code> is uninterpreted client data. The ID is used when comparing
66      * two nodes with each other: i.e. the differencing engine performs a content compare
67      * on two nodes if their IDs are equal.
68      *
69      * @param typeCode a type code for this node
70      * @param id an identifier for this node
71      * @param document document on which this node is based on
72      * @param start start position of range within document
73      * @param length length of range
74      */

75     public DocumentRangeNode(int typeCode, String JavaDoc id, IDocument document, int start, int length) {
76         this(null, typeCode, id, document, start, length);
77     }
78     
79     /**
80      * Creates a new <code>DocumentRangeNode</code> for the given range within the specified
81      * document. The <code>typeCode</code> is uninterpreted client data. The ID is used when comparing
82      * two nodes with each other: i.e. the differencing engine performs a content compare
83      * on two nodes if their IDs are equal.
84      *
85      * @param parent the parent node
86      * @param typeCode a type code for this node
87      * @param id an identifier for this node
88      * @param document document on which this node is based on
89      * @param start start position of range within document
90      * @param length length of range
91      * @since 3.3
92      */

93     public DocumentRangeNode(DocumentRangeNode parent, int typeCode, String JavaDoc id, IDocument document, int start, int length) {
94         fParent = parent;
95         fTypeCode= typeCode;
96         fID= id;
97         fBaseDocument= document;
98         registerPositionUpdater(start, length);
99     }
100
101     private void registerPositionUpdater(int start, int length) {
102         fBaseDocument.addPositionCategory(RANGE_CATEGORY);
103         fRange= new Position(start, length);
104         try {
105             fBaseDocument.addPosition(RANGE_CATEGORY, fRange);
106         } catch (BadPositionCategoryException ex) {
107             CompareUIPlugin.log(ex);
108         } catch (BadLocationException ex) {
109             CompareUIPlugin.log(ex);
110         }
111     }
112
113     /* (non Javadoc)
114      * see IDocumentRange.getDocument
115      */

116     public IDocument getDocument() {
117         return fBaseDocument;
118     }
119     
120     /* (non Javadoc)
121      * see IDocumentRange.getRange
122      */

123     public Position getRange() {
124         return fRange;
125     }
126     
127     /**
128      * Returns the type code of this node.
129      * The type code is uninterpreted client data which can be set in the constructor.
130      *
131      * @return the type code of this node
132      */

133     public int getTypeCode() {
134         return fTypeCode;
135     }
136     
137     /**
138      * Returns this node's id.
139      * It is used in <code>equals</code> and <code>hashcode</code>.
140      *
141      * @return the node's id
142      */

143     public String JavaDoc getId() {
144         return fID;
145     }
146
147     /**
148      * Sets this node's id.
149      * It is used in <code>equals</code> and <code>hashcode</code>.
150      *
151      * @param id the new id for this node
152      */

153     public void setId(String JavaDoc id) {
154         fID= id;
155     }
156
157     /**
158      * Adds the given node as a child.
159      *
160      * @param node the node to add as a child
161      */

162     public void addChild(DocumentRangeNode node) {
163         if (fChildren == null)
164             fChildren= new ArrayList JavaDoc();
165         fChildren.add(node);
166     }
167
168     /* (non Javadoc)
169      * see IStructureComparator.getChildren
170      */

171     public Object JavaDoc[] getChildren() {
172         if (fChildren != null)
173             return fChildren.toArray();
174         return new Object JavaDoc[0];
175     }
176
177     /**
178      * Sets the length of the range of this node.
179      *
180      * @param length the length of the range
181      */

182     public void setLength(int length) {
183         getRange().setLength(length);
184     }
185
186     /**
187      * Sets a position within the document range that can be used to (legally) insert
188      * text without breaking the syntax of the document.
189      * <p>
190      * E.g. when parsing a Java document the "append position" of a <code>DocumentRangeNode</code>
191      * representing a Java class could be the character position just before the closing bracket.
192      * Inserting the text of a new method there would not disturb the syntax of the class.
193      *
194      * @param pos the character position within the underlying document where text can be legally inserted
195      */

196     public void setAppendPosition(int pos) {
197         if (fAppendPosition != null)
198             try {
199                 fBaseDocument.removePosition(RANGE_CATEGORY, fAppendPosition);
200             } catch (BadPositionCategoryException e) {
201                 // Ignore
202
}
203         try {
204             // TODO: Avoid an exception for a position that is past the end of the document
205
if (pos <= getDocument().getLength()) {
206                 Position p= new Position(pos);
207                 fBaseDocument.addPosition(RANGE_CATEGORY, p);
208                 fAppendPosition= p;
209             }
210         } catch (BadPositionCategoryException ex) {
211             // silently ignored
212
} catch (BadLocationException ex) {
213             // silently ignored
214
}
215     }
216
217     /**
218      * Returns the position that has been set with <code>setAppendPosition</code>.
219      * If <code>setAppendPosition</code> hasn't been called, the position after the last character
220      * of this range is returned. This method will return <code>null</code> if the position
221      * could not be registered with the document.
222      *
223      * @return a position where text can be legally inserted
224      */

225     public Position getAppendPosition() {
226         if (fAppendPosition == null) {
227             try {
228                 Position p= new Position(fBaseDocument.getLength());
229                 fBaseDocument.addPosition(RANGE_CATEGORY, p);
230                 fAppendPosition= p;
231                 return fAppendPosition;
232             } catch (BadPositionCategoryException ex) {
233                 // silently ignored
234
} catch (BadLocationException ex) {
235                 // silently ignored
236
}
237         }
238         return new Position(fBaseDocument.getLength());
239     }
240
241     /**
242      * Implementation based on <code>getID</code>.
243      * @param other the object to compare this <code>DocumentRangeNode</code> against.
244      * @return <code>true</code> if the <code>DocumentRangeNodes</code>are equal; <code>false</code> otherwise.
245      */

246     public boolean equals(Object JavaDoc other) {
247         if (other != null && other.getClass() == getClass()) {
248             DocumentRangeNode tn= (DocumentRangeNode) other;
249             return fTypeCode == tn.fTypeCode && fID.equals(tn.fID);
250         }
251         return super.equals(other);
252     }
253
254     /**
255      * Implementation based on <code>getID</code>.
256      * @return a hash code for this object.
257      */

258     public int hashCode() {
259         return fID.hashCode();
260     }
261
262     /*
263      * Find corresponding position
264      */

265     private Position findCorrespondingPosition(DocumentRangeNode otherParent, DocumentRangeNode child) {
266
267         // we try to find a predecessor of left Node which exists on the right side
268

269         if (child != null && fChildren != null) {
270             int ix= otherParent.fChildren.indexOf(child);
271             if (ix >= 0) {
272
273                 for (int i= ix - 1; i >= 0; i--) {
274                     DocumentRangeNode c1= (DocumentRangeNode) otherParent.fChildren.get(i);
275                     int i2= fChildren.indexOf(c1);
276                     if (i2 >= 0) {
277                         DocumentRangeNode c= (DocumentRangeNode) fChildren.get(i2);
278                         //System.out.println(" found corresponding: " + i2 + " " + c);
279
Position p= c.fRange;
280
281                         //try {
282
Position po= new Position(p.getOffset() + p.getLength() + 1, 0);
283                         //c.fBaseDocument.addPosition(RANGE_CATEGORY, po);
284
return po;
285                         //} catch (BadLocationException ex) {
286
//}
287
//break;
288
}
289                 }
290
291                 for (int i= ix; i < otherParent.fChildren.size(); i++) {
292                     DocumentRangeNode c1= (DocumentRangeNode) otherParent.fChildren.get(i);
293                     int i2= fChildren.indexOf(c1);
294                     if (i2 >= 0) {
295                         DocumentRangeNode c= (DocumentRangeNode) fChildren.get(i2);
296                         //System.out.println(" found corresponding: " + i2 + " " + c);
297
Position p= c.fRange;
298                         //try {
299
Position po= new Position(p.getOffset(), 0);
300                         //c.fBaseDocument.addPosition(RANGE_CATEGORY, po);
301
return po;
302                         //} catch (BadLocationException ex) {
303
//}
304
//break;
305
}
306                 }
307
308             }
309         }
310         return getAppendPosition();
311     }
312
313     private void add(String JavaDoc s, DocumentRangeNode parent, DocumentRangeNode child) {
314
315         Position p= findCorrespondingPosition(parent, child);
316         if (p != null) {
317             try {
318                 fBaseDocument.replace(p.getOffset(), p.getLength(), s);
319             } catch (BadLocationException ex) {
320                 CompareUIPlugin.log(ex);
321             }
322         }
323     }
324     
325     /* (non Javadoc)
326      * see IStreamContentAccessor.getContents
327      */

328     public InputStream JavaDoc getContents() {
329         String JavaDoc s;
330         try {
331             s= fBaseDocument.get(fRange.getOffset(), fRange.getLength());
332         } catch (BadLocationException ex) {
333             s= ""; //$NON-NLS-1$
334
}
335         return new ByteArrayInputStream JavaDoc(Utilities.getBytes(s, UTF_16));
336     }
337
338
339     /**
340      * If this node has a parent, return the editability of the parent.
341      * Otherwise return <code>true</code>. Subclasses may override.
342      * @see org.eclipse.compare.IEditableContent#isEditable()
343      */

344     public boolean isEditable() {
345         if (fParent != null)
346             return fParent.isEditable();
347         return true;
348     }
349         
350     /* (non Javadoc)
351      * see IEditableContent.replace
352      */

353     public ITypedElement replace(ITypedElement child, ITypedElement other) {
354
355         if (fParent == null) {
356             // TODO: I don't believe this code does anything useful but just in case
357
// I'm leaving it in but disabling it for the shared document case
358
// since all the subclasses that have been converted overrode the method anyway
359
DocumentRangeNode SRC= null;
360             String JavaDoc srcContents= ""; //$NON-NLS-1$
361

362             if (other != null) {
363                 src= (DocumentRangeNode) child;
364                 
365                 if (other instanceof IStreamContentAccessor) {
366                     try {
367                         srcContents= Utilities.readString((IStreamContentAccessor)other);
368                     } catch(CoreException ex) {
369                         // NeedWork
370
CompareUIPlugin.log(ex);
371                     }
372                 }
373             }
374     
375             if (child == null) // no destination: we have to add the contents into the parent
376
add(srcContents, null, src);
377         }
378         nodeChanged(this);
379         return child;
380     }
381     
382     /**
383      * Default implementation that calls {@link #internalSetContents(byte[])}
384      * and then {@link #nodeChanged(DocumentRangeNode)}. Subclasses
385      * may override but should then call {@link #nodeChanged(DocumentRangeNode)}
386      * after the contents have been set.
387      * @see org.eclipse.compare.IEditableContent#setContent(byte[])
388      */

389     public void setContent(byte[] content) {
390         internalSetContents(content);
391         nodeChanged(this);
392     }
393
394     /**
395      * Method that is invoked from {@link #setContent(byte[])}. By default,
396      * this method does nothing. Subclasses may override.
397      * @param content the new content
398      * @since 3.3
399      */

400     protected void internalSetContents(byte[] content) {
401         // By default, do nothing
402

403     }
404
405     /* (non-Javadoc)
406      * @see org.eclipse.compare.IStreamContentAccessor#getEncoding()
407      */

408     public String JavaDoc getCharset() {
409         return UTF_16;
410     }
411     
412     /**
413      * Method that should be invoked whenever the contents of this node are
414      * changed. the change is propagated to the parent if there is one.
415      * @param node the node that has changed.
416      * @since 3.3
417      */

418     protected void nodeChanged(DocumentRangeNode node) {
419         if (fParent != null)
420             fParent.nodeChanged(node);
421     }
422     
423     /**
424      * Implement {@link IAdaptable#getAdapter(Class)} in order to provide
425      * an {@link ISharedDocumentAdapter} that provides the proper look up key based
426      * on the input from which this structure node was created. The proper
427      * shared document adapter is obtained by calling {@link #getAdapter(Class)}
428      * on this node's parent if there is one.
429      * @param adapter the adapter class to look up
430      * @return the object adapted to the given class or <code>null</code>
431      * @see IAdaptable#getAdapter(Class)
432      * @since 3.3
433      */

434     public Object JavaDoc getAdapter(Class JavaDoc adapter) {
435         if (adapter == ISharedDocumentAdapter.class && fParent != null)
436             return fParent.getAdapter(adapter);
437         
438         return Platform.getAdapterManager().getAdapter(this, adapter);
439     }
440
441     /* (non-Javadoc)
442      * @see org.eclipse.compare.IEditableContentExtension#isReadOnly()
443      */

444     public boolean isReadOnly() {
445         if (fParent != null)
446             return fParent.isReadOnly();
447         return false;
448     }
449
450     /* (non-Javadoc)
451      * @see org.eclipse.compare.IEditableContentExtension#validateEdit(org.eclipse.swt.widgets.Shell)
452      */

453     public IStatus validateEdit(Shell shell) {
454         if (fParent != null)
455             return fParent.validateEdit(shell);
456         return Status.OK_STATUS;
457     }
458
459     /**
460      * Return the parent of this node or <code>null</code>
461      * if the node doesn't have a parent or the parent is not known.
462      * @return the parent of this node or <code>null</code>
463      */

464     public Object JavaDoc getParentNode() {
465         return fParent;
466     }
467 }
468
469
Popular Tags