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

Last change on this file since 8650 was 8474, checked in by mdewsnip, 20 years ago

Changed all show() to setVisible(true) and all hide() to setVisible(false) for Java 1.5.0.

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