org.openide.text.Line Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.text;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Represents one line in a text document.
* The line number may change
* when the text is modified, but the identity of the line is retained. It is designed to allow line-dependent
* modules of the IDE (such as the compiler and debugger) to make use of a line consistently even as the text is modified.
*
* @author Ales Novak, Petr Hamernik, Jan Jancura, Jaroslav Tulach, David Konecny
*/
public abstract class Line extends Annotatable implements Serializable {
/** generated Serialized Version UID */
private static final long serialVersionUID = 9113186289600795476L;
static final Logger LOG = Logger.getLogger(Line.class.getName());
/** Property name of the line number */
public static final String PROP_LINE_NUMBER = "lineNumber"; // NOI18N
/** Shows the line only if the editor is open.
* @see #show(int) show
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#NONE}
* and {@link ShowVisibilityType#NONE} instead.
*/
@Deprecated
public static final int SHOW_TRY_SHOW = 0;
/** Opens the editor if necessary and shows the line.
* @see #show(int) show
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
* and {@link ShowVisibilityType#NONE} instead.
*/
@Deprecated
public static final int SHOW_SHOW = 1;
/** Opens the editor if necessary, shows the line, and takes the focus.
* @see #show(int) show
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
* and {@link ShowVisibilityType#FOCUS} instead.
*/
@Deprecated
public static final int SHOW_GOTO = 2;
/** Same as SHOW_GOTO except that the Window Manager attempts to front the
* editor window (i.e. make it the top most window).
* @see #show(int) show
* @see org.openide.windows.TopComponent#toFront()
* @since 5.8
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#OPEN}
* and {@link ShowVisibilityType#FRONT} instead.
*/
@Deprecated
public static final int SHOW_TOFRONT = 3;
/** Takes the focus in case the editor is already opened and shows the line.
* Replaces (closes) the last editor opened using SHOW_REUSE in case
* the user haven't interacted with it much (e.g. haven't modified it).
* Opens a new editor in case there is no such reusable editor
* and marks it for editor reusal.
* @see #show(int) show
* @since org.openide.text 6.14
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#REUSE}
* and {@link ShowVisibilityType#FOCUS} instead.
*/
@Deprecated
public static final int SHOW_REUSE = 4;
/** Focuses or opens given editor, marking it as reusable editor if it
* was not opened before. Similar to {@link #SHOW_REUSE} but ignores
* currently reusable editor.
* @see #show(int) show
* @since org.openide.text 6.14
* @deprecated Deprecated since 6.21. Use {@link ShowOpenType#REUSE_NEW}
* and {@link ShowVisibilityType#FOCUS} instead.
*/
@Deprecated
public static final int SHOW_REUSE_NEW = 5;
/** ShowOpenType and ShowVisibilityType is replacement for constants SHOW_TRY_SHOW, SHOW_SHOW,
* SHOW_GOTO, SHOW_TOFRONT, SHOW_REUSE, SHOW_REUSE_NEW. It is to provide full control
* over show method behavior without need to add new constant for missing flag combination.
*
*
Note: Any modification of editor marked for reuse resets reuse flag. There is one global static reference
* so only one or none editor can be marked for reuse.
*
* @see #show(ShowOpenType, ShowVisibilityType) show
* @see ShowVisibilityType ShowVisibilityType
* @since org.openide.text 6.21
*
*/
public enum ShowOpenType {
/** shows the line only if the editor is open */
NONE,
/** opens editor if necessary (editor was not opened) and shows the line */
OPEN,
/** replaces editor marked for reuse (last editor opened using {@link ShowOpenType#REUSE}
* or {@link ShowOpenType#REUSE_NEW} and opens editor if necessary, if editor is being opened (editor was not opened)
* marks it for reuse, shows the line */
REUSE,
/** ignores editor marked for reuse (resets reference to editor marked for reuse),
* opens editor if necessary, if editor is being opened (editor was not opened)
* marks it for reuse, shows the line */
REUSE_NEW
};
/** ShowOpenType and ShowVisibilityType is replacement for constants SHOW_TRY_SHOW, SHOW_SHOW,
* SHOW_GOTO, SHOW_TOFRONT, SHOW_REUSE, SHOW_REUSE_NEW. It is to provide full control
* over show method behavior without need to add new constant for missing flag combination.
*
* @since org.openide.text 6.21
* @see #show(ShowOpenType, ShowVisibilityType) show
* @see ShowOpenType ShowOpenType
*/
public enum ShowVisibilityType {
/** no action */
NONE,
/** fronts editor component to become visible */
FRONT,
/** front editor component to become visible and activates/focuses it. It does
* the same as {@link ShowVisibilityType#FRONT} plus activates/focuses editor */
FOCUS
};
/** Instance of null implementation of Line.Part */
private static final Line.Part nullPart = new Line.NullPart();
/** context of this line */
private org.openide.util.Lookup dataObject;
/** Create a new line object based on a given data object.
* This implementation is abstract, so the specific line number is not used here.
* Subclasses should somehow specify the position.
*
* The context argument shall contain information about the
* producer of the Line, that can be then extracted by {@link Line#getLookup} call.
*
* @param context the context for this line
*/
public Line(Lookup context) {
if (context == null) {
throw new NullPointerException();
}
dataObject = context;
}
/**
* Create a new line object based on a given data object.
* This implementation is abstract, so the specific line number is not used here. Subclasses should somehow specify the position.
* @param source the object that is producing the Line
*/
public Line(Object source) {
this((source instanceof Lookup) ? (Lookup) source : Lookups.singleton(source));
if (source == null) {
throw new NullPointerException();
}
}
/** Composes a human presentable name for the line. The default
* implementation uses the name of data object and the line number
* to create the display name.
*
* @return human presentable name that should identify the line
*/
public String getDisplayName() {
return getClass().getName() + ":" + getLineNumber(); // NOI18N
}
/** Provides access to the context passed into the line constructor.
* For example lines produced by DataEditorSupport
* provide DataObject
as the content of the lookup.
* One can use:
*
* dataObjectOrNull = (DataObject)line.getLookup ().lookup (DataObject.class);
*
* to get the access.
*
* @return context associated with the line
* @since 4.3
*/
public final org.openide.util.Lookup getLookup() {
return dataObject;
}
/** Get the line number. The last condition in following should
* always be true:
* {@code
* Line.Set lineSet =
* Line l =
*
* l.equals (lineSet.getCurrent (l.getLineNumber ()))
* }
*
* @return current line number (may change as text is edited) (starting at 0)
*/
public abstract int getLineNumber();
/** Show the line.
* @param kind one of {@link #SHOW_TRY_SHOW}, {@link #SHOW_SHOW}, or {@link #SHOW_GOTO}
* @param column the column of this line which should be selected (starting at 0),
* value -1 does not change previously selected column
* @deprecated Deprecated since 6.21. Use {@link #show(ShowOpenType, ShowVisibilityType, int)} instead.
*/
@Deprecated
public abstract void show(int kind, int column);
/** Shows the line (at the first column).
* @param kind one of {@link #SHOW_TRY_SHOW}, {@link #SHOW_SHOW}, {@link #SHOW_GOTO},
* {@link #SHOW_REUSE} or {@link #SHOW_REUSE_NEW}
* @see #show(int, int)
* @deprecated Deprecated since 6.21. Use {@link #show(ShowOpenType, ShowVisibilityType)} instead.
*/
@Deprecated
public void show(int kind) {
show(kind, 0);
}
/** Show the line.
* @param openType one of {@link ShowOpenType#NONE}, {@link ShowOpenType#OPEN},
* {@link ShowOpenType#REUSE} or {@link ShowOpenType#REUSE_NEW}
* @param visibilityType one of {@link ShowVisibilityType#NONE},
* {@link ShowVisibilityType#FRONT} or {@link ShowVisibilityType#FOCUS}
* @param column the column of this line which should be selected (starting at 0),
* value -1 does not change previously selected column
* @since org.openide.text 6.21
*/
public void show(ShowOpenType openType, ShowVisibilityType visibilityType, int column) {
if (openType == ShowOpenType.NONE) {
if (visibilityType == ShowVisibilityType.NONE) {
show(SHOW_TRY_SHOW, column);
} else {
LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
show(SHOW_TRY_SHOW, column);
}
} else if (openType == ShowOpenType.OPEN) {
if (visibilityType == ShowVisibilityType.NONE) {
show(SHOW_SHOW, column);
} else if (visibilityType == ShowVisibilityType.FOCUS) {
show(SHOW_GOTO, column);
} else if (visibilityType == ShowVisibilityType.FRONT) {
show(SHOW_TOFRONT, column);
}
} else if (openType == ShowOpenType.REUSE) {
if (visibilityType == ShowVisibilityType.FOCUS) {
show(SHOW_REUSE, column);
} else {
LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
show(SHOW_REUSE, column);
}
} else if (openType == ShowOpenType.REUSE_NEW) {
if (visibilityType == ShowVisibilityType.FOCUS) {
show(SHOW_REUSE_NEW, column);
} else {
LOG.warning("Line.show(ShowOpenType, ShowVisibilityType, int) uses unsupported combination of parameters");
show(SHOW_REUSE_NEW, column);
}
}
}
/** Shows the line (at the first column).
* @param openType one of {@link ShowOpenType#NONE}, {@link ShowOpenType#OPEN},
* {@link ShowOpenType#REUSE} or {@link ShowOpenType#REUSE_NEW}
* @param visibilityType one of {@link ShowVisibilityType#NONE},
* {@link ShowVisibilityType#FRONT} or {@link ShowVisibilityType#FOCUS}
* @see #show(ShowOpenType, ShowVisibilityType, int)
* @since org.openide.text 6.21
*/
public void show(ShowOpenType openType, ShowVisibilityType visibilityType) {
show(openType, visibilityType, 0);
}
/** Set or clear a (debugger) breakpoint at this line.
* @param b true
to turn on
* @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
*/
@Deprecated
public abstract void setBreakpoint(boolean b);
/** Test if there is a breakpoint set at this line.
* @return true
is there is
* @deprecated Deprecated since 1.20. Use {@link Annotation} instead.
*/
@Deprecated
public abstract boolean isBreakpoint();
/** Mark an error at this line.
* @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
*/
@Deprecated
public abstract void markError();
/** Unmark error at this line.
* @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
*/
@Deprecated
public abstract void unmarkError();
/** Mark this line as current.
* @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
*/
@Deprecated
public abstract void markCurrentLine();
/** Unmark this line as current.
* @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
*/
@Deprecated
public abstract void unmarkCurrentLine();
/** Method that should allow the debugger to communicate with lines that
* wants to have a control over the current line of debugger. It allows the
* line to refuse the current status and force the debugger to continue
* over this line.
*
* The default implementation simply returns true.
*
* @param action type of action that is trying to mark this line as current
* one of constants (Debugger.ACTION_BREAKPOINT_HIT,
* Debugger.ACTION_TRACE_OVER, etc.)
* @param previousLine previous line (if any) or null
*
* @return true if this line accepts the "current" state or false if the
* line wants the debugger to proceed with next instruction
*
* @deprecated Deprecated since 1.20, as {@link #markCurrentLine} is deprecated by {@link Annotation#attach}.
*/
@Deprecated
public boolean canBeMarkedCurrent(int action, Line previousLine) {
return true;
}
/** Create object which represent part of the text on the line. This part
* of the line can be used for attaching of annotations.
* @param column starting column of the part of the text (starting at 0)
* @param length length of the part of the text
* @return instance of the Line.Part which represent the part of the text
* @since 1.20
*/
public Line.Part createPart(int column, int length) {
return nullPart;
}
public String getText() {
return null;
}
/** Representation of the part of the Line's text. The part of the text is defined by
* the starting column, length of the part and reference to Line. The length of the
* part never cross the end of the line.
* @since 1.20
*/
public abstract static class Part extends Annotatable {
/** Property name for the line attribute */
public static final String PROP_LINE = "line"; // NOI18N
/** Property name for the column attribute */
public static final String PROP_COLUMN = "column"; // NOI18N
/** Property name for the length attribute */
public static final String PROP_LENGTH = "length"; // NOI18N
/** Start column of annotation
* @return column at which this part begining (starting at 0)
*/
public abstract int getColumn();
/** Length of the annotated text. The length does not cross line end. If the annotated text is
* split during the editing, the annotation is shorten till the end of the line. Modules can listen on
* changes of this value
* @return length of the part
*/
public abstract int getLength();
/** Line can change during editting
* @return reference to the Line to which this part belongs
*/
public abstract Line getLine();
}
/** Implementation of Line.Part which is presenting empty part */
private static final class NullPart extends Part {
NullPart() {
}
public int getColumn() {
return 0;
}
public int getLength() {
return 0;
}
public Line getLine() {
return null;
}
public String getText() {
return null;
}
}
/** Object that represents a snapshot of lines at the time it was created.
* It is used to create a mapping from line
* numbers to line objects, for example when the file is saved.
* Such a mapping can then be used by the compiler, e.g., to find
* the correct {@link Line} object, assuming it has a line number.
*
* Mappings of line numbers to line objects will survive modifications
* of the text, and continue to represent the original lines as close as possible.
* For example: if a new line is inserted at the 10th line of a document
* and the compiler module asks for the 25th line (because the compiler reports an error at line 25 in the saved file) via the line set, the 26th line
* of the current document will be marked as being in error.
*/
public abstract static class Set extends Object {
/** date when the object has been created */
private Date date;
/**
* Binary-searchable vector of lines.
*/
private LineVector lineVector;
/** Create a new snapshot. Remembers the date when it was created. */
public Set() {
date = new Date();
}
/** Returns a set of line objects sorted by their
* line numbers. This immutable list will contains all lines held by this
* line set.
*
* @return list of lines
*/
public abstract List extends Line> getLines();
/** Get creation time for this line set.
* @return time
*/
public final Date getDate() {
return date;
}
/** Find line object in the line set corresponding to original line number.
* That is, finds the line in the current document which originally had the indicated line number.
* If there have been modifications of that line, find one as close as possible.
*
* @param line number of the line (starting at 0)
* @return line object
* @exception IndexOutOfBoundsException if line
is an invalid index for the original set of lines
*/
public abstract Line getOriginal(int line) throws IndexOutOfBoundsException;
/** Find line object representing the line in current document.
*
*
* @param line number of the line in current state of the document (starting at 0)
* @return line object
* @exception IndexOutOfBoundsException if line
is an invalid index for the original set of lines
*/
public abstract Line getCurrent(int line) throws IndexOutOfBoundsException;
/** Finds an original line number for given line in this line set.
* @param line the line to look for
* @return the number (starting at 0) that best matches the line number of the line or -1
* if the line does seem to be produced by this line set
* @since 4.38
*/
public int getOriginalLineNumber(Line line) {
return computeOriginal(this, line);
}
/**
* Lazily creates or finds binary-searchable vector of registered lines.
*/
LineVector findLineVector() {
synchronized (date) {
if (lineVector != null) {
return lineVector;
}
lineVector = new LineVector();
return lineVector;
}
}
/** Registers the line to this Line.Set
.
* @param lineIndex line index
* @param lineCreator Line
to register
* @return registered Line
. Note: the retruned
* Line
could be different (identityHashCode not equal)
* from the one passed in */
final Line findOrCreateLine(int lineIndex, LineVector.LineCreator lineCreator) {
// beware of null argument
if (lineCreator == null) {
throw new NullPointerException();
}
LineVector lineVector = findLineVector();
return lineVector.findOrCreateLine(lineIndex, lineCreator);
}
/** Finds whether a line equal to provided is already registered.
* @param line the line to register
* @return the registered line equal to line or null
*/
final Line findLine(Line line) {
LineVector lineVector = findLineVector();
return lineVector.findOrCreateLine(line.getLineNumber(), null);
}
/** A method that for a given Line.Set and a line computes the best
* original line number based on the querying the set. This is called
* in default implementation of getOriginal (Line) to provide
* inefficient (but better then most people would write) way to
* compute the number. It is static so it can be tested from
* tests working on DocumentLine objects that override the
* getOriginal (Line) method.
*
* @param set the set to search in
* @param line the line to look for
* @return closest possible line number for given line
*/
static int computeOriginal(Line.Set set, Line line) {
int n = line.getLineNumber();
Line current = null;
try {
current = set.getOriginal(n);
if (line.equals(current)) {
return n;
}
} catch (IndexOutOfBoundsException ex) {
// ok, few lines have been added and this one is now
// bellow the end of the document
}
if (current == null) {
return binarySearch(set, n, 0, findMaxLine(set));
}
if (n < current.getLineNumber()) {
return binarySearch(set, n, 0, current.getLineNumber());
} else {
return binarySearch(set, n, current.getLineNumber(), findMaxLine(set));
}
}
/** Does a search for a given line number in a given Line.Set.
*/
private static int binarySearch(Line.Set set, int number, int from, int to) {
while (from < to) {
int middle = (from + to) / 2;
Line l = set.getOriginal(middle);
int ln = l.getLineNumber();
if (ln == number) {
return middle;
}
if (ln < number) {
// try after the middle
from = middle + 1;
} else {
// try before the middle
to = middle - 1;
}
}
return from;
}
private static int findMaxLine(Line.Set set) {
int from = 0;
int to = 32000;
// probably larger than any existing document
for (;;) {
try {
set.getOriginal(to);
// if the line exists, double the max number, but keep
// for reference that it exists
from = to;
to *= 2;
} catch (IndexOutOfBoundsException ex) {
break;
}
}
while (from < to) {
int middle = (from + to + 1) / 2;
try {
set.getOriginal(middle);
// line exists
from = middle;
} catch (IndexOutOfBoundsException ex) {
// line does not exists, we have to search lower
to = middle - 1;
}
}
return from;
}
}
// End of class Line.Set.
}