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

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

Removed unnecessary imports of org.greenstone.gatherer.Gatherer.

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