source: trunk/gli/src/org/greenstone/gatherer/util/AppendLineOnlyFileDocument.java@ 5581

Last change on this file since 5581 was 5230, checked in by jmt12, 21 years ago

Changed QUOTE_CHARACTER to SPEECH_CHARACTER

  • Property svn:keywords set to Author Date Id Revision
File size: 18.6 KB
Line 
1package org.greenstone.gatherer.util;
2
3import java.awt.*;
4import java.awt.event.*;
5import java.io.*;
6import java.util.*;
7import javax.swing.*;
8import javax.swing.event.*;
9import javax.swing.text.*;
10
11import org.greenstone.gatherer.Gatherer;
12import org.greenstone.gatherer.util.StaticStrings;
13
14/** A Document whose underlying data is stored in a RandomAccessFile, and whose Element implementations lack the memory hogging problems associated with anything that extends the AbstractDocument class. This Document, for reasons of time constraints and sanity, only provides an editting ability of appending new lines to the end of the current document, perfect for our logging needs, completely useless for text editing purposes. Furthermore, since the append actions tend to somewhat swamp the IO, I'll temporarily store strings in the structure model, and write them out using a separate thread. */
15public class AppendLineOnlyFileDocument
16 implements Document {
17
18 static final private String EMPTY_STR = "";
19 static final private String GLI_HEADER_STR = "X:GLI Import and Build log:";
20
21 private AppendLineOnlyFileDocumentElement root_element;
22 private AppendLineOnlyFileDocumentOwner owner;
23 private EventListenerList listeners_list;
24 private HashMap cache;
25 private HashMap properties;
26 private long length;
27 private RandomAccessFile file;
28 private WriterThread writer;
29
30 public AppendLineOnlyFileDocument(String filename) {
31 System.err.println("Creating log: " + filename);
32 // Initialization
33 this.cache = new HashMap();
34 this.listeners_list = new EventListenerList();
35 this.properties = new HashMap();
36 this.writer = new WriterThread();
37 writer.start();
38 // Open underlying file
39 try {
40 file = new RandomAccessFile(filename, "rw");
41 // Create the root element.
42 length = file.length();
43 root_element = new AppendLineOnlyFileDocumentElement();
44 // Now quickly read through the underlying file, building an Element for each line.
45 long start_offset = 0L;
46 file.seek(start_offset);
47 int character = -1;
48 while((character = file.read()) != -1) {
49 if(character == StaticStrings.NEW_LINE_CHAR) {
50 long end_offset = file.getFilePointer();
51 Element child_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset);
52 root_element.add(child_element);
53 child_element = null;
54 start_offset = end_offset;
55 }
56 }
57 // If there we no lines found, then append the file header.
58 if(root_element.getElementCount() == 0) {
59 appendLine(GLI_HEADER_STR);
60 }
61 }
62 catch (Exception error) {
63 Gatherer.printStackTrace(error);
64 }
65 }
66
67 /** Adds a document listener for notification of any changes. */
68 public void addDocumentListener(DocumentListener listener) {
69 ///ystem.err.println("addDocumentListener(" + listener + ")");
70 listeners_list.add(DocumentListener.class, listener);
71 }
72
73 /** Append some content after the document. */
74 public void appendLine(String str) {
75 // Ensure the string ends in a newline
76 if(!str.endsWith("\n")) {
77 str = str + "\n";
78 }
79 try {
80 int str_length = str.length();
81 long start_offset = length;
82 long end_offset = start_offset + (long) str_length;
83 length = length + str_length;
84 //write(start_offset, end_offset, str, str_length);
85 // Create a new element to represent this line
86 AppendLineOnlyFileDocumentElement new_line_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset, str);
87 root_element.add(new_line_element);
88 // Queue the content to be written.
89 writer.queue(new_line_element);
90 // Now fire an event so everyone knows the content has changed.
91 DocumentEvent event = new AppendLineOnlyFileDocumentEvent(new_line_element, (int)start_offset, str_length, DocumentEvent.EventType.INSERT);
92 Object[] listeners = listeners_list.getListenerList();
93 for (int i = listeners.length - 2; i >= 0; i = i - 2) {
94 if (listeners[i] == DocumentListener.class) {
95 ((DocumentListener)listeners[i+1]).insertUpdate(event);
96 }
97 }
98 listeners = null;
99 event = null;
100 new_line_element = null;
101 }
102 catch(Exception error) {
103 Gatherer.printStackTrace(error);
104 }
105 }
106
107 /** Returns a position that will track change as the document is altered. */
108 public Position createPosition(int offs) {
109 return new AppendLineOnlyFileDocumentPosition(offs);
110 }
111
112 /** Gets the default root element for the document model. */
113 public Element getDefaultRootElement() {
114 return root_element;
115 }
116
117 /** Returns the length of the data. */
118 public int getLength() {
119 return (int) length;
120 }
121
122 public Object getProperty(Object key) {
123 return properties.get(key);
124 }
125
126 public boolean isStillWriting() {
127 return writer.isStillWriting();
128 }
129
130 /** Gets a sequence of text from the document. */
131 public String getText(int offset, int l)
132 throws BadLocationException {
133 String request = "getText(" + offset + ", " + l + ")";
134 ///ystem.err.println(request);
135 String text = (String) cache.get(request);
136 if(text == null || text.length() < l) {
137 try {
138 int file_length = (int) file.length();
139 ///ystem.err.println("file_length = " + file_length + ", length = " + length);
140 if(l == 0) {
141 text = EMPTY_STR;
142 }
143 else if(0 <= offset && offset < length && (offset + l) <= length) {
144 if(offset < file_length) {
145 text = read((long)offset, l);
146 if(text.length() != l) {
147 ///ystem.err.println("Asked for " + l + " characters of text. But only read " + text.length());
148 throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
149 }
150 }
151 else {
152 int index = root_element.getElementIndex(offset);
153 if(index < root_element.getElementCount()) {
154 AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) root_element.getElement(index);
155 text = element.getContent();
156 }
157 else {
158 ///ystem.err.println("Index is " + index + " but there are only " + root_element.getElementCount() + " children.");
159 throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
160 }
161 }
162
163 }
164 }
165 catch (IOException error) {
166 Gatherer.printStackTrace(error);
167 }
168 if(text == null) {
169 ///ystem.err.println("Text is null.");
170 throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
171 }
172 cache.put(request, text);
173 }
174 request = null;
175 return text;
176 }
177
178 /** Fetches the text contained within the given portion of the document. */
179 public void getText(int offset, int length, Segment txt)
180 throws javax.swing.text.BadLocationException {
181 String str = getText(offset, length);
182 txt.array = str.toCharArray();
183 txt.count = str.length();
184 txt.offset = 0;
185 str = null;
186 }
187
188 public void putProperty(Object key, Object value) {
189 properties.put(key, value);
190 }
191
192 public boolean ready() {
193 return (file != null);
194 }
195
196 //Removes a document listener.
197 public void removeDocumentListener(DocumentListener listener) {
198 ///ystem.err.println("removeDocumentListener()");
199 listeners_list.remove(DocumentListener.class, listener);
200 }
201
202 public void setExit() {
203 writer.exit();
204 }
205
206 public void setOwner(AppendLineOnlyFileDocumentOwner owner) {
207 this.owner = owner;
208 }
209
210 /** To record the final state of the logging process we reserve a single character at the start of the file. */
211 public synchronized void setSpecialCharacter(char character) {
212 try {
213 file.seek(0L);
214 file.write((int)character);
215 }
216 catch (Exception error) {
217 Gatherer.printStackTrace(error);
218 }
219 }
220
221 private synchronized String read(long start_offset, int l)
222 throws IOException {
223 //print("read(" + start_offset + ", " + l + ")... ");
224 byte[] buffer = new byte[l];
225 file.seek(start_offset);
226 int result = file.read(buffer, 0, l);
227 //print("read() complete");
228 return new String(buffer);
229 }
230
231 private synchronized void write(long start_offset, long end_offset, String str, int l)
232 throws IOException {
233 //print("write(" + start_offset + ", " + end_offset + ", " + str + ", " + l + ")");
234 file.setLength(end_offset);
235 file.seek(start_offset);
236 file.write(str.getBytes(), 0, l);
237 //print("write() complete");
238 }
239
240 private class AppendLineOnlyFileDocumentElement
241 extends ArrayList
242 implements Element {
243
244 private Element parent;
245 private long end;
246 private long start;
247 private String content;
248
249 /** Construct a new root element, which can have no content, but calculates its start and end from its children.
250 * @param start the starting offset as a long
251 * @param end the ending offset as a long.
252 */
253 public AppendLineOnlyFileDocumentElement() {
254 super();
255 this.end = 0L;
256 this.parent = null;
257 this.start = 0L;
258 }
259
260
261 /** Construct a new element, whose content is found in bytes start to end - 1 within the random access file backing this document.
262 * @param start the starting offset as a long
263 * @param end the ending offset as a long.
264 */
265 public AppendLineOnlyFileDocumentElement(long start, long end) {
266 super();
267 this.end = end;
268 this.parent = null;
269 this.start = start;
270 }
271
272 /** Construct a new element, whose content is provided, but should at some later time be written to bytes start to end - 1 in the random access file backing this document.
273 * @param start the starting offset as a long
274 * @param end the ending offset as a long.
275 */
276 public AppendLineOnlyFileDocumentElement(long start, long end, String content) {
277 super();
278 this.content = content;
279 this.end = end;
280 this.parent = null;
281 this.start = start;
282 }
283
284 public void add(Element child) {
285 super.add(child);
286 ((AppendLineOnlyFileDocumentElement)child).setParent(this);
287 }
288
289 public void clearContent() {
290 content = null;
291 }
292
293 /** This document does not allow content markup. */
294 public AttributeSet getAttributes() {
295 return null;
296 }
297
298 public String getContent() {
299 return content;
300 }
301
302 /** Fetches the document associated with this element.
303 * @return the AppendLineOnlyDocument containing this element
304 */
305 public Document getDocument() {
306 return AppendLineOnlyFileDocument.this;
307 }
308
309 /** Fetches the child element at the given index. */
310 public Element getElement(int index) {
311 Element element;
312 if(0 <= index && index < size()) {
313 element = (Element) get(index);
314 }
315 else {
316 throw new IndexOutOfBoundsException("AppendLineOnlyDocument.AppendLineOnlyFileDocumentElement.getElement(" + index + ")");
317 }
318 return element;
319 }
320
321 /** Gets the number of child elements contained by this element. */
322 public int getElementCount() {
323 return size();
324 }
325
326 /** Gets the child element index closest to the given offset. */
327 public int getElementIndex(int offset) {
328 int index = -1;
329 if(parent != null) {
330 index = -1;
331 }
332 else if(offset < 0) {
333 index = 0;
334 }
335 else if(offset >= length) {
336 index = size() - 1;
337 }
338 else {
339 int size = size();
340 for(int i = 0; index == -1 && i < size; i++) {
341 Element child = (Element) get(i);
342 if(child.getStartOffset() <= offset && offset < child.getEndOffset()) {
343 index = i;
344 }
345 child = null;
346 }
347 }
348 return index;
349 }
350
351 /** Fetches the offset from the beginning of the document that this element ends at. */
352 public int getEndOffset() {
353 if(parent != null) {
354 return (int) end;
355 }
356 // Return the Documents length.
357 else {
358 return (int) length;
359 }
360 }
361
362 /** This method retrieves the name of the element, however names are not important in this document so the name is always an empty string.
363 * @return an empty String
364 */
365 public String getName() {
366 return StaticStrings.EMPTY_STR;
367 }
368
369 /** Fetches the parent element. */
370 public Element getParentElement() {
371 return parent;
372 }
373
374 /** Fetches the offset from the beginning of the document that this element begins at. */
375 public int getStartOffset() {
376 if(parent != null) {
377 return (int) start;
378 }
379 else {
380 return 0;
381 }
382 }
383
384 /** Since this is a very simple model, only the root node can have children. All the children are leaves. */
385 public boolean isLeaf() {
386 return (parent != null);
387 }
388
389 public void setParent(Element parent) {
390 this.parent = parent;
391 }
392 }
393
394 private class AppendLineOnlyFileDocumentEvent
395 implements DocumentEvent {
396
397 private DocumentEvent.EventType type;
398 private AppendLineOnlyFileDocumentElement element;
399 private AppendLineOnlyFileDocumentElementChange element_change;
400 private int len;
401 private int offset;
402
403 public AppendLineOnlyFileDocumentEvent(AppendLineOnlyFileDocumentElement element, int offset, int len, DocumentEvent.EventType type) {
404 this.element = element;
405 this.element_change = null;
406 this.len = len;
407 this.offset = offset;
408 this.type = type;
409 }
410
411 public Document getDocument() {
412 return AppendLineOnlyFileDocument.this;
413 }
414
415 public int getLength() {
416 return len;
417 }
418
419 public int getOffset() {
420 return offset;
421 }
422
423 public DocumentEvent.EventType getType() {
424 return type;
425 }
426
427 // ***** IGNORE *****
428 public DocumentEvent.ElementChange getChange(Element elem) {
429 if(element_change == null) {
430 element_change = new AppendLineOnlyFileDocumentElementChange();
431 }
432 return element_change;
433 }
434
435 private class AppendLineOnlyFileDocumentElementChange
436 implements DocumentEvent.ElementChange {
437
438 private Element[] children_added;
439 private Element[] children_removed;
440 private int index;
441 public AppendLineOnlyFileDocumentElementChange() {
442 children_added = new Element[1];
443 children_added[0] = element;
444 children_removed = new Element[0];
445 index = root_element.indexOf(element);
446 }
447 /** Gets the child element that was added to the given parent element.
448 * @return an Element[] containing the added element
449 */
450 public Element[] getChildrenAdded() {
451 return children_added;
452 }
453 /** This model does not allow elements to be removed.
454 * @return an Element[] containing nothing
455 */
456 public Element[] getChildrenRemoved() {
457 return children_removed;
458 }
459
460 /** Returns the root element, as our document structure is only two layers deep.
461 * @return the root Element
462 */
463 public Element getElement() {
464 return root_element;
465 }
466
467 /** Fetches the index within the element represented.
468 * @return an int specifying the index of change within the root element
469 */
470 public int getIndex() {
471 return index;
472 }
473 }
474 }
475
476 private class AppendLineOnlyFileDocumentPosition
477 implements Position {
478
479 private int offset;
480
481 public AppendLineOnlyFileDocumentPosition(int offset) {
482 this.offset = offset;
483 }
484
485 public int getOffset() {
486 return offset;
487 }
488 }
489
490 private class WriterThread
491 extends Thread {
492
493 private boolean exit;
494 private boolean running;
495 private Vector queue;
496
497 public WriterThread() {
498 super("WriterThread");
499 exit = false;
500 queue = new Vector();
501 }
502
503 public synchronized void exit() {
504 //print("WriterThread.exit() start");
505 exit = true;
506 notify();
507 //print("WriterThread.exit() complete");
508 }
509
510 public boolean isStillWriting() {
511 return running;
512 }
513
514 public void run() {
515 running = true;
516 while(!exit) {
517 if(!queue.isEmpty()) {
518 AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) queue.remove(0);
519 // Write the content to file.
520 String content = element.getContent();
521 if(content != null) {
522 try {
523 write(element.getStartOffset(), element.getEndOffset(), content, content.length());
524 }
525 catch(Exception error) {
526 Gatherer.printStackTrace(error);
527 }
528 element.clearContent();
529 }
530 }
531 else {
532 synchronized(this) {
533 try {
534 //print("WriterThread.wait() start");
535 wait();
536 //print("WriterThread.wait() complete");
537 }
538 catch(Exception error) {
539 }
540 }
541 }
542 }
543 running = false;
544 if(owner != null) {
545 owner.remove(AppendLineOnlyFileDocument.this);
546 }
547 }
548
549 public synchronized void queue(Element element) {
550 //print("WriterThread.queue() start");
551 queue.add(element);
552 notify();
553 //print("WriterThread.queue() complete");
554 }
555 }
556
557 // ***** METHODS WE ARE NOW IGNORING BECAUSE WE ARE VIRTUALLY READ-ONLY *****
558
559 /** Adds an undo listener for notification of any changes. */
560 public void addUndoableEditListener(UndoableEditListener listener) {}
561
562 /** */
563 public Position getEndPosition() {
564 ///ystem.err.println("getEndPosition()");
565 return null;
566 }
567
568 /** Gets all root elements defined. */
569 public Element[] getRootElements() {return null;}
570
571 public Position getStartPosition() {
572 ///ystem.err.println("getStartPosition()");
573 return null;
574 }
575
576 public void insertString(int offset, String str, AttributeSet a) {}
577
578 /** Removes some content from the document. */
579 public void remove(int offs, int len) {}
580
581 /** Removes an undo listener. */
582 public void removeUndoableEditListener(UndoableEditListener listener) {}
583
584 /** Renders a runnable apparently. */
585 public void render(Runnable r) {}
586
587 static synchronized public void print(String message) {
588 Gatherer.println(message);
589 }
590
591 static public void main(String[] args) {
592 JFrame frame = new JFrame("AppendLineOnlyFileDocument Test");
593 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
594 frame.setSize(640,480);
595 JPanel content = (JPanel) frame.getContentPane();
596
597 //PlainDocument document = new PlainDocument();
598 //document.setAsynchronousLoadPriority(-1);
599 final AppendLineOnlyFileDocument document = new AppendLineOnlyFileDocument("temp.txt");
600
601 final JTextArea text_area = new JTextArea(document);
602
603 JButton read_button = new JButton("Read Huge File");
604 read_button.addActionListener(new ActionListener() {
605 public void actionPerformed(ActionEvent event) {
606 Thread task = new Thread("LoadHugeFileThread") {
607 public void run() {
608 // Load the specified document
609 try {
610 BufferedReader in = new BufferedReader(new FileReader(new File("big.txt")));
611 String line;
612
613 while ((line = in.readLine()) != null) {
614 document.appendLine(line);
615 try {
616 // Wait a random ammount of time.
617 synchronized(this) {
618 //print("LoadHugeFileThread.wait() start");
619 wait(100);
620 //print("LoadHugeFileThread.wait() complete");
621 }
622 }
623 catch(Exception error) {
624 error.printStackTrace();
625 }
626 }
627
628 } catch (Exception error) {
629 error.printStackTrace();
630 }
631 }
632 };
633 task.start();
634
635 }
636 });
637 content.setLayout(new BorderLayout());
638 content.add(new JScrollPane(text_area), BorderLayout.CENTER);
639 content.add(read_button, BorderLayout.SOUTH);
640
641 frame.show();
642 }
643}
Note: See TracBrowser for help on using the repository browser.