001/* PlainDocument.java --
002   Copyright (C) 2002, 2004, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.util.ArrayList;
042
043/**
044 * A simple document class which maps lines to {@link Element}s.
045 *
046 * @author Anthony Balkissoon (abalkiss@redhat.com)
047 * @author Graydon Hoare (graydon@redhat.com)
048 * @author Roman Kennke (roman@kennke.org)
049 * @author Michael Koch (konqueror@gmx.de)
050 * @author Robert Schuster (robertschuster@fsfe.org)
051 */
052public class PlainDocument extends AbstractDocument
053{
054  private static final long serialVersionUID = 4758290289196893664L;
055
056  public static final String lineLimitAttribute = "lineLimit";
057  public static final String tabSizeAttribute = "tabSize";
058
059  /**
060   * The default root element of this document. This is made type Element
061   * because the RI seems to accept other types of elements as well from
062   * createDefaultRoot() (when overridden by a subclass).
063   */
064  private Element rootElement;
065
066  public PlainDocument()
067  {
068    this(new GapContent());
069  }
070
071  public PlainDocument(AbstractDocument.Content content)
072  {
073    super(content);
074    rootElement = createDefaultRoot();
075
076    // This property has been determined using a Mauve test.
077    putProperty("tabSize", new Integer(8));
078  }
079
080  private void reindex()
081  {
082    Element[] lines;
083    try
084      {
085        String str = content.getString(0, content.length());
086
087        ArrayList elts = new ArrayList();
088        int j = 0;
089        for (int i = str.indexOf('\n', 0); i != -1; i = str.indexOf('\n', i + 1))
090          {
091            elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, i + 1));
092            j = i + 1;
093          }
094
095        if (j < content.length())
096          elts.add(createLeafElement(rootElement, SimpleAttributeSet.EMPTY, j, content.length()));
097
098        lines = new Element[elts.size()];
099        for (int i = 0; i < elts.size(); ++i)
100          lines[i] = (Element) elts.get(i);
101      }
102    catch (BadLocationException e)
103      {
104        lines = new Element[1];
105        lines[0] = createLeafElement(rootElement, SimpleAttributeSet.EMPTY, 0, 1);
106      }
107
108    ((BranchElement) rootElement).replace(0, rootElement.getElementCount(), lines);
109  }
110
111  protected AbstractDocument.AbstractElement createDefaultRoot()
112  {
113    BranchElement root =
114      (BranchElement) createBranchElement(null, null);
115
116    Element[] array = new Element[1];
117    array[0] = createLeafElement(root, null, 0, 1);
118    root.replace(0, 0, array);
119
120    return root;
121  }
122
123  protected void insertUpdate(DefaultDocumentEvent event,
124                              AttributeSet attributes)
125  {
126
127    String text = null;
128    int offset = event.getOffset();
129    int length = event.getLength();
130    try
131      {
132        text = getText(offset, length);
133      }
134    catch (BadLocationException ex)
135      {
136        AssertionError err = new AssertionError();
137        err.initCause(ex);
138        throw err;
139      }
140
141    boolean hasLineBreak = text.indexOf('\n') != -1;
142    boolean prevCharIsLineBreak = false;
143    try
144      {
145        prevCharIsLineBreak =
146          offset > 0 && getText(offset - 1, 1).charAt(0) == '\n';
147      }
148    catch (BadLocationException ex)
149      {
150        AssertionError err = new AssertionError();
151        err.initCause(ex);
152        throw err;
153      }
154    boolean lastCharIsLineBreak = text.charAt(text.length() - 1) == '\n';
155    int lineIndex = -1;
156    int lineStart = -1;
157    int lineEnd = -1;
158    Element[] removed = null;
159    BranchElement root = (BranchElement) rootElement;
160    boolean updateStructure = true;
161
162    if (prevCharIsLineBreak && ! lastCharIsLineBreak)
163      {
164        // We must fix the structure a little if the previous char
165        // is a linebreak and the last char isn't.
166        lineIndex = root.getElementIndex(offset - 1);
167        Element prevLine = root.getElement(lineIndex);
168        Element nextLine = root.getElement(lineIndex + 1);
169        lineStart = prevLine.getStartOffset();
170        lineEnd = nextLine.getEndOffset();
171        removed = new Element[]{ prevLine, nextLine };
172      }
173    else if (hasLineBreak)
174      {
175        lineIndex = root.getElementIndex(offset);
176        Element line = root.getElement(lineIndex);
177        lineStart = line.getStartOffset();
178        lineEnd = line.getEndOffset();
179        removed = new Element[]{ line };
180      }
181    else
182      {
183        updateStructure = false;
184      }
185
186    if (updateStructure)
187      {
188        // Break the lines between lineStart and lineEnd.
189        ArrayList lines = new ArrayList();
190        int len = lineEnd - lineStart;
191        try
192          {
193            text = getText(lineStart, len);
194          }
195        catch (BadLocationException ex)
196          {
197            AssertionError err = new AssertionError();
198            err.initCause(ex);
199            throw err;
200          }
201        int prevLineBreak = 0;
202        int lineBreak = text.indexOf('\n');
203        do
204          {
205            lineBreak++;
206            lines.add(createLeafElement(root, null, lineStart + prevLineBreak,
207                                        lineStart + lineBreak));
208            prevLineBreak = lineBreak;
209            lineBreak = text.indexOf('\n', prevLineBreak);
210          } while (prevLineBreak < len);
211
212        // Update the element structure and prepare document event.
213        Element[] added = (Element[]) lines.toArray(new Element[lines.size()]);
214        event.addEdit(new ElementEdit(root, lineIndex, removed, added));
215        root.replace(lineIndex, removed.length, added);
216      }
217    super.insertUpdate(event, attributes);
218  }
219
220  protected void removeUpdate(DefaultDocumentEvent event)
221  {
222    super.removeUpdate(event);
223
224    // added and removed are Element arrays used to add an ElementEdit
225    // to the DocumentEvent if there were entire lines added or removed
226    // from the Document
227    Element[] added = new Element[1];
228    Element[] removed;
229    int p0 = event.getOffset();
230
231    // check if we must collapse some elements
232    int i1 = rootElement.getElementIndex(p0);
233    int i2 = rootElement.getElementIndex(p0 + event.getLength());
234    if (i1 != i2)
235      {
236        // If there were lines removed then we have to add an ElementEdit
237        // to the DocumentEvent so we set it up now by filling the Element
238        // arrays "removed" and "added" appropriately
239        removed = new Element [i2 - i1 + 1];
240        for (int i = i1; i <= i2; i++)
241          removed[i-i1] = rootElement.getElement(i);
242
243        int start = rootElement.getElement(i1).getStartOffset();
244        int end = rootElement.getElement(i2).getEndOffset();
245        added[0] = createLeafElement(rootElement,
246                                          SimpleAttributeSet.EMPTY,
247                                          start, end);
248
249        // Now create and add the ElementEdit
250        ElementEdit e = new ElementEdit(rootElement, i1, removed, added);
251        event.addEdit(e);
252
253        // collapse elements if the removal spans more than 1 line
254        ((BranchElement) rootElement).replace(i1, i2 - i1 + 1, added);
255      }
256  }
257
258  public Element getDefaultRootElement()
259  {
260    return rootElement;
261  }
262
263  public Element getParagraphElement(int pos)
264  {
265    Element root = getDefaultRootElement();
266    return root.getElement(root.getElementIndex(pos));
267  }
268
269  /**
270   * Inserts a string into the document. If the document property
271   * '<code>filterNewLines</code>' is set to <code>Boolean.TRUE</code>, then
272   * all newlines in the inserted string are replaced by space characters,
273   * otherwise the superclasses behaviour is executed.
274   *
275   * Inserting content causes a write lock to be acquired during this method
276   * call.
277   *
278   * @param offs the offset at which to insert the string
279   * @param str the string to be inserted
280   * @param atts the text attributes of the string to be inserted
281   *
282   * @throws BadLocationException
283   */
284  public void insertString(int offs, String str, AttributeSet atts)
285    throws BadLocationException
286  {
287    String string = str;
288    if (str != null && Boolean.TRUE.equals(getProperty("filterNewlines")))
289      string = str.replaceAll("\n", " ");
290    super.insertString(offs, string, atts);
291  }
292}