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

Last change on this file since 15622 was 15622, checked in by ak19, 16 years ago

DocumentEvent only fired in the WriterThread.run() method now since that is when the queued line (string) is finally written to the file

  • Property svn:keywords set to Author Date Id Revision
File size: 36.8 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Greenstone Librarian Interface (GLI) application,
5 * part of the Greenstone digital library software suite from the New
6 * Zealand Digital Library Project at the University of Waikato,
7 * New Zealand.
8 *
9 * Author: John Thompson, Greenstone Project, New Zealand Digital Library,
10 * University of Waikato
11 * http://www.nzdl.org
12 *
13 * Copyright (C) 2004 New Zealand Digital Library, University of Waikato
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 *########################################################################
29 */
30
31package org.greenstone.gatherer.util;
32
33import java.awt.*;
34import java.awt.event.*;
35import java.io.*;
36import java.util.*;
37import javax.swing.*;
38import javax.swing.event.*;
39import javax.swing.text.*;
40import org.greenstone.gatherer.DebugStream;
41import org.greenstone.gatherer.gui.GLIButton;
42
43/** 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.
44 * @author John Thompson, Greenstone Project, New Zealand Digital Library, University of Waikato
45 * @version 2.41 final
46 */
47public class AppendLineOnlyFileDocument
48 implements Document {
49 /** A default string to append at the start of each log file. Of special note is the beginning '.' character that will, upon completion of the import/build process, be replaced with a letter indicating the final status of this build attempt. */
50 static final private String GLI_HEADER_STR = ".";
51 /** The root element of the document model. */
52 private AppendLineOnlyFileDocumentElement root_element;
53 /** The current owner of this document, useful for callbacks when certain tasks are complete. */
54 private AppendLineOnlyFileDocumentOwner owner;
55 /** Is this document a build log, and hence has some extra functionality, of just a straight log. */
56 private boolean build_log = false;
57 /** A list of listeners attached to this document. */
58 private EventListenerList listeners_list;
59 /** This properties hash map allows you to store any metadata about this document for later review. */
60 private HashMap properties;
61 /** Indicates the current length of the underlying file (which may not equal the current number of characters in the document). */
62 private long length;
63 /** The random access file which will back this document model. */
64 private RandomAccessFile file;
65 /** The filename given to the underlying file. */
66 private String filename;
67 /** An independant thread responsible for writing the 'in memory' contents of the document model to the random access file as appropriate. This is done so IO lag does not effect the gui as much as it used to. */
68 private WriterThread writer;
69
70
71 public AppendLineOnlyFileDocument(String filename) {
72 this(filename, true);
73 }
74
75 /** The constructor is responsible for creating several necessary data structures as well as opening the random access file. When it creates the new file, as it is wont to do, it will immediately add the special header line defined above.
76 * @param filename the absolute path name of the file as a String
77 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement
78 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread
79 * @see org.greenstone.gatherer.util.StaticStrings#NEW_LINE_CHAR
80 */
81 public AppendLineOnlyFileDocument(String filename, boolean build_log) {
82 DebugStream.println("Creating log: " + filename);
83 // Initialization
84 this.build_log = build_log;
85 this.filename = filename;
86 this.listeners_list = new EventListenerList();
87 this.properties = new HashMap();
88 this.writer = new WriterThread();
89 writer.start();
90 // Open underlying file
91 try {
92 file = new RandomAccessFile(filename, "rw");
93 // Create the root element.
94 length = file.length();
95 root_element = new AppendLineOnlyFileDocumentElement();
96 // Now quickly read through the underlying file, building an Element for each line.
97 long start_offset = 0L;
98 file.seek(start_offset);
99 int character = -1;
100 while((character = file.read()) != -1) {
101 if(character == StaticStrings.NEW_LINE_CHAR) {
102 long end_offset = file.getFilePointer();
103 Element child_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset);
104 root_element.add(child_element);
105 child_element = null;
106 start_offset = end_offset;
107 }
108 }
109 // If there we no lines found, and we are a build log, then append the file header.
110 if(build_log && root_element.getElementCount() == 0) {
111 appendLine(GLI_HEADER_STR);
112 }
113 }
114 catch (Exception error) {
115 DebugStream.printStackTrace(error);
116 }
117 }
118
119 /** Adds a document listener for notification of any changes.
120 * @param listener the new DocumentListener to keep track of
121 */
122 public void addDocumentListener(DocumentListener listener) {
123 ///ystem.err.println("addDocumentListener(" + listener + ")");
124 listeners_list.add(DocumentListener.class, listener);
125 }
126
127 /** Append some content after the document.
128 * @param str the String to add as a new line in the document
129 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement
130 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentEvent
131 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread#queue
132 */
133 public void appendLine(String str) {
134 // Ensure the string ends in a newline
135 try {
136 if (!str.endsWith("\n")) {
137 str = str + "\n";
138 }
139 int str_length = str.getBytes("UTF-8").length;
140 long start_offset = length;
141 long end_offset = start_offset + (long) str_length;
142 length = length + str_length;
143 // Create a new element to represent this line
144 AppendLineOnlyFileDocumentElement new_line_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset, str);
145 root_element.add(new_line_element);
146 // Queue the content to be written.
147 writer.queue(new_line_element);
148 // moved the firing of DocumentEvent to the WriterThread.run() method for when the new line is *actually* written to the document
149 new_line_element = null;
150 }
151 catch(Exception error) {
152 DebugStream.printStackTrace(error);
153 }
154 }
155
156 /** Returns a position that will track change as the document is altered.
157 * @param offs the position offset as an int
158 * @return the Position to be monitored
159 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentPosition
160 */
161 public Position createPosition(int offs) {
162 return new AppendLineOnlyFileDocumentPosition(offs);
163 }
164
165 /** The destructor is implemented to ensure the current log file has finished transferring from memory to disk, and that the random access file is properly closed, before GLI exits.
166 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread#finish
167 */
168 public void destroy() {
169 try {
170 writer.finish();
171 file.close();
172 }
173 catch(Exception exception) {
174 DebugStream.println("Exception in AppendLineOnlyFileDocument.destroy() - unexpected");
175 DebugStream.printStackTrace(exception);
176 }
177 }
178
179 /** Gets the default root element for the document model.
180 * @return the root Element of this document
181 */
182 public Element getDefaultRootElement() {
183 return root_element;
184 }
185
186 /** Returns the length of the data.
187 * @return the length as an int (not a long)
188 */
189 public int getLength() {
190 return (int) length;
191 }
192
193 /** A version of get length which returns the offset to the start of the last line in the document.
194 * @return the offset length as an int
195 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement
196 */
197 public int getLengthToNearestLine() {
198 AppendLineOnlyFileDocumentElement last_element = (AppendLineOnlyFileDocumentElement)root_element.getElement(root_element.getElementCount() - 1);
199 if(last_element != null ) {
200 return last_element.getStartOffset();
201 }
202 else {
203 return (int) length; // The best we can do.
204 }
205 }
206
207 /** Retrieve a property that has previously been set for this document.
208 * @param key the key String under which the vaue is stored
209 * @return the value which is also a String, or null if no such value
210 */
211 public Object getProperty(Object key) {
212 return properties.get(key);
213 }
214
215 /** Determine if the writer thread is still running, ie a log file which is currently open for writing
216 * @return true if the writer is still active, false otherwise
217 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread#isStillWriting
218 */
219 public boolean isStillWriting() {
220 return writer.isStillWriting();
221 }
222
223 /** Gets a sequence of text from the document.
224 * @param offset the starting offset for the fragment of text required, as an int
225 * @param l the length of the text, also as an int
226 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement
227 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
228 */
229 public String getText(int offset, int l)
230 throws BadLocationException {
231
232 if (l == 0) {
233 return "";
234 }
235
236 try {
237 return read((long) offset, l);
238 }
239 catch (Exception ex) {
240 }
241
242 return "";
243
244// // System.err.println("AppendLineOnlyFileDocument::getText() request...");
245// String request = "getText(" + offset + ", " + l + ")";
246// ///ystem.err.println(request);
247// String text = null;
248// if(text == null || text.length() < l) {
249// try {
250// int file_length = (int) file.length();
251// ///ystem.err.println("file_length = " + file_length + ", length = " + length);
252// if(l == 0) {
253// text = StaticStrings.EMPTY_STR;
254// }
255// else if(0 <= offset && offset < length && (offset + l) <= length) {
256// if(offset < file_length) {
257// text = read((long)offset, l);
258// if(text.length() != l) {
259// ///ystem.err.println("Asked for " + l + " characters of text. But only read " + text.length());
260// throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
261// }
262// }
263// else {
264// int index = root_element.getElementIndex(offset);
265// if(index < root_element.getElementCount()) {
266// AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) root_element.getElement(index);
267// text = element.getContent();
268// }
269// else {
270// ///ystem.err.println("Index is " + index + " but there are only " + root_element.getElementCount() + " children.");
271// throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
272// }
273// }
274
275// }
276// }
277// catch (IOException error) {
278// DebugStream.printStackTrace(error);
279// }
280// if(text == null) {
281// ///ystem.err.println("Text is null.");
282// throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
283// }
284// }
285// request = null;
286// return text;
287 }
288
289 /** Fetches the text contained within the given portion of the document.
290 * @param offset the starting offset of the desired text fragment, as an int
291 * @param length the length of the text also as an int
292 * @param txt the Segment into which the text fragment should be placed
293 */
294 public void getText(int offset, int length, Segment txt)
295 throws javax.swing.text.BadLocationException {
296 String str = getText(offset, length);
297 txt.array = str.toCharArray();
298 txt.count = str.length();
299 txt.offset = 0;
300 str = null;
301 }
302
303 /** Set a property for this document.
304 * @param key the key to store the value under, as a String
305 * @param value the property value itself also as a String
306 */
307 public void putProperty(Object key, Object value) {
308 properties.put(key, value);
309 }
310
311 /** Determine if this document is ready by testing if it has an open descriptor to the random access file.
312 * @return true if the file exists thus the document is ready, false otherwise
313 */
314 public boolean ready() {
315 return (file != null);
316 }
317
318 /** Removes a document listener.
319 * @param listener the Document we wish to remove
320 */
321 public void removeDocumentListener(DocumentListener listener) {
322 listeners_list.remove(DocumentListener.class, listener);
323 }
324
325 /** Whenever the user requests a change of the loaded log, this method is called to ensure the previous log has been completely written to file. Note that this only garuentees that text fragments currently queued for writing will be correctly written - while the behaviour is undefined for text fragments added after this call (some may write depending on several race conditions).
326 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread#exit
327 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.WriterThread#finish
328 */
329 public void setExit() {
330 writer.exit();
331 writer.finish();
332 }
333
334 /** Establish the current owner of this document.
335 * @param owner the current AppendLineOnlyFileDocumentOwner
336 */
337 public void setOwner(AppendLineOnlyFileDocumentOwner owner) {
338 this.owner = owner;
339 }
340
341 /** To record the final state of the logging process we reserve a single character at the start of the file, and then replace it when the build process is complete.
342 * @param character the final status char to replace the X at the start of the log
343 */
344 public synchronized void setSpecialCharacter(char character) {
345 if(build_log) {
346 try {
347 file.seek(0L);
348 file.write((int)character);
349 }
350 catch (Exception error) {
351 DebugStream.printStackTrace(error);
352 }
353 }
354 }
355
356 /** Request a text representation of this document which currently returns the filename.
357 * @return the text as a String
358 */
359 public String toString() {
360 return filename;
361 }
362
363 /** This method reads a fragment of text from the underlying random access file. It is synchronized so that the file isn't being written to at the same time.
364 * @param start_offset the offset within the file to set the read pointer at, as a long
365 * @param l the number of characters to read as an int
366 */
367 private synchronized String read(long start_offset, int l)
368 throws IOException {
369 //print("read(" + start_offset + ", " + l + ")... ");
370 byte[] buffer = new byte[l];
371 file.seek(start_offset);
372 int result = file.read(buffer, 0, l);
373 return new String(buffer, "UTF-8");
374 //print("read() complete");
375 }
376
377 /** This methods writes a fragment of text to the underlying random access file. It is synchronized so that the file isn't being read from at the same time.
378 * @param start_offset the offset within the file to set the write pointer at, as a long
379 * @param end_offset the final offset of the new text fragment within the file, as a long. This is used to extend the file to be the correct length
380 * @param str the String to be written
381 * @param l the number of characters from str to be written, as an int
382 */
383 private synchronized void write(long start_offset, long end_offset, String str, int l)
384 throws IOException {
385 //print("write(" + start_offset + ", " + end_offset + ", " + str + ", " + l + ")");
386 file.setLength(end_offset);
387 file.seek(start_offset);
388 file.write(str.getBytes("UTF-8"), 0, l);
389 //print("write() complete");
390 }
391
392 /** This class provides the building-block for the document model. Each element is a line in the document, or has no content but contains several child elements. Due to the basic nature of what we are modelling only the root node has children. */
393 private class AppendLineOnlyFileDocumentElement
394 extends ArrayList
395 implements Element {
396 /** Our parent Element. */
397 private Element parent;
398 /** The offset to the end of our fragment(s) of text, in respect to the entire document. */
399 private long end;
400 /** The offset to the start of our fragment(s) of text, in respect to the entire document. */
401 private long start;
402 /** If we haven't been written to file yet, this contains the text fragment itself. */
403 private String content;
404
405 /** Construct a new root element, which can have no content, but calculates its start and end from its children. */
406 public AppendLineOnlyFileDocumentElement() {
407 super();
408 this.end = 0L;
409 this.parent = null;
410 this.start = 0L;
411 }
412
413 /** Construct a new element, whose content is found in bytes start to end - 1 within the random access file backing this document.
414 * @param start the starting offset as a long
415 * @param end the ending offset as a long
416 */
417 public AppendLineOnlyFileDocumentElement(long start, long end) {
418 super();
419 this.end = end;
420 this.parent = null;
421 this.start = start;
422 }
423
424 /** 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.
425 * @param start the starting offset as a long
426 * @param end the ending offset as a long
427 * @param content the text fragment String which we represent in the model
428 */
429 public AppendLineOnlyFileDocumentElement(long start, long end, String content) {
430 super();
431 this.content = content;
432 this.end = end;
433 this.parent = null;
434 this.start = start;
435 }
436
437 /** Add the given node as one of our children.
438 * @param child the new child Element
439 */
440 public void add(Element child) {
441 super.add(child);
442 ((AppendLineOnlyFileDocumentElement)child).setParent(this);
443 }
444
445 /** Having written the content to file this method removes it from the element. */
446 public void clearContent() {
447 content = null;
448 }
449
450 /** This document does not allow content markup.
451 * @return always returns null
452 */
453 public AttributeSet getAttributes() {
454 return null;
455 }
456
457 /** Retrieve the current content of this element, which may be the text fragment this element represents.
458 * @return either the text fragment as a String, or null if the fragment has already been written to disk
459 */
460 public String getContent() {
461 return content;
462 }
463
464 /** Fetches the document associated with this element.
465 * @return the AppendLineOnlyDocument containing this element
466 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument
467 */
468 public Document getDocument() {
469 return AppendLineOnlyFileDocument.this;
470 }
471
472 /** Fetches the child element at the given index.
473 * @param index the int index of the element to retrieve
474 * @return the requested Element
475 */
476 public Element getElement(int index) {
477 Element element;
478 if(0 <= index && index < size()) {
479 element = (Element) get(index);
480 }
481 else {
482 throw new IndexOutOfBoundsException("AppendLineOnlyDocument.AppendLineOnlyFileDocumentElement.getElement(" + index + ")");
483 }
484 return element;
485 }
486
487 /** Gets the number of child elements contained by this element.
488 * @return an int
489 */
490 public int getElementCount() {
491 return size();
492 }
493
494 /** Gets the child element index closest to the given offset.
495 * @param offset the offset within the text of the document, which due to the way the model is built must fll within the bounds of one of our child nodes
496 * @return the closest index as an int
497 */
498 public int getElementIndex(int offset) {
499 int index = -1;
500 if(parent != null) {
501 index = -1;
502 }
503 else if(offset < 0) {
504 index = 0;
505 }
506 else if(offset >= length) {
507 index = size() - 1;
508 }
509 else {
510 int size = size();
511 for(int i = 0; index == -1 && i < size; i++) {
512 Element child = (Element) get(i);
513 if(child.getStartOffset() <= offset && offset < child.getEndOffset()) {
514 index = i;
515 }
516 child = null;
517 }
518 }
519 return index;
520 }
521
522 /** Fetches the offset from the beginning of the document that this element ends at.
523 * @return the offset as an int
524 */
525 public int getEndOffset() {
526 if(parent != null) {
527 return (int) end;
528 }
529 // Return the Documents length.
530 else {
531 return (int) length;
532 }
533 }
534
535 /** This method retrieves the name of the element, however names are not important in this document so the name is always an empty string.
536 * @return an empty String
537 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
538 */
539 public String getName() {
540 return StaticStrings.EMPTY_STR;
541 }
542
543 /** Fetches the parent element.
544 * @return the parent Element
545 */
546 public Element getParentElement() {
547 return parent;
548 }
549
550 /** Fetches the offset from the beginning of the document that this element begins at.
551 * @return the offset as an int
552 */
553 public int getStartOffset() {
554 if(parent != null) {
555 return (int) start;
556 }
557 else {
558 return 0;
559 }
560 }
561
562 /** Since this is a very simple model, only the root node can have children. All the children are leaves.
563 * @return true if this is the root node, false otherwise
564 */
565 public boolean isLeaf() {
566 return (parent != null);
567 }
568
569 /** Establish the parent of this element
570 * @param parent the parent Element
571 */
572 public void setParent(Element parent) {
573 this.parent = parent;
574 }
575 }
576 // AppendLineOnlyFileDocumentElement
577
578 /** This event is created to encapsulate the details of some change to the document. */
579 private class AppendLineOnlyFileDocumentEvent
580 implements DocumentEvent {
581 /** The type of change event. */
582 private DocumentEvent.EventType type;
583 /** The element this change occured to, or in. */
584 private AppendLineOnlyFileDocumentElement element;
585 /** Another encapsulating class which contains further detail about the change itself. */
586 private AppendLineOnlyFileDocumentElementChange element_change;
587 /** The length of the text fragment affected by the change. */
588 private int len;
589 /** The offset within the document of the start of the change. */
590 private int offset;
591
592 /** Construct a new AppendLineOnlyFileDocumentEvent given the pertinant details.
593 * @param element the AppendLineOnlyFileDocumentElement affected by this change
594 * @param offset the offset within the document of the start of the change
595 * @param len the length of the text fragment affected by the change
596 * @param type the type of change event
597 */
598 public AppendLineOnlyFileDocumentEvent(AppendLineOnlyFileDocumentElement element, int offset, int len, DocumentEvent.EventType type) {
599 this.element = element;
600 this.element_change = null;
601 this.len = len;
602 this.offset = offset;
603 this.type = type;
604 }
605
606 /** Retrieve the docment which generated this event.
607 * @return the Document
608 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument
609 */
610 public Document getDocument() {
611 return AppendLineOnlyFileDocument.this;
612 }
613
614 /** Retrieve the length of the change.
615 * @return the length as an int
616 */
617 public int getLength() {
618 return len;
619 }
620
621 /** Retrieve the start offset of the change.
622 * @return the offset as an int
623 */
624 public int getOffset() {
625 return offset;
626 }
627
628 /** Retrieve the type of change.
629 * @return the type as a DocumentEvent.EventType
630 */
631 public DocumentEvent.EventType getType() {
632 return type;
633 }
634
635 /** Retrieve the element change object which further describes the changes. Of course given that we only allow the appending of new lines there really is only one type of event.
636 * @param elem implementation side-effect
637 * @return a DocumentEvent.ElementChange
638 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentEvent.AppendLineOnlyFileDocumentElementChange
639 */
640 public DocumentEvent.ElementChange getChange(Element elem) {
641 if(element_change == null) {
642 element_change = new AppendLineOnlyFileDocumentElementChange();
643 }
644 return element_change;
645 }
646
647 /** A pretty unexciting implementation of the document element change class. This usually describes the myriad of possible change events that can occur on a document. However we only allow line appends so there is really only one type of change. */
648 private class AppendLineOnlyFileDocumentElementChange
649 implements DocumentEvent.ElementChange {
650 /** This array always contains the one Element that was appended. */
651 private Element[] children_added;
652 /** This Element array is always empty. */
653 private Element[] children_removed;
654 /** The index of the affected element within its parents children. */
655 private int index;
656 /** Construct a new AppendLineOnlyFileDocumentElementChange with the default 'append line' details. */
657 public AppendLineOnlyFileDocumentElementChange() {
658 children_added = new Element[1];
659 children_added[0] = element;
660 children_removed = new Element[0];
661 index = root_element.indexOf(element);
662 }
663
664 /** Gets the child element that was appended to the parent element.
665 * @return an Element[] containing the added element
666 */
667 public Element[] getChildrenAdded() {
668 return children_added;
669 }
670
671 /** This model does not allow elements to be removed.
672 * @return an Element[] containing nothing
673 */
674 public Element[] getChildrenRemoved() {
675 return children_removed;
676 }
677
678 /** Returns the root element, as our document structure is only two layers deep.
679 * @return the root Element
680 */
681 public Element getElement() {
682 return root_element;
683 }
684
685 /** Fetches the index within the element represented.
686 * @return an int specifying the index of change within the root element
687 */
688 public int getIndex() {
689 return index;
690 }
691 }
692 // AppendLineOnlyFileDocumentElementChange
693 }
694 // AppendLineOnlyFileDocumentEvent
695
696 /** This class denoted a position within our document by an offset. Quite possibly the last interesting class I've ever had to deal with, but you have to implement it so here goes... */
697 private class AppendLineOnlyFileDocumentPosition
698 implements Position {
699 /** The offset within our document. */
700 private int offset;
701 /** Construct a new position given an offset.
702 * @param offset the offset as an int
703 */
704 public AppendLineOnlyFileDocumentPosition(int offset) {
705 this.offset = offset;
706 }
707 /** Retrieve the offset of this position.
708 * @return the offset as an int
709 */
710 public int getOffset() {
711 return offset;
712 }
713 }
714 // AppendLineOnlyFileDocumentPosition
715
716 /** This thread is responsible to writing the in memory text fragment contents of the document elements out to the random access file. The writing proceedure is implemented this way so that the gui doesn't take the performance hit of the IO. */
717 private class WriterThread
718 extends Thread {
719 /** When true was are being told to assume the contents of the queue are now static, and once completed the queue will be empty (and not expecting any new jobs). Used to finish the queue before the GLI exits. */
720 private boolean empty_queue;
721 /** Setting this flag to true tells the writer to stop regardless of whether there are items left on the queue. */
722 private boolean exit;
723 /** When the writer thread is busy writing content to the file this flag is set to true. */
724 private boolean running;
725 /** The fragment queue as a Vector (for thread reasons). */
726 private Vector queue;
727 /** Construct a new writer thread with an initially empty queue. */
728 public WriterThread() {
729 super("WriterThread");
730 empty_queue = false;
731 exit = false;
732 queue = new Vector();
733 }
734
735 /** This method is called to ask the writer thread to die after it finishes any current write proceceedure. */
736 public synchronized void exit() {
737 //print("WriterThread.exit() start");
738 exit = true;
739 notify();
740 //print("WriterThread.exit() complete");
741 }
742
743 /** When this returns there are no jobs waiting in the queue. */
744 public void finish() {
745 if(!queue.isEmpty() && running) {
746 ///ystem.err.println("Somethings gone wrong, as there are still " + queue.size() + " items to be written out and yet the thread seems to be waiting.");
747 empty_queue = true;
748 run();
749 }
750 }
751
752 /** Determine if the writer is currently running.
753 * @return true if it is, false if it has been exited or if it was never running in the first place (an old log)
754 */
755 public boolean isStillWriting() {
756 return running;
757 }
758
759 /** The run method of this thread checks if there are any document elements queued to be written to file, and then writes them as necessary.
760 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument
761 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement
762 */
763 public void run() {
764 exit = false;
765 running = true;
766 while(!exit) {
767 if(!queue.isEmpty()) {
768 AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) queue.remove(0);
769 // Write the content to file.
770 String content = element.getContent();
771 if(content != null) {
772 try {
773 write(element.getStartOffset(), element.getEndOffset(), content, content.getBytes("UTF-8").length);
774 // Now fire an event so everyone knows the Document's content has changed.
775 int str_length = content.getBytes("UTF-8").length;
776 long start_offset = length;
777 long end_offset = start_offset + (long) str_length;
778 length = length + str_length;
779 DocumentEvent event = new AppendLineOnlyFileDocumentEvent(element, (int)start_offset, str_length, DocumentEvent.EventType.INSERT);
780 Object[] listeners = AppendLineOnlyFileDocument.this.listeners_list.getListenerList();
781 for (int i = listeners.length - 2; i >= 0; i = i - 2) {
782 if (listeners[i] == DocumentListener.class) {
783 ((DocumentListener)listeners[i+1]).insertUpdate(event);
784 }
785 }
786 listeners = null;
787 event = null;
788 }
789 catch(Exception error) {
790 DebugStream.printStackTrace(error);
791 }
792 element.clearContent();
793 }
794 }
795 else if(empty_queue) {
796 exit = true;
797 }
798 else {
799 synchronized(this) {
800 try {
801 print("WriterThread.wait() start");
802 wait();
803 }
804 catch(Exception exception) {
805 }
806 print("WriterThread.wait() complete");
807 }
808 }
809 }
810 print("WriterThread completely finished. Exiting.");
811 running = false;
812 if(owner != null) {
813 owner.remove(AppendLineOnlyFileDocument.this);
814 }
815 }
816
817 /** Enqueue a document element to have its text content written to file.
818 * @param element the Element needing its content written out
819 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.AppendLineOnlyFileDocumentElement#getContent
820 */
821 public synchronized void queue(Element element) {
822 print("WriterThread.queue(): " + ((AppendLineOnlyFileDocumentElement)element).getContent());
823 queue.add(element);
824 notify();
825 }
826 }
827 // WriterThread
828
829 // ***** METHODS WE ARE NOW IGNORING BECAUSE WE ARE VIRTUALLY READ-ONLY *****
830
831 /** Not implemented. Add a new undo listener to this document.
832 * @param listener UndoableEditListener
833 */
834 public void addUndoableEditListener(UndoableEditListener listener) {}
835
836 /** Not implemented. Get the last position in the document.
837 * @return null
838 */
839 public Position getEndPosition() { return null; }
840
841 /** Not implemented. Gets all root elements defined.
842 * @return null
843 */
844 public Element[] getRootElements() { return null; }
845
846 /** Not implemented. Retrieve the first position in the document.
847 * @return null
848 */
849 public Position getStartPosition() { return null; }
850
851 /** Not implemented. Insert a text fragment into our document.
852 * @param offset int
853 * @param str String
854 * @param a AttributeSet
855 */
856 public void insertString(int offset, String str, AttributeSet a) {}
857
858 /** Not implemented. Removes some content from the document.
859 * @param offs int
860 * @param len int
861 */
862 public void remove(int offs, int len) {}
863
864 /** Not implemented. Removes an undo listener.
865 * @param listener UndoableEditListener
866 */
867 public void removeUndoableEditListener(UndoableEditListener listener) {}
868
869 /** Not implemented. Renders a runnable apparently - whatever that means.
870 * @param r Runnable
871 */
872 public void render(Runnable r) {}
873
874 // DEBUG ONLY
875
876 /** Print a message to the GLI debug file stream.
877 * @param message the message to be written as a String
878 */
879 static synchronized public void print(String message) {
880 if (message.endsWith("\n")) {
881 DebugStream.print(message);
882 }
883 else {
884 DebugStream.println(message);
885 }
886 }
887
888 /** Create a test document with the ability to add new lines of text as necessary.
889 * @param args the initial starting arguments as a String array
890 * @see org.greenstone.gatherer.gui.GLIButton
891 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument
892 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.ReadButtonListener
893 */
894 static public void main(String[] args) {
895 JFrame frame = new JFrame("AppendLineOnlyFileDocument Test");
896 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
897 frame.setSize(640,480);
898 JPanel content = (JPanel) frame.getContentPane();
899
900 //PlainDocument document = new PlainDocument();
901 //document.setAsynchronousLoadPriority(-1);
902 final AppendLineOnlyFileDocument document = new AppendLineOnlyFileDocument("temp.txt");
903
904 final JTextArea text_area = new JTextArea(document);
905
906 JButton read_button = new GLIButton("Read Huge File");
907 read_button.addActionListener(new ReadButtonListener(document));
908 content.setLayout(new BorderLayout());
909 content.add(new JScrollPane(text_area), BorderLayout.CENTER);
910 content.add(read_button, BorderLayout.SOUTH);
911
912 frame.setVisible(true);
913 }
914
915 /** Listens for actions on the read button, and if detected creates a new ReadTask to test the document.
916 * @author John Thompson, Greenstone Project, New Zealand Digital Library, University of Waikato
917 * @version 2.41 final
918 */
919 static private class ReadButtonListener
920 implements ActionListener {
921 /** The AppendLineOnlyFileDocument object we are testing. */
922 private AppendLineOnlyFileDocument document;
923
924 /** All the constructor does is take a copy of the document to test.
925 * @param document the Document
926 */
927 public ReadButtonListener(AppendLineOnlyFileDocument document) {
928 this.document = document;
929 }
930
931 /** When the button is clicked this method is called to create the new task and run it.
932 * @param event an ActionEvent containing further information about the button click
933 * @see org.greenstone.gatherer.util.AppendLineOnlyFileDocument.ReadTask
934 */
935 public void actionPerformed(ActionEvent event) {
936 Thread task = new ReadTask(document);
937 task.start();
938 }
939 }
940
941 /** This threaded task opens a large document, aptly named 'big.txt', and then bombards the document object we are testing with lines from the file. This file should be several megs (such as Alice or TREC) to fully test functionality, thread conditions etc.
942 * @author John Thompson, Greenstone Project, New Zealand Digital Library, University of Waikato
943 * @version 2.41 final
944 */
945 static private class ReadTask
946 extends Thread {
947 /** The AppendLineOnlyFileDocument object we are testing. */
948 private AppendLineOnlyFileDocument document;
949
950 /** Construct a new task which will perform testing on the given document.
951 * @param document the AppendLineOnlyFileDocument to append lines to
952 */
953 public ReadTask(AppendLineOnlyFileDocument document) {
954 super("LoadHugeFileThread");
955 this.document = document;
956 }
957
958 /** The runable part of this class opens the file, then reads it in a line at a time, appending these lines to the ever growing document. */
959 public void run() {
960 // Load the specified document
961 try {
962 BufferedReader in = new BufferedReader(new FileReader(new File("big.txt")));
963 String line;
964
965 while ((line = in.readLine()) != null) {
966 document.appendLine(line);
967 try {
968 // Wait a random ammount of time.
969 synchronized(this) {
970 //print("LoadHugeFileThread.wait() start");
971 wait(100);
972 //print("LoadHugeFileThread.wait() complete");
973 }
974 }
975 catch(Exception error) {
976 error.printStackTrace();
977 }
978 }
979 in.close();
980 }
981 catch (Exception error) {
982 error.printStackTrace();
983 }
984 }
985 }
986 // ReadTask
987}
Note: See TracBrowser for help on using the repository browser.