0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.openide.text;
0042:
0043: import java.util.logging.Level;
0044: import java.util.logging.Logger;
0045: import org.openide.text.EnhancedChangeEvent;
0046:
0047: import java.io.*;
0048:
0049: import java.util.*;
0050:
0051: import javax.swing.event.*;
0052: import javax.swing.text.*;
0053:
0054: /** Implementation of a line in a {@link StyledDocument}.
0055: * One object
0056: * of this class represents a line in the document by holding
0057: * a {@link PositionRef}, which can represent a position in an open or
0058: * closed document.
0059: *
0060: * @author Jaroslav Tulach, David Konecny
0061: */
0062: public abstract class DocumentLine extends Line {
0063: /** weak map that assignes to editor supports whether they have current or error line
0064: * selected. (EditorSupport, DocumentLine[2]), where Line[0] is current and Line[1] is error */
0065: private static Map<CloneableEditorSupport, DocumentLine[]> assigned = new WeakHashMap<CloneableEditorSupport, DocumentLine[]>(
0066: 5);
0067: static final long serialVersionUID = 3213776466939427487L;
0068:
0069: /** reference to one position on the line */
0070: protected PositionRef pos;
0071:
0072: /** is breakpoint there - presistent state
0073: @deprecated since 1.20 */
0074: @Deprecated
0075: private boolean breakpoint;
0076:
0077: /** error line - transient state
0078: @deprecated since 1.20 */
0079: @Deprecated
0080: private transient boolean error;
0081:
0082: /** current line - transient state
0083: @deprecated since 1.20 */
0084: @Deprecated
0085: private transient boolean current;
0086:
0087: /** listener for changes of state of the document */
0088: private transient LR listener;
0089:
0090: /** weak document listener assigned to the document or null */
0091: private transient DocumentListener docL;
0092:
0093: /** List of Line.Part which exist for this line*/
0094: private List<Part> lineParts = new ArrayList<Part>(3);
0095:
0096: /** Constructor.
0097: * @param obj context we belong to
0098: * @param pos position on the line
0099: *
0100: * @since 4.3
0101: */
0102: public DocumentLine(org.openide.util.Lookup obj, PositionRef pos) {
0103: super (obj);
0104: this .pos = pos;
0105: }
0106:
0107: /** Init listeners
0108: */
0109: void init() {
0110: listener = new LR();
0111: pos.getCloneableEditorSupport().addChangeListener(
0112: org.openide.util.WeakListeners.change(listener, pos
0113: .getCloneableEditorSupport()));
0114: }
0115:
0116: /* Get the line number.
0117: * The number may change if the
0118: * text is modified.
0119: *
0120: * @return Returns current line number.
0121: */
0122: public int getLineNumber() {
0123: try {
0124: return pos.getLine();
0125: } catch (IOException ex) {
0126: // what else?
0127: return 0;
0128: }
0129: }
0130:
0131: /* Shows the line.
0132: * @param kind one of SHOW_XXX constants.
0133: * @column the column of this line which should be selected
0134: */
0135: public abstract void show(int kind, int column);
0136:
0137: /* Sets the breakpoint. */
0138: @SuppressWarnings("deprecation")
0139: public void setBreakpoint(boolean b) {
0140: if (breakpoint != b) {
0141: breakpoint = b;
0142: refreshState();
0143: }
0144: }
0145:
0146: /* Tests if the breakpoint is set. */
0147: @SuppressWarnings("deprecation")
0148: public boolean isBreakpoint() {
0149: return breakpoint;
0150: }
0151:
0152: /* Marks the error. */
0153: @SuppressWarnings("deprecation")
0154: public void markError() {
0155: DocumentLine previous = registerLine(1, this );
0156:
0157: if (previous != null) {
0158: previous.error = false;
0159: previous.refreshState();
0160: }
0161:
0162: error = true;
0163:
0164: refreshState();
0165: }
0166:
0167: /* Unmarks error at this line. */
0168: @SuppressWarnings("deprecation")
0169: public void unmarkError() {
0170: error = false;
0171: registerLine(1, null);
0172:
0173: refreshState();
0174: }
0175:
0176: /* Marks this line as current. */
0177: @SuppressWarnings("deprecation")
0178: public void markCurrentLine() {
0179: DocumentLine previous = registerLine(0, this );
0180:
0181: if (previous != null) {
0182: previous.current = false;
0183: previous.refreshState();
0184: }
0185:
0186: current = true;
0187: refreshState();
0188: }
0189:
0190: /* Unmarks this line as current. */
0191: @SuppressWarnings("deprecation")
0192: public void unmarkCurrentLine() {
0193: current = false;
0194: registerLine(0, null);
0195:
0196: refreshState();
0197: }
0198:
0199: /** Refreshes the current line.
0200: *
0201: * @deprecated since 1.20. */
0202: @Deprecated
0203: synchronized void refreshState() {
0204: StyledDocument doc = pos.getCloneableEditorSupport()
0205: .getDocument();
0206:
0207: if (doc != null) {
0208: // the document is in memory, mark the state
0209: if (docL != null) {
0210: doc.removeDocumentListener(docL);
0211: }
0212:
0213: // error line
0214: if (error) {
0215: NbDocument.markError(doc, pos.getOffset());
0216:
0217: doc
0218: .addDocumentListener(docL = org.openide.util.WeakListeners
0219: .document(listener, doc));
0220:
0221: return;
0222: }
0223:
0224: // current line
0225: if (current) {
0226: NbDocument.markCurrent(doc, pos.getOffset());
0227:
0228: return;
0229: }
0230:
0231: // breakpoint line
0232: if (breakpoint) {
0233: NbDocument.markBreakpoint(doc, pos.getOffset());
0234:
0235: return;
0236: }
0237:
0238: NbDocument.markNormal(doc, pos.getOffset());
0239:
0240: return;
0241: }
0242: }
0243:
0244: public int hashCode() {
0245: return pos.getCloneableEditorSupport().hashCode();
0246: }
0247:
0248: public boolean equals(Object o) {
0249: if (o instanceof DocumentLine) {
0250: DocumentLine dl = (DocumentLine) o;
0251:
0252: if (dl.pos.getCloneableEditorSupport() == pos
0253: .getCloneableEditorSupport()) {
0254: return dl.getLineNumber() == getLineNumber();
0255: }
0256: }
0257:
0258: return false;
0259: }
0260:
0261: //
0262: // Work with global hash table
0263: //
0264:
0265: /** Register this line as the one stored
0266: * under indx-index (0 = current, 1 = error).
0267: *
0268: * @param indx index to register
0269: * @param line value to add (this or null)
0270: * @return the previous value
0271: *
0272: * @deprecated since 1.20 */
0273: @Deprecated
0274: private DocumentLine registerLine(int indx, DocumentLine line) {
0275: DocumentLine prev;
0276:
0277: CloneableEditorSupport es = pos.getCloneableEditorSupport();
0278:
0279: DocumentLine[] arr = assigned.get(es);
0280:
0281: if (arr != null) {
0282: // remember the previous
0283: prev = arr[indx];
0284: } else {
0285: // create new array
0286: arr = new DocumentLine[2];
0287: assigned.put(es, arr);
0288: prev = null;
0289: }
0290:
0291: arr[indx] = line;
0292:
0293: return prev;
0294: }
0295:
0296: //
0297: // Serialization
0298: //
0299:
0300: /** Write fields.
0301: */
0302: private void writeObject(ObjectOutputStream oos) throws IOException {
0303: // do not do default read/write object
0304: oos.writeObject(pos);
0305: oos.writeBoolean(breakpoint);
0306: }
0307:
0308: /** Read important fields.
0309: */
0310: private void readObject(ObjectInputStream ois) throws IOException,
0311: ClassNotFoundException {
0312: pos = (PositionRef) ois.readObject();
0313: setBreakpoint(ois.readBoolean());
0314: lineParts = new ArrayList<Part>(3);
0315: }
0316:
0317: /** Register line.
0318: */
0319: Object readResolve() throws ObjectStreamException {
0320: // return Set.registerLine (this);
0321: //Set.registerPendingLine(this);
0322: return this .pos.getCloneableEditorSupport().getLineSet()
0323: .registerLine(this );
0324: }
0325:
0326: /** Add annotation to this Annotatable class
0327: * @param anno annotation which will be attached to this class */
0328: protected void addAnnotation(Annotation anno) {
0329: super .addAnnotation(anno);
0330:
0331: StyledDocument doc = pos.getCloneableEditorSupport()
0332: .getDocument();
0333:
0334: // document is not opened and so the annotation will be added to document later
0335: if (doc == null) {
0336: return;
0337: }
0338:
0339: pos.getCloneableEditorSupport().prepareDocument()
0340: .waitFinished();
0341:
0342: try {
0343: if (!anno.isInDocument()) {
0344: anno.setInDocument(true);
0345:
0346: // #33165 - find position that is surely at begining of line
0347: FindAnnotationPosition fap = new FindAnnotationPosition(
0348: doc, pos.getPosition());
0349: doc.render(fap);
0350: NbDocument.addAnnotation(doc, fap
0351: .getAnnotationPosition(), -1, anno);
0352: }
0353: } catch (IOException ex) {
0354: Logger.getLogger(DocumentLine.class.getName()).log(
0355: Level.WARNING, null, ex);
0356: }
0357: }
0358:
0359: /** Remove annotation to this Annotatable class
0360: * @param anno annotation which will be detached from this class */
0361: protected void removeAnnotation(Annotation anno) {
0362: super .removeAnnotation(anno);
0363:
0364: StyledDocument doc = pos.getCloneableEditorSupport()
0365: .getDocument();
0366:
0367: // document is not opened and so no annotation is attached to it
0368: if (doc == null) {
0369: return;
0370: }
0371:
0372: pos.getCloneableEditorSupport().prepareDocument()
0373: .waitFinished();
0374:
0375: if (anno.isInDocument()) {
0376: anno.setInDocument(false);
0377: NbDocument.removeAnnotation(doc, anno);
0378: }
0379: }
0380:
0381: /** When document is opened or closed the annotations must be added or
0382: * removed.
0383: * @since 1.27 */
0384: void attachDetachAnnotations(StyledDocument doc, boolean closing) {
0385: // #33165 - find position that is surely at begining of line
0386: Position annoPos = null;
0387:
0388: if (!closing) {
0389: try {
0390: annoPos = pos.getPosition();
0391:
0392: FindAnnotationPosition fap = new FindAnnotationPosition(
0393: doc, annoPos);
0394: doc.render(fap);
0395: annoPos = fap.getAnnotationPosition();
0396: } catch (IOException ex) {
0397: Logger.getLogger(DocumentLine.class.getName()).log(
0398: Level.WARNING, null, ex);
0399: }
0400: }
0401:
0402: List<? extends Annotation> list = getAnnotations();
0403: synchronized (list) {
0404: for (Annotation anno : list) {
0405: if (!closing) {
0406: if (!anno.isInDocument()) {
0407: anno.setInDocument(true);
0408: NbDocument
0409: .addAnnotation(doc, annoPos, -1, anno);
0410: }
0411: } else {
0412: if (anno.isInDocument()) {
0413: anno.setInDocument(false);
0414: NbDocument.removeAnnotation(doc, anno);
0415: }
0416: }
0417: }
0418: }
0419:
0420: // notify also all Line.Part attached to this Line
0421: for (Part p : lineParts) {
0422: p.attachDetachAnnotations(doc, closing);
0423: }
0424: }
0425:
0426: public String getText() {
0427: final StyledDocument doc = pos.getCloneableEditorSupport()
0428: .getDocument();
0429:
0430: // document is not opened
0431: if (doc == null) {
0432: return null;
0433: }
0434:
0435: final String[] retStringArray = new String[1];
0436: doc.render(new Runnable() {
0437: public void run() {
0438: // Part of #33165 - the following code is wrapped by doc.render()
0439: int lineNumber = getLineNumber();
0440: int lineStart = NbDocument.findLineOffset(doc,
0441: lineNumber);
0442:
0443: // #24434: Check whether the next line exists
0444: // (the current one could be the last one).
0445: int lineEnd;
0446:
0447: if ((lineNumber + 1) >= NbDocument.findLineRootElement(
0448: doc).getElementCount()) {
0449: lineEnd = doc.getLength();
0450: } else {
0451: lineEnd = NbDocument.findLineOffset(doc,
0452: lineNumber + 1);
0453: }
0454:
0455: try {
0456: retStringArray[0] = doc.getText(lineStart, lineEnd
0457: - lineStart);
0458: } catch (BadLocationException ex) {
0459: Logger.getLogger(DocumentLine.class.getName()).log(
0460: Level.WARNING, null, ex);
0461: retStringArray[0] = null;
0462: }
0463:
0464: // End of the code wrapped by doc.render()
0465: }
0466: });
0467:
0468: return retStringArray[0];
0469: }
0470:
0471: /** Attach created Line.Part to the parent Line */
0472: void addLinePart(DocumentLine.Part linePart) {
0473: lineParts.add(linePart);
0474: }
0475:
0476: /** Move Line.Part from this Line to a new one*/
0477: void moveLinePart(DocumentLine.Part linePart, DocumentLine newLine) {
0478: lineParts.remove(linePart);
0479: newLine.addLinePart(linePart);
0480: linePart.changeLine(newLine);
0481: }
0482:
0483: /** Notify Line.Part(s) that content of the line was changed and that Line.Part(s) may be affected by that*/
0484: void notifyChange(DocumentEvent p0, DocumentLine.Set set,
0485: StyledDocument doc) {
0486: DocumentLine.Part part;
0487:
0488: for (int i = 0; i < lineParts.size();) {
0489: part = lineParts.get(i);
0490:
0491: // notify Line.Part about the change
0492: part.handleDocumentChange(p0);
0493:
0494: // if necessary move Line.Part to new Line
0495: if (NbDocument.findLineNumber(doc, part.getOffset()) != part
0496: .getLine().getLineNumber()) {
0497: DocumentLine line = (DocumentLine) set
0498: .getCurrent(NbDocument.findLineNumber(doc, part
0499: .getOffset()));
0500: moveLinePart(part, line);
0501: } else {
0502: i++;
0503: }
0504: }
0505:
0506: // #33165 - fix the position in the positionRef in case this line changes
0507: // and reattach the annotations.
0508: // The fix of #32764 in notifyMove() would only reattach
0509: // the annotations in case the position does not go at a line begining
0510: // after the modification but that is not enough
0511: // to fix undo-related issues.
0512: Position p;
0513:
0514: try {
0515: p = pos.getPosition();
0516: } catch (IOException ex) {
0517: Logger.getLogger(DocumentLine.class.getName()).log(
0518: Level.WARNING, null, ex);
0519: p = null;
0520: }
0521:
0522: if (p != null) {
0523: int lineStartOffset = NbDocument.findLineOffset(doc,
0524: NbDocument.findLineNumber(doc, p.getOffset()));
0525: CloneableEditorSupport support = pos
0526: .getCloneableEditorSupport();
0527:
0528: // Recreate positionRef unconditionally to avoid undo problems
0529: pos = new PositionRef(support.getPositionManager(),
0530: lineStartOffset, Position.Bias.Forward);
0531:
0532: List<? extends Annotation> annos = getAnnotations();
0533: synchronized (annos) {
0534: if (annos.size() > 0) {
0535: try {
0536: p = pos.getPosition();
0537: } catch (IOException e) {
0538: throw new IllegalArgumentException(); // should not fail
0539: }
0540:
0541: for (Annotation anno : annos) {
0542:
0543: if (anno.isInDocument()) {
0544: anno.setInDocument(false);
0545: NbDocument.removeAnnotation(support
0546: .getDocument(), anno);
0547: }
0548:
0549: if (!anno.isInDocument()) {
0550: anno.setInDocument(true);
0551: NbDocument.addAnnotation(support
0552: .getDocument(), p, -1, anno);
0553: }
0554: }
0555: }
0556: }
0557: }
0558: }
0559:
0560: /** Notify Line.Part(s) that line was moved. */
0561: void notifyMove() {
0562: updatePositionRef();
0563:
0564: for (int i = 0; i < lineParts.size(); i++) {
0565: ((DocumentLine.Part) lineParts.get(i)).firePropertyChange(
0566: Line.Part.PROP_LINE, null, null);
0567: }
0568: }
0569:
0570: /** Updates <code>pos</code> the way it points at the start of line. */
0571: private void updatePositionRef() {
0572: // #33165 - Moved handling that follows into notifyChange()
0573: // due to problems with undo operations.
0574: // Rest of notifyMove() should work as the code in notifyChange()
0575: // in fact includes the same work as done here and notifyChange()
0576: // is called before notifyMove()
0577: // (see linesChanged()/linesMoved() in LineListener).
0578:
0579: /* CloneableEditorSupport support = pos.getCloneableEditorSupport();
0580: int startOffset = NbDocument.findLineOffset(support.getDocument(),
0581: getLineNumber());
0582:
0583: if(pos.getOffset() != startOffset) {
0584: pos = new PositionRef(
0585: support.getPositionManager(), startOffset, Position.Bias.Forward
0586: );
0587:
0588: // fix of #32764
0589: List annos = getAnnotations();
0590: for (int i=0; i<annos.size(); i++) {
0591: Annotation anno = (Annotation)annos.get(i);
0592: if (anno.isInDocument()) {
0593: anno.setInDocument(false);
0594: NbDocument.removeAnnotation(support.getDocument(), anno);
0595: }
0596:
0597: try {
0598: if (!anno.isInDocument()) {
0599: anno.setInDocument(true);
0600: NbDocument.addAnnotation (support.getDocument(), pos.getPosition(), -1, anno);
0601: }
0602: } catch (IOException ex) {
0603: ErrorManager.getDefault ().notify ( ErrorManager.EXCEPTION, ex);
0604: }
0605: }
0606: }
0607: */
0608: }
0609:
0610: /**
0611: * Runnable used to find a position where the annotation should be attached.
0612: * It is intended to be run under document's readlock.
0613: */
0614: private static final class FindAnnotationPosition implements
0615: Runnable {
0616: private StyledDocument doc;
0617: private Position annoPos;
0618:
0619: FindAnnotationPosition(StyledDocument doc, Position pos) {
0620: this .doc = doc;
0621: this .annoPos = pos; // by default assume the given one is correct
0622: }
0623:
0624: public void run() {
0625: int offset = annoPos.getOffset();
0626: int lineStartOffset = doc.getParagraphElement(offset)
0627: .getStartOffset();
0628:
0629: if (offset != lineStartOffset) { // not at line start -> correct
0630:
0631: try {
0632: annoPos = doc.createPosition(lineStartOffset);
0633: } catch (BadLocationException e) {
0634: throw new IllegalArgumentException(); // should never fail
0635: }
0636: }
0637: }
0638:
0639: Position getAnnotationPosition() {
0640: return annoPos;
0641: }
0642: }
0643:
0644: /** Implementation of Line.Part abstract class*/
0645: static class Part extends Line.Part {
0646: /** Reference of this part to the document*/
0647: private PositionRef position;
0648:
0649: /** Reference to Line to which this part belongs*/
0650: private Line line;
0651:
0652: /** Length of the annotated text*/
0653: private int length;
0654:
0655: /** Offset of this Part before the modification. This member is used in
0656: * listener on document changes and it is updated after each change. */
0657: private int previousOffset;
0658:
0659: public Part(Line line, PositionRef position, int length) {
0660: this .position = position;
0661: this .line = line;
0662: previousOffset = position.getOffset();
0663: this .length = limitLength(length);
0664: }
0665:
0666: private int limitLength(int suggestedLength) {
0667: Document d = position.getCloneableEditorSupport()
0668: .getDocument();
0669: if (d == null) {
0670: // Can happen when closing a document, don't know why.
0671: return suggestedLength;
0672: }
0673: int end = position.getOffset() + suggestedLength;
0674:
0675: if (end > d.getLength()) {
0676: end = d.getLength();
0677: }
0678:
0679: if (end < position.getOffset()) {
0680: return 0;
0681: }
0682:
0683: try {
0684: String text = d.getText(position.getOffset(), end
0685: - position.getOffset());
0686: int newLine = text.indexOf('\n');
0687:
0688: return (newLine == -1) ? text.length() : (newLine + 1);
0689: } catch (BadLocationException ex) {
0690: IllegalStateException i = new IllegalStateException(ex
0691: .getMessage());
0692: i.initCause(ex);
0693: throw i;
0694: }
0695: }
0696:
0697: /** Start column of annotation */
0698: public int getColumn() {
0699: try {
0700: return position.getColumn();
0701: } catch (IOException ex) {
0702: return 0; //TODO: change this
0703: }
0704: }
0705:
0706: /** Length of the annotated text. The length does not cross line end. If the annotated text is
0707: * split during the editing, the annotation is shorten till the end of the line. Modules can listen on
0708: * changes of this value*/
0709: public int getLength() {
0710: return length;
0711: }
0712:
0713: /** Line can change during editting*/
0714: public Line getLine() {
0715: return line;
0716: }
0717:
0718: /** Offset of the Line.Part*/
0719: int getOffset() {
0720: return position.getOffset();
0721: }
0722:
0723: /** Line can change during editting*/
0724: void changeLine(Line line) {
0725: this .line = line;
0726:
0727: // TODO: check whether there is really some change
0728: firePropertyChange(PROP_LINE_NUMBER, null, line);
0729: }
0730:
0731: /** Add annotation to this Annotatable class
0732: * @param anno annotation which will be attached to this class */
0733: protected void addAnnotation(Annotation anno) {
0734: super .addAnnotation(anno);
0735:
0736: StyledDocument doc = position.getCloneableEditorSupport()
0737: .getDocument();
0738:
0739: // document is not opened and so the annotation will be added to document later
0740: if (doc == null) {
0741: return;
0742: }
0743:
0744: position.getCloneableEditorSupport().prepareDocument()
0745: .waitFinished();
0746:
0747: try {
0748: if (!anno.isInDocument()) {
0749: anno.setInDocument(true);
0750: NbDocument.addAnnotation(doc, position
0751: .getPosition(), length, anno);
0752: }
0753: } catch (IOException ex) {
0754: Logger.getLogger(DocumentLine.class.getName()).log(
0755: Level.WARNING, null, ex);
0756: }
0757: }
0758:
0759: /** Remove annotation to this Annotatable class
0760: * @param anno annotation which will be detached from this class */
0761: protected void removeAnnotation(Annotation anno) {
0762: super .removeAnnotation(anno);
0763:
0764: StyledDocument doc = position.getCloneableEditorSupport()
0765: .getDocument();
0766:
0767: // document is not opened and so no annotation is attached to it
0768: if (doc == null) {
0769: return;
0770: }
0771:
0772: position.getCloneableEditorSupport().prepareDocument()
0773: .waitFinished();
0774:
0775: if (anno.isInDocument()) {
0776: anno.setInDocument(false);
0777: NbDocument.removeAnnotation(doc, anno);
0778: }
0779: }
0780:
0781: public String getText() {
0782: final StyledDocument doc = position
0783: .getCloneableEditorSupport().getDocument();
0784:
0785: // document is not opened
0786: if (doc == null) {
0787: return null;
0788: }
0789:
0790: final String[] retStringArray = new String[1];
0791: doc.render(new Runnable() {
0792: public void run() {
0793: // Part of #33165 - the following code is wrapped by doc.render()
0794: try {
0795: int p = position.getOffset();
0796:
0797: if (p >= doc.getLength()) {
0798: retStringArray[0] = "";
0799: } else {
0800: retStringArray[0] = doc.getText(position
0801: .getOffset(), getLength());
0802: }
0803: } catch (BadLocationException ex) {
0804: Logger.getLogger(DocumentLine.class.getName())
0805: .log(Level.WARNING, null, ex);
0806: retStringArray[0] = null;
0807: }
0808:
0809: // End of the code wrapped by doc.render()
0810: }
0811: });
0812:
0813: return retStringArray[0];
0814: }
0815:
0816: /** When document is opened or closed the annotations must be added or
0817: * removed.*/
0818: void attachDetachAnnotations(StyledDocument doc, boolean closing) {
0819: List<? extends Annotation> list = getAnnotations();
0820:
0821: synchronized (list) {
0822: for (Annotation anno : list) {
0823:
0824: if (!closing) {
0825: try {
0826: if (!anno.isInDocument()) {
0827: anno.setInDocument(true);
0828: NbDocument.addAnnotation(doc, position
0829: .getPosition(), getLength(),
0830: anno);
0831: }
0832: } catch (IOException ex) {
0833: Logger.getLogger(
0834: DocumentLine.class.getName()).log(
0835: Level.WARNING, null, ex);
0836: }
0837: } else {
0838: if (anno.isInDocument()) {
0839: anno.setInDocument(false);
0840: NbDocument.removeAnnotation(doc, anno);
0841: }
0842: }
0843: }
0844: }
0845: }
0846:
0847: /** Handle DocumentChange event. If the change affect this Part, fire
0848: * the PROP_TEXT event. */
0849: void handleDocumentChange(DocumentEvent p0) {
0850: if (p0.getType().equals(DocumentEvent.EventType.INSERT)) {
0851: if ((p0.getOffset() >= previousOffset)
0852: && (p0.getOffset() < (previousOffset + getLength()))) {
0853: firePropertyChange(Annotatable.PROP_TEXT, null,
0854: null);
0855: }
0856: }
0857:
0858: if (p0.getType().equals(DocumentEvent.EventType.REMOVE)) {
0859: if (((p0.getOffset() >= previousOffset) && (p0
0860: .getOffset() < (previousOffset + getLength())))
0861: || ((p0.getOffset() < previousOffset) && ((p0
0862: .getOffset() + p0.getLength()) > previousOffset))) {
0863: length = limitLength(length);
0864: firePropertyChange(Annotatable.PROP_TEXT, null,
0865: null);
0866: }
0867: }
0868:
0869: if ((p0.getType().equals(DocumentEvent.EventType.INSERT) || p0
0870: .getType().equals(DocumentEvent.EventType.REMOVE))
0871: && (p0.getOffset() < previousOffset)) {
0872: firePropertyChange(Line.Part.PROP_COLUMN, null, null);
0873: }
0874:
0875: previousOffset = position.getOffset();
0876: }
0877: }
0878:
0879: /** Definition of actions performed in Listener */
0880: private final class LR implements Runnable, ChangeListener,
0881: DocumentListener {
0882: private static final int REFRESH = 0;
0883: private static final int UNMARK = 1;
0884: private static final int ATTACH_DETACH = 2;
0885: private int actionId;
0886: private EnhancedChangeEvent ev;
0887:
0888: public LR() {
0889: }
0890:
0891: public LR(int actionId) {
0892: this .actionId = actionId;
0893: }
0894:
0895: public LR(EnhancedChangeEvent ev) {
0896: this .actionId = ATTACH_DETACH;
0897: this .ev = ev;
0898: }
0899:
0900: public void run() {
0901: switch (actionId) {
0902: case REFRESH:
0903: refreshState();
0904:
0905: break;
0906:
0907: case UNMARK:
0908: unmarkError();
0909:
0910: break;
0911:
0912: case ATTACH_DETACH:
0913: attachDetachAnnotations(ev.getDocument(), ev
0914: .isClosingDocument());
0915: ev = null;
0916:
0917: break;
0918: }
0919: }
0920:
0921: private void invoke(int op) {
0922: // part of #33165 - done synchronously not invoking into EQ
0923: //SwingUtilities.invokeLater(new LR(op));
0924: new LR(op).run();
0925: }
0926:
0927: private void invoke(EnhancedChangeEvent ev) {
0928: // part of #33165 - done synchronously not invoking into EQ
0929: //SwingUtilities.invokeLater(new LR(ev));
0930: new LR(ev).run();
0931: }
0932:
0933: public void stateChanged(ChangeEvent ev) {
0934: invoke(REFRESH);
0935: invoke((EnhancedChangeEvent) ev);
0936: }
0937:
0938: public void removeUpdate(
0939: final javax.swing.event.DocumentEvent p0) {
0940: invoke(UNMARK);
0941: }
0942:
0943: public void insertUpdate(
0944: final javax.swing.event.DocumentEvent p0) {
0945: invoke(UNMARK);
0946: }
0947:
0948: public void changedUpdate(
0949: final javax.swing.event.DocumentEvent p0) {
0950: }
0951: }
0952:
0953: /** Abstract implementation of {@link Line.Set}.
0954: * Defines
0955: * ways to obtain a line set for documents following
0956: * NetBeans conventions.
0957: */
0958: public static abstract class Set extends Line.Set {
0959: /** listener on document changes, accessed from LazyLines */
0960: final LineListener listener;
0961:
0962: /** all lines in the set or null */
0963: private List<Line> list;
0964:
0965: /** Constructor.
0966: * @param doc document to work on
0967: */
0968: public Set(StyledDocument doc) {
0969: this (doc, null);
0970: }
0971:
0972: Set(StyledDocument doc, CloneableEditorSupport support) {
0973: listener = new LineListener(doc, support);
0974: }
0975:
0976: /** Find the line given as parameter in list of all lines attached to this set
0977: * and if the line exist in the list, notify it about being edited. */
0978: void linesChanged(int startLineNumber, int endLineNumber,
0979: DocumentEvent p0) {
0980: List changedLines = getLinesFromRange(startLineNumber,
0981: endLineNumber);
0982: StyledDocument doc = listener.support.getDocument();
0983:
0984: for (Iterator it = changedLines.iterator(); it.hasNext();) {
0985: Line line = (Line) it.next();
0986:
0987: line.firePropertyChange(Annotatable.PROP_TEXT, null,
0988: null);
0989:
0990: // revalidate all parts attached to this line
0991: // that they are still part of the line
0992: if (doc != null && line instanceof DocumentLine) {
0993: ((DocumentLine) line).notifyChange(p0, this , doc);
0994: }
0995: }
0996: }
0997:
0998: /** Find the line given as parameter in list of all lines attached to this set
0999: * and if the line exist in the list, notify it about being moved. */
1000: void linesMoved(int startLineNumber, int endLineNumber) {
1001: List movedLines = getLinesFromRange(startLineNumber,
1002: endLineNumber);
1003:
1004: for (Iterator it = movedLines.iterator(); it.hasNext();) {
1005: Line line = (Line) it.next();
1006: line.firePropertyChange(Line.PROP_LINE_NUMBER, null,
1007: null);
1008:
1009: // notify all parts attached to this line
1010: // that they were moved
1011: if (line instanceof DocumentLine) {
1012: ((DocumentLine) line).notifyMove();
1013: }
1014: }
1015: }
1016:
1017: /** Gets the lines with line number whitin the range inclusive.
1018: * @return <code>List</code> of lines from range inclusive */
1019: private List<Line> getLinesFromRange(int startLineNumber,
1020: int endLineNumber) {
1021: List<Line> linesInRange = new ArrayList<Line>(10);
1022:
1023: synchronized (findWeakHashMap()) {
1024: for (Line line : findWeakHashMap().keySet()) {
1025: int lineNumber = line.getLineNumber();
1026:
1027: if ((startLineNumber <= lineNumber)
1028: && (lineNumber <= endLineNumber)) {
1029: linesInRange.add(line);
1030: }
1031: }
1032: }
1033:
1034: return linesInRange;
1035: }
1036:
1037: /* Returns an unmodifiable set of Lines sorted by their
1038: * line numbers that contains all lines holded by this
1039: * Line.Set.
1040: *
1041: * @return list of Line objects
1042: */
1043: public synchronized List<? extends Line> getLines() {
1044: if (list == null) {
1045: list = new LazyLines(this );
1046: }
1047:
1048: return list;
1049: }
1050:
1051: /* Finder method that for the given line number finds right
1052: * Line object that represent as closely as possible the line number
1053: * in the time when the Line.Set has been created.
1054: *
1055: * @param line is a number of the line (text line) we want to acquire
1056: * @exception IndexOutOfBoundsException if <code>line</code> is invalid.
1057: */
1058: public Line getOriginal(int line)
1059: throws IndexOutOfBoundsException {
1060: int newLine = listener.getLine(line);
1061: return safelyRegisterLine(createLine(offset(newLine)));
1062: }
1063:
1064: public int getOriginalLineNumber(Line line) {
1065: Line find = findLine(line);
1066:
1067: if (find != null) {
1068: return listener.getOld(find.getLineNumber());
1069: } else {
1070: return -1;
1071: }
1072: }
1073:
1074: /* Creates current line.
1075: *
1076: * @param line is a number of the line (text line) we want to acquire
1077: * @exception IndexOutOfBoundsException if <code>line</code> is invalid.
1078: */
1079: public Line getCurrent(int line)
1080: throws IndexOutOfBoundsException {
1081:
1082: return safelyRegisterLine(createLine(offset(line)));
1083: }
1084:
1085: private int offset(int line) {
1086: StyledDocument doc = listener.support.getDocument();
1087: int offset = doc == null ? 0 : NbDocument.findLineOffset(
1088: doc, line);
1089: return offset;
1090: }
1091:
1092: /** Creates a {@link Line} for a given offset.
1093: * @param offset the beginning offset of the line
1094: * @return line object representing the line at this offset
1095: */
1096: protected abstract Line createLine(int offset);
1097:
1098: /** Registers line, but only after obtaining the lock of the document.
1099: * This is a fix to issue 37767 as this creates ordering of locks (first
1100: * of all obtain documentrenderer, then ask for any other locks like
1101: * Line.Set.lines.
1102: *
1103: * @param line line we want to register
1104: * @return the line or some line that already was registered
1105: */
1106: private Line safelyRegisterLine(final Line line) {
1107: assert line != null;
1108: class DocumentRenderer implements Runnable {
1109: public Line result;
1110:
1111: public void run() {
1112: result = DocumentLine.Set.super .registerLine(line);
1113: }
1114: }
1115: StyledDocument doc = listener.support.getDocument();
1116: DocumentRenderer renderer = new DocumentRenderer();
1117: if (doc != null) {
1118: doc.render(renderer);
1119: } else {
1120: renderer.run();
1121: }
1122:
1123: return renderer.result;
1124: }
1125: }
1126: }
|