source: other-projects/FileTransfer-WebSocketPair/testGXTWithGreenstone/src/org/greenstone/gatherer/util/AppendLineOnlyFileDocument.java@ 33053

Last change on this file since 33053 was 33053, checked in by ak19, 5 years ago

I still had some stuff of Nathan Kelly's (FileTransfer-WebSocketPair) sitting on my USB. Had already commited the Themes folder at the time, 2 years back. Not sure if he wanted this additional folder commited. But I didn't want to delete it and decided it will be better off on SVN. When we use his project, if we find we didn't need this test folder, we can remove it from svn then.

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