View Javadoc

1   /***
2    * Redistribution  and use  in source  and binary  forms, with  or without
3    * modification, are permitted provided  that the following conditions are
4    * met :
5    *
6    * . Redistributions  of  source  code  must  retain  the  above copyright
7    *   notice, this list of conditions and the following disclaimer.
8    *
9    * . Redistributions in  binary form  must reproduce  the above  copyright
10   *   notice, this list of conditions  and the following disclaimer in  the
11   *   documentation and/or other materials provided with the distribution.
12   *
13   * . The name of the author may not be used to endorse or promote products
14   *   derived from this software without specific prior written permission.
15   *
16   * THIS SOFTWARE IS  PROVIDED BY THE  AUTHOR ``AS IS''  AND ANY EXPRESS  OR
17   * IMPLIED  WARRANTIES,  INCLUDING,  BUT   NOT  LIMITED  TO,  THE   IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE ARE
19   * DISCLAIMED.  IN NO  EVENT SHALL  THE AUTHOR  BE LIABLE  FOR ANY  DIRECT,
20   * INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR  CONSEQUENTIAL  DAMAGES
21   * (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT OF  SUBSTITUTE  GOODS OR
22   * SERVICES;  LOSS  OF USE,  DATA,  OR PROFITS;  OR  BUSINESS INTERRUPTION)
23   * HOWEVER CAUSED  AND ON  ANY THEORY  OF LIABILITY,  WHETHER IN  CONTRACT,
24   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25   * ANY  WAY  OUT OF  THE  USE OF  THIS  SOFTWARE, EVEN  IF  ADVISED OF  THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   *
28   * $Id$
29   */
30  
31  package palmed.edit.text;
32  
33  import java.io.DataInputStream;
34  import java.io.DataOutputStream;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.io.OutputStream;
38  import java.util.Enumeration;
39  import java.util.Vector;
40  import palmed.edit.util.Coordinate;
41  import palmed.io.ISerializable;
42  
43  /***
44   * This class handles the text.
45   *
46   * @author Mathieu Champlon
47   * @version $Revision$ $Date$
48   */
49  public final class Text implements IText, ISerializable
50  {
51      /***
52       * The size of each chunks when reading input stream.
53       */
54      private static final int CHUNK_SIZE = 1000; // FIXME calibrate this
55      /***
56       * The text data structure.
57       */
58      private final Vector text_;
59      /***
60       * The line delimiter.
61       */
62      private LineDelimiterInspector delimiter_;
63      /***
64       * An optional custom delimiter overriding the scanned one.
65       */
66      private String forcedDelimiter_;
67      /***
68       * Whether the text has been modified or not.
69       */
70      private boolean hasBeenModified_;
71      /***
72       * The views.
73       */
74      private final Vector views_;
75  
76      /***
77       * Create a text.
78       */
79      public Text()
80      {
81          text_ = new Vector();
82          text_.addElement( "" );
83          delimiter_ = new LineDelimiterInspector();
84          views_ = new Vector();
85          hasBeenModified_ = false;
86      }
87  
88      private int computeWidth()
89      {
90          int width = 0;
91          for( int y = 0; y < text_.size(); ++y )
92          {
93              final int newWidth = ((String)text_.elementAt( y )).length();
94              if( newWidth > width )
95                  width = newWidth;
96          }
97          return width;
98      }
99  
100     /***
101      * {@inheritDoc}
102      */
103     public int getHeight()
104     {
105         return text_.size();
106     }
107 
108     /***
109      * {@inheritDoc}
110      */
111     public String getLine( final int y )
112     {
113         if( y < 0 || y >= getHeight() )
114             return null;
115         return (String)text_.elementAt( y );
116     }
117 
118     private void checkPosition( final Coordinate position )
119     {
120         if( position.y_ < 0 || position.y_ >= getHeight() || position.x_ < 0
121                 || position.x_ > getLine( position.y_ ).length() )
122             throw new RuntimeException( "invalid position : " + position );
123     }
124 
125     /***
126      * {@inheritDoc}
127      */
128     public void insert( final Coordinate position, final char c )
129     {
130         checkPosition( position );
131         final String line = getLine( position.y_ );
132         text_.setElementAt( line.substring( 0, position.x_ ) + c + line.substring( position.x_ ), position.y_ );
133         hasBeenModified_ = true;
134         update();
135     }
136 
137     /***
138      * {@inheritDoc}
139      */
140     public void insertNewLine( final Coordinate position )
141     {
142         checkPosition( position );
143         insertNewLine( position.x_, position.y_ );
144         hasBeenModified_ = true;
145         update();
146     }
147 
148     private void insertNewLine( final int x, final int y )
149     {
150         final String line = getLine( y );
151         text_.setElementAt( line.substring( 0, x ), y );
152         text_.insertElementAt( line.substring( x ), y + 1 );
153     }
154 
155     /***
156      * {@inheritDoc}
157      */
158     public void remove( final Coordinate from, final Coordinate to )
159     {
160         if( from.equals( to ) )
161             return;
162         if( to.lessThan( from ) )
163             remove( to, from );
164         checkPosition( from );
165         checkPosition( to );
166         text_.setElementAt( getLine( from.y_ ).substring( 0, from.x_ ) + getLine( to.y_ ).substring( to.x_ ), from.y_ );
167         for( int y = from.y_; y < to.y_; ++y )
168             text_.removeElementAt( from.y_ + 1 );
169         hasBeenModified_ = true;
170         update();
171     }
172 
173     /***
174      * Register a view to receive lines invalidation events.
175      *
176      * @param view the view to register
177      */
178     public void register( final ITextView view )
179     {
180         if( view == null )
181             throw new IllegalArgumentException( "parameter 'view' is null" );
182         views_.addElement( view );
183         view.update( computeWidth(), text_.size() );
184         view.modified( hasBeenModified_ );
185     }
186 
187     private void update()
188     {
189         final Enumeration views = views_.elements();
190         while( views.hasMoreElements() )
191         {
192             final ITextView view = (ITextView)views.nextElement();
193             view.update( computeWidth(), text_.size() );
194             view.modified( hasBeenModified_ );
195         }
196     }
197 
198     private void updateModificationStatus()
199     {
200         final Enumeration views = views_.elements();
201         while( views.hasMoreElements() )
202             ((ITextView)views.nextElement()).modified( hasBeenModified_ );
203     }
204 
205     /***
206      * {@inheritDoc}
207      */
208     public void clear()
209     {
210         text_.removeAllElements();
211         text_.addElement( "" );
212         hasBeenModified_ = false;
213         update();
214     }
215 
216     /***
217      * {@inheritDoc}
218      */
219     public void read( final InputStream stream ) throws IOException
220     {
221         readText( stream );
222         hasBeenModified_ = false;
223         update();
224     }
225 
226     private void readText( final InputStream stream ) throws IOException
227     {
228         delimiter_ = new LineDelimiterInspector( stream );
229         text_.removeAllElements();
230         text_.addElement( "" );
231         read( new TextReader( delimiter_, new ITextBuilder()
232         {
233             public void append( final byte[] buffer, final int offset, final int length )
234             {
235                 final String line = text_.elementAt( text_.size() - 1 ) + new String( buffer, offset, length );
236                 text_.setElementAt( line, text_.size() - 1 );
237             }
238 
239             public void appendNewLine()
240             {
241                 text_.addElement( "" );
242             }
243         } ) );
244     }
245 
246     private void read( final TextReader reader ) throws IOException
247     {
248         final byte[] buffer = new byte[CHUNK_SIZE];
249         int size = 0;
250         do
251             size = reader.read( buffer );
252         while( size != -1 );
253     }
254 
255     /***
256      * {@inheritDoc}
257      */
258     public void write( final OutputStream stream ) throws IOException
259     {
260         writeText( stream );
261         hasBeenModified_ = false;
262         updateModificationStatus();
263     }
264 
265     private void writeText( final OutputStream stream ) throws IOException
266     {
267         stream.write( getLine( 0 ).getBytes() );
268         for( int line = 1; line < text_.size(); ++line )
269         {
270             writeDelimiter( stream );
271             stream.write( getLine( line ).getBytes() );
272         }
273     }
274 
275     /***
276      * {@inheritDoc}
277      */
278     public void write( final OutputStream stream, final Coordinate from, final Coordinate to ) throws IOException
279     {
280         if( to.lessThan( from ) )
281             write( stream, to, from );
282         checkPosition( from );
283         checkPosition( to );
284         if( from.y_ == to.y_ )
285             stream.write( getLine( from.y_ ).substring( from.x_, to.x_ ).getBytes() );
286         else
287         {
288             stream.write( getLine( from.y_ ).substring( from.x_ ).getBytes() );
289             for( int line = from.y_ + 1; line < to.y_; ++line )
290             {
291                 writeDelimiter( stream );
292                 stream.write( getLine( line ).getBytes() );
293             }
294             writeDelimiter( stream );
295             stream.write( getLine( to.y_ ).substring( 0, to.x_ ).getBytes() );
296         }
297     }
298 
299     private void writeDelimiter( final OutputStream stream ) throws IOException
300     {
301         if( forcedDelimiter_ != null )
302             stream.write( forcedDelimiter_.getBytes() );
303         else
304             delimiter_.write( stream );
305     }
306 
307     /***
308      * {@inheritDoc}
309      */
310     public void read( final InputStream stream, final Coordinate from ) throws IOException
311     {
312         checkPosition( from );
313         read( new TextReader( stream, new ITextBuilder()
314         {
315             private int x_ = from.x_;
316             private int y_ = from.y_;
317 
318             public void append( final byte[] buffer, final int offset, final int length )
319             {
320                 final String line = getLine( y_ );
321                 final String insertion = new String( buffer, offset, length );
322                 text_.setElementAt( line.substring( 0, x_ ) + insertion + line.substring( x_ ), y_ );
323                 x_ += length;
324             }
325 
326             public void appendNewLine()
327             {
328                 insertNewLine( x_, y_ );
329                 x_ = 0;
330                 ++y_;
331             }
332         } ) );
333         hasBeenModified_ = true;
334         update();
335     }
336 
337     /***
338      * {@inheritDoc}
339      */
340     public void unmarshall( final InputStream stream ) throws IOException
341     {
342         final DataInputStream input = new DataInputStream( stream );
343         hasBeenModified_ = input.readBoolean();
344         readText( stream );
345         update();
346     }
347 
348     /***
349      * {@inheritDoc}
350      */
351     public void marshall( final OutputStream stream ) throws IOException
352     {
353         final DataOutputStream output = new DataOutputStream( stream );
354         output.writeBoolean( hasBeenModified_ );
355         write( stream );
356     }
357 
358     /***
359      * {@inheritDoc}
360      */
361     public void delete()
362     {
363         clear();
364     }
365 
366     /***
367      * {@inheritDoc}
368      */
369     public void setLineSeparator( final String separator )
370     {
371         forcedDelimiter_ = separator;
372     }
373 }