001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.xml.xdm.diff;
043:
044: import java.util.ArrayList;
045: import java.util.Collections;
046: import java.util.HashMap;
047: import java.util.Iterator;
048: import java.util.List;
049: import java.util.Map;
050: import org.netbeans.modules.xml.spi.dom.NodeListImpl;
051: import org.netbeans.modules.xml.xam.dom.ElementIdentity;
052: import org.netbeans.modules.xml.xdm.nodes.Document;
053: import org.netbeans.modules.xml.xdm.nodes.Element;
054: import org.netbeans.modules.xml.xdm.nodes.Node;
055: import org.netbeans.modules.xml.xdm.nodes.NodeImpl;
056: import org.netbeans.modules.xml.xdm.nodes.Text;
057: import org.netbeans.modules.xml.xdm.nodes.Token;
058: import org.netbeans.modules.xml.xdm.visitor.PathFromRootVisitor;
059: import org.netbeans.modules.xml.xdm.nodes.Attribute;
060: import org.w3c.dom.DOMException;
061: import org.w3c.dom.NamedNodeMap;
062: import org.w3c.dom.NodeList;
063:
064: /**
065: * This class is used by XDMTreeDiff (Both Sync and Undo/Redo) to find difference
066: * between 2 XML and returns List<DiffEvent>
067: *
068: * @author Ayub Khan
069: */
070: public class DiffFinder {
071: protected DiffFinder() {
072: }
073:
074: public DiffFinder(ElementIdentity eID) {
075: this .eID = eID;
076: }
077:
078: /*
079: * findDiff between 2 XML documents
080: *
081: * @param d1 - an XML document
082: * @param d2 - an XML document
083: */
084: public List<Difference> findDiff(Document d1, Document d2) {
085: this .oldDoc = d1;
086: this .newDoc = d2;
087:
088: List<Difference> deList = new ArrayList<Difference>();
089: List<Change.Type> changes = checkChange(d1, d2, 0, 0);
090: if (changes.size() > 0) {
091: markChange(new ArrayList<Node>(), d1, d2, 0, 0, changes,
092: new ArrayList<Node>(), deList);
093: }
094:
095: List<Node> ancestors1 = new ArrayList<Node>();
096: List<Node> ancestors2 = new ArrayList<Node>();
097: ancestors1.add(oldDoc);
098: ancestors2.add(newDoc);
099: compareChildren(ancestors1, ancestors2, deList);
100:
101: //remove any (sequential) position change events
102: if (deList.size() > 0) {
103: deList = findOptimized(deList);
104: }
105:
106: return deList;
107: }
108:
109: protected void compareChildren(List<Node> ancestors1,
110: List<Node> ancestors2, List<Difference> deList) {
111: Node parent1 = ancestors1.get(0);
112: Node parent2 = ancestors2.get(0);
113: NodeList p1ChildNodes = parent1.getChildNodes();
114: NodeList p2ChildNodes = parent2.getChildNodes();
115:
116: if (p1ChildNodes == NodeListImpl.EMPTY
117: && p2ChildNodes == NodeListImpl.EMPTY)
118: return;
119:
120: cInfo1 = new ChildInfo(parent1);
121: cInfo2 = new ChildInfo(parent2);
122: List<Node> p2ChildList = getChildList(parent2);
123:
124: List<int[]> pairList = new ArrayList<int[]>();
125: List<Node> foundList = new ArrayList<Node>();
126: if (p1ChildNodes != null) {
127: int length = p1ChildNodes.getLength();
128: for (int i = 0; i < length; i++) {
129: Node child = (Node) p1ChildNodes.item(i);
130: Node foundNode = null;
131: if (child instanceof Element) {
132: foundNode = findMatch((Element) child, p2ChildList,
133: parent1);
134: } else if (child instanceof Text) {
135: foundNode = findMatch((Text) child, p2ChildList);
136: }
137: if (foundNode == null) {
138: List<Node> path1 = copy(ancestors1);
139: List<Node> path2 = copy(ancestors2);
140: markDelete(path1, child, i, cInfo1
141: .getSiblingBefore(child), path2, deList);
142: } else {
143: cInfo2.addMatchNode(foundNode, child);
144: foundList.add(foundNode);
145: p2ChildList.remove(foundNode);
146: int[] pair = new int[] { i,
147: cInfo2.getIndex(foundNode) };
148: pairList.add(pair);
149: }
150: }
151:
152: for (int i = 0; i < p2ChildList.size(); i++) {
153: Node child = (Node) p2ChildList.get(i);
154: SiblingInfo sInfo = new SiblingInfo(child,
155: p2ChildNodes, foundList);
156: Node originalSiblingBefore = null;
157: if (sInfo.getNode() != null) {
158: originalSiblingBefore = cInfo2.getMatchNode(sInfo
159: .getNode());
160: }
161:
162: int absolutePos = cInfo2.getIndex(child);
163: markAdd(copy(ancestors1), child, absolutePos, sInfo
164: .getPosition(), originalSiblingBefore,
165: copy(ancestors2), deList);
166: }
167:
168: //sort pairs
169: Collections.sort(pairList, new PairComparator());
170:
171: for (int i = 0; i < pairList.size(); i++) {
172: int[] pair = pairList.get(i);
173: int px1 = pair[0];
174: int px2 = pair[1];
175: Node p1 = (Node) parent1.getChildNodes().item(px1);
176: Node p2 = (Node) parent2.getChildNodes().item(px2);
177:
178: int tp1 = cInfo1.getPosition(p1);
179: int tp2 = cInfo2.getPosition(p2);
180:
181: //check node content and position change
182: List<Change.Type> changes = checkChange(p1, p2, tp1,
183: tp2);
184: if (changes.size() > 0) {
185: markChange(copy(ancestors1), p1, p2, px1, px2,
186: changes, copy(ancestors2), deList);
187: }
188: }
189: //we are done with the maps, clear
190: cInfo1 = null;
191: cInfo2 = null;
192:
193: for (int i = 0; i < pairList.size(); i++) {
194: int[] pair = pairList.get(i);
195: int px1 = pair[0];
196: int px2 = pair[1];
197: Node p1 = (Node) parent1.getChildNodes().item(px1);
198: Node p2 = (Node) parent2.getChildNodes().item(px2);
199: if (p1 instanceof Element) {
200: ancestors1.add(0, p1);
201: ancestors2.add(0, p2);
202: //Since p1 and p2 are similar nodes, now compare their childrens
203: compareChildren(ancestors1, ancestors2, deList);
204: ancestors1.remove(0);
205: ancestors2.remove(0);
206: }
207: }
208: }
209: }
210:
211: private List<Node> copy(List<Node> l) {
212: return new ArrayList<Node>(l);
213: }
214:
215: public class PairComparator implements java.util.Comparator {
216: public int compare(Object o1, Object o2) {
217: int[] pair1 = (int[]) o1;
218: int[] pair2 = (int[]) o2;
219: int px2_1 = pair1[1];
220: int px2_2 = pair2[1];
221: if (px2_1 < px2_2)
222: return -1;
223: else if (px2_1 > px2_2)
224: return +1;
225: else
226: return 0;
227: }
228: }
229:
230: public static NodeInfo.NodeType getNodeType(final Node child)
231: throws DOMException {
232: NodeInfo.NodeType nodeType = NodeInfo.NodeType.ELEMENT;
233: if (child instanceof Text)
234: if (isWhiteSpaceOnly((Text) child))
235: nodeType = NodeInfo.NodeType.WHITE_SPACE;
236: else
237: nodeType = NodeInfo.NodeType.TEXT;
238: else if (child instanceof Attribute)
239: nodeType = NodeInfo.NodeType.ATTRIBUTE;
240: return nodeType;
241: }
242:
243: protected List<Change.Type> checkChange(final Node n1,
244: final Node n2, final int p1, final int p2) {
245: List<Change.Type> changes = checkChange(n1, n2);
246: if (p1 != p2)
247: changes.add(Change.Type.POSITION);
248: return changes;
249: }
250:
251: protected List<Change.Type> checkChange(final Node p1, final Node p2) {
252: List<Change.Type> changes = new ArrayList<Change.Type>();
253: if (!checkTokensEqual(p1, p2)) {
254: changes.add(Change.Type.TOKEN);
255: }
256: if (p1 instanceof Element && p2 instanceof Element) {
257: if (!checkAttributesEqual((Element) p1, (Element) p2)) {
258: changes.add(Change.Type.ATTRIBUTE);
259: }
260: } else if (!p1.getClass().isAssignableFrom(p2.getClass())
261: || !p2.getClass().isAssignableFrom(p1.getClass())) {
262: changes.add(Change.Type.UNKNOWN);
263: }
264: return changes;
265: }
266:
267: protected boolean checkTokensEqual(final Node p1, final Node p2) {
268: if (p1 instanceof NodeImpl && p2 instanceof NodeImpl) {
269: List<Token> t1List = ((NodeImpl) p1).getTokens();
270: List<Token> t2List = ((NodeImpl) p2).getTokens();
271: return compareTokenEquals(t1List, t2List);
272: }
273: return false;
274: }
275:
276: protected boolean checkAttributesEqual(final Element p1,
277: final Element p2) {
278: if (p1 == null || p2 == null)
279: return false;
280: NamedNodeMap nm1 = p1.getAttributes();
281: NamedNodeMap nm2 = p2.getAttributes();
282: if (nm1.getLength() != nm2.getLength())
283: return false;
284:
285: for (int i = 0; i < nm1.getLength(); i++) {
286: Node attr2 = (Node) nm2.getNamedItem(nm1.item(i)
287: .getNodeName());
288: if (attr2 == null)
289: return false;
290: if (nm2.item(i) != attr2)
291: return false;
292: List<Token> t1List = ((NodeImpl) nm1.item(i)).getTokens();
293: List<Token> t2List = ((NodeImpl) attr2).getTokens();
294: boolean status = compareTokenEquals(t1List, t2List);
295: if (!status)
296: return false;
297: }
298: return true;
299: }
300:
301: protected boolean compareTokenEquals(List<Token> t1List,
302: List<Token> t2List) {
303: if (t1List.size() != t2List.size())
304: return false;
305:
306: //compare element tokens
307: for (int i = 0; i < t1List.size(); i++) {
308: Token t1 = t1List.get(i);
309: Token t2 = t2List.get(i);
310: if (t1.getValue().intern() != t2.getValue().intern())
311: return false;
312: }
313:
314: return true;
315: }
316:
317: protected Node findMatch(Element child, List<Node> childNodes,
318: org.w3c.dom.Node parent1) {
319: for (Node otherChild : childNodes) {
320: if (otherChild instanceof Element) {
321: if (((DefaultElementIdentity) eID).compareElement(
322: child, (Element) otherChild, parent1,
323: this .oldDoc, this .newDoc))
324: return otherChild;
325: }
326: }
327: return null;
328: }
329:
330: protected Node findMatch(Text child, List<Node> childNodes) {
331: for (Node otherChild : childNodes) {
332: if (otherChild instanceof Text
333: && compareText(child, (Text) otherChild))
334: return otherChild;
335: }
336: return null;
337: }
338:
339: protected Difference createAddEvent(List<Node> ancestors1, Node n,
340: int absolutePos, List<Node> ancestors2) {
341: assert n != null : "add node null";
342: return new Add(getNodeType(n), ancestors1, ancestors2, n,
343: absolutePos);
344: }
345:
346: protected Difference createDeleteEvent(List<Node> ancestors1,
347: Node n, int pos, List<Node> ancestors2) {
348: assert n != null : "delete node null";
349: return new Delete(getNodeType(n), ancestors1, ancestors2, n,
350: pos);
351: }
352:
353: protected Difference createChangeEvent(List<Node> ancestors1,
354: Node n1, Node n2, int n1Pos, int n2Pos,
355: List<Change.Type> changes, List<Node> ancestors2) {
356: assert n1 != null && n2 != null : "change nodes null";
357: if (n1 instanceof Element)
358: assert n1.getLocalName().equals(n2.getLocalName());
359: Change de = new Change(getNodeType(n1), ancestors1, ancestors2,
360: n1, n2, n1Pos, n2Pos, changes);
361:
362: if (de.getNewNodeInfo().getNewAncestors().size() > 0) {
363: assert de.getNewNodeInfo().getNode().getId() != de
364: .getNewNodeInfo().getNewAncestors().get(0).getId();
365: }
366:
367: return de;
368: }
369:
370: protected void markAdd(List<Node> ancestors1, Node n,
371: int absolutePos, int posFromSibling, Node siblingBefore,
372: List<Node> ancestors2, List<Difference> deList) {
373: deList.add(createAddEvent(ancestors1, n, absolutePos,
374: ancestors2));
375: }
376:
377: protected void markDelete(List<Node> ancestors1, Node n, int pos,
378: Node siblingBefore, List<Node> ancestors2,
379: List<Difference> deList) {
380: deList.add(createDeleteEvent(ancestors1, n, pos, ancestors2));
381: }
382:
383: protected void markChange(List<Node> ancestors1, Node n1, Node n2,
384: int n1Pos, int n2Pos, List<Change.Type> changes,
385: List<Node> ancestors2, List<Difference> deList) {
386: deList.add(createChangeEvent(ancestors1, n1, n2, n1Pos, n2Pos,
387: changes, ancestors2));
388: }
389:
390: public static List<Difference> filterWhitespace(
391: List<Difference> deList) {
392: List<Difference> returnList = new ArrayList<Difference>();
393: for (Difference de : deList) {
394: NodeInfo.NodeType nodeType = de.getNodeType();
395: if (de.getNodeType() != NodeInfo.NodeType.WHITE_SPACE)
396: returnList.add(de);
397: }
398: return returnList;
399: }
400:
401: public static List<Node> getPathToRoot(Node node) {
402: assert node.getOwnerDocument() != null;
403: List<Node> pathToRoot = new PathFromRootVisitor().findPath(node
404: .getOwnerDocument(), node);
405: //assert pathToRoot != null && pathToRoot.size() > 0;
406: return pathToRoot;
407: }
408:
409: public List<Difference> findOptimized(List<Difference> deList) {
410: if (deList == null || deList.isEmpty())
411: return Collections.emptyList();
412: List<Difference> optimizedList = new ArrayList<Difference>();
413: HashMap<Node, List<Difference>> deMap = new HashMap<Node, List<Difference>>();
414: List<Node> parentList = new ArrayList<Node>();
415: for (Difference de : deList) {
416: Node parent = de.getOldNodeInfo().getParent();
417: List<Difference> childDeList = deMap.get(parent);
418: if (childDeList == null) {
419: parentList.add(parent);
420: childDeList = new ArrayList<Difference>();
421: deMap.put(parent, childDeList);
422: }
423: childDeList.add(de);
424: }
425:
426: for (Node parent : parentList) {
427: List<Difference> childDeList = deMap.get(parent);
428: Collections.sort(childDeList, new PairComparator2());
429: HashMap<Difference, Integer> oldPosMap = new HashMap<Difference, Integer>();
430: for (int i = 0; i < childDeList.size(); i++) {
431: Difference de = childDeList.get(i);
432: modifyPositionFromIndex(i + 1, childDeList, de,
433: oldPosMap);
434: }
435:
436: //Now remove position change events
437: for (int i = 0; i < childDeList.size(); i++) {
438: Difference de = childDeList.get(i);
439: Integer oldPos = oldPosMap.get(de);
440: int px1 = oldPos != null ? oldPos.intValue() : de
441: .getOldNodeInfo().getPosition();
442: int px2 = de.getNewNodeInfo().getPosition();
443: if (de instanceof Change
444: && ((Change) de).isPositionChanged()) {
445: if (px1 == px2 && px1 != -1)
446: ((Change) de).setPositionChanged(false);//reset position change
447: if (((Change) de).isValid())
448: optimizedList.add(de);
449: } else
450: optimizedList.add(de);
451: }
452: }
453: return optimizedList;
454: }
455:
456: public class PairComparator2 implements java.util.Comparator {
457: public int compare(Object o1, Object o2) {
458: Difference de1 = (Difference) o1;
459: Difference de2 = (Difference) o2;
460: int px2_1 = de1.getNewNodeInfo().getPosition();
461: int px2_2 = de2.getNewNodeInfo().getPosition();
462: if (px2_1 < px2_2)
463: return -1;
464: else if (px2_1 > px2_2)
465: return +1;
466: else
467: return 0;
468: }
469: }
470:
471: protected void modifyPositionFromIndex(int index,
472: List<Difference> childDeList, Difference de,
473: HashMap<Difference, Integer> oldPosMap) {
474: // int x = de.getOldNodeInfo().getPosition();
475: Integer oldx = oldPosMap.get(de);
476: int x = oldx != null ? oldx.intValue() : de.getOldNodeInfo()
477: .getPosition();
478: int y = de.getNewNodeInfo().getPosition();
479: for (int i = index; i < childDeList.size(); i++) {
480: Difference cde = childDeList.get(i);
481: Integer oldp1 = oldPosMap.get(cde);
482: int p1 = oldp1 != null ? oldp1.intValue() : cde
483: .getOldNodeInfo().getPosition();
484: int p2 = cde.getNewNodeInfo().getPosition();
485:
486: if (de instanceof Add && x == -1 && y >= 0 && y <= p1) {
487: oldPosMap.put(cde, p1 + 1);
488: } else if (de instanceof Delete && x >= 0 && y == -1
489: && x <= p1) {
490: oldPosMap.put(cde, p1 - 1);
491: } else if (de instanceof Change && x != y) {
492: if (x > p1 && y <= p1)
493: oldPosMap.put(cde, p1 + 1);
494: else if (y > p1 && x <= p1)
495: oldPosMap.put(cde, p1 - 1);
496: }
497: }
498: }
499:
500: public static boolean isPossibleWhiteSpace(String text) {
501: return text.length() > 0
502: && Character.isWhitespace(text.charAt(0))
503: && Character.isWhitespace(text
504: .charAt(text.length() - 1));
505: }
506:
507: public static boolean isWhiteSpaceOnly(Text txt) {
508: String tn = "";
509: if (((NodeImpl) txt).getTokens().size() == 1)
510: tn = ((NodeImpl) txt).getTokens().get(0).getValue();
511: else
512: tn = txt.getNodeValue();
513: return isPossibleWhiteSpace(tn) && tn.trim().length() == 0;
514: }
515:
516: protected boolean compareText(Text n1, Text n2) {
517: if (isWhiteSpaceOnly(n1) && isWhiteSpaceOnly(n2))
518: return compareWhiteSpaces(n1, n2);
519: else
520: return compareTextByValue(n1, n2);
521: }
522:
523: protected boolean compareWhiteSpaces(Text n1, Text n2) {
524: Node nodeBefore1 = cInfo1.getSiblingBefore(n1);
525: Node nodeBefore2 = cInfo2.getSiblingBefore(n2);
526: boolean siblingCompare = false;
527: if (nodeBefore1 == null && nodeBefore2 == null)
528: siblingCompare = true;
529: else if (nodeBefore1 instanceof Element
530: && nodeBefore2 instanceof Element) {
531: if (cInfo2.getMatchNode(nodeBefore2) == nodeBefore1
532: || eID.compareElement((Element) nodeBefore1,
533: (Element) nodeBefore2, this .oldDoc,
534: this .newDoc))
535: siblingCompare = true;
536: } else if (nodeBefore1 instanceof Text
537: && nodeBefore2 instanceof Text
538: && nodeBefore1.getNodeValue().intern() == nodeBefore2
539: .getNodeValue().intern())
540: siblingCompare = true;
541:
542: if (siblingCompare)
543: return compareTextByValue(n1, n2);
544:
545: return false;
546: }
547:
548: protected boolean compareTextByValue(Text n1, Text n2) {
549: return n1.getNodeValue().intern() == n2.getNodeValue().intern();
550: }
551:
552: protected List<Node> getChildList(Node parent) {
553: NodeList childs = parent.getChildNodes();
554: List<Node> childList = new ArrayList<Node>(childs.getLength());
555: for (int i = 0; i < childs.getLength(); i++) {
556: Node child = (Node) childs.item(i);
557: childList.add(child);
558: }
559: return childList;
560: }
561:
562: static class ChildInfo {
563:
564: public ChildInfo(Node parent) {
565: NodeList childNodes = parent.getChildNodes();
566: compareNodeMap = new HashMap<Node, Node>();
567: siblingBeforeMap = new HashMap<Node, Node>(childNodes
568: .getLength());
569: posMap = new HashMap<Node, int[]>(childNodes.getLength());
570: HashMap<Class, Integer> posCounter = new HashMap<Class, Integer>(
571: 7);
572: Node siblingBefore = null;
573: for (int i = 0; i < childNodes.getLength(); i++) {
574: Node child = (Node) childNodes.item(i);
575: siblingBeforeMap.put(child, siblingBefore);
576: siblingBefore = child;
577: // TODO adding Comment and CData makes more
578: // buckets and thus the changes are relative to
579: // the type of child. If comment, cdata, and
580: // text should be considered as one then the
581: // a method needs to be invoked to determine
582: // the correct bucket to put the count into
583: Class bucket = getBucket(child);
584: Integer count = posCounter.get(bucket);
585: if (count == null)
586: posCounter.put(bucket, -1);
587: int newCount = posCounter.get(bucket) + 1;
588: posCounter.put(bucket, newCount);
589: int[] pos = new int[] { i, newCount };
590: posMap.put(child, pos);
591: }
592: }
593:
594: /**
595: * @return correct class to determine relative position
596: */
597: private Class getBucket(Node child) {
598: return child instanceof Text ? Text.class : child
599: .getClass();
600: }
601:
602: public Node getSiblingBefore(Node n) {
603: return siblingBeforeMap.get(n);
604: }
605:
606: public int getIndex(Node n) {
607: return posMap.get(n)[0];
608: }
609:
610: public int getPosition(Node n) {
611: return posMap.get(n)[1];
612: }
613:
614: public Node getMatchNode(Node n) {
615: return compareNodeMap.get(n);
616: }
617:
618: public void addMatchNode(Node n1, Node n2) {
619: compareNodeMap.put(n1, n2);
620: }
621:
622: public void clear() {
623: compareNodeMap.clear();
624: siblingBeforeMap.clear();
625: posMap.clear();
626: }
627:
628: HashMap<Node, int[]> posMap;
629: HashMap<Node, Node> siblingBeforeMap;
630: HashMap<Node, Node> compareNodeMap;
631: }
632:
633: static class SiblingInfo {
634:
635: public SiblingInfo(Node child, NodeList p2ChildNodes,
636: List<Node> foundList) {
637: for (int j = 0; j < p2ChildNodes.getLength(); j++) {
638: Node node = (Node) p2ChildNodes.item(j);
639: if (node == child) {
640: if (j - 1 >= 0) {
641: for (int k = j - 1; k >= 0; k--) {//go backwards and find
642: //a node that hasn't changed its pos
643: if (p2ChildNodes.item(k) instanceof Element
644: && foundList.contains(p2ChildNodes
645: .item(k))) {
646: siblingBefore = (Node) p2ChildNodes
647: .item(k);
648: relativePos = j - k;
649: break;
650: }
651: }
652: }
653: if (siblingBefore != null)
654: break;
655: }
656: }
657: }
658:
659: public Node getNode() {
660: return siblingBefore;
661: }
662:
663: public int getPosition() {
664: return relativePos;
665: }
666:
667: Node siblingBefore;
668: int relativePos = 0;
669: }
670:
671: ////////////////////////////////////////////////////////////////////////////////
672: // Member variables
673: ////////////////////////////////////////////////////////////////////////////////
674:
675: private ElementIdentity eID;
676:
677: private ChildInfo cInfo1;
678:
679: private ChildInfo cInfo2;
680:
681: private Document oldDoc;
682:
683: private Document newDoc;
684: }
|