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

Last change on this file since 31720 was 22807, checked in by ak19, 14 years ago

Fixed a bug in GLI where we weren't able to delete a collection just built (even without any docs in it) and which had just been closed. The reason was that the build_log file was never properly closed until a new collection was opened, at which point we could finally delete the previously closed collection. Now on CollectionManager.closeCollection(), GLI ensures that any currently active build_log in OptionsPane is closed (which calls the new close method in AppendLineOnlyFileDocument.

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