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

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

FireDocumentEvent prints exceptions to debugstream

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