0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013:
0014: package org.netbeans.editor;
0015:
0016: import java.beans.PropertyChangeEvent;
0017: import java.beans.PropertyChangeListener;
0018: import java.io.IOException;
0019: import java.io.Reader;
0020: import java.io.Writer;
0021: import java.util.ArrayList;
0022: import java.util.Dictionary;
0023: import java.util.Enumeration;
0024: import java.util.Hashtable;
0025:
0026: import javax.swing.event.DocumentEvent;
0027: import javax.swing.event.EventListenerList;
0028: import javax.swing.event.UndoableEditEvent;
0029: import javax.swing.text.AbstractDocument;
0030: import javax.swing.text.AttributeSet;
0031: import javax.swing.text.BadLocationException;
0032: import javax.swing.text.DefaultEditorKit;
0033: import javax.swing.text.Document;
0034: import javax.swing.text.Element;
0035: import javax.swing.text.Position;
0036: import javax.swing.text.StyleConstants;
0037: import javax.swing.undo.CannotRedoException;
0038: import javax.swing.undo.CannotUndoException;
0039: import javax.swing.undo.CompoundEdit;
0040: import javax.swing.undo.UndoableEdit;
0041:
0042: /**
0043: * Document implementation
0044: *
0045: * @author Miloslav Metelka
0046: * @version 1.00
0047: */
0048:
0049: public class BaseDocument extends AbstractDocument implements
0050: SettingsChangeListener {
0051:
0052: /** Registry identification property */
0053: public static final String ID_PROP = "id"; // NOI18N
0054:
0055: /** Line separator property for reading files in */
0056: public static final String READ_LINE_SEPARATOR_PROP = DefaultEditorKit.EndOfLineStringProperty;
0057:
0058: /**
0059: * Line separator property for writing content into files. If not set the
0060: * writing defaults to the READ_LINE_SEPARATOR_PROP.
0061: */
0062: public static final String WRITE_LINE_SEPARATOR_PROP = "write-line-separator"; // NOI18N
0063:
0064: /** File name property */
0065: public static final String FILE_NAME_PROP = "file-name"; // NOI18N
0066:
0067: /** Wrap search mark property */
0068: public static final String WRAP_SEARCH_MARK_PROP = "wrap-search-mark"; // NOI18N
0069:
0070: /**
0071: * Undo manager property. This can be used to implement undo in a simple
0072: * way. Default undo and redo actions try to get this property and perform
0073: * undo and redo through it.
0074: */
0075: public static final String UNDO_MANAGER_PROP = "undo-manager"; // NOI18N
0076:
0077: /**
0078: * Kit class property. This can become useful for getting the settings that
0079: * logicaly belonging to the document.
0080: */
0081: public static final String KIT_CLASS_PROP = "kit-class"; // NOI18N
0082:
0083: /** String forward finder property */
0084: public static final String STRING_FINDER_PROP = "string-finder"; // NOI18N
0085:
0086: /** String backward finder property */
0087: public static final String STRING_BWD_FINDER_PROP = "string-bwd-finder"; // NOI18N
0088:
0089: /** Highlight search finder property. */
0090: public static final String BLOCKS_FINDER_PROP = "blocks-finder"; // NOI18N
0091:
0092: /**
0093: * Maximum line width encountered during the initial read operation. This is
0094: * filled by Analyzer and used by UI to set the correct initial width of the
0095: * component. Values: java.lang.Integer
0096: */
0097: public static final String LINE_LIMIT_PROP = "line-limit"; // NOI18N
0098:
0099: /**
0100: * Size of the line batch. Line batch can be used at various places
0101: * especially when processing lines by syntax scanner.
0102: */
0103: public static final String LINE_BATCH_SIZE = "line-batch-size"; // NOI18N
0104:
0105: /** Line separator is marked by CR (Macintosh) */
0106: public static final String LS_CR = "\r"; // NOI18N
0107:
0108: /** Line separator is marked by LF (Unix) */
0109: public static final String LS_LF = "\n"; // NOI18N
0110:
0111: /** Line separator is marked by CR and LF (Windows) */
0112: public static final String LS_CRLF = "\r\n"; // NOI18N
0113:
0114: /**
0115: * Maximum of concurrent read threads (other will wait until one of these
0116: * will leave).
0117: */
0118: private static final int MAX_READ_THREADS = 10;
0119:
0120: /** Write lock without write lock */
0121: private static final String WRITE_LOCK_MISSING = "extWriteUnlock() without extWriteLock()"; // NOI18N
0122:
0123: /** Debug modifications performed on the document */
0124: private static final boolean debug = Boolean
0125: .getBoolean("netbeans.debug.editor.document"); // NOI18N
0126: /** Debug the stack of calling of the insert/remove */
0127: private static final boolean debugStack = Boolean
0128: .getBoolean("netbeans.debug.editor.document.stack"); // NOI18N
0129:
0130: /**
0131: * Document operations support class for this document. It presents the base
0132: * synchronization level for most of the operations. Some of the operations
0133: * are available through <tt>Utilities</tt> class.
0134: */
0135: DocOp op;
0136:
0137: /** How many spaces should be displayed instead of '\t' character */
0138: private int tabSize = SettingsDefaults.defaultTabSize.intValue();
0139:
0140: /**
0141: * Size of one indentation level. If this variable is null (value is not set
0142: * in Settings, then the default algorithm will be used.
0143: */
0144: private Integer shiftWidth;
0145:
0146: /** How many times current writer requested writing */
0147: private int writeDeep;
0148:
0149: /** How many times atomic writer requested writing */
0150: private int atomicDepth;
0151:
0152: /* Was the document initialized by reading? */
0153: protected boolean inited;
0154:
0155: /* Was the document modified by doing inert/remove */
0156: protected boolean modified;
0157:
0158: /** Listener list */
0159: protected EventListenerList listenerList = new EventListenerList();
0160:
0161: /** Listener to changes in find support */
0162: PropertyChangeListener findSupportListener;
0163:
0164: /** Default element - lazily inited */
0165: protected BaseElement defaultRootElem;
0166:
0167: private SyntaxSupport syntaxSupport;
0168:
0169: /** Layer list for document level layers */
0170: private DrawLayerList drawLayerList = new DrawLayerList();
0171:
0172: /** Chain of document level bookmarks */
0173: private MarkChain bookmarkChain;
0174:
0175: /** Reset merging next created undoable edit to the last one. */
0176: boolean undoMergeReset;
0177:
0178: /** Kit class stored here */
0179: Class kitClass;
0180:
0181: /**
0182: * Undo event for atomic events is fired after the successful atomic
0183: * operation is finished. The changes are stored in this variable during the
0184: * atomic operation. If the operation is broken, these edits are used to
0185: * restore previous state.
0186: */
0187: private CompoundEdit atomicEdits;
0188:
0189: private Acceptor identifierAcceptor;
0190:
0191: private Acceptor whitespaceAcceptor;
0192:
0193: private ArrayList syntaxList = new ArrayList();
0194:
0195: /** List of the positions used by storePosition() */
0196: private ArrayList posList = new ArrayList();
0197:
0198: /** List of the integers marking the free positions in the posList. */
0199: private ArrayList posFreeList = new ArrayList();
0200:
0201: /** Root element of line elements representation */
0202: protected LineRootElement lineRootElement;
0203:
0204: private LeafElement composedTextElement;
0205:
0206: /**
0207: * Last document event to be undone. The field is filled by the lastly done
0208: * modification undoable edit. BaseDocumentEvent.canUndo() checks this flag.
0209: */
0210: UndoableEdit lastModifyUndoEdit; // #8692 check last modify undo edit
0211:
0212: /** List of annotations for this document. */
0213: private Annotations annotations;
0214:
0215: /** List of bookmarks attached to this document */
0216: private Bookmarks bookmarks;
0217:
0218: /**
0219: * Create base document with a specified syntax.
0220: *
0221: * @param kitClass
0222: * class used to initialize this document with proper settings
0223: * category based on the editor kit for which this document is
0224: * created
0225: * @param syntax
0226: * syntax scanner to use with this document
0227: */
0228: public BaseDocument(Class kitClass, boolean addToRegistry) {
0229: this (kitClass, addToRegistry, new DocOp());
0230: }
0231:
0232: private BaseDocument(Class kitClass, boolean addToRegistry, DocOp op) {
0233: super (op);
0234: this .op = op;
0235: this .kitClass = kitClass;
0236:
0237: setDocumentProperties(new LazyPropertyMap(
0238: getDocumentProperties()));
0239:
0240: settingsChange(null); // initialize variables from settings
0241: Settings.addSettingsChangeListener(this );
0242:
0243: op.setDocument(this );
0244:
0245: // Line separators default to platform ones
0246: putProperty(READ_LINE_SEPARATOR_PROP, Analyzer.getPlatformLS());
0247:
0248: bookmarkChain = new MarkChain(this ,
0249: DrawLayerFactory.BOOKMARK_LAYER_NAME);
0250:
0251: // Add document draw-layers
0252: addLayer(new DrawLayerFactory.SyntaxLayer(),
0253: DrawLayerFactory.SYNTAX_LAYER_VISIBILITY);
0254:
0255: addLayer(new DrawLayerFactory.HighlightSearchLayer(),
0256: DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_VISIBILITY);
0257:
0258: addLayer(new DrawLayerFactory.BookmarkLayer(),
0259: DrawLayerFactory.BOOKMARK_LAYER_VISIBILITY);
0260:
0261: // Additional initialization of the document through the kit
0262: BaseKit kit = BaseKit.getKit(kitClass);
0263: if (kit != null) {
0264: kit.initDocument(this );
0265: }
0266:
0267: // Possibly add the document to registry
0268: if (addToRegistry) {
0269: Registry.addDocument(this ); // add if created thru the kit
0270: }
0271:
0272: // Start listen on find-support
0273: findSupportListener = new PropertyChangeListener() {
0274: public void propertyChange(PropertyChangeEvent evt) {
0275: findSupportChange(evt);
0276: }
0277: };
0278: FindSupport.getFindSupport().addPropertyChangeListener(
0279: findSupportListener);
0280: findSupportChange(null); // update doc by find settings
0281: }
0282:
0283: private void findSupportChange(PropertyChangeEvent evt) {
0284: // set all finders to null
0285: putProperty(STRING_FINDER_PROP, null);
0286: putProperty(STRING_BWD_FINDER_PROP, null);
0287: putProperty(BLOCKS_FINDER_PROP, null);
0288:
0289: DrawLayerFactory.HighlightSearchLayer hsl = (DrawLayerFactory.HighlightSearchLayer) findLayer(DrawLayerFactory.HIGHLIGHT_SEARCH_LAYER_NAME);
0290:
0291: Boolean b = (Boolean) FindSupport.getFindSupport()
0292: .getPropertyNoInit(SettingsNames.FIND_HIGHLIGHT_SEARCH);
0293: hsl.setEnabled((b != null) ? b.booleanValue() : false);
0294:
0295: fireChangedUpdate(createDocumentEvent(0, getLength(),
0296: DocumentEvent.EventType.CHANGE)); // refresh
0297: // whole
0298: // document
0299: }
0300:
0301: /**
0302: * Called when settings were changed. The method is called also in
0303: * constructor, so the code must count with the evt being null.
0304: */
0305: public void settingsChange(SettingsChangeEvent evt) {
0306: String settingName = (evt != null) ? evt.getSettingName()
0307: : null;
0308:
0309: if (settingName == null
0310: || SettingsNames.TAB_SIZE.equals(settingName)) {
0311: tabSize = SettingsUtil.getPositiveInteger(kitClass,
0312: SettingsNames.TAB_SIZE,
0313: SettingsDefaults.defaultTabSize);
0314: }
0315:
0316: if (settingName == null
0317: || SettingsNames.INDENT_SHIFT_WIDTH.equals(settingName)) {
0318: Object shw = Settings.getValue(kitClass,
0319: SettingsNames.INDENT_SHIFT_WIDTH);
0320: if (shw instanceof Integer) { // currently only Integer values are
0321: // supported
0322: shiftWidth = (Integer) shw;
0323: }
0324: }
0325:
0326: if (settingName == null
0327: || SettingsNames.READ_BUFFER_SIZE.equals(settingName)) {
0328: int readBufferSize = SettingsUtil.getPositiveInteger(
0329: kitClass, SettingsNames.READ_BUFFER_SIZE,
0330: SettingsDefaults.defaultReadBufferSize);
0331: putProperty(SettingsNames.READ_BUFFER_SIZE, new Integer(
0332: readBufferSize));
0333: }
0334:
0335: if (settingName == null
0336: || SettingsNames.WRITE_BUFFER_SIZE.equals(settingName)) {
0337: int writeBufferSize = SettingsUtil.getPositiveInteger(
0338: kitClass, SettingsNames.WRITE_BUFFER_SIZE,
0339: SettingsDefaults.defaultWriteBufferSize);
0340: putProperty(SettingsNames.WRITE_BUFFER_SIZE, new Integer(
0341: writeBufferSize));
0342: }
0343:
0344: if (settingName == null
0345: || SettingsNames.MARK_DISTANCE.equals(settingName)) {
0346: int markDistance = SettingsUtil.getPositiveInteger(
0347: kitClass, SettingsNames.MARK_DISTANCE,
0348: SettingsDefaults.defaultMarkDistance);
0349: putProperty(SettingsNames.MARK_DISTANCE, new Integer(
0350: markDistance));
0351: }
0352:
0353: if (settingName == null
0354: || SettingsNames.MAX_MARK_DISTANCE.equals(settingName)) {
0355: int maxMarkDistance = SettingsUtil.getPositiveInteger(
0356: kitClass, SettingsNames.MAX_MARK_DISTANCE,
0357: SettingsDefaults.defaultMaxMarkDistance);
0358: putProperty(SettingsNames.MAX_MARK_DISTANCE, new Integer(
0359: maxMarkDistance));
0360: }
0361:
0362: if (settingName == null
0363: || SettingsNames.MIN_MARK_DISTANCE.equals(settingName)) {
0364: int minMarkDistance = SettingsUtil.getPositiveInteger(
0365: kitClass, SettingsNames.MIN_MARK_DISTANCE,
0366: SettingsDefaults.defaultMinMarkDistance);
0367: putProperty(SettingsNames.MIN_MARK_DISTANCE, new Integer(
0368: minMarkDistance));
0369: }
0370:
0371: if (settingName == null
0372: || SettingsNames.READ_MARK_DISTANCE.equals(settingName)) {
0373: int readMarkDistance = SettingsUtil.getPositiveInteger(
0374: kitClass, SettingsNames.READ_MARK_DISTANCE,
0375: SettingsDefaults.defaultReadMarkDistance);
0376: putProperty(SettingsNames.READ_MARK_DISTANCE, new Integer(
0377: readMarkDistance));
0378: }
0379:
0380: if (settingName == null
0381: || SettingsNames.SYNTAX_UPDATE_BATCH_SIZE
0382: .equals(settingName)) {
0383: int syntaxUpdateBatchSize = SettingsUtil
0384: .getPositiveInteger(
0385: kitClass,
0386: SettingsNames.SYNTAX_UPDATE_BATCH_SIZE,
0387: SettingsDefaults.defaultSyntaxUpdateBatchSize);
0388: putProperty(SettingsNames.SYNTAX_UPDATE_BATCH_SIZE,
0389: new Integer(syntaxUpdateBatchSize));
0390: }
0391:
0392: if (settingName == null
0393: || SettingsNames.LINE_BATCH_SIZE.equals(settingName)) {
0394: int lineBatchSize = SettingsUtil.getPositiveInteger(
0395: kitClass, SettingsNames.LINE_BATCH_SIZE,
0396: SettingsDefaults.defaultLineBatchSize);
0397: putProperty(SettingsNames.LINE_BATCH_SIZE, new Integer(
0398: lineBatchSize));
0399: }
0400:
0401: if (settingName == null
0402: || SettingsNames.IDENTIFIER_ACCEPTOR
0403: .equals(settingName)) {
0404: identifierAcceptor = SettingsUtil.getAcceptor(kitClass,
0405: SettingsNames.IDENTIFIER_ACCEPTOR,
0406: AcceptorFactory.LETTER_DIGIT);
0407: }
0408:
0409: if (settingName == null
0410: || SettingsNames.WHITESPACE_ACCEPTOR
0411: .equals(settingName)) {
0412: whitespaceAcceptor = SettingsUtil.getAcceptor(kitClass,
0413: SettingsNames.WHITESPACE_ACCEPTOR,
0414: AcceptorFactory.WHITESPACE);
0415: }
0416:
0417: boolean stopOnEOL = SettingsUtil.getBoolean(kitClass,
0418: SettingsNames.WORD_MOVE_NEWLINE_STOP, true);
0419: if (settingName == null
0420: || SettingsNames.NEXT_WORD_FINDER.equals(settingName)) {
0421: putProperty(SettingsNames.NEXT_WORD_FINDER, SettingsUtil
0422: .getValue(kitClass, SettingsNames.NEXT_WORD_FINDER,
0423: new FinderFactory.NextWordFwdFinder(this ,
0424: stopOnEOL, false)));
0425: }
0426:
0427: if (settingName == null
0428: || SettingsNames.PREVIOUS_WORD_FINDER
0429: .equals(settingName)) {
0430: putProperty(SettingsNames.PREVIOUS_WORD_FINDER,
0431: SettingsUtil.getValue(kitClass,
0432: SettingsNames.PREVIOUS_WORD_FINDER,
0433: new FinderFactory.PreviousWordBwdFinder(
0434: this , stopOnEOL, false)));
0435: }
0436:
0437: }
0438:
0439: Syntax getFreeSyntax() {
0440: synchronized (syntaxList) {
0441: int cnt = syntaxList.size();
0442: return (cnt > 0) ? (Syntax) syntaxList.remove(cnt - 1)
0443: : BaseKit.getKit(kitClass).createSyntax(this );
0444: }
0445: }
0446:
0447: void releaseSyntax(Syntax syntax) {
0448: synchronized (syntaxList) {
0449: syntaxList.add(syntax);
0450: }
0451: }
0452:
0453: /** Get the formatter for this document. */
0454: public Formatter getFormatter() {
0455: return Formatter.getFormatter(kitClass);
0456: }
0457:
0458: public SyntaxSupport getSyntaxSupport() {
0459: if (syntaxSupport == null) {
0460: syntaxSupport = BaseKit.getKit(kitClass)
0461: .createSyntaxSupport(this );
0462: }
0463: return syntaxSupport;
0464: }
0465:
0466: /**
0467: * Perform any generic text processing. The advantage of this method is that
0468: * it allows the text to processed in line batches. The initial size of the
0469: * batch is given by the SettingsNames.LINE_BATCH_SIZE. The
0470: * TextBatchProcessor.processTextBatch() method is called for every text
0471: * batch. If the method returns true, it means the processing should
0472: * continue with the next batch of text which will have double line count
0473: * compared to the previous one. This guarantees there will be not too many
0474: * batches so the processing should be more efficient.
0475: *
0476: * @param tbp
0477: * text batch processor to be used to process the text batches
0478: * @param startPos
0479: * starting position of the processing.
0480: * @param endPos
0481: * ending position of the processing. This can be -1 to signal
0482: * the end of document. If the endPos is lower than startPos then
0483: * the batches are created in the backward direction.
0484: * @return the returned value from the last tpb.processTextBatch() call. The
0485: * -1 will be returned for (startPos == endPos).
0486: */
0487: public int processText(TextBatchProcessor tbp, int startPos,
0488: int endPos) throws BadLocationException {
0489: if (endPos == -1) {
0490: endPos = getLength();
0491: }
0492: int batchLineCnt = ((Integer) getProperty(SettingsNames.LINE_BATCH_SIZE))
0493: .intValue();
0494: int batchStart = startPos;
0495: int ret = -1;
0496: if (startPos < endPos) { // batching in forward direction
0497: while (ret < 0 && batchStart < endPos) {
0498: int batchEnd = Math.min(Utilities.getRowStart(this ,
0499: batchStart, batchLineCnt), endPos);
0500: if (batchEnd == -1) { // getRowStart() returned -1
0501: batchEnd = endPos;
0502: }
0503: ret = tbp.processTextBatch(this , batchStart, batchEnd,
0504: (batchEnd == endPos));
0505: batchLineCnt *= 2; // double the scanned area
0506: batchStart = batchEnd;
0507: }
0508: } else {
0509: while (ret < 0 && batchStart > endPos) {
0510: int batchEnd = Math.max(Utilities.getRowStart(this ,
0511: batchStart, -batchLineCnt), endPos);
0512: ret = tbp.processTextBatch(this , batchStart, batchEnd,
0513: (batchEnd == endPos));
0514: batchLineCnt *= 2; // double the scanned area
0515: batchStart = batchEnd;
0516: }
0517: }
0518: return ret;
0519: }
0520:
0521: public boolean isIdentifierPart(char ch) {
0522: return identifierAcceptor.accept(ch);
0523: }
0524:
0525: public boolean isWhitespace(char ch) {
0526: return whitespaceAcceptor.accept(ch);
0527: }
0528:
0529: /**
0530: * Length of document.
0531: *
0532: * @return number of characters >= 0
0533: */
0534: public final int getLength() {
0535: return op.length();
0536: }
0537:
0538: /**
0539: * Create the mark for the given position and store it in the list. The
0540: * position can be later retrieved through its ID.
0541: */
0542: int storePosition(int pos) throws BadLocationException {
0543: Mark mark = op.insertMark(pos, false);
0544: int ind;
0545: if (posFreeList.size() > 0) {
0546: ind = ((Integer) posFreeList.remove(posFreeList.size() - 1))
0547: .intValue();
0548: posList.set(ind, mark);
0549: } else { // no free indexes
0550: ind = posList.size();
0551: posList.add(mark);
0552: }
0553: return ind;
0554: }
0555:
0556: int getStoredPosition(int posID) {
0557: if (posID < 0 || posID >= posList.size()) {
0558: return -1;
0559: }
0560:
0561: try {
0562: return ((Mark) posList.get(posID)).getOffset();
0563: } catch (InvalidMarkException e) {
0564: return -1;
0565: }
0566: }
0567:
0568: void removeStoredPosition(int posID) {
0569: if (posID >= 0 || posID < posList.size()) {
0570: Mark mark = (Mark) posList.get(posID);
0571: posList.set(posID, null); // clear the index
0572: posFreeList.add(new Integer(posID));
0573:
0574: // Remove the mark #19429
0575: try {
0576: mark.remove();
0577: } catch (InvalidMarkException e) {
0578: }
0579: }
0580: }
0581:
0582: /** Inserts string into document */
0583: public void insertString(int offset, String text, AttributeSet a)
0584: throws BadLocationException {
0585: if (text == null || text.length() == 0) {
0586: return;
0587: }
0588:
0589: // Check offset correctness
0590: if (offset < 0 || offset > getLength()) {
0591: throw new BadLocationException("Wrong insert position",
0592: offset); // NOI18N
0593: }
0594:
0595: // possible CR-LF conversion
0596: text = Analyzer.convertLSToLF(text);
0597:
0598: // Perform the insert
0599: extWriteLock();
0600: try {
0601:
0602: preInsertCheck(offset, text, a);
0603:
0604: // Do the real insert into the content
0605: UndoableEdit edit = op.insertString(offset, text);
0606:
0607: if (debug) {
0608: System.err.println("BaseDocument.insertString(): doc="
0609: + this // NOI18N
0610: + (modified ? "" : " - first modification") // NOI18N
0611: + ", offset=" + offset // NOI18N
0612: + ", text='" + text + "'" // NOI18N
0613: );
0614: }
0615: if (debugStack) {
0616: Thread.dumpStack();
0617: }
0618:
0619: BaseDocumentEvent evt = createDocumentEvent(offset, text
0620: .length(), DocumentEvent.EventType.INSERT);
0621: if (edit != null) {
0622: evt.addEdit(edit);
0623:
0624: lastModifyUndoEdit = edit; // #8692 check last modify undo edit
0625: }
0626:
0627: modified = true;
0628:
0629: if (atomicDepth > 0) {
0630: if (atomicEdits == null) {
0631: atomicEdits = new AtomicCompoundEdit();
0632: }
0633: atomicEdits.addEdit(evt); // will be added
0634: }
0635:
0636: insertUpdate(evt, a);
0637:
0638: evt.end();
0639:
0640: fireInsertUpdate(evt);
0641:
0642: boolean isComposedText = ((a != null) && (a
0643: .isDefined(StyleConstants.ComposedTextAttribute)));
0644:
0645: if (atomicDepth == 0 && !isComposedText) { // !!! check
0646: fireUndoableEditUpdate(new UndoableEditEvent(this , evt));
0647: }
0648: } finally {
0649: extWriteUnlock();
0650: }
0651: }
0652:
0653: /** Removes portion of a document */
0654: public void remove(int offset, int len) throws BadLocationException {
0655: if (len > 0) {
0656: extWriteLock();
0657: try {
0658: int docLen = getLength();
0659: if (offset < 0 || offset > docLen) {
0660: throw new BadLocationException(
0661: "Wrong remove position", offset); // NOI18N
0662: }
0663: if (offset + len > docLen) {
0664: throw new BadLocationException(
0665: "End offset of removed text is too big",
0666: offset + len); // NOI18N
0667: }
0668:
0669: preRemoveCheck(offset, len);
0670:
0671: BaseDocumentEvent evt = createDocumentEvent(offset,
0672: len, DocumentEvent.EventType.REMOVE);
0673:
0674: removeUpdate(evt);
0675:
0676: UndoableEdit edit = op.remove(offset, len);
0677: if (edit != null) {
0678: evt.addEdit(edit);
0679:
0680: lastModifyUndoEdit = edit; // #8692 check last modify undo
0681: // edit
0682: }
0683:
0684: if (debug) {
0685: System.err.println("BaseDocument.remove(): doc="
0686: + this // NOI18N
0687: + ", offset=" + offset + ", len=" + len); // NOI18N
0688: }
0689: if (debugStack) {
0690: Thread.dumpStack();
0691: }
0692:
0693: if (atomicDepth > 0) { // add edits as soon as possible
0694: if (atomicEdits == null) {
0695: atomicEdits = new AtomicCompoundEdit();
0696: }
0697: atomicEdits.addEdit(evt); // will be added
0698: }
0699:
0700: postRemoveUpdate(evt);
0701:
0702: evt.end();
0703:
0704: fireRemoveUpdate(evt);
0705: if (atomicDepth == 0) {
0706: fireUndoableEditUpdate(new UndoableEditEvent(this ,
0707: evt));
0708: }
0709: } finally {
0710: extWriteUnlock();
0711: }
0712: }
0713: }
0714:
0715: /**
0716: * This method is called automatically before the document insertion occurs
0717: * and can be used to revoke the insertion before it occurs by throwing the
0718: * <tt>BadLocationException</tt>.
0719: *
0720: * @param offset
0721: * position where the insertion will be done
0722: * @param text
0723: * string to be inserted
0724: * @param a
0725: * attributes of the inserted text
0726: */
0727: protected void preInsertCheck(int offset, String text,
0728: AttributeSet a) throws BadLocationException {
0729: }
0730:
0731: /**
0732: * This method is called automatically before the document removal occurs
0733: * and can be used to revoke the removal before it occurs by throwing the
0734: * <tt>BadLocationException</tt>.
0735: *
0736: * @param offset
0737: * position where the insertion will be done
0738: * @param len
0739: * length of the removal
0740: */
0741: protected void preRemoveCheck(int offset, int len)
0742: throws BadLocationException {
0743: }
0744:
0745: public String getText(int[] block) throws BadLocationException {
0746: return getText(block[0], block[1] - block[0]);
0747: }
0748:
0749: public char[] getChars(int pos, int len)
0750: throws BadLocationException {
0751: return op.getChars(pos, len);
0752: }
0753:
0754: public char[] getChars(int[] block) throws BadLocationException {
0755: return getChars(block[0], block[1] - block[0]);
0756: }
0757:
0758: public void getChars(int pos, char ret[], int offset, int len)
0759: throws BadLocationException {
0760: op.getChars(pos, ret, offset, len);
0761: }
0762:
0763: /**
0764: * Find something in document using a finder.
0765: *
0766: * @param finder
0767: * finder to be used for the search
0768: * @param startPos
0769: * position in the document where the search will start
0770: * @param limitPos
0771: * position where the search will be end with reporting that
0772: * nothing was found.
0773: */
0774: public int find(Finder finder, int startPos, int limitPos)
0775: throws BadLocationException {
0776: if (finder instanceof AdjustFinder) {
0777: int docLen = getLength();
0778: if (limitPos == -1) {
0779: limitPos = docLen;
0780: }
0781: if (startPos == -1) {
0782: startPos = docLen;
0783: }
0784:
0785: if (startPos == limitPos) { // stop immediately
0786: finder.reset(); // reset() should be called in all the cases
0787: return -1; // must stop here because wouldn't know if fwd/bwd
0788: // search?
0789: }
0790:
0791: boolean forward = (startPos < limitPos);
0792: startPos = ((AdjustFinder) finder).adjustStartPos(this ,
0793: startPos);
0794: limitPos = ((AdjustFinder) finder).adjustLimitPos(this ,
0795: limitPos);
0796: boolean voidSearch = (forward ? (startPos >= limitPos)
0797: : (startPos <= limitPos));
0798: if (voidSearch) {
0799: finder.reset();
0800: return -1;
0801: }
0802: }
0803:
0804: return op.find(finder, startPos, limitPos);
0805: }
0806:
0807: /** Fire the change event to repaint the given block of text. */
0808: public void repaintBlock(int startOffset, int endOffset) {
0809: BaseDocumentEvent evt = createDocumentEvent(startOffset,
0810: endOffset - startOffset, DocumentEvent.EventType.CHANGE);
0811: fireChangedUpdate(evt);
0812: }
0813:
0814: public void print(PrintContainer container) {
0815: readLock();
0816: try {
0817: EditorUI editorUI = BaseKit.getKit(kitClass)
0818: .createPrintEditorUI(this );
0819: DrawGraphics.PrintDG printDG = new DrawGraphics.PrintDG(
0820: container);
0821: DrawEngine.getDrawEngine().draw(printDG, editorUI, 0,
0822: getLength(), 0, 0, Integer.MAX_VALUE);
0823: } catch (BadLocationException e) {
0824: e.printStackTrace();
0825: } finally {
0826: readUnlock();
0827: }
0828: }
0829:
0830: /** Create biased position in document */
0831: public Position createPosition(int offset, Position.Bias bias)
0832: throws BadLocationException {
0833: return op.createPosition(offset, bias);
0834: }
0835:
0836: /** Return array of root elements - usually only one */
0837: public Element[] getRootElements() {
0838: Element[] elems = new Element[1];
0839: elems[0] = getDefaultRootElement();
0840: return elems;
0841: }
0842:
0843: /** Return default root element */
0844: public Element getDefaultRootElement() {
0845: if (defaultRootElem == null) {
0846: defaultRootElem = new org.netbeans.editor.LeafElement(this ,
0847: null, null, 0, getLength(), false, false);
0848: }
0849: return defaultRootElem;
0850: }
0851:
0852: /** Runs the runnable under read lock. */
0853: public void render(Runnable r) {
0854: readLock();
0855: try {
0856: r.run();
0857: } finally {
0858: readUnlock();
0859: }
0860: }
0861:
0862: /**
0863: * Runs the runnable under write lock. This is a stronger version of the
0864: * runAtomicAsUser() method, because if there any locked sections in the
0865: * documents this methods breaks the modification locks and modifies the
0866: * document. If there are any excpeptions thrown during the processing of
0867: * the runnable, all the document modifications are rolled back
0868: * automatically.
0869: */
0870: public void runAtomic(Runnable r) {
0871: runAtomicAsUser(r);
0872: }
0873:
0874: /**
0875: * Runs the runnable under write lock. If there are any excpeptions thrown
0876: * during the processing of the runnable, all the document modifications are
0877: * rolled back automatically.
0878: */
0879: public void runAtomicAsUser(Runnable r) {
0880: boolean completed = false;
0881: atomicLock();
0882: try {
0883: r.run();
0884: completed = true;
0885: } finally {
0886: try {
0887: if (!completed) {
0888: breakAtomicLock();
0889: }
0890: } finally {
0891: atomicUnlock();
0892: }
0893: }
0894: }
0895:
0896: /**
0897: * Insert contents of reader at specified position into document.
0898: *
0899: * @param reader
0900: * reader from which data will be read
0901: * @param pos
0902: * on which position that data will be inserted
0903: */
0904: public void read(Reader reader, int pos) throws IOException,
0905: BadLocationException {
0906: extWriteLock();
0907: try {
0908:
0909: if (pos < 0 || pos > getLength()) {
0910: throw new BadLocationException("BaseDocument.read()",
0911: pos); // NOI18N
0912: }
0913:
0914: if (inited || modified) { // was the document already initialized?
0915: Analyzer.read(this , reader, pos);
0916: } else { // not initialized yet, we can use initialRead()
0917: Analyzer.initialRead(this , reader, true);
0918: BaseDocumentEvent evt = createDocumentEvent(0, 0,
0919: DocumentEvent.EventType.INSERT);
0920: evt.end();
0921: fireInsertUpdate(evt); // fire the insert event with zero
0922: // length to notify about the change
0923: inited = true; // initialized but not modified
0924: }
0925: } finally {
0926: extWriteUnlock();
0927: }
0928: }
0929:
0930: /**
0931: * Write part of the document into specified writer.
0932: *
0933: * @param writer
0934: * writer into which data will be written.
0935: * @param pos
0936: * from which position get the data
0937: * @param len
0938: * how many characters write
0939: */
0940: public void write(Writer writer, int pos, int len)
0941: throws IOException, BadLocationException {
0942: readLock();
0943: try {
0944:
0945: if ((pos < 0) || ((pos + len) > getLength())) {
0946: throw new BadLocationException("BaseDocument.write()",
0947: pos); // NOI18N
0948: }
0949: Analyzer.write(this , writer, pos, len);
0950: writer.flush();
0951: } finally {
0952: readUnlock();
0953: }
0954: }
0955:
0956: /**
0957: * Invalidate the state-infos in all the syntax-marks in the whole document.
0958: * The Syntax can call this method if it changes its internal state in the
0959: * way that affects the future returned tokens. The syntax-state-info in all
0960: * the marks is reset and it will be lazily restored when necessary.
0961: */
0962: public void invalidateSyntaxMarks() {
0963: extWriteLock();
0964: try {
0965: op.invalidateSyntaxMarks();
0966: repaintBlock(0, getLength());
0967: } finally {
0968: extWriteUnlock();
0969: }
0970: }
0971:
0972: /**
0973: * Get the number of spaces the TAB character ('\t') visually represents.
0974: * This is related to <code>SettingsNames.TAB_SIZE</code> setting.
0975: */
0976: public int getTabSize() {
0977: return tabSize;
0978: }
0979:
0980: /**
0981: * Get the width of one indentation level. The algorithm first checks
0982: * whether there's a value for the INDENT_SHIFT_WIDTH setting. If so it uses
0983: * it, otherwise it uses <code>formatter.getSpacesPerTab()</code>.
0984: *
0985: * @see getTabSize()
0986: * @see Formatter.getSpacesPerTab()
0987: */
0988: public int getShiftWidth() {
0989: if (shiftWidth != null) {
0990: return shiftWidth.intValue();
0991:
0992: } else {
0993: return getFormatter().getSpacesPerTab();
0994: }
0995: }
0996:
0997: public final Class getKitClass() {
0998: return kitClass;
0999: }
1000:
1001: /**
1002: * This method prohibits merging of the next document modification with the
1003: * previous one even if it would be normally possible.
1004: */
1005: public void resetUndoMerge() {
1006: undoMergeReset = true;
1007: }
1008:
1009: /*
1010: * Defined because of the hack for undo() in the BaseDocumentEvent.
1011: */
1012: protected void fireChangedUpdate(DocumentEvent e) {
1013: super .fireChangedUpdate(e);
1014: }
1015:
1016: protected void fireInsertUpdate(DocumentEvent e) {
1017: super .fireInsertUpdate(e);
1018: }
1019:
1020: protected void fireRemoveUpdate(DocumentEvent e) {
1021: super .fireRemoveUpdate(e);
1022: }
1023:
1024: /**
1025: * Extended write locking of the document allowing reentrant write lock
1026: * acquiring.
1027: */
1028: public synchronized final void extWriteLock() {
1029: if (Thread.currentThread() != getCurrentWriter()) {
1030: super .writeLock();
1031: } else { // inner locking block
1032: writeDeep++; // only increase write deepness
1033: }
1034: }
1035:
1036: /**
1037: * Extended write unlocking.
1038: *
1039: * @see extWriteLock()
1040: */
1041: public synchronized final void extWriteUnlock() {
1042: if (Thread.currentThread() != getCurrentWriter()) {
1043: throw new RuntimeException(WRITE_LOCK_MISSING);
1044: }
1045:
1046: if (writeDeep == 0) { // most outer locking block
1047: super .writeUnlock();
1048: } else { // just inner locking block
1049: writeDeep--;
1050: }
1051: }
1052:
1053: public synchronized final void atomicLock() {
1054: extWriteLock();
1055: atomicDepth++;
1056: }
1057:
1058: public synchronized final void atomicUnlock() {
1059: extWriteUnlock();
1060: if (atomicDepth == 0) {
1061: return;
1062: }
1063: if (--atomicDepth == 0) { // must fire possible undo event
1064: if (atomicEdits != null) {
1065: atomicEdits.end();
1066: fireUndoableEditUpdate(new UndoableEditEvent(this ,
1067: atomicEdits));
1068: atomicEdits = null;
1069: }
1070: }
1071: }
1072:
1073: /**
1074: * Is the document currently atomically locked? It's not synced as this
1075: * method must be called only from writer thread.
1076: */
1077: public final boolean isAtomicLock() {
1078: return (atomicDepth > 0);
1079: }
1080:
1081: /**
1082: * Break the atomic lock so that doc is no longer in atomic mode. All the
1083: * performed changes are rolled back automatically. Even after calling this
1084: * method, the atomicUnlock() must still be called. This method is not
1085: * synced as it must be called only from writer thread.
1086: */
1087: public final void breakAtomicLock() {
1088: atomicDepth = 0;
1089: if (atomicEdits != null) {
1090: atomicEdits.end();
1091: atomicEdits.undo();
1092: atomicEdits = null;
1093: }
1094: }
1095:
1096: protected final int getAtomicDepth() {
1097: return atomicDepth;
1098: }
1099:
1100: protected BaseDocumentEvent createDocumentEvent(int pos,
1101: int length, DocumentEvent.EventType type) {
1102: return new BaseDocumentEvent(this , pos, length, type);
1103: }
1104:
1105: /**
1106: * Was the document modified by either insert/remove but not the initial
1107: * read)?
1108: */
1109: public boolean isModified() {
1110: return modified;
1111: }
1112:
1113: /** Get the layer with the specified name */
1114: public DrawLayer findLayer(String layerName) {
1115: return drawLayerList.findLayer(layerName);
1116: }
1117:
1118: public boolean addLayer(DrawLayer layer, int visibility) {
1119: if (drawLayerList.add(layer, visibility)) {
1120: BaseDocumentEvent evt = createDocumentEvent(0, 0,
1121: DocumentEvent.EventType.CHANGE);
1122: evt.addEdit(new BaseDocumentEvent.DrawLayerChange(layer
1123: .getName(), visibility));
1124: fireChangedUpdate(evt);
1125: return true;
1126: } else {
1127: return false;
1128: }
1129: }
1130:
1131: final DrawLayerList getDrawLayerList() {
1132: return drawLayerList;
1133: }
1134:
1135: /** Toggle the bookmark for the current line */
1136: public boolean toggleBookmark(int pos) throws BadLocationException {
1137: pos = Utilities.getRowStart(this , pos);
1138: boolean marked = bookmarkChain.toggleMark(pos);
1139: BaseDocumentEvent evt = createDocumentEvent(pos, 0,
1140: DocumentEvent.EventType.CHANGE);
1141: fireChangedUpdate(evt);
1142: return marked;
1143: }
1144:
1145: /**
1146: * Get the position of the next bookmark.
1147: *
1148: * @pos position from which to search
1149: * @wrap wrap around the end of document
1150: * @return position of the next bookmark or -1 if there is no mark
1151: */
1152: public int getNextBookmark(int pos, boolean wrap)
1153: throws BadLocationException {
1154: try {
1155: pos = Utilities.getRowStart(this , pos);
1156: int rel = bookmarkChain.compareMark(pos);
1157: MarkFactory.ChainDrawMark mark = bookmarkChain.getCurMark();
1158: if (rel <= 0) { // right at this line, go next
1159: if (mark != null) {
1160: if (mark.next != null) {
1161: return mark.next.getOffset();
1162: } else { // last bookmark
1163: return (wrap && bookmarkChain.chain != null) ? bookmarkChain.chain
1164: .getOffset()
1165: : -1;
1166: }
1167: } else { // no marks
1168: return -1;
1169: }
1170: } else { // mark after pos
1171: return mark.getOffset();
1172: }
1173: } catch (InvalidMarkException e) {
1174: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
1175: e.printStackTrace();
1176: }
1177: return 0;
1178: }
1179: }
1180:
1181: private LineRootElement getLineRootElement() {
1182: if (lineRootElement == null) {
1183: lineRootElement = new LineRootElement();
1184: }
1185: return lineRootElement;
1186: }
1187:
1188: public Element getParagraphElement(int pos) {
1189: return getLineRootElement().getElement(
1190: getLineRootElement().getElementIndex(pos));
1191: }
1192:
1193: /**
1194: * Returns object which represent list of annotations which are attached to
1195: * this document.
1196: *
1197: * @return object which represent attached annotations
1198: */
1199: public synchronized Annotations getAnnotations() {
1200: if (annotations == null) {
1201: annotations = new Annotations(this );
1202: }
1203: return annotations;
1204: }
1205:
1206: /**
1207: * Returns object which represent list of annotations which are attached to
1208: * this document.
1209: *
1210: * @return object which represent attached annotations
1211: */
1212: public synchronized Bookmarks getBookmarks() {
1213: if (bookmarks == null) {
1214: bookmarks = new Bookmarks();
1215: }
1216: return bookmarks;
1217: }
1218:
1219: public String toString() {
1220: return super .toString() + ", kitClass=" + getKitClass() // NOI18N
1221: + ", docLen=" + getLength(); // NOI18N
1222: }
1223:
1224: /** Detailed debug info about the document */
1225: public String toStringDetail() {
1226: return toString();
1227: }
1228:
1229: /** Substitution for (each line = element) behavior */
1230: class LineRootElement implements Element {
1231:
1232: public Document getDocument() {
1233: return BaseDocument.this ;
1234: }
1235:
1236: public Element getParentElement() {
1237: return null;
1238: }
1239:
1240: public String getName() {
1241: return "line-elements-root"; // NOI18N
1242: }
1243:
1244: public AttributeSet getAttributes() {
1245: return null;
1246: }
1247:
1248: public int getStartOffset() {
1249: return 0;
1250: }
1251:
1252: public int getEndOffset() {
1253: return getLength();
1254: }
1255:
1256: public int getElementIndex(int offset) {
1257: try {
1258: return Utilities.getLineOffset(BaseDocument.this ,
1259: offset);
1260: } catch (BadLocationException e) {
1261: return 0;
1262: }
1263: }
1264:
1265: public int getElementCount() {
1266: return Utilities.getRowCount(BaseDocument.this );
1267: }
1268:
1269: public Element getElement(int index) {
1270: if (index < 0) {
1271: // throwing IOOBE to be compatible with swing's AbstractDocument
1272: // that does not check index < 0
1273: throw new IndexOutOfBoundsException("Line index="
1274: + index + " must be >= 0"); // NOI18N
1275: }
1276:
1277: try {
1278: int startPos = Utilities.getRowStartFromLineOffset(
1279: BaseDocument.this , index);
1280:
1281: if (startPos < 0) { // line index was invalid
1282: /*
1283: * throw new IllegalArgumentException("Line index=" + index //
1284: * NOI18N + " is invalid. Maximum index is " // NOI18N +
1285: * (Utilities.getRowCount(BaseDocument.this) - 1) + " in the
1286: * document " // NOI18N +
1287: * Utilities.debugDocument(BaseDocument.this) + "." // NOI18N );
1288: */
1289:
1290: return getElement(Utilities
1291: .getRowCount(BaseDocument.this ) - 1); // last
1292: // line
1293:
1294: }
1295:
1296: LineElement elem = null;
1297: MarkFactory.LineMark mark = (MarkFactory.LineMark) op
1298: .getOffsetMark(startPos,
1299: MarkFactory.LineMark.class);
1300: if (mark != null) {
1301: elem = mark.lineElemRef; // non-weak ref due to #17351
1302: }
1303:
1304: if (elem == null) {
1305: // Create the mark if necessary
1306: if (mark == null) {
1307: mark = new MarkFactory.LineMark();
1308: elem = new LineElement(mark);
1309: mark.lineElemRef = elem;
1310:
1311: try {
1312: op.insertMark(mark, startPos);
1313: } catch (InvalidMarkException e) {
1314: if (Boolean
1315: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
1316: e.printStackTrace();
1317: }
1318: }
1319: }
1320:
1321: }
1322:
1323: return elem;
1324:
1325: } catch (BadLocationException e) {
1326: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
1327: e.printStackTrace();
1328: }
1329: return null;
1330: }
1331: }
1332:
1333: public boolean isLeaf() {
1334: return false;
1335: }
1336:
1337: }
1338:
1339: /** Line element representation */
1340: class LineElement implements Element {
1341:
1342: /** Mark at the begining of the line */
1343: MarkFactory.LineMark startMark;
1344:
1345: LineElement(MarkFactory.LineMark startMark)
1346: throws BadLocationException {
1347: this .startMark = startMark;
1348: }
1349:
1350: public Document getDocument() {
1351: return BaseDocument.this ;
1352: }
1353:
1354: public int getStartOffset() {
1355: try {
1356: return startMark.getOffset();
1357: } catch (InvalidMarkException e) {
1358: return 0;
1359: }
1360: }
1361:
1362: public int getEndOffset() {
1363: try {
1364: return op.getEOLNL(getStartOffset());
1365: } catch (BadLocationException e) {
1366: return 0;
1367: }
1368: }
1369:
1370: public Element getParentElement() {
1371: return lineRootElement;
1372: }
1373:
1374: public String getName() {
1375: return "line-element"; // NOI18N
1376: }
1377:
1378: public AttributeSet getAttributes() {
1379: return null;
1380: }
1381:
1382: public int getElementIndex(int offset) {
1383: return 0;
1384: }
1385:
1386: public int getElementCount() {
1387: return 0;
1388: }
1389:
1390: public Element getElement(int index) {
1391: return null;
1392: }
1393:
1394: public boolean isLeaf() {
1395: return true;
1396: }
1397:
1398: public void finalize() throws Throwable {
1399: try {
1400: startMark.remove();
1401: } catch (InvalidMarkException e) {
1402: }
1403: super .finalize();
1404: }
1405:
1406: public String toString() {
1407: return "getStartOffset()=" + getStartOffset() // NOI18N
1408: + ", getEndOffset()=" + getEndOffset() // NOI18N
1409: + ", getParentElement()=" + getParentElement(); // NOI18N
1410: }
1411:
1412: }
1413:
1414: /**
1415: * Compound edit that write-locks the document for the whole processing of
1416: * its undo operation.
1417: */
1418: class AtomicCompoundEdit extends CompoundEdit {
1419:
1420: public void undo() throws CannotUndoException {
1421: extWriteLock();
1422: try {
1423: super .undo();
1424: } finally {
1425: extWriteUnlock();
1426: }
1427: }
1428:
1429: public void redo() throws CannotRedoException {
1430: extWriteLock();
1431: try {
1432: super .redo();
1433: } finally {
1434: extWriteUnlock();
1435: }
1436: }
1437:
1438: }
1439:
1440: /**
1441: * Property evaluator is useful for lazy evaluation of properties of the
1442: * document when
1443: * {@link javax.swing.text.Document#getProperty(java.lang.String)} is
1444: * called.
1445: */
1446: public interface PropertyEvaluator {
1447:
1448: /** Get the real value of the property */
1449: public Object getValue();
1450:
1451: }
1452:
1453: private class LazyPropertyMap extends Hashtable {
1454:
1455: LazyPropertyMap(Dictionary dict) {
1456: super (5);
1457:
1458: Enumeration en = dict.keys();
1459: while (en.hasMoreElements()) {
1460: Object key = en.nextElement();
1461: put(key, dict.get(key));
1462: }
1463: }
1464:
1465: public Object get(Object key) {
1466: Object val = super .get(key);
1467: if (val instanceof PropertyEvaluator) {
1468: val = ((PropertyEvaluator) val).getValue();
1469: }
1470:
1471: return val;
1472: }
1473:
1474: }
1475:
1476: }
|