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

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

Added a fireInsertUpdate(DocEvent) method

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