| /******************************************************************************* |
| * Copyright (c) 2006 Sybase, Inc. and others. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Sybase, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.pagedesigner.range; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.jst.pagedesigner.parts.DocumentEditPart; |
| import org.eclipse.jst.pagedesigner.viewer.DesignPosition; |
| import org.eclipse.jst.pagedesigner.viewer.DesignRange; |
| |
| /** |
| * @author mengbo |
| */ |
| public class RangeUtil { |
| /** |
| * append the child after the reference node as next sibling. |
| * |
| * @param child |
| * can't be null |
| * @param reference |
| * can't be null |
| * @return ?? |
| */ |
| //TODO: dead |
| // private static Node appendAfter(Node child, Node reference) { |
| // Node next = reference.getNextSibling(); |
| // if (next == null) |
| // { |
| // return reference.getParentNode().appendChild(child); |
| // } |
| // return reference.getParentNode().insertBefore(child, next); |
| // } |
| |
| /** |
| * @param child |
| * @param reference |
| * @return ?? |
| */ |
| // TODO: dead |
| // private static Node insertBefore(Node child, Node reference) { |
| // return reference.getParentNode().insertBefore(child, reference); |
| // } |
| |
| /** |
| * Insert a node into the specified position. The node can be an element or |
| * DocumentFragment. |
| * |
| * @param node |
| * @param position |
| */ |
| // TODO: dead |
| // private static Node insertElement(DesignPosition position, Element node) { |
| // EditPart containerEditPart = position.getContainerPart(); |
| // int offset = position.getOffset(); |
| // |
| // if (containerEditPart instanceof TextEditPart) { |
| // TextEditPart textPart = (TextEditPart) containerEditPart; |
| // String textData = textPart.getTextData(); |
| // Node textNode = (Node) textPart.getModel(); |
| // if (offset == 0) |
| // return insertBefore(node, textNode); |
| // else if (offset == textData.length()) |
| // return appendAfter(node, textNode); |
| // else { |
| // // inserting the element in the middle of text. |
| // String before = textData.substring(0, offset); |
| // String after = textData.substring(offset); |
| // |
| // // XXX: don't know whether setNodeValue() will do all those |
| // // escape or not. |
| // textNode.setNodeValue(after); |
| // Node newnode = insertBefore(node, textNode); |
| // |
| // // XXX: don't know whether createTextNode() will do all those |
| // // escape or not |
| // Text t = textNode.getOwnerDocument().createTextNode(before); |
| // |
| // insertBefore(t, newnode); |
| // return newnode; |
| // } |
| // } |
| // return insertIntoEditPart(containerEditPart, node, offset); |
| // } |
| |
| /** |
| * @param containerEditPart |
| * @param node |
| * @param offset |
| * @return |
| */ |
| // TODO: dead |
| // private static Node insertIntoEditPart(EditPart containerEditPart, |
| // Node node, int offset) { |
| // Node parent = (Node) containerEditPart.getModel(); |
| // List childParts = containerEditPart.getChildren(); |
| // if (offset >= childParts.size()) { |
| // // to the end of parent |
| // return parent.appendChild(node); |
| // } |
| // Node child = (Node) ((EditPart) childParts.get(offset)).getModel(); |
| // return insertBefore(node, child); |
| // } |
| |
| // TODO: dead |
| // private static TextPosition insertText(DesignPosition position, String data) { |
| // // TODO: never read EditPart containerEditPart = position.getContainerPart(); |
| // |
| // position = moveIntoText(position); |
| // int offset = position.getOffset(); |
| // |
| // if (position.getContainerPart() instanceof TextEditPart) { |
| // // it is guaranteeed that now the containing edit part is text node. |
| // TextEditPart textPart = (TextEditPart) position.getContainerPart(); |
| // String textData = textPart.getTextData(); |
| // String before = textData.substring(0, offset); |
| // String after = textData.substring(offset); |
| // if (data.startsWith(" ") && before.endsWith(" ")) { |
| // before = before.substring(0, before.length() - 1) + " "; |
| // } |
| // if (after.startsWith(" ") && data.endsWith(" ")) { |
| // data = data.substring(0, data.length() - 1) + (char) 160; |
| // } |
| // String nextData = before + data + after; |
| // IDOMText text = (IDOMText) textPart.getModel(); |
| // text.setData(nextData); |
| // return new TextPosition(text, offset + data.length()); |
| // } |
| // // can't merge into a neighboring text node. So create a text node |
| // // of it's own |
| // EditPart part = position.getContainerPart(); |
| // Node parent = (Node) part.getModel(); |
| // Text text = parent.getOwnerDocument().createTextNode(data); |
| // insertIntoEditPart(part, text, offset); |
| // return new TextPosition((IDOMText) text, offset); |
| // } |
| |
| /** |
| * Try to make the position move into a text node. |
| * |
| * @param position |
| * @return |
| */ |
| // TODO: dead |
| // private static DesignPosition moveIntoText(DesignPosition position) { |
| // EditPart container = position.getContainerPart(); |
| // if (container instanceof TextEditPart) |
| // return position; |
| // if (position.getOffset() > 0) { |
| // EditPart pre = (EditPart) container.getChildren().get( |
| // position.getOffset() - 1); |
| // if (pre instanceof TextEditPart) { |
| // return new DesignPosition(pre, ((TextEditPart) pre) |
| // .getTextData().length()); |
| // } |
| // } |
| // if (position.getOffset() < container.getChildren().size()) { |
| // EditPart next = (EditPart) container.getChildren().get( |
| // position.getOffset()); |
| // if (next instanceof TextEditPart) { |
| // return new DesignPosition(next, 0); |
| // } |
| // } |
| // return position; |
| // } |
| |
| /** |
| * try to move the position up to not inside a text. if the position is at 0 |
| * index or last index of a text node, then try to move it up. |
| * |
| * @param position |
| * @return |
| */ |
| // TODO: dead |
| // private static DesignPosition moveOutFromText(DesignPosition position) { |
| // EditPart container = position.getContainerPart(); |
| // if (container instanceof TextEditPart) { |
| // int offset = position.getOffset(); |
| // String text = ((TextEditPart) container).getTextData(); |
| // if (offset == 0) { |
| // return new DesignPosition(container.getParent(), container |
| // .getParent().getChildren().indexOf(container)); |
| // } else if (offset == text.length()) { |
| // return new DesignPosition(container.getParent(), container |
| // .getParent().getChildren().indexOf(container) + 1); |
| // } |
| // } |
| // return position; |
| // } |
| |
| // private static void insertDocumentFragment(DesignPosition position, |
| // DocumentFragment fragment) { |
| // // FIXME: NOT DONE. |
| // } |
| |
| /** |
| * Test whether the range intersect with the part. |
| * |
| * @param range |
| * @param part |
| * @return true if thereis an intersection |
| */ |
| public static boolean intersect(DesignRange range, EditPart part) { |
| if (range == null || !range.isValid()) |
| return false; |
| range = normalize(range); |
| if (part instanceof DocumentEditPart) |
| return true; |
| EditPart parent = part.getParent(); |
| int index = parent.getChildren().indexOf(part); |
| DesignPosition left = new DesignPosition(parent, index); |
| DesignPosition right = new DesignPosition(parent, index + 1); |
| int compare = compareDesignPosition(left, range.getEndPosition()); |
| if (compare == 1 || compare == 0 || compare == Integer.MIN_VALUE) |
| return false; |
| |
| compare = compareDesignPosition(right, range.getStartPosition()); |
| if (compare == -1 || compare == 0 || compare == Integer.MIN_VALUE) |
| return false; |
| |
| return true; |
| } |
| |
| /** |
| * make sure the start position is before end position. If the original |
| * range is already normalized, then the original range will be returned |
| * without constructing a new one. |
| * |
| * @param range |
| * @return the normalized range |
| */ |
| public static DesignRange normalize(DesignRange range) { |
| if (range == null || !range.isValid()) { |
| return range; |
| } |
| int result = compareDesignPosition(range.getStartPosition(), range |
| .getEndPosition()); |
| if (result == 1) |
| { |
| return new DesignRange(range.getEndPosition(), range |
| .getStartPosition()); |
| } |
| return range; |
| } |
| |
| /** |
| * |
| * @param p1 |
| * @param p2 |
| * @return 0 means equal. 1 Means p1 is after p2. -1 means p1 is before p2. |
| * Integer.MIN_VALUE means some error and can't compare. |
| */ |
| private static int compareDesignPosition(DesignPosition p1, DesignPosition p2) { |
| if (!p1.isValid() || !p2.isValid()) |
| return Integer.MIN_VALUE; |
| if (p1.equals(p2)) |
| return 0; |
| int offset1 = p1.getOffset(); |
| int offset2 = p2.getOffset(); |
| List a1 = getAncesters(p1.getContainerPart()); |
| List a2 = getAncesters(p2.getContainerPart()); |
| if (a1 == null || a2 == null) |
| return Integer.MIN_VALUE; |
| if (a1.get(0) != a2.get(0)) |
| return Integer.MIN_VALUE; // not same DocumentEditPart |
| for (int i = 1;; i++) { |
| EditPart p1a = (EditPart) a1.get(i); |
| EditPart p2a = (EditPart) a2.get(i); |
| if (p1a == p2a) { |
| if (p1a != null) |
| { |
| continue; // same ancester |
| } |
| // both are null. just compare the offset. |
| return offset1 < offset2 ? -1 |
| : (offset1 == offset2 ? 0 : 1); |
| } |
| // p1a != p2a. now we can just compare p1a and p2a to decide the |
| // order. |
| if (p1a != null) |
| offset1 = p1a.getParent().getChildren().indexOf(p1a); |
| if (p2a != null) |
| offset2 = p2a.getParent().getChildren().indexOf(p2a); |
| if ((p1a == null && p2a == null) || (p1a != null && p2a != null)) { |
| return offset1 < offset2 ? -1 : (offset1 == offset2 ? 0 : 1); |
| } else if (p1a == null) { |
| return offset1 <= offset2 ? -1 : 1; |
| } else { |
| return offset1 >= offset2 ? 1 : -1; |
| } |
| } |
| } |
| |
| /** |
| * Get a list of ancester nodes starting from the DocumentEditPart till the |
| * node. |
| * |
| * @param part |
| * @return |
| */ |
| private static List getAncesters(EditPart part) { |
| List list = new ArrayList(); |
| while (part != null) { |
| list.add(part); |
| if (part instanceof DocumentEditPart) |
| { |
| break; |
| } |
| part = part.getParent(); |
| } |
| if (part == null) { |
| // if part ==null, means we didn't find a DocumentEditPart, |
| // something must be wrong. |
| return null; |
| } |
| // reverse to make it starting from the docuemnteditpart node. |
| Collections.reverse(list); |
| list.add(null); // add an null terminator. |
| return list; |
| } |
| |
| /** |
| * find the smallest common ancester of two edit part. |
| * |
| * @param part1 |
| * @param part2 |
| * @return |
| */ |
| private static EditPart findCommonAncester(EditPart part1, EditPart part2) { |
| if (part1 == part2) { |
| return part1; |
| } |
| List list1 = getAncesters(part1); |
| if (list1 == null) |
| return null; |
| List list2 = getAncesters(part2); |
| if (list2 == null) |
| return null; |
| if (list1.get(0) != list2.get(0)) |
| return null; |
| EditPart common = (EditPart) list1.get(0); |
| for (int i = 1;; i++) { |
| EditPart p1 = (EditPart) list1.get(i); |
| EditPart p2 = (EditPart) list2.get(i); |
| if (p1 == null || p2 == null) |
| return common; |
| if (p1 != p2) |
| return common; |
| common = p1; |
| } |
| |
| } |
| |
| /** |
| * @param range |
| * @return the common ancestor |
| */ |
| public static EditPart findCommonAncestor(DesignRange range) { |
| if (!range.isValid()) { |
| return null; |
| } |
| DesignPosition startPosition = range.getStartPosition(); |
| DesignPosition endPosition = range.getEndPosition(); |
| return findCommonAncester(startPosition.getContainerPart(), endPosition |
| .getContainerPart()); |
| } |
| } |