/* * 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.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.*; import javax.swing.text.Position; import javax.swing.text.StyledDocument; import javax.swing.text.BadLocationException; import org.openide.loaders.DataObject; import org.openide.util.RequestProcessor; /** Reference to one position in a document. * This position is held as an integer offset, or as a {@link Position} object. * There is also support for serialization of positions. * * @author Petr Hamernik */ public final class PositionRef extends Object implements Serializable { static final long serialVersionUID = -4931337398907426948L; /** Which type of position is currently holded - int X Position */ transient private Manager.Kind kind; /** Manager for this position */ private Manager manager; /** insert after? */ private boolean insertAfter; /** Creates new <code>PositionRef</code> using the given manager at the specified * position offset. * @param manager manager for the position * @param offset - position in the document * @param bias the bias for the position */ PositionRef (Manager manager, int offset, Position.Bias bias) { this (manager, manager.new OffsetKind (offset), bias); } /** Creates new <code>PositionRef</code> using the given manager at the specified * line and column. * @param manager manager for the position * @param line line number * @param column column number * @param bias the bias for the position */ PositionRef (Manager manager, int line, int column, Position.Bias bias) { this (manager, manager.new LineKind (line, column), bias); } /** Constructor for everything. * @param manager manager that we are refering to * @param kind kind of position we hold * @param bias bias for the position */ private PositionRef (Manager manager, Manager.Kind kind, Position.Bias bias) { this.manager = manager; this.kind = kind; insertAfter = (bias == Position.Bias.Backward); init (); } /** Initialize variables after construction and after deserialization. */ private void init() { kind = manager.addPosition(this); } /** Writes the manager and the offset (int). */ private void writeObject(ObjectOutputStream out) throws IOException { out.writeBoolean (insertAfter); out.writeObject (manager); kind.write (out); } /** Reads the manager and the offset (int). */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { insertAfter = in.readBoolean (); manager = (Manager)in.readObject(); kind = manager.readKind (in); init (); } /** @return the appropriate manager for this position ref. */ public CloneableEditorSupport getCloneableEditorSupport () { return manager.getCloneableEditorSupport (); } /** Getter for the editor support this position ref is associated with. * But because the possition ref can be associated with any CloneableEditorSupport * there can be situations when the editor support cannot be found. * That is why this method can ClassCastException. * * @return editor support * @exception ClassCastException if the position is attached to CloneableEditorSupport * that is not subclass of EditorSupport * @deprecated Please use {@link #getCloneableEditorSupport} instead. */ public EditorSupport getEditorSupport () { return EditorSupport.extract (getCloneableEditorSupport ()); } /** @return the bias of the position */ public Position.Bias getPositionBias() { return insertAfter ? Position.Bias.Backward : Position.Bias.Forward; } /** @return the position as swing.text.Position object. * @exception IOException when an exception occured during reading the file. */ public Position getPosition() throws IOException { if(manager.getCloneableEditorSupport().getDocument() == null) { manager.getCloneableEditorSupport ().openDocument (); } synchronized(manager) { Manager.PositionKind p = (Manager.PositionKind)kind; return p.pos; } } /** @return the position as offset index in the file. */ public int getOffset() { return kind.getOffset (); } /** Get the line number where this position points to. * @return the line number for this position * @throws IOException if the document could not be opened to check the line number */ public int getLine() throws IOException { return kind.getLine (); } /** Get the column number where this position points to. * @return the column number within a line (counting starts from zero) * @exception IOException if the document could not be opened to check the column number */ public int getColumn() throws IOException { return kind.getColumn (); } public String toString() { return "Pos[" + getOffset () + "]"; // NOI18N } /** This class is responsible for the holding the Document object * and the switching the status of PositionRef (Position X offset) * objects which depends to this manager. * It has one abstract method for the creating the StyledDocument. */ static final class Manager extends Object implements Runnable, Serializable { /** Head item of data structure replacing linked list here. * @see ChainItem */ private transient ChainItem head; /** ReferenceQueue where all <code>ChainedItem</code>'s will be enqueued to. */ private transient ReferenceQueue queue; /** Counter which counts enqued items and after reaching * number 100 schedules sweepTask. */ private transient int counter; /** Task which is run in RequestProcessor thread and provides * full pass sweep, i.e. removes items with garbaged referents from * data strucure. */ private transient RequestProcessor.Task sweepTask; /** support for the editor */ transient private CloneableEditorSupport support; /** the document for this manager or null if the manager is not in memory */ transient private StyledDocument doc; static final long serialVersionUID =-4374030124265110801L; /** Creates new manager * @param supp support to work with */ public Manager(CloneableEditorSupport supp) { support = supp; init(); } /** Initialize the variables to the default values. */ protected void init() { queue = new ReferenceQueue(); // A stable mark used to simplify operations with the list head = new ChainItem(null, null, null); } /** Reads the object and initialize */ private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { Object firstObject = in.readObject(); if (firstObject instanceof DataObject) { DataObject obj = (DataObject)firstObject; support = (CloneableEditorSupport) obj.getCookie(CloneableEditorSupport.class); } else { // first object is environment CloneableEditorSupport.Env env = (CloneableEditorSupport.Env)firstObject; support = (CloneableEditorSupport)env.findCloneableOpenSupport (); } if (support == null) { //PENDING - what about now ? does exist better way ? throw new IOException(); } } final Object readResolve () { return support.getPositionManager (); } private void writeObject(ObjectOutputStream out) throws IOException { // old serialization version out.writeObject(support.findDataObject()); out.writeObject (support.env ()); } /** @return the styled document or null if the document is not loaded. */ public CloneableEditorSupport getCloneableEditorSupport () { return support; } /** Converts all positions into document one. */ void documentOpened (StyledDocument doc) { this.doc = doc; processPositions(true); } /** Closes the document and switch all positionRefs to the offset (int) * holding status (Position objects willbe forgotten. */ void documentClosed () { processPositions(false); doc = null; } /** Puts/gets positions to/from memory. It also provides full * pass sweep of the data structure (inlined in the code). * @param toMemory puts positions to memory if <code>true</code>, * from memory if <code>false</code> */ private void processPositions(boolean toMemory) { // clear the queue, we'll do the sweep inline anyway while(queue.poll() != null); counter = 0; synchronized(this) { ChainItem previous = head; ChainItem ref = previous.next; while(ref != null) { PositionRef pos = (PositionRef)ref.get(); if(pos == null) { // Remove the item from data structure. previous.next = ref.next; } else { // Process the PostionRef. if(toMemory) { pos.kind = pos.kind.toMemory(pos.insertAfter); } else { pos.kind = pos.kind.fromMemory(); } previous = ref; } ref = ref.next; } } } /** Polls queue and increases the <code>counter</code> accordingly. * Schedule full sweep task if counter exceedes 100. */ private void checkQueue() { while(queue.poll() != null) { counter++; } if(counter > 100) { counter = 0; if(sweepTask == null) { sweepTask = RequestProcessor.getDefault().post(this); } else if(sweepTask.isFinished()) { sweepTask.schedule(0); } } } /** Implements <code>Runnable</code> interface. * Does full pass sweep in <code>RequestProcessor</code> thread. */ public synchronized void run() { ChainItem previous = head; ChainItem ref = previous.next; while(ref != null) { if(ref.get() == null) { // Remove the item from data structure. previous.next = ref.next; } else { previous = ref; } ref = ref.next; } } /** Adds the position to this manager. */ Kind addPosition(PositionRef pos) { Kind kind; synchronized(this) { head.next = new ChainItem(pos, queue, head.next); kind = (doc == null ? pos.kind : pos.kind.toMemory(pos.insertAfter)); } checkQueue(); return kind; } // // Kinds // /** Loads the kind from the stream */ Kind readKind (DataInput is) throws IOException { int offset = is.readInt (); int line = is.readInt (); int column = is.readInt (); if (offset == -1) { // line and column must be valid return new LineKind (line, column); } if (line == -1 || column == -1) { // offset kind return new OffsetKind (offset); } // out of memory representation return new OutKind (offset, line, column); } // #19694. Item of special data structure replacing // for our purposed LinkedList due to performance reasons. /** One item which chained instanced provides data structure * keeping positions for this Manager. */ private static class ChainItem extends WeakReference { /** Next reference keeping the position. */ ChainItem next; /** Cointructs chanined item. * @param position <code>PositionRef</code> as referent for this * instance * @param queue <code>ReferenceQueue</code> to be used for this instance * @param next next chained item */ public ChainItem(PositionRef position, ReferenceQueue queue, ChainItem next) { super(position, queue); this.next = next; } } // End of class ChainItem. /** Base kind with all methods */ private abstract class Kind extends Object { Kind() {} /** Offset */ public abstract int getOffset (); /** Get the line number */ public abstract int getLine() throws IOException; /** Get the column number */ public abstract int getColumn() throws IOException; /** Writes the kind to stream */ public abstract void write (DataOutput os) throws IOException; /** Converts the kind to representation in memory */ public PositionKind toMemory (boolean insertAfter) { // try to find the right position Position p; try { p = NbDocument.createPosition (doc, getOffset (), insertAfter ? Position.Bias.Forward : Position.Bias.Backward); } catch (BadLocationException e) { p = doc.getEndPosition (); } return new PositionKind (p); } /** Converts the kind to representation out from memory */ public Kind fromMemory () { return this; } } /** Kind for representing position when the document is * in memory. */ private final class PositionKind extends Kind { /** position */ private Position pos; /** Constructor */ public PositionKind (Position pos) { this.pos = pos; } /** Offset */ public int getOffset () { return pos.getOffset (); } /** Get the line number */ public int getLine() { return NbDocument.findLineNumber(doc, getOffset()); } /** Get the column number */ public int getColumn() { return NbDocument.findLineColumn(doc, getOffset()); } /** Writes the kind to stream */ public void write (DataOutput os) throws IOException { int offset = getOffset(); int line = getLine(); int column = getColumn(); if(offset < 0 || line < 0 || column < 0) { throw new IOException( "Illegal PositionKind: " + pos + "[offset=" // NOI18N + offset + ",line=" // NOI18N + line + ",column=" + column + "] in " // NOI18N + doc + " used by " + support + "." // NOI18N ); } os.writeInt(offset); os.writeInt(line); os.writeInt(column); } /** Converts the kind to representation in memory */ public PositionKind toMemory (boolean insertAfter) { return this; } /** Converts the kind to representation out from memory */ public Kind fromMemory () { return new OutKind (this); } } /** Kind for representing position when the document is * out from memory. There are all infomation about the position, * including offset, line and column. */ private final class OutKind extends Kind { private int offset; private int line; private int column; /** Constructs the out kind from the position kind. */ public OutKind (PositionKind kind) { int offset = kind.getOffset(); int line = kind.getLine(); int column = kind.getColumn(); if(offset < 0 || line < 0 || column < 0) { throw new IndexOutOfBoundsException( "Illegal OutKind[offset=" // NOI18N + offset + ",line=" // NOI18N + line + ",column=" + column + "] in " // NOI18N + doc + " used by " + support + "." // NOI18N ); } this.offset = offset; this.line = line; this.column = column; } /** Constructs the out kind. */ OutKind (int offset, int line, int column) { this.offset = offset; this.line = line; this.column = column; } /** Offset */ public int getOffset () { return offset; } /** Get the line number */ public int getLine() { return line; } /** Get the column number */ public int getColumn() { return column; } /** Writes the kind to stream */ public void write (DataOutput os) throws IOException { if(offset < 0 || line < 0 || column < 0) { throw new IOException( "Illegal OutKind[offset=" // NOI18N + offset + ",line=" // NOI18N + line + ",column=" + column + "] in " // NOI18N + doc + " used by " + support + "." // NOI18N ); } os.writeInt (offset); os.writeInt (line); os.writeInt (column); } } // OutKind /** Kind for representing position when the document is * out from memory. Represents only offset in the document. */ private final class OffsetKind extends Kind { private int offset; /** Constructs the out kind from the position kind. */ public OffsetKind (int offset) { if(offset < 0) { throw new IndexOutOfBoundsException( "Illegal OffsetKind[offset=" // NOI18N + offset + "] in " + doc + " used by " // NOI18N + support + "." // NOI18N ); } this.offset = offset; } /** Offset */ public int getOffset () { return offset; } /** Get the line number */ public int getLine() throws IOException { return NbDocument.findLineNumber(getCloneableEditorSupport().openDocument(), offset); } /** Get the column number */ public int getColumn() throws IOException { return NbDocument.findLineColumn (getCloneableEditorSupport().openDocument(), offset); } /** Writes the kind to stream */ public void write (DataOutput os) throws IOException { if(offset < 0) { throw new IOException( "Illegal OffsetKind[offset=" // NOI18N + offset + "] in " + doc + " used by " // NOI18N + support + "." // NOI18N ); } os.writeInt (offset); os.writeInt (-1); os.writeInt (-1); } } /** Kind for representing position when the document is * out from memory. Represents only line and column in the document. */ private final class LineKind extends Kind { private int line; private int column; /** Constructor. */ public LineKind (int line, int column) { if(line < 0 || column < 0) { throw new IndexOutOfBoundsException( "Illegal LineKind[line=" // NOI18N + line + ",column=" + column + "] in " // NOI18N + doc + " used by " + support + "." // NOI18N ); } this.line = line; this.column = column; } /** Offset */ public int getOffset () { try { StyledDocument doc = getCloneableEditorSupport().getDocument(); if (doc == null) { doc = getCloneableEditorSupport().openDocument(); } return NbDocument.findLineOffset (doc, line) + column; } catch (IOException e) { // what to do? hopefully unlikelly return 0; } } /** Get the line number */ public int getLine() throws IOException { return line; } /** Get the column number */ public int getColumn() throws IOException { return column; } /** Writes the kind to stream */ public void write (DataOutput os) throws IOException { if(line < 0 || column < 0) { throw new IOException( "Illegal LineKind[line=" // NOI18N + line + ",column=" + column + "] in " // NOI18N + doc + " used by " + support + "." // NOI18N ); } os.writeInt (-1); os.writeInt (line); os.writeInt (column); } /** Converts the kind to representation in memory */ public PositionKind toMemory (boolean insertAfter) { // try to find the right position Position p; try { p = NbDocument.createPosition (doc, NbDocument.findLineOffset (doc, line) + column, insertAfter ? Position.Bias.Forward : Position.Bias.Backward); } catch (BadLocationException e) { p = doc.getEndPosition (); } return new PositionKind (p); } } } }