1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text;
26
27import java.util.Vector;
28import java.io.Serializable;
29import javax.swing.undo.*;
30import javax.swing.SwingUtilities;
31
32/**
33 * An implementation of the AbstractDocument.Content interface that is
34 * a brute force implementation that is useful for relatively small
35 * documents and/or debugging.  It manages the character content
36 * as a simple character array.  It is also quite inefficient.
37 * <p>
38 * It is generally recommended that the gap buffer or piece table
39 * implementations be used instead.  This buffer does not scale up
40 * to large sizes.
41 * <p>
42 * <strong>Warning:</strong>
43 * Serialized objects of this class will not be compatible with
44 * future Swing releases. The current serialization support is
45 * appropriate for short term storage or RMI between applications running
46 * the same version of Swing.  As of 1.4, support for long term storage
47 * of all JavaBeans&trade;
48 * has been added to the <code>java.beans</code> package.
49 * Please see {@link java.beans.XMLEncoder}.
50 *
51 * @author  Timothy Prinzing
52 */
53@SuppressWarnings("serial") // Same-version serialization only
54public final class StringContent implements AbstractDocument.Content, Serializable {
55
56    /**
57     * Creates a new StringContent object.  Initial size defaults to 10.
58     */
59    public StringContent() {
60        this(10);
61    }
62
63    /**
64     * Creates a new StringContent object, with the initial
65     * size specified.  If the length is &lt; 1, a size of 1 is used.
66     *
67     * @param initialLength the initial size
68     */
69    public StringContent(int initialLength) {
70        if (initialLength < 1) {
71            initialLength = 1;
72        }
73        data = new char[initialLength];
74        data[0] = '\n';
75        count = 1;
76    }
77
78    /**
79     * Returns the length of the content.
80     *
81     * @return the length &gt;= 1
82     * @see AbstractDocument.Content#length
83     */
84    public int length() {
85        return count;
86    }
87
88    /**
89     * Inserts a string into the content.
90     *
91     * @param where the starting position &gt;= 0 &amp;&amp; &lt; length()
92     * @param str the non-null string to insert
93     * @return an UndoableEdit object for undoing
94     * @exception BadLocationException if the specified position is invalid
95     * @see AbstractDocument.Content#insertString
96     */
97    public UndoableEdit insertString(int where, String str) throws BadLocationException {
98        if (where >= count || where < 0) {
99            throw new BadLocationException("Invalid location", count);
100        }
101        char[] chars = str.toCharArray();
102        replace(where, 0, chars, 0, chars.length);
103        if (marks != null) {
104            updateMarksForInsert(where, str.length());
105        }
106        return new InsertUndo(where, str.length());
107    }
108
109    /**
110     * Removes part of the content.  where + nitems must be &lt; length().
111     *
112     * @param where the starting position &gt;= 0
113     * @param nitems the number of characters to remove &gt;= 0
114     * @return an UndoableEdit object for undoing
115     * @exception BadLocationException if the specified position is invalid
116     * @see AbstractDocument.Content#remove
117     */
118    public UndoableEdit remove(int where, int nitems) throws BadLocationException {
119        if (where + nitems >= count) {
120            throw new BadLocationException("Invalid range", count);
121        }
122        String removedString = getString(where, nitems);
123        UndoableEdit edit = new RemoveUndo(where, removedString);
124        replace(where, nitems, empty, 0, 0);
125        if (marks != null) {
126            updateMarksForRemove(where, nitems);
127        }
128        return edit;
129
130    }
131
132    /**
133     * Retrieves a portion of the content.  where + len must be &lt;= length().
134     *
135     * @param where the starting position &gt;= 0
136     * @param len the length to retrieve &gt;= 0
137     * @return a string representing the content; may be empty
138     * @exception BadLocationException if the specified position is invalid
139     * @see AbstractDocument.Content#getString
140     */
141    public String getString(int where, int len) throws BadLocationException {
142        if (where + len > count) {
143            throw new BadLocationException("Invalid range", count);
144        }
145        return new String(data, where, len);
146    }
147
148    /**
149     * Retrieves a portion of the content.  where + len must be &lt;= length()
150     *
151     * @param where the starting position &gt;= 0
152     * @param len the number of characters to retrieve &gt;= 0
153     * @param chars the Segment object to return the characters in
154     * @exception BadLocationException if the specified position is invalid
155     * @see AbstractDocument.Content#getChars
156     */
157    public void getChars(int where, int len, Segment chars) throws BadLocationException {
158        if (where + len > count) {
159            throw new BadLocationException("Invalid location", count);
160        }
161        chars.array = data;
162        chars.offset = where;
163        chars.count = len;
164    }
165
166    /**
167     * Creates a position within the content that will
168     * track change as the content is mutated.
169     *
170     * @param offset the offset to create a position for &gt;= 0
171     * @return the position
172     * @exception BadLocationException if the specified position is invalid
173     */
174    public Position createPosition(int offset) throws BadLocationException {
175        // some small documents won't have any sticky positions
176        // at all, so the buffer is created lazily.
177        if (marks == null) {
178            marks = new Vector<PosRec>();
179        }
180        return new StickyPosition(offset);
181    }
182
183    // --- local methods ---------------------------------------
184
185    /**
186     * Replaces some of the characters in the array
187     * @param offset  offset into the array to start the replace
188     * @param length  number of characters to remove
189     * @param replArray replacement array
190     * @param replOffset offset into the replacement array
191     * @param replLength number of character to use from the
192     *   replacement array.
193     */
194    void replace(int offset, int length,
195                 char[] replArray, int replOffset, int replLength) {
196        int delta = replLength - length;
197        int src = offset + length;
198        int nmove = count - src;
199        int dest = src + delta;
200        if ((count + delta) >= data.length) {
201            // need to grow the array
202            int newLength = Math.max(2*data.length, count + delta);
203            char[] newData = new char[newLength];
204            System.arraycopy(data, 0, newData, 0, offset);
205            System.arraycopy(replArray, replOffset, newData, offset, replLength);
206            System.arraycopy(data, src, newData, dest, nmove);
207            data = newData;
208        } else {
209            // patch the existing array
210            System.arraycopy(data, src, data, dest, nmove);
211            System.arraycopy(replArray, replOffset, data, offset, replLength);
212        }
213        count = count + delta;
214    }
215
216    void resize(int ncount) {
217        char[] ndata = new char[ncount];
218        System.arraycopy(data, 0, ndata, 0, Math.min(ncount, count));
219        data = ndata;
220    }
221
222    synchronized void updateMarksForInsert(int offset, int length) {
223        if (offset == 0) {
224            // zero is a special case where we update only
225            // marks after it.
226            offset = 1;
227        }
228        int n = marks.size();
229        for (int i = 0; i < n; i++) {
230            PosRec mark = marks.elementAt(i);
231            if (mark.unused) {
232                // this record is no longer used, get rid of it
233                marks.removeElementAt(i);
234                i -= 1;
235                n -= 1;
236            } else if (mark.offset >= offset) {
237                mark.offset += length;
238            }
239        }
240    }
241
242    synchronized void updateMarksForRemove(int offset, int length) {
243        int n = marks.size();
244        for (int i = 0; i < n; i++) {
245            PosRec mark = marks.elementAt(i);
246            if (mark.unused) {
247                // this record is no longer used, get rid of it
248                marks.removeElementAt(i);
249                i -= 1;
250                n -= 1;
251            } else if (mark.offset >= (offset + length)) {
252                mark.offset -= length;
253            } else if (mark.offset >= offset) {
254                mark.offset = offset;
255            }
256        }
257    }
258
259    /**
260     * Returns a Vector containing instances of UndoPosRef for the
261     * Positions in the range
262     * <code>offset</code> to <code>offset</code> + <code>length</code>.
263     * If <code>v</code> is not null the matching Positions are placed in
264     * there. The vector with the resulting Positions are returned.
265     * <p>
266     * This is meant for internal usage, and is generally not of interest
267     * to subclasses.
268     *
269     * @param v the Vector to use, with a new one created on null
270     * @param offset the starting offset &gt;= 0
271     * @param length the length &gt;= 0
272     * @return the set of instances
273     */
274    @SuppressWarnings({"rawtypes", "unchecked"}) // UndoPosRef type cannot be exposed
275    protected Vector getPositionsInRange(Vector v, int offset,
276                                         int length) {
277        int n = marks.size();
278        int end = offset + length;
279        Vector placeIn = (v == null) ? new Vector() : v;
280        for (int i = 0; i < n; i++) {
281            PosRec mark = marks.elementAt(i);
282            if (mark.unused) {
283                // this record is no longer used, get rid of it
284                marks.removeElementAt(i);
285                i -= 1;
286                n -= 1;
287            } else if(mark.offset >= offset && mark.offset <= end)
288                placeIn.addElement(new UndoPosRef(mark));
289        }
290        return placeIn;
291    }
292
293    /**
294     * Resets the location for all the UndoPosRef instances
295     * in <code>positions</code>.
296     * <p>
297     * This is meant for internal usage, and is generally not of interest
298     * to subclasses.
299     *
300     * @param positions the positions of the instances
301     */
302    @SuppressWarnings("rawtypes") // UndoPosRef type cannot be exposed
303    protected void updateUndoPositions(Vector positions) {
304        for(int counter = positions.size() - 1; counter >= 0; counter--) {
305            UndoPosRef ref = (UndoPosRef) positions.elementAt(counter);
306            // Check if the Position is still valid.
307            if(ref.rec.unused) {
308                positions.removeElementAt(counter);
309            }
310            else
311                ref.resetLocation();
312        }
313    }
314
315    private static final char[] empty = new char[0];
316    private char[] data;
317    private int count;
318    transient Vector<PosRec> marks;
319
320    /**
321     * holds the data for a mark... separately from
322     * the real mark so that the real mark can be
323     * collected if there are no more references to
324     * it.... the update table holds only a reference
325     * to this grungy thing.
326     */
327    final class PosRec {
328
329        PosRec(int offset) {
330            this.offset = offset;
331        }
332
333        int offset;
334        boolean unused;
335    }
336
337    /**
338     * This really wants to be a weak reference but
339     * in 1.1 we don't have a 100% pure solution for
340     * this... so this class trys to hack a solution
341     * to causing the marks to be collected.
342     */
343    final class StickyPosition implements Position {
344
345        StickyPosition(int offset) {
346            rec = new PosRec(offset);
347            marks.addElement(rec);
348        }
349
350        public int getOffset() {
351            return rec.offset;
352        }
353
354        @SuppressWarnings("deprecation")
355        protected void finalize() throws Throwable {
356            // schedule the record to be removed later
357            // on another thread.
358            rec.unused = true;
359        }
360
361        public String toString() {
362            return Integer.toString(getOffset());
363        }
364
365        PosRec rec;
366    }
367
368    /**
369     * Used to hold a reference to a Position that is being reset as the
370     * result of removing from the content.
371     */
372    final class UndoPosRef {
373        UndoPosRef(PosRec rec) {
374            this.rec = rec;
375            this.undoLocation = rec.offset;
376        }
377
378        /**
379         * Resets the location of the Position to the offset when the
380         * receiver was instantiated.
381         */
382        protected void resetLocation() {
383            rec.offset = undoLocation;
384        }
385
386        /** Location to reset to when resetLocatino is invoked. */
387        protected int undoLocation;
388        /** Position to reset offset. */
389        protected PosRec rec;
390    }
391
392    /**
393     * UnoableEdit created for inserts.
394     */
395    class InsertUndo extends AbstractUndoableEdit {
396        protected InsertUndo(int offset, int length) {
397            super();
398            this.offset = offset;
399            this.length = length;
400        }
401
402        public void undo() throws CannotUndoException {
403            super.undo();
404            try {
405                synchronized(StringContent.this) {
406                    // Get the Positions in the range being removed.
407                    if(marks != null)
408                        posRefs = getPositionsInRange(null, offset, length);
409                    string = getString(offset, length);
410                    remove(offset, length);
411                }
412            } catch (BadLocationException bl) {
413              throw new CannotUndoException();
414            }
415        }
416
417        public void redo() throws CannotRedoException {
418            super.redo();
419            try {
420                synchronized(StringContent.this) {
421                    insertString(offset, string);
422                    string = null;
423                    // Update the Positions that were in the range removed.
424                    if(posRefs != null) {
425                        updateUndoPositions(posRefs);
426                        posRefs = null;
427                    }
428              }
429            } catch (BadLocationException bl) {
430              throw new CannotRedoException();
431            }
432        }
433
434        // Where the string goes.
435        protected int offset;
436        // Length of the string.
437        protected int length;
438        // The string that was inserted. To cut down on space needed this
439        // will only be valid after an undo.
440        protected String string;
441        // An array of instances of UndoPosRef for the Positions in the
442        // range that was removed, valid after undo.
443        @SuppressWarnings("rawtypes") // UndoPosRef type cannot be exposed
444        protected Vector posRefs;
445    }
446
447
448    /**
449     * UndoableEdit created for removes.
450     */
451    class RemoveUndo extends AbstractUndoableEdit {
452        @SuppressWarnings("unchecked")
453        protected RemoveUndo(int offset, String string) {
454            super();
455            this.offset = offset;
456            this.string = string;
457            this.length = string.length();
458            if(marks != null)
459                posRefs = getPositionsInRange(null, offset, length);
460        }
461
462        public void undo() throws CannotUndoException {
463            super.undo();
464            try {
465                synchronized(StringContent.this) {
466                    insertString(offset, string);
467                    // Update the Positions that were in the range removed.
468                    if(posRefs != null) {
469                        updateUndoPositions(posRefs);
470                        posRefs = null;
471                    }
472                    string = null;
473                }
474            } catch (BadLocationException bl) {
475              throw new CannotUndoException();
476            }
477        }
478
479        @SuppressWarnings("unchecked")
480        public void redo() throws CannotRedoException {
481            super.redo();
482            try {
483                synchronized(StringContent.this) {
484                    string = getString(offset, length);
485                    // Get the Positions in the range being removed.
486                    if(marks != null)
487                        posRefs = getPositionsInRange(null, offset, length);
488                    remove(offset, length);
489                }
490            } catch (BadLocationException bl) {
491              throw new CannotRedoException();
492            }
493        }
494
495        // Where the string goes.
496        protected int offset;
497        // Length of the string.
498        protected int length;
499        // The string that was inserted. This will be null after an undo.
500        protected String string;
501        // An array of instances of UndoPosRef for the Positions in the
502        // range that was removed, valid before undo.
503        protected Vector<UndoPosRef> posRefs;
504    }
505}
506