KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > pde > internal > core > text > plugin > XMLTextChangeListener


1 /*******************************************************************************
2  * Copyright (c) 2005, 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.pde.internal.core.text.plugin;
12
13 import java.util.ArrayList JavaDoc;
14
15 import org.eclipse.jface.text.BadLocationException;
16 import org.eclipse.jface.text.IDocument;
17 import org.eclipse.jface.text.IRegion;
18 import org.eclipse.jface.text.Region;
19 import org.eclipse.jface.text.TextUtilities;
20 import org.eclipse.pde.core.IModelChangedEvent;
21 import org.eclipse.pde.internal.core.text.AbstractTextChangeListener;
22 import org.eclipse.pde.internal.core.text.IDocumentAttribute;
23 import org.eclipse.pde.internal.core.text.IDocumentNode;
24 import org.eclipse.pde.internal.core.text.IDocumentTextNode;
25 import org.eclipse.pde.internal.core.util.PDEXMLHelper;
26 import org.eclipse.pde.internal.core.util.PropertiesUtil;
27 import org.eclipse.text.edits.DeleteEdit;
28 import org.eclipse.text.edits.InsertEdit;
29 import org.eclipse.text.edits.MoveSourceEdit;
30 import org.eclipse.text.edits.MoveTargetEdit;
31 import org.eclipse.text.edits.MultiTextEdit;
32 import org.eclipse.text.edits.ReplaceEdit;
33 import org.eclipse.text.edits.TextEdit;
34
35 public class XMLTextChangeListener extends AbstractTextChangeListener {
36     
37     private ArrayList JavaDoc fOperationList = new ArrayList JavaDoc();
38     
39     public XMLTextChangeListener(IDocument document) {
40         super(document);
41     }
42
43     public TextEdit[] getTextOperations() {
44         if (fOperationList.size() == 0)
45             return new TextEdit[0];
46         
47         MultiTextEdit edit = new MultiTextEdit();
48         try {
49             if (PropertiesUtil.isNewlineNeeded(fDocument))
50                 insert(edit, new InsertEdit(fDocument.getLength(), TextUtilities.getDefaultLineDelimiter(fDocument)));
51         } catch (BadLocationException e) {
52         }
53         Object JavaDoc[] operations = fOperationList.toArray();
54         for (int i = 0; i < operations.length; i++)
55             insert(edit, (TextEdit)operations[i]);
56         
57         return new TextEdit[] {edit};
58     }
59     
60     protected static void insert(TextEdit parent, TextEdit edit) {
61         if (!parent.hasChildren()) {
62             parent.addChild(edit);
63             if (edit instanceof MoveSourceEdit) {
64                 parent.addChild(((MoveSourceEdit)edit).getTargetEdit());
65             }
66             return;
67         }
68         TextEdit[] children= parent.getChildren();
69         // First dive down to find the right parent.
70
for (int i= 0; i < children.length; i++) {
71             TextEdit child= children[i];
72             if (covers(child, edit)) {
73                 insert(child, edit);
74                 return;
75             }
76         }
77         // We have the right parent. Now check if some of the children have to
78
// be moved under the new edit since it is covering it.
79
for (int i= children.length - 1; i >= 0; i--) {
80             TextEdit child= children[i];
81             if (covers(edit, child)) {
82                 parent.removeChild(i);
83                 edit.addChild(child);
84             }
85         }
86         parent.addChild(edit);
87         if (edit instanceof MoveSourceEdit) {
88             parent.addChild(((MoveSourceEdit)edit).getTargetEdit());
89         }
90     }
91     
92     protected static boolean covers(TextEdit thisEdit, TextEdit otherEdit) {
93         if (thisEdit.getLength() == 0) // an insertion point can't cover anything
94
return false;
95         
96         int thisOffset= thisEdit.getOffset();
97         int thisEnd= thisEdit.getExclusiveEnd();
98         if (otherEdit.getLength() == 0) {
99             int otherOffset= otherEdit.getOffset();
100             return thisOffset < otherOffset && otherOffset < thisEnd;
101         }
102         int otherOffset= otherEdit.getOffset();
103         int otherEnd= otherEdit.getExclusiveEnd();
104         return thisOffset <= otherOffset && otherEnd <= thisEnd;
105     }
106     
107     
108     protected void deleteNode(IDocumentNode node) {
109         // delete previous op on this node, if any
110
TextEdit old = (TextEdit)fOperationTable.get(node);
111         if (old != null) {
112             Object JavaDoc op = fOperationTable.remove(node);
113             fOperationList.remove(op);
114         }
115         
116         // if node has an offset, delete it
117
if (node.getOffset() > -1) {
118             // Create a delete op for this node
119
TextEdit op = getDeleteNodeOperation(node);
120             fOperationTable.put(node, op);
121             fOperationList.add(op);
122         } else if (old == null){
123             // No previous op on this non-offset node, just rewrite highest ancestor with an offset
124
insertNode(node);
125         }
126     }
127
128     protected void insertNode(IDocumentNode node) {
129         TextEdit op = null;
130         node = getHighestNodeToBeWritten(node);
131         if (node.getParentNode() == null) {
132             // Only add the insertion edit operation if the node is a root node
133
// Otherwise the insertion edit operation will specify to add the
134
// node to the beginning of the file and corrupt it
135
// See Bugs 163161, 166520
136
if (node.isRoot()) {
137                 op = new InsertEdit(0, node.write(true));
138             }
139         } else {
140             if (node.getOffset() > -1) {
141                 // this is an element that was of the form <element/>
142
// it now needs to be broken up into <element><new/></element>
143
op = new ReplaceEdit(node.getOffset(), node.getLength(), node.write(false));
144             } else {
145                 // try to insert after last sibling that has an offset
146
op = insertAfterSibling(node);
147                 // insert as first child of its parent
148
if (op == null) {
149                     op = insertAsFirstChild(node);
150                 }
151             }
152         }
153         fOperationTable.put(node, op);
154         fOperationList.add(op);
155     }
156
157     private InsertEdit insertAfterSibling(IDocumentNode node) {
158         IDocumentNode sibling = node.getPreviousSibling();
159         for (;;) {
160             if (sibling == null)
161                 break;
162             if (sibling.getOffset() > -1) {
163                 node.setLineIndent(sibling.getLineIndent());
164                 return new InsertEdit(sibling.getOffset() + sibling.getLength(), fSep + node.write(true));
165             }
166             sibling = sibling.getPreviousSibling();
167         }
168         return null;
169     }
170     
171     private InsertEdit insertAsFirstChild(IDocumentNode node) {
172         int offset = node.getParentNode().getOffset();
173         int length = getNextPosition(fDocument, offset, '>');
174         node.setLineIndent(node.getParentNode().getLineIndent() + 3);
175         return new InsertEdit(offset+ length + 1, fSep + node.write(true));
176     }
177     
178
179     protected void modifyNode(IDocumentNode node, IModelChangedEvent event) {
180         IDocumentNode oldNode = (IDocumentNode)event.getOldValue();
181         IDocumentNode newNode = (IDocumentNode)event.getNewValue();
182         
183         IDocumentNode node1 = (oldNode.getPreviousSibling() == null || oldNode.equals(newNode.getPreviousSibling())) ? oldNode : newNode;
184         IDocumentNode node2 = node1.equals(oldNode) ? newNode : oldNode;
185         
186         if (node1.getOffset() < 0 && node2.getOffset() < 0) {
187             TextEdit op = (TextEdit)fOperationTable.get(node1);
188             if (op == null) {
189                 // node 1 has no rule, so node 2 has no rule, therefore rewrite parent/ancestor
190
insertNode(node);
191             }
192         } else if (node1.getOffset() > -1 && node2.getOffset() > -1) {
193             // both nodes have offsets, so create a move target/source combo operation
194
IRegion region = getMoveRegion(node1);
195             MoveSourceEdit source = new MoveSourceEdit(region.getOffset(), region.getLength());
196             region = getMoveRegion(node2);
197             source.setTargetEdit(new MoveTargetEdit(region.getOffset()));
198             fOperationTable.put(node, source);
199             fOperationList.add(source);
200         } else {
201             // one node with offset, the other without offset. Delete/reinsert the one without offset
202
insertNode((node1.getOffset() < 0) ? node1 : node2);
203         }
204     }
205     
206     private IRegion getMoveRegion(IDocumentNode node) {
207         int offset = node.getOffset();
208         int length = node.getLength();
209         int i = 1;
210         try {
211             for (;;i++) {
212                 char ch = fDocument.get(offset - i, 1).toCharArray()[0];
213                 if (!Character.isWhitespace(ch)) {
214                     i -= 1;
215                     break;
216                 }
217             }
218         } catch (BadLocationException e) {
219         }
220         return new Region(offset - i, length + i);
221     }
222
223     protected void addAttributeOperation(IDocumentAttribute attr, IModelChangedEvent event) {
224         int offset = attr.getValueOffset();
225         Object JavaDoc newValue = event.getNewValue();
226         Object JavaDoc changedObject = attr;
227         TextEdit op = null;
228         if (offset > -1) {
229             if (newValue == null || newValue.toString().length() == 0) {
230                 int length = attr.getValueOffset() + attr.getValueLength() + 1 - attr.getNameOffset();
231                 op = getAttributeDeleteEditOperation(attr.getNameOffset(), length);
232             } else {
233                 op = new ReplaceEdit(offset, attr.getValueLength(), getWritableString(event.getNewValue().toString()));
234             }
235         }
236                 
237         if (op == null) {
238             IDocumentNode node = attr.getEnclosingElement();
239             if (node.getOffset() > -1) {
240                 changedObject = node;
241                 int len = getNextPosition(fDocument, node.getOffset(), '>');
242                 op = new ReplaceEdit(node.getOffset(), len + 1, node.writeShallow(shouldTerminateElement(fDocument, node.getOffset() + len)));
243             } else {
244                 insertNode(node);
245                 return;
246             }
247         }
248         fOperationTable.put(changedObject, op);
249         fOperationList.add(op);
250     }
251     
252     protected void addElementContentOperation(IDocumentTextNode textNode) {
253         TextEdit op = null;
254         Object JavaDoc changedObject = textNode;
255         if (textNode.getOffset() > -1) {
256             String JavaDoc newText = getWritableString(textNode.getText());
257             op = new ReplaceEdit(textNode.getOffset(), textNode.getLength(), newText);
258         } else {
259             IDocumentNode parent = textNode.getEnclosingElement();
260             if (parent.getOffset() > -1) {
261                 try {
262                     String JavaDoc endChars = fDocument.get(parent.getOffset() + parent.getLength() - 2, 2);
263                     if ("/>".equals(endChars)) { //$NON-NLS-1$
264
// parent element is of the form <element/>, rewrite it
265
insertNode(parent);
266                         return;
267                     }
268                 } catch (BadLocationException e) {
269                 }
270                 // add text as first child
271
changedObject = parent;
272                 StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(fSep);
273                 for (int i = 0; i < parent.getLineIndent(); i++)
274                     buffer.append(" "); //$NON-NLS-1$
275
buffer.append(" " + getWritableString(textNode.getText())); //$NON-NLS-1$
276
int offset = parent.getOffset();
277                 int length = getNextPosition(fDocument, offset, '>');
278                 op = new InsertEdit(offset+ length + 1, buffer.toString());
279             } else {
280                 insertNode(parent);
281                 return;
282             }
283         }
284         fOperationTable.put(changedObject, op);
285         fOperationList.add(op);
286     }
287
288     private boolean shouldTerminateElement(IDocument doc, int offset) {
289         try {
290             return doc.get(offset-1, 1).toCharArray()[0] == '/';
291         } catch (BadLocationException e) {
292         }
293         return false;
294     }
295     
296     private int getNextPosition(IDocument doc, int offset, char ch) {
297         int i = 0;
298         try {
299             for (i = 0; i + offset < doc.getLength() ;i++) {
300                 if (ch == doc.get(offset + i, 1).toCharArray()[0])
301                     break;
302             }
303         } catch (BadLocationException e) {
304         }
305         return i;
306     }
307     
308     private DeleteEdit getAttributeDeleteEditOperation(int offset, int length) {
309         try {
310             for (;;) {
311                 char ch = fDocument.get(offset + length, 1).toCharArray()[0];
312                 if (!Character.isWhitespace(ch)) {
313                     break;
314                 }
315                 
316                 length += 1;
317             }
318         } catch (BadLocationException e) {
319         }
320         return new DeleteEdit(offset, length);
321     }
322     
323
324     private DeleteEdit getDeleteNodeOperation(IDocumentNode node) {
325         int offset = node.getOffset();
326         int length = node.getLength();
327         try {
328             // node starts on this line:
329
int startLine = fDocument.getLineOfOffset(offset);
330             // 1st char on startLine has this offset:
331
int startLineOffset = fDocument.getLineOffset(startLine);
332             // hunt down 1st whitespace/start of line with startOffset:
333
int startOffset;
334             // loop backwards to the beginning of the line, stop if we find non-whitespace
335
for (startOffset = offset - 1; startOffset >= startLineOffset; startOffset -= 1)
336                 if (!Character.isWhitespace(fDocument.getChar(startOffset)))
337                     break;
338             
339             // move forward one (loop stopped after reaching too far)
340
startOffset += 1;
341             
342             // node ends on this line:
343
int endLine = fDocument.getLineOfOffset(offset + length);
344             // length of last line's delimiter:
345
int endLineDelimLength = fDocument.getLineDelimiter(endLine).length();
346             // hunt last whitespace/end of line with extraLength:
347
int extraLength = length;
348             while (true) {
349                 extraLength += 1;
350                 if (!Character.isWhitespace(fDocument.getChar(offset + extraLength))) {
351                     // found non-white space, move back one
352
extraLength -= 1;
353                     break;
354                 }
355                 if (fDocument.getLineOfOffset(offset + extraLength) > endLine) {
356                     // don't want to touch the lineDelimeters
357
extraLength -= endLineDelimLength;
358                     break;
359                 }
360             }
361             
362             // if we reached start of line, remove newline
363
if (startOffset == startLineOffset)
364                 startOffset -= fDocument.getLineDelimiter(startLine).length();
365             
366             // add difference of new offset
367
length = extraLength + (offset - startOffset);
368             offset = startOffset;
369 // printDeletionRange(offset, length);
370
} catch (BadLocationException e) {
371         }
372         return new DeleteEdit(offset, length);
373     }
374
375     protected void printDeletionRange(int offset, int length) {
376         try {
377             // newlines printed as \n
378
// carriage returns printed as \r
379
// tabs printed as \t
380
// spaces printed as *
381
String JavaDoc string = fDocument.get(offset, length);
382             StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
383             for (int i = 0; i < string.length(); i++) {
384                 char c = string.charAt(i);
385                 if (c == '\n')
386                     buffer.append("\\n"); //$NON-NLS-1$
387
else if (c == '\r')
388                     buffer.append("\\r"); //$NON-NLS-1$
389
else if (c == '\t')
390                     buffer.append("\\t"); //$NON-NLS-1$
391
else if (c == ' ')
392                     buffer.append('*');
393                 else
394                     buffer.append(c);
395             }
396             System.out.println(buffer.toString());
397         } catch (BadLocationException e) {
398         }
399     }
400     
401     private IDocumentNode getHighestNodeToBeWritten(IDocumentNode node) {
402         IDocumentNode parent = node.getParentNode();
403         if (parent == null)
404             return node;
405         if (parent.getOffset() > -1) {
406             try {
407                 String JavaDoc endChars = fDocument.get(parent.getOffset() + parent.getLength() - 2, 2);
408                 return ("/>".equals(endChars)) ? parent : node; //$NON-NLS-1$
409
} catch (BadLocationException e) {
410                 return node;
411             }
412             
413         }
414         return getHighestNodeToBeWritten(parent);
415     }
416     
417     private String JavaDoc getWritableString(String JavaDoc source) {
418         return PDEXMLHelper.getWritableString(source);
419     }
420
421     public void modelChanged(IModelChangedEvent event) {
422         Object JavaDoc[] objects = event.getChangedObjects();
423         if (objects == null)
424             return;
425         for (int i = 0; i < objects.length; i++) {
426             if (!(objects[i] instanceof IDocumentNode))
427                 continue;
428             IDocumentNode node = (IDocumentNode)objects[i];
429             Object JavaDoc op = fOperationTable.remove(node);
430             fOperationList.remove(op);
431             switch (event.getChangeType()) {
432                 case IModelChangedEvent.REMOVE:
433                     deleteNode(node);
434                     break;
435                 case IModelChangedEvent.INSERT:
436                     insertNode(node);
437                     break;
438                 case IModelChangedEvent.CHANGE:
439                     IDocumentAttribute attr = node.getDocumentAttribute(event.getChangedProperty());
440                     if (attr != null) {
441                         addAttributeOperation(attr, event);
442                     } else {
443                         if (event.getOldValue() instanceof IDocumentTextNode) {
444                             addElementContentOperation((IDocumentTextNode)event.getOldValue());
445                         } else if (event.getOldValue() instanceof IDocumentNode && event.getNewValue() instanceof IDocumentNode){
446                             // swapping of nodes
447
modifyNode(node, event);
448                         }
449                     }
450             }
451         }
452     }
453 }
454
Popular Tags