001    /* PlainDocument.java --
002       Copyright (C) 2002, 2004, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import 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     */
052    public 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    }