| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jface.text; |
| |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.NoSuchElementException; |
| |
| import org.eclipse.swt.events.VerifyEvent; |
| |
| |
| /** |
| * Represents a text modification as a document replace command. The text modification is given |
| * as a <code>VerifyEvent</code> and translated into a document replace command relative |
| * to a given offset. A document command can also be used to initialize a given <code>VerifyEvent</code>.<p> |
| * A document command can also represent a list of related changes. |
| */ |
| public class DocumentCommand { |
| |
| /** |
| * A command which is added to document commands. |
| * @since 2.1 |
| */ |
| private static class Command implements Comparable { |
| /** The offset of the range to be replaced */ |
| private final int fOffset; |
| /** The length of the range to be replaced. */ |
| private final int fLength; |
| /** The replacement text */ |
| private final String fText; |
| /** The listern who owns this command */ |
| private final IDocumentListener fOwner; |
| |
| /** |
| * Creates a new command with the given specification. |
| * |
| * @param offset the offset of the replace command |
| * @param length the length of the replace command |
| * @param text the text to replace with, may be <code>null</code> |
| * @param owner the document command owner, may be <code>null</code> |
| */ |
| public Command(int offset, int length, String text, IDocumentListener owner) { |
| if (offset < 0 || length < 0) |
| throw new IllegalArgumentException(); |
| fOffset= offset; |
| fLength= length; |
| fText= text; |
| fOwner= owner; |
| } |
| |
| /** |
| * Returns the length delta for this command. |
| * |
| * @return the length delta for this command |
| */ |
| public int getDeltaLength() { |
| return (fText == null ? 0 : fText.length()) - fLength; |
| } |
| |
| /** |
| * Executes the document command on the specified document. |
| * |
| * @param document the document on which to execute the command. |
| * @throws BadLocationException in case this commands cannot be executed |
| */ |
| public void execute(IDocument document) throws BadLocationException { |
| |
| if (fLength == 0 && fText == null) |
| return; |
| |
| if (fOwner != null) |
| document.removeDocumentListener(fOwner); |
| |
| document.replace(fOffset, fLength, fText); |
| |
| if (fOwner != null) |
| document.addDocumentListener(fOwner); |
| } |
| |
| /* |
| * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) |
| */ |
| public int compareTo(final Object object) { |
| if (equals(object)) |
| return 0; |
| |
| final Command command= (Command) object; |
| |
| // diff middle points if not intersecting |
| if (fOffset + fLength <= command.fOffset || command.fOffset + command.fLength <= fOffset) { |
| int value= (2 * fOffset + fLength) - (2 * command.fOffset + command.fLength); |
| if (value != 0) |
| return value; |
| } |
| // the answer |
| return 42; |
| } |
| |
| /* |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object object) { |
| if (object == this) |
| return true; |
| if (! (object instanceof Command)) |
| return false; |
| final Command command= (Command) object; |
| return command.fOffset == fOffset && command.fLength == fLength; |
| } |
| } |
| |
| /** |
| * An iterator, which iterates in reverse over a list. |
| */ |
| private static class ReverseListIterator implements Iterator { |
| |
| /** The list iterator. */ |
| private final ListIterator fListIterator; |
| |
| /** |
| * Creates a reverse list iterator. |
| */ |
| public ReverseListIterator(ListIterator listIterator) { |
| if (listIterator == null) |
| throw new IllegalArgumentException(); |
| fListIterator= listIterator; |
| } |
| |
| /* |
| * @see java.util.Iterator#hasNext() |
| */ |
| public boolean hasNext() { |
| return fListIterator.hasPrevious(); |
| } |
| |
| /* |
| * @see java.util.Iterator#next() |
| */ |
| public Object next() { |
| return fListIterator.previous(); |
| } |
| |
| /* |
| * @see java.util.Iterator#remove() |
| */ |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * A command iterator. |
| */ |
| private static class CommandIterator implements Iterator { |
| |
| /** The command iterator. */ |
| private final Iterator fIterator; |
| |
| /** The original command. */ |
| private Command fCommand; |
| |
| /** A flag indicating the direction of iteration. */ |
| private boolean fForward; |
| |
| /** |
| * Creates a command iterator. |
| * |
| * @param commands an ascending ordered list of commands |
| * @param command the original command |
| * @param forward the direction |
| */ |
| public CommandIterator(final List commands, final Command command, final boolean forward) { |
| if (commands == null || command == null) |
| throw new IllegalArgumentException(); |
| fIterator= forward ? commands.iterator() : new ReverseListIterator(commands.listIterator(commands.size())); |
| fCommand= command; |
| fForward= forward; |
| } |
| |
| /* |
| * @see java.util.Iterator#hasNext() |
| */ |
| public boolean hasNext() { |
| return fCommand != null || fIterator.hasNext(); |
| } |
| |
| /* |
| * @see java.util.Iterator#next() |
| */ |
| public Object next() { |
| |
| if (! hasNext()) |
| throw new NoSuchElementException(); |
| |
| if (fCommand == null) |
| return fIterator.next(); |
| |
| if (!fIterator.hasNext()) { |
| final Command tempCommand= fCommand; |
| fCommand= null; |
| return tempCommand; |
| } |
| |
| final Command command= (Command) fIterator.next(); |
| final int compareValue= command.compareTo(fCommand); |
| |
| if ((compareValue < 0) ^ ! fForward) { |
| return command; |
| |
| } else if ((compareValue > 0) ^ ! fForward) { |
| final Command tempCommand= fCommand; |
| fCommand= command; |
| return tempCommand; |
| |
| } else { |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| /* |
| * @see java.util.Iterator#remove() |
| */ |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** Must the command be updated */ |
| public boolean doit= false; |
| /** The offset of the command. */ |
| public int offset; |
| /** The length of the command */ |
| public int length; |
| /** The text to be inserted */ |
| public String text; |
| /** |
| * The owner of the document command which will not be notified. |
| * @since 2.1 |
| */ |
| public IDocumentListener owner; |
| /** |
| * The caret offset with respect to the document before the document command is executed. |
| * @since 2.1 |
| */ |
| public int caretOffset; |
| /** Additional document commands. */ |
| private final List fCommands= new ArrayList(); |
| |
| /** |
| * Creates a new document command. |
| */ |
| protected DocumentCommand() { |
| } |
| |
| /** |
| * Translates a verify event into a document replace command using the given offset. |
| * |
| * @param event the event to be translated |
| * @param modelRange the event range as model range |
| */ |
| void setEvent(VerifyEvent event, IRegion modelRange) { |
| |
| doit= true; |
| text= event.text; |
| |
| offset= modelRange.getOffset(); |
| length= modelRange.getLength(); |
| |
| owner= null; |
| caretOffset= -1; |
| fCommands.clear(); |
| } |
| |
| /** |
| * Fills the given verify event with the replace text and the doit |
| * flag of this document command. Returns whether the document command |
| * covers the same range as the verify event considering the given offset. |
| * |
| * @param event the event to be changed |
| * @param modelRange to be considered for range comparison |
| * @return <code>true</code> if this command and the event cover the same range |
| */ |
| boolean fillEvent(VerifyEvent event, IRegion modelRange) { |
| event.text= text; |
| event.doit= (offset == modelRange.getOffset() && length == modelRange.getLength() && doit); |
| return event.doit; |
| } |
| |
| /** |
| * Adds an additional replace command. The added replace command must not overlap |
| * with existing ones. If the document command owner is not <code>null</code>, it will not |
| * get document change notifications for the particular command. |
| * |
| * @param offset the offset of the region to replace |
| * @param length the length of the region to replace |
| * @param text the text to replace with, may be <code>null</code> |
| * @param owner the command owner, may be <code>null</code> |
| * @throws BadLocationException if the added command intersects with an existing one |
| * @since 2.1 |
| */ |
| public void addCommand(int offset, int length, String text, IDocumentListener owner) throws BadLocationException { |
| final Command command= new Command(offset, length, text, owner); |
| |
| if (intersects(command)) |
| throw new BadLocationException(); |
| |
| final int index= Collections.binarySearch(fCommands, command); |
| |
| // a command with exactly the same ranges exists already |
| if (index >= 0) |
| throw new BadLocationException(); |
| |
| // binary search result is defined as (-(insertionIndex) - 1) |
| final int insertionIndex= -(index + 1); |
| |
| // overlaps to the right? |
| if (insertionIndex != fCommands.size() && intersects((Command) fCommands.get(insertionIndex), command)) |
| throw new BadLocationException(); |
| |
| // overlaps to the left? |
| if (insertionIndex != 0 && intersects((Command) fCommands.get(insertionIndex - 1), command)) |
| throw new BadLocationException(); |
| |
| fCommands.add(insertionIndex, command); |
| } |
| |
| /** |
| * Returns an iterator over the commands in ascending position order. |
| * The iterator includes the original document command. |
| * Commands cannot be removed. |
| * |
| * @return returns the command iterator |
| */ |
| public Iterator getCommandIterator() { |
| Command command= new Command(offset, length, text, owner); |
| return new CommandIterator(fCommands, command, true); |
| } |
| |
| /** |
| * Returns the number of commands including the original document command. |
| * |
| * @return returns the number of commands |
| * @since 2.1 |
| */ |
| public int getCommandCount() { |
| return 1 + fCommands.size(); |
| } |
| |
| /** |
| * Returns whether the two given commands intersect. |
| * |
| * @param command0 the first command |
| * @param command1 the second command |
| * @return <code>true</code> if the commands intersect |
| * @since 2.1 |
| */ |
| private boolean intersects(Command command0, Command command1) { |
| // diff middle points if not intersecting |
| if (command0.fOffset + command0.fLength <= command1.fOffset || command1.fOffset + command1.fLength <= command0.fOffset) |
| return (2 * command0.fOffset + command0.fLength) - (2 * command1.fOffset + command1.fLength) == 0; |
| else |
| return true; |
| } |
| |
| /** |
| * Returns whether the given command intersects with this command. |
| * |
| * @param command the command |
| * @return <code>true</code> if the command intersects with this command |
| * @since 2.1 |
| */ |
| private boolean intersects(Command command) { |
| // diff middle points if not intersecting |
| if (offset + length <= command.fOffset || command.fOffset + command.fLength <= offset) |
| return (2 * offset + length) - (2 * command.fOffset + command.fLength) == 0; |
| else |
| return true; |
| } |
| |
| /** |
| * Executes the document commands on a document. |
| * |
| * @param document the document on which to execute the commands |
| * @since 2.1 |
| */ |
| void execute(IDocument document) throws BadLocationException { |
| |
| if (length == 0 && text == null && fCommands.size() == 0) |
| return; |
| |
| final Command originalCommand= new Command(offset, length, text, owner); |
| |
| for (final Iterator iterator= new CommandIterator(fCommands, originalCommand, false); iterator.hasNext(); ) { |
| final Command command= (Command) iterator.next(); |
| command.execute(document); |
| if (caretOffset != -1 && command.fOffset + command.fLength <= caretOffset) |
| caretOffset += command.getDeltaLength(); |
| } |
| } |
| |
| } |