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;
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 }