org.openide.text.Line Maven / Gradle / Ivy
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.text;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.WeakHashMap;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
/** 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 java.io.Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 9113186289600795476L;
/** Property name of the line number */
public static final String PROP_LINE_NUMBER = "lineNumber"; // NOI18N
/** 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();
}
/** Shows the line only if the editor is open.
* @see #show(int) show
*/
public final static int SHOW_TRY_SHOW = 0;
/** Opens the editor if necessary and shows the line.
* @see #show(int) show
*/
public final static int SHOW_SHOW = 1;
/** Opens the editor if necessary, shows the line, and takes the focus.
* @see #show(int) show
*/
public final static int SHOW_GOTO = 2;
/** Instance of null implementation of Line.Part */
static final private Line.Part nullPart = new Line.NullPart();
/** 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:
*
* Line.Set lineSet =
* Line l =
*
* l.equals (lineSet.getCurrent (l.getLineNumber ()))
*
*
* @return current line number (may change as text is edited)
*/
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
*/
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}, or {@link #SHOW_GOTO}
* @see #show(int, int)
*/
public void show(int kind) {
show(kind, 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.
*/
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.
*/
public abstract boolean isBreakpoint();
/** Mark an error at this line.
* @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
*/
public abstract void markError();
/** Unmark error at this line.
* @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
*/
public abstract void unmarkError();
/** Mark this line as current.
* @deprecated Deprecated since 1.20. Use {@link Annotation#attach} instead.
*/
public abstract void markCurrentLine();
/** Unmark this line as current.
* @deprecated Deprecated since 1.20. Use {@link Annotation#detach} instead.
*/
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}.
*/
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
* @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 static abstract 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
*/
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 */
static final private 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 static abstract class Set extends Object {
/** date when the object has been created */
private Date date;
/** Map
which contains all lines as keys and
* values weakReferences on itself. There woudl be better use
* set but there is missing get method, returning equal object.
* belonging to this Line.Set
.
* @see DocumentLine#hashCode
* @see DocumentLine#equals
* @see #registerLine */
private WeakHashMap whm;
/** 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 element type {@link Line}
*/
public abstract java.util.List 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
* @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
* @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 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);
}
/** Lazyly creates or finds already created map for internal use.
*/
WeakHashMap findWeakHashMap () {
synchronized (date) {
if (whm != null) return whm;
whm = new WeakHashMap ();
return whm;
}
}
/** Registers the line to this Line.Set
.
* @param line Line
to register
* @return registered Line
. Note: the retruned
* Line
could be different (identityHashCode not equal)
* from the one passed in */
final Line registerLine(Line line) {
// beware of null argument
if (line == null) throw new NullPointerException();
WeakHashMap lines = findWeakHashMap ();
synchronized(lines) {
Reference r = (Reference)lines.get(line);
Line in = (r != null ? (Line)r.get() : null);
if(in == null) {
if(line instanceof DocumentLine) {
((DocumentLine)line).init();
}
lines.put(line, new WeakReference(line));
in = line;
}
return in;
}
}
/** 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) {
WeakHashMap lines = findWeakHashMap ();
synchronized (lines) {
Reference r = (Reference)lines.get(line);
Line in = (r != null ? (Line)r.get() : null);
return in;
}
}
/** 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.
}