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.IOException;
34 import java.io.InputStream;
35 import java.io.OutputStream;
36 import java.util.Enumeration;
37 import java.util.Vector;
38 import palmed.edit.util.Coordinate;
39
40 /***
41 * This class implements a composite of cachable text chunks.
42 *
43 * @author Mathieu Champlon
44 * @version $Revision$ $Date$
45 */
46 public final class LargeText implements IText
47 {
48 /***
49 * The maximum size of each text chunk in bytes.
50 */
51 private final int threshold_;
52 /***
53 * The views.
54 */
55 private final Vector views_;
56 /***
57 * The line inserter.
58 */
59 private final NewLineInserter newLineInserter_;
60 /***
61 * The text eraser.
62 */
63 private final TextEraser textEraser_;
64 /***
65 * The cache.
66 */
67 private final ICache cache_;
68
69 /***
70 * Create a large text.
71 *
72 * @param cache the cache
73 * @param threshold the size of each text chunk in bytes.
74 */
75 public LargeText( final ICache cache, final int threshold )
76 {
77 if( cache == null )
78 throw new IllegalArgumentException( "parameter 'cache' is null" );
79 if( threshold <= 0 )
80 throw new IllegalArgumentException( "parameter 'threshold' must be > 0" );
81 cache_ = cache;
82 threshold_ = threshold;
83 views_ = new Vector();
84 newLineInserter_ = new NewLineInserter();
85 textEraser_ = new TextEraser();
86 cache_.add( new Chunk( cache_ ) );
87 }
88
89 private boolean apply( final ILineExtractor functor, final int line )
90 {
91 if( line < 0 )
92 return false;
93 final Enumeration chunks = cache_.elements();
94 int height = 0;
95 while( chunks.hasMoreElements() && line >= height )
96 height += ((IChunk)chunks.nextElement()).handle( functor, line - height );
97 return line <= height;
98 }
99
100 private void apply( final ICoordinateExtractor functor, final Coordinate coordinate )
101 {
102 checkPosition( coordinate );
103 apply( new CoordinateExtractor( coordinate, functor ), coordinate.y_ );
104 }
105
106 private void apply( final ITextExtractor functor, final Coordinate from, final Coordinate to )
107 {
108 final Enumeration chunks = cache_.elements();
109 Coordinate start = new Coordinate();
110 while( chunks.hasMoreElements() && to.y_ >= start.y_ )
111 start = ((IChunk)chunks.nextElement()).handle( functor, start, from, to );
112 }
113
114 /***
115 * {@inheritDoc}
116 */
117 public int getHeight()
118 {
119 int height = 1;
120 final Enumeration chunks = cache_.elements();
121 while( chunks.hasMoreElements() )
122 height += ((IChunk)chunks.nextElement()).getHeight();
123 return height;
124 }
125
126 /***
127 * {@inheritDoc}
128 */
129 public String getLine( final int y )
130 {
131 final StringBuffer result = new StringBuffer();
132 if( apply( new LineBuilder( result ), y ) )
133 return result.toString();
134 return null;
135 }
136
137 /***
138 * {@inheritDoc}
139 */
140 public void insert( final Coordinate position, final char c )
141 {
142 apply( new CharacterInserter( c ), position );
143 update();
144 }
145
146 private void checkPosition( final Coordinate position )
147 {
148 if( position.y_ >= getHeight() || position.x_ > getLine( position.y_ ).length() )
149 throw new RuntimeException( "invalid position : " + position );
150 }
151
152 /***
153 * {@inheritDoc}
154 */
155 public void insertNewLine( final Coordinate position )
156 {
157 apply( newLineInserter_, position );
158 update();
159 }
160
161 /***
162 * {@inheritDoc}
163 */
164 public void remove( final Coordinate from, final Coordinate to )
165 {
166 apply( textEraser_, from, to );
167 update();
168 }
169
170 /***
171 * {@inheritDoc}
172 */
173 public void register( final ITextView view )
174 {
175 if( view == null )
176 throw new IllegalArgumentException( "parameter 'view' is null" );
177 views_.addElement( view );
178 }
179
180 /***
181 * {@inheritDoc}
182 */
183 public void clear()
184 {
185 cache_.clear();
186 cache_.add( new Chunk( cache_ ) );
187 update();
188 }
189
190 /***
191 * {@inheritDoc}
192 */
193 public void write( final OutputStream stream, final Coordinate from, final Coordinate to ) throws IOException
194 {
195 final Vector exceptions = new Vector();
196 apply( new TextSerializer( stream, exceptions ), from, to );
197 if( !exceptions.isEmpty() )
198 throw (IOException)exceptions.firstElement();
199 }
200
201 /***
202 * {@inheritDoc}
203 */
204 public void read( final InputStream stream, final Coordinate from ) throws IOException
205 {
206 final Vector exceptions = new Vector();
207 apply( new TextDeserializer( stream, exceptions ), from );
208 if( !exceptions.isEmpty() )
209 throw (IOException)exceptions.firstElement();
210 update();
211 }
212
213 private void update()
214 {
215 final Coordinate size = new Coordinate( 0, 1 );
216 int length = 0;
217 boolean hasBeenModified = false;
218 final Enumeration chunks = cache_.elements();
219 while( chunks.hasMoreElements() )
220 {
221 final IChunk chunk = (IChunk)chunks.nextElement();
222 length = chunk.count( size, length );
223 hasBeenModified |= chunk.hasBeenModified();
224 }
225 final Enumeration views = views_.elements();
226 while( views.hasMoreElements() )
227 {
228 final ITextView view = (ITextView)views.nextElement();
229 view.update( size.x_, size.y_ );
230 view.modified( hasBeenModified );
231 }
232 }
233
234 private void updateModificationStatus()
235 {
236 boolean hasBeenModified = false;
237 final Enumeration chunks = cache_.elements();
238 while( chunks.hasMoreElements() )
239 {
240 final IChunk chunk = (IChunk)chunks.nextElement();
241 hasBeenModified |= chunk.hasBeenModified();
242 }
243 final Enumeration views = views_.elements();
244 while( views.hasMoreElements() )
245 ((ITextView)views.nextElement()).modified( hasBeenModified );
246 }
247
248 /***
249 * {@inheritDoc}
250 */
251 public void read( final InputStream stream ) throws IOException
252 {
253 final MultiInputStream multi = new MultiInputStream( stream, threshold_ );
254 cache_.delete();
255 do
256 {
257 final IChunk chunk = new Chunk( cache_ );
258 chunk.read( multi );
259 cache_.add( chunk );
260 multi.next();
261 }
262 while( stream.available() > 0 );
263 update();
264 }
265
266 /***
267 * {@inheritDoc}
268 */
269 public void write( final OutputStream stream ) throws IOException
270 {
271 final Enumeration chunks = cache_.elements();
272 while( chunks.hasMoreElements() )
273 ((IChunk)chunks.nextElement()).write( stream );
274 updateModificationStatus();
275 }
276
277 /***
278 * {@inheritDoc}
279 */
280 public void unmarshall( final InputStream stream ) throws IOException
281 {
282 cache_.unmarshall( stream );
283 update();
284 }
285
286 /***
287 * {@inheritDoc}
288 */
289 public void marshall( final OutputStream stream ) throws IOException
290 {
291 cache_.marshall( stream );
292 }
293
294 /***
295 * {@inheritDoc}
296 */
297 public void delete()
298 {
299 cache_.delete();
300 }
301
302 /***
303 * {@inheritDoc}
304 */
305 public void setLineSeparator( final String separator )
306 {
307
308 throw new RuntimeException( "TODO support custom separator in large text" );
309 }
310 }