Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/greenstone/gatherer/util/AppendLineOnlyFileDocument.java


1   package org.greenstone.gatherer.util;
2   
3   import java.awt.*;
4   import java.awt.event.*;
5   import java.io.*;
6   import java.util.*;
7   import javax.swing.*;
8   import javax.swing.event.*;
9   import javax.swing.text.*;
10  
11  import org.greenstone.gatherer.Gatherer;
12  import org.greenstone.gatherer.util.StaticStrings;
13  
14  /** 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. */
15  public class AppendLineOnlyFileDocument
16      implements Document {
17  
18      static final private String EMPTY_STR      = "";
19      static final private String GLI_HEADER_STR = "X:GLI Import and Build log:";
20  
21      private AppendLineOnlyFileDocumentElement root_element;
22      private AppendLineOnlyFileDocumentOwner owner;
23      private EventListenerList listeners_list;
24      private HashMap cache;
25      private HashMap properties;
26      private long length;
27      private RandomAccessFile file;
28      private WriterThread writer;
29  
30      public AppendLineOnlyFileDocument(String filename) {
31         ///ystem.err.println("Creating log: " + filename);
32    // Initialization
33    this.cache = new HashMap();
34    this.listeners_list = new EventListenerList();
35    this.properties = new HashMap();
36    this.writer = new WriterThread();
37    writer.start();
38    // Open underlying file
39    try {
40        file = new RandomAccessFile(filename, "rw");
41        // Create the root element.
42        length = file.length();
43        root_element = new AppendLineOnlyFileDocumentElement();
44        // Now quickly read through the underlying file, building an Element for each line.
45        long start_offset = 0L;
46        file.seek(start_offset);
47        int character = -1;
48        while((character = file.read()) != -1) {
49      if(character == StaticStrings.NEW_LINE_CHAR) {
50          long end_offset = file.getFilePointer();
51          Element child_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset);
52          root_element.add(child_element);
53          child_element = null;
54          start_offset = end_offset;
55      }
56        }
57        // If there we no lines found, then append the file header.
58        if(root_element.getElementCount() == 0) {
59      appendLine(GLI_HEADER_STR);
60        }
61    }
62    catch (Exception error) {
63        Gatherer.printStackTrace(error);
64    }
65      }
66      
67      /** Adds a document listener for notification of any changes. */
68      public void addDocumentListener(DocumentListener listener) {
69    ///ystem.err.println("addDocumentListener(" + listener + ")");
70    listeners_list.add(DocumentListener.class, listener);
71      }
72  
73      /** Append some content after the document. */
74      public void appendLine(String str) {
75    // Ensure the string ends in a newline
76    if(!str.endsWith("\n")) {
77        str = str + "\n";
78    }
79    try {
80        int str_length = str.length();
81        long start_offset = length;
82        long end_offset = start_offset + (long) str_length;
83        length = length + str_length;
84        //write(start_offset, end_offset, str, str_length);
85        // Create a new element to represent this line
86        AppendLineOnlyFileDocumentElement new_line_element = new AppendLineOnlyFileDocumentElement(start_offset, end_offset, str);
87        root_element.add(new_line_element);
88        // Queue the content to be written.
89        writer.queue(new_line_element);
90        // Now fire an event so everyone knows the content has changed.
91        DocumentEvent event = new AppendLineOnlyFileDocumentEvent(new_line_element, (int)start_offset, str_length, DocumentEvent.EventType.INSERT);
92        Object[] listeners = listeners_list.getListenerList();
93        for (int i = listeners.length - 2; i >= 0; i = i - 2) {
94      if (listeners[i] == DocumentListener.class) {
95          ((DocumentListener)listeners[i+1]).insertUpdate(event);
96      }
97        }
98        listeners = null;
99        event = null;
100       new_line_element = null;
101   }
102   catch(Exception error) {
103       Gatherer.printStackTrace(error);
104   }
105     }
106 
107     /** Returns a position that will track change as the document is altered. */
108     public Position createPosition(int offs)  {
109   return new AppendLineOnlyFileDocumentPosition(offs);
110     }
111 
112     /** Gets the default root element for the document model. */
113     public Element getDefaultRootElement() {
114   return root_element;
115     }
116 
117     /** Returns the length of the data. */
118     public int getLength() {
119   return (int) length;
120     }
121 
122     /** A version of get length which essentially returns the offset to the start of the last line in the document.
123      * @return the offset length as an int
124      */
125     public int getLengthToNearestLine() {
126   AppendLineOnlyFileDocumentElement last_element = (AppendLineOnlyFileDocumentElement)root_element.getElement(root_element.getElementCount() - 1);
127   if(last_element != null ) {
128       return last_element.getStartOffset();
129   }
130   else {
131       return (int) length; // The best we can do.
132   }
133     }
134   
135     public Object getProperty(Object key) {
136   return properties.get(key);
137     }
138 
139     public boolean isStillWriting() {
140   return writer.isStillWriting();
141     }
142 
143     /** Gets a sequence of text from the document. */
144     public String getText(int offset, int l) 
145   throws BadLocationException {
146   String request = "getText(" + offset + ", " + l + ")";
147   ///ystem.err.println(request);
148   String text = null;//(String) cache.get(request);
149   if(text == null || text.length() < l) {
150       try {
151     int file_length = (int) file.length();
152     ///ystem.err.println("file_length = " + file_length + ", length = " + length);
153     if(l == 0) {
154         text = EMPTY_STR;
155     }
156     else if(0 <= offset && offset < length && (offset + l) <= length) {
157         if(offset < file_length) {
158       text = read((long)offset, l);
159       if(text.length() != l) {
160           ///ystem.err.println("Asked for " + l + " characters of text. But only read " + text.length());
161           throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset); 
162       }
163         }
164         else {
165       int index = root_element.getElementIndex(offset);
166       if(index < root_element.getElementCount()) {
167           AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) root_element.getElement(index);
168           text = element.getContent();
169       }
170       else {
171           ///ystem.err.println("Index is " + index + " but there are only " + root_element.getElementCount() + " children.");
172           throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset); 
173       }
174         }
175         
176     }
177       }
178       catch (IOException error) {
179     Gatherer.printStackTrace(error);
180       }
181       if(text == null) {
182     ///ystem.err.println("Text is null.");
183     throw new BadLocationException("AppendLineOnlyDocument.getText(" + offset + ", " + l + ")", offset);
184       }
185       //cache.put(request, text);
186   }
187   request = null;
188   return text;
189     }
190 
191     /** Fetches the text contained within the given portion of the document. */
192     public void getText(int offset, int length, Segment txt) 
193   throws javax.swing.text.BadLocationException  {
194   String str = getText(offset, length);
195   txt.array = str.toCharArray();
196   txt.count = str.length();
197   txt.offset = 0;
198   str = null;
199     }
200   
201     public void putProperty(Object key, Object value) {
202   properties.put(key, value);
203     }
204 
205     public boolean ready() {
206   return (file != null);
207     }
208   
209     //Removes a document listener.
210     public void removeDocumentListener(DocumentListener listener) {
211   ///ystem.err.println("removeDocumentListener()");
212   listeners_list.remove(DocumentListener.class, listener);
213     }
214 
215     public void setExit() {
216   writer.exit();
217     }
218 
219     public void setOwner(AppendLineOnlyFileDocumentOwner owner) {
220   this.owner = owner;
221     }
222 
223     /** To record the final state of the logging process we reserve a single character at the start of the file. */
224     public synchronized void setSpecialCharacter(char character) {
225   try {
226       file.seek(0L);
227       file.write((int)character);
228   }
229   catch (Exception error) {
230       Gatherer.printStackTrace(error);
231   }
232     }
233 
234     private synchronized String read(long start_offset, int l) 
235   throws IOException {
236   //print("read(" + start_offset + ", " + l + ")... ");
237   byte[] buffer = new byte[l];
238   file.seek(start_offset);
239   int result = file.read(buffer, 0, l);
240   //print("read() complete");
241   return new String(buffer);
242     }
243 
244     private synchronized void write(long start_offset, long end_offset, String str, int l) 
245   throws IOException {  
246   //print("write(" + start_offset + ", " + end_offset + ", " + str + ", " + l + ")");
247   file.setLength(end_offset);
248   file.seek(start_offset);
249   file.write(str.getBytes(), 0, l);
250   //print("write() complete");
251     }
252 
253     private class AppendLineOnlyFileDocumentElement
254   extends ArrayList
255   implements Element {
256 
257   private Element parent;
258   private long end;
259   private long start;
260   private String content;
261   
262   /** Construct a new root element, which can have no content, but calculates its start and end from its children.
263    * @param  start the starting offset as a long
264    * @param  end the ending offset as a long.
265    */
266   public AppendLineOnlyFileDocumentElement() {
267       super();
268       this.end = 0L;
269       this.parent = null;
270       this.start = 0L;
271   }
272 
273 
274   /** Construct a new element, whose content is found in bytes start to end - 1 within the random access file backing this document.
275    * @param  start the starting offset as a long
276    * @param  end the ending offset as a long.
277    */
278   public AppendLineOnlyFileDocumentElement(long start, long end) {
279       super();
280       this.end = end;
281       this.parent = null;
282       this.start = start;
283   }
284 
285   /** 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.
286    * @param  start the starting offset as a long
287    * @param  end the ending offset as a long.
288    */
289   public AppendLineOnlyFileDocumentElement(long start, long end, String content) {
290       super();
291       this.content = content;
292       this.end = end;
293       this.parent = null;
294       this.start = start;
295   }
296 
297   public void add(Element child) {
298       super.add(child);
299       ((AppendLineOnlyFileDocumentElement)child).setParent(this);
300   }
301 
302   public void clearContent() {
303       content = null;
304   }
305 
306   /** This document does not allow content markup. */
307   public AttributeSet getAttributes() {
308       return null;
309   }
310 
311   public String getContent() {
312       return content;
313   }
314 
315   /** Fetches the document associated with this element. 
316    * @return the AppendLineOnlyDocument containing this element
317    */
318   public Document getDocument() {
319       return AppendLineOnlyFileDocument.this;
320   }
321 
322   /** Fetches the child element at the given index. */
323   public Element getElement(int index) {
324       Element element;
325       if(0 <= index && index < size()) {
326     element = (Element) get(index);
327       }
328       else {
329     throw new IndexOutOfBoundsException("AppendLineOnlyDocument.AppendLineOnlyFileDocumentElement.getElement(" + index + ")");
330       }
331       return element;
332   }
333           
334   /** Gets the number of child elements contained by this element. */
335   public int getElementCount() {
336       return size();
337   }
338 
339   /** Gets the child element index closest to the given offset. */
340   public int getElementIndex(int offset) {
341       int index = -1;
342       if(parent != null) {
343     index = -1;
344       }
345       else if(offset < 0) {
346     index = 0;
347       }
348       else if(offset >= length) {
349     index = size() - 1;
350       }
351       else {
352     int size = size();
353     for(int i = 0; index == -1 && i < size; i++) {
354         Element child = (Element) get(i);
355         if(child.getStartOffset() <= offset && offset < child.getEndOffset()) {
356       index = i;
357         }
358         child = null;
359     }
360       }
361       return index;
362   }
363           
364   /** Fetches the offset from the beginning of the document that this element ends at. */
365   public int getEndOffset() {
366       if(parent != null) {
367     return (int) end;
368       }
369       // Return the Documents length.
370       else {
371     return (int) length;
372       }
373   }
374           
375   /** This method retrieves the name of the element, however names are not important in this document so the name is always an empty string.
376    * @return an empty String
377    */
378   public String getName() {
379       return StaticStrings.EMPTY_STR;
380   }
381           
382   /** Fetches the parent element. */
383   public Element getParentElement() {
384       return parent;
385   }
386           
387   /** Fetches the offset from the beginning of the document that this element begins at. */
388   public int getStartOffset() {
389       if(parent != null) {
390     return (int) start;
391       }
392       else {
393     return 0;
394       }
395   }
396           
397   /** Since this is a very simple model, only the root node can have children. All the children are leaves. */
398   public boolean isLeaf() {
399       return (parent != null);
400   } 
401 
402   public void setParent(Element parent) {
403       this.parent = parent;
404   }
405     }
406 
407     private class AppendLineOnlyFileDocumentEvent
408   implements DocumentEvent {
409 
410   private DocumentEvent.EventType type;
411   private AppendLineOnlyFileDocumentElement element;
412   private AppendLineOnlyFileDocumentElementChange element_change;
413   private int len;
414   private int offset;
415   
416   public AppendLineOnlyFileDocumentEvent(AppendLineOnlyFileDocumentElement element, int offset, int len, DocumentEvent.EventType type) {
417       this.element = element;
418       this.element_change = null;
419       this.len = len;
420       this.offset = offset;
421       this.type = type;
422   }
423 
424   public Document getDocument() {
425       return AppendLineOnlyFileDocument.this;
426   }
427 
428   public int getLength() {
429       return len;
430   }
431 
432   public int getOffset() {
433       return offset;
434   }
435 
436   public DocumentEvent.EventType getType() {
437       return type;
438   }
439 
440   // ***** IGNORE *****
441   public DocumentEvent.ElementChange getChange(Element elem) {
442       if(element_change == null) {
443     element_change = new AppendLineOnlyFileDocumentElementChange();
444       }
445       return element_change;
446   }
447 
448   private class AppendLineOnlyFileDocumentElementChange
449       implements DocumentEvent.ElementChange {
450 
451       private Element[] children_added;
452       private Element[] children_removed;
453       private int index;
454       public AppendLineOnlyFileDocumentElementChange() {
455     children_added = new Element[1];
456     children_added[0] = element;
457     children_removed = new Element[0];
458     index = root_element.indexOf(element);
459       }
460       /** Gets the child element that was added to the given parent element. 
461        * @return an Element[] containing the added element
462        */
463       public Element[] getChildrenAdded() {
464     return children_added;
465       }
466       /** This model does not allow elements to be removed.
467        * @return an Element[] containing nothing
468        */
469       public Element[] getChildrenRemoved() {
470     return children_removed;
471       }
472           
473       /** Returns the root element, as our document structure is only two layers deep.
474        * @return the root Element
475        */
476       public Element getElement() {
477     return root_element;
478       }
479           
480       /** Fetches the index within the element represented. 
481        * @return an int specifying the index of change within the root element
482        */
483       public int getIndex() {
484     return index;
485       }
486   }
487     }
488 
489     private class AppendLineOnlyFileDocumentPosition
490   implements Position {
491 
492   private int offset;
493 
494   public AppendLineOnlyFileDocumentPosition(int offset) {
495       this.offset = offset;
496   }
497 
498   public int getOffset() {
499       return offset;
500   }
501     }
502 
503     private class WriterThread
504   extends Thread {
505 
506   private boolean exit;
507   private boolean running;
508   private Vector queue;
509 
510   public WriterThread() {
511       super("WriterThread");
512       exit = false;
513       queue = new Vector();
514   }
515 
516   public synchronized void exit() {
517       //print("WriterThread.exit() start");
518       exit = true;
519       notify();
520       //print("WriterThread.exit() complete");
521   }
522 
523   public boolean isStillWriting() {
524       return running;
525   }
526 
527   public void run() {
528       running = true;
529       while(!exit) {
530     if(!queue.isEmpty()) { 
531         AppendLineOnlyFileDocumentElement element = (AppendLineOnlyFileDocumentElement) queue.remove(0);
532         // Write the content to file.
533         String content = element.getContent();
534         if(content != null) {
535       try {
536           write(element.getStartOffset(), element.getEndOffset(), content, content.length());
537       }
538       catch(Exception error) {
539           Gatherer.printStackTrace(error);
540       }
541       element.clearContent();
542         }
543     }
544     else {
545         synchronized(this) {
546       try {
547           //print("WriterThread.wait() start");
548           wait();
549           //print("WriterThread.wait() complete");
550       }
551       catch(Exception error) {
552       }
553        }
554     }
555       }
556       running = false;
557       if(owner != null) {
558     owner.remove(AppendLineOnlyFileDocument.this);
559       }
560   }
561 
562   public synchronized void queue(Element element) {
563       //print("WriterThread.queue() start");
564       queue.add(element);
565       notify();
566       //print("WriterThread.queue() complete");
567   }
568     }
569 
570     // ***** METHODS WE ARE NOW IGNORING BECAUSE WE ARE VIRTUALLY READ-ONLY *****
571     
572     /** Adds an undo listener for notification of any changes. */
573     public void addUndoableEditListener(UndoableEditListener listener) {}
574       
575     /** */
576     public Position getEndPosition() {
577   ///ystem.err.println("getEndPosition()");
578   return null;
579     }
580 
581     /** Gets all root elements defined. */
582     public Element[] getRootElements() {return null;}
583   
584     public Position getStartPosition() {
585   ///ystem.err.println("getStartPosition()");
586   return null;
587     }
588     
589     public void insertString(int offset, String str, AttributeSet a) {}
590 
591     /** Removes some content from the document. */
592     public void remove(int offs, int len) {}
593 
594     /** Removes an undo listener. */
595     public void removeUndoableEditListener(UndoableEditListener listener) {}
596     
597     /** Renders a runnable apparently. */
598     public void render(Runnable r) {}
599 
600     static synchronized public void print(String message) {
601   Gatherer.println(message);
602     }
603 
604     static public void main(String[] args) {
605   JFrame frame = new JFrame("AppendLineOnlyFileDocument Test");
606   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
607   frame.setSize(640,480);
608   JPanel content = (JPanel) frame.getContentPane();
609   
610   //PlainDocument document = new PlainDocument();
611   //document.setAsynchronousLoadPriority(-1);
612   final AppendLineOnlyFileDocument document = new AppendLineOnlyFileDocument("temp.txt");
613 
614   final JTextArea text_area = new JTextArea(document);
615 
616   JButton read_button = new JButton("Read Huge File");
617   read_button.addActionListener(new ActionListener() {
618     public void actionPerformed(ActionEvent event) {
619         Thread task = new Thread("LoadHugeFileThread") {
620           public void run() {
621         // Load the specified document
622         try {
623             BufferedReader in = new BufferedReader(new FileReader(new File("big.txt")));
624             String line;
625             
626             while ((line = in.readLine()) != null) {
627           document.appendLine(line);
628           try {
629               // Wait a random ammount of time.
630               synchronized(this) {
631             //print("LoadHugeFileThread.wait() start");
632             wait(100);
633             //print("LoadHugeFileThread.wait() complete");
634               }
635           }
636           catch(Exception error) {
637               error.printStackTrace();
638           }
639             }
640             
641         } catch (Exception error) {
642             error.printStackTrace();
643         }
644           }
645       };
646         task.start();
647         
648     }
649       });
650   content.setLayout(new BorderLayout());
651   content.add(new JScrollPane(text_area), BorderLayout.CENTER);
652   content.add(read_button, BorderLayout.SOUTH);
653 
654   frame.show();
655     }
656 }