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

Last change on this file since 6539 was 6539, checked in by jmt12, 20 years ago

Heres a bunch of other changed files. If it wasn't a Friday afternoon I might be bothered finding out what I actually changed in them. Such changes include: a new option or three on preferences, a bug fix for the GDM classes, several changes to CDM to allow for G2.39 configuration files, a fix to Codec to allow for quotes in format strings and more work on CommandTokenizer to allow for stupid, stupid, stupid collectionextra's starting with speech marks then a new line. Plus other stuff. And things. Peace Out.

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