View Javadoc

1   package org.devaki.nextobjects.ui.editor;
2   /*
3   
4   nextobjects Copyright (C) 2001-2005 Emmanuel Florent
5   
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by the
8   Free Software Foundation; either version 2 of the License, or (at your
9   option) any later version.
10  
11  This program is distributed in the hope that it will
12  be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  PURPOSE. See the GNU General Public License for more details.
15  
16  You should have received a copy of the GNU General Public License along
17  with this program; if not, write to the Free Software Foundation, Inc., 59
18  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  
20  */
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.io.Reader;
26  import java.util.Comparator;
27  import java.util.HashSet;
28  import java.util.Hashtable;
29  import java.util.Iterator;
30  import java.util.NoSuchElementException;
31  import java.util.SortedSet;
32  import java.util.TreeSet;
33  import java.util.Vector;
34  import java.awt.Color;
35  import java.awt.GridBagConstraints;
36  import java.awt.GridBagLayout;
37  import java.awt.Insets;
38  import java.awt.event.MouseEvent;
39  import javax.swing.JPanel;
40  import javax.swing.JScrollPane;
41  import javax.swing.JTextPane;
42  import javax.swing.text.AbstractDocument;
43  import javax.swing.text.AttributeSet;
44  import javax.swing.text.BadLocationException;
45  import javax.swing.text.DefaultStyledDocument;
46  import javax.swing.text.SimpleAttributeSet;
47  import javax.swing.text.StyleConstants;
48  import javax.swing.event.MouseInputAdapter;
49  import org.devaki.nextobjects.ui.editor.syntax.Lexer;
50  import org.devaki.nextobjects.ui.editor.syntax.SqlLexer;
51  import org.devaki.nextobjects.ui.editor.syntax.Token;
52  import org.devaki.nextobjects.ui.menus.MenuFactory;
53  public class SqlEditor extends JPanel
54  {
55      /***
56       * The place where to draw text
57       */
58      private static JTextPane jTextPane;
59      /***
60      * the styled document that is the model for the textPane
61      **/
62      private static HighLightedDocument document;
63      /***
64       * A reader wrapped around the document so that the document can be fed into
65       */
66      private static DocumentReader documentReader;
67      /*** 
68      *The lexer that tells us what colors different words should be
69      */
70      private static Lexer syntaxLexer;
71      /***
72       *  A thread that handles the actual coloring
73       */
74      private static Colorer colorer;
75      /***
76       * A lock for modifying the document, or for actions that depend on the
77      * document not being modified.
78      */
79      private Object fDoclock = new Object();
80      /***
81       * A hashtable containing the text styles.
82       */
83      private static Hashtable styles = new Hashtable();
84      /***
85       * This class contain the textpane of the SQL editor
86       *
87       */
88      public SqlEditor()
89      {
90          //Initialization
91          super(true);
92          // Components
93          // Text Panes
94          document = new HighLightedDocument();
95          SqlEditor.jTextPane = new JTextPane(document);
96          
97          jTextPane.addMouseListener(new MouseInputAdapter()
98          {
99              public void mousePressed(MouseEvent evt)
100             {
101                 if (evt.isPopupTrigger())
102                 {
103 					MenuFactory.getEditorPopupMenu().setTextpane(jTextPane);
104                     MenuFactory.getEditorPopupMenu().show(evt.getComponent(), evt.getX(), evt.getY());
105                 }
106             }
107         });
108         colorer = new Colorer();
109         colorer.start();
110         this.initStyles();
111         documentReader = new DocumentReader(document);
112         syntaxLexer = new SqlLexer(documentReader);
113         // Panels
114         this.setLayout(new GridBagLayout());
115         // Assembling components
116         // Content panel
117         this.add(
118             new JScrollPane(jTextPane),
119             new GridBagConstraints(
120                 0,
121                 0,
122                 1,
123                 1,
124                 1.0,
125                 1.0,
126                 GridBagConstraints.CENTER,
127                 GridBagConstraints.BOTH,
128                 new Insets(3, 3, 3, 3),
129                 0,
130                 0));
131     }
132     /***
133      * Edit the file given as paramater
134      * @param pFile
135      */
136     public static void setFile(File pFile)
137     {
138         try
139         {
140             document.remove(0, document.getLength());
141             BufferedReader reader = new BufferedReader(new FileReader(pFile));
142             String tmp;
143             while ((tmp = reader.readLine()) != null)
144             {
145                 document.insertString(document.getLength(), tmp + "\n", null);
146             }
147             reader.close();
148         }
149         catch (IOException ex)
150         {
151         }
152         catch (BadLocationException ex)
153         {
154         }
155     }
156     /***
157      * Description: Run the Syntax Highlighting as a separate thread.
158      * Things that need to be colored are messaged to the
159      * thread and put in a list.
160      */
161     private class Colorer extends Thread
162     {
163         /*** Initial places detected when the lexer has been initialized */
164         private TreeSet fIniPositions =
165             new TreeSet(new DocPositionComparator());
166         /*** New places detected */
167         private HashSet newPositions = new HashSet();
168         /*** Vector that stores the communication between the two threads */
169         private volatile Vector v = new Vector();
170         /***  Changes that occured before the currently highlighting place */
171         private volatile int change = 0;
172         /*** The last position colored */
173         private volatile int lastPosition = -1;
174         /*** ?? */
175         private volatile boolean asleep = false;
176         /***  Synchronize 'this' object */
177         private Object lock = new Object();
178         /***  A simple wrapper representing something that needs to be colored */
179         private class RecolorEvent
180         {
181             /*** position */
182             public int position;
183             /*** adjustement */
184             public int adjustment;
185             public RecolorEvent(int pPosition, int pAdjustment)
186             {
187                 this.position = pPosition;
188                 this.adjustment = pAdjustment;
189             }
190         }
191         /***
192          * Tell the Syntax Highlighting thread to take another look at this section
193          * of the document.  It will process this as a FIFO.
194          * This method should be done inside a doclock.
195          * @param position position
196          * @param adjustment adjustement
197          */
198         public void color(int position, int adjustment)
199         {
200             if (position < lastPosition)
201             {
202                 if (lastPosition < position - adjustment)
203                 {
204                     change -= lastPosition - position;
205                 }
206                 else
207                 {
208                     change += adjustment;
209                 }
210             }
211             synchronized (lock)
212             {
213                 v.add(new RecolorEvent(position, adjustment));
214                 if (asleep)
215                 {
216                     this.interrupt();
217                 }
218             }
219         }
220         /***
221          * The colorer runs forever and may sleep for long periods of time.
222          * It should be interrupted every time there is something for it to do.
223          */
224         public void run()
225         {
226             int position = -1;
227             int adjustment = 0;
228             // if we just finish, we can't go to sleep until we ensure there is
229             // nothing else for us to do.
230             boolean tryAgain = false;
231             for (;;)
232             {
233                 synchronized (lock)
234                 {
235                     if (v.size() > 0)
236                     {
237                         RecolorEvent re = (RecolorEvent) (v.elementAt(0));
238                         v.removeElementAt(0);
239                         position = re.position;
240                         adjustment = re.adjustment;
241                     }
242                     else
243                     {
244                         tryAgain = false;
245                         position = -1;
246                         adjustment = 0;
247                     }
248                 }
249                 if (position != -1)
250                 {
251                     SortedSet workingSet;
252                     Iterator workingIt;
253                     DocPosition startRequest = new DocPosition(position);
254                     DocPosition endRequest =
255                         new DocPosition(
256                             position
257                                 + ((adjustment >= 0) ? adjustment : -adjustment));
258                     DocPosition dp;
259                     DocPosition dpStart = null;
260                     DocPosition dpEnd = null;
261                     // find the starting position.
262                     try
263                     {
264                         // all the good positions before
265                         workingSet = fIniPositions.headSet(startRequest);
266                         // the last of the stuff before
267                         dpStart = ((DocPosition) workingSet.last());
268                     }
269                     catch (NoSuchElementException x)
270                     {
271                         // if there were no good positions before the requested start,
272                         // we can always start at the very beginning.
273                         dpStart = new DocPosition(0);
274                     }
275                     // if stuff was removed, take any removed positions off the list.
276                     if (adjustment < 0)
277                     {
278                         workingSet =
279                             fIniPositions.subSet(startRequest, endRequest);
280                         workingIt = workingSet.iterator();
281                         while (workingIt.hasNext())
282                         {
283                             workingIt.next();
284                             workingIt.remove();
285                         }
286                     }
287                     // adjust the positions of everything after the insertion/removal
288                     workingSet = fIniPositions.tailSet(startRequest);
289                     workingIt = workingSet.iterator();
290                     while (workingIt.hasNext())
291                     {
292                         ((DocPosition) workingIt.next()).adjustPosition(
293                             adjustment);
294                     }
295                     // now go through and highlight as much as needed
296                     workingSet = fIniPositions.tailSet(dpStart);
297                     workingIt = workingSet.iterator();
298                     dp = null;
299                     if (workingIt.hasNext())
300                     {
301                         dp = (DocPosition) workingIt.next();
302                     }
303                     try
304                     {
305                         Token t;
306                         boolean done = false;
307                         dpEnd = dpStart;
308                         synchronized (fDoclock)
309                         {
310                             // we are playing some games with the lexer for efficiency.
311                             // we could just create a new lexer each time here, but instead,
312                             // we will just reset it so that it thinks it is starting at the
313                             // beginning of the document but reporting a funny start position.
314                             // Reseting the lexer causes the close() method on the reader
315                             // to be called but because the close() method has no effect on the
316                             // DocumentReader, we can do this.
317                             syntaxLexer.reset(
318                                 documentReader,
319                                 0,
320                                 dpStart.getPosition(),
321                                 0);
322                             // After the lexer has been set up, scroll the reader so that it
323                             // is in the correct spot as well.
324                             documentReader.seek(dpStart.getPosition());
325                             // we will highlight tokens until we reach a good stopping place.
326                             // the first obvious stopping place is the end of the document.
327                             // the lexer will return null at the end of the document and wee
328                             // need to stop there.
329                             t = syntaxLexer.getNextToken();
330                         }
331                         newPositions.add(dpStart);
332                         while (!done && t != null)
333                         {
334                             // this is the actual command that colors the stuff.
335                             // Color stuff with the description of the style matched
336                             // to the hash table that has been set up ahead of time.
337                             synchronized (fDoclock)
338                             {
339                                 if (t.getCharEnd() <= document.getLength())
340                                 {
341                                     document.setCharacterAttributes(
342                                         t.getCharBegin() + change,
343                                         t.getCharEnd() - t.getCharBegin(),
344                                         getStyle(t.getDescription()),
345                                         true);
346                                     // record the position of the last bit of text that we colored
347                                     dpEnd = new DocPosition(t.getCharEnd());
348                                 }
349                                 lastPosition = (t.getCharEnd() + change);
350                             }
351                             // The other more complicated reason for doing no more highlighting
352                             // is that all the colors are the same from here on out anyway.
353                             // We can detect this by seeing if the place that the lexer returned
354                             // to the initial state last time we highlighted is the same as the
355                             // place that returned to the inital state this time.
356                             // As long as that place is after the last changed text, everything
357                             // from there on is fine already.
358                             if (t.getState() == Token.INITIAL_STATE)
359                             {
360                                 //System.out.println(t);
361                                 // look at all the positions from last time that are less than or
362                                 // equal to the current position
363                                 while (dp != null
364                                     && dp.getPosition() <= t.getCharEnd())
365                                 {
366                                     if (dp.getPosition() == t.getCharEnd()
367                                         && dp.getPosition()
368                                             >= endRequest.getPosition())
369                                     {
370                                         // we have found a state that is the same
371                                         done = true;
372                                         dp = null;
373                                     }
374                                     else if (workingIt.hasNext())
375                                     {
376                                         // didn't find it, try again.
377                                         dp = (DocPosition) workingIt.next();
378                                     }
379                                     else
380                                     {
381                                         // didn't find it, and there is no more info from last
382                                         // time.  This means that we will just continue
383                                         // until the end of the document.
384                                         dp = null;
385                                     }
386                                 }
387                                 // so that we can do this check next time, record all the
388                                 // initial states from this time.
389                                 newPositions.add(dpEnd);
390                             }
391                             synchronized (fDoclock)
392                             {
393                                 t = syntaxLexer.getNextToken();
394                             }
395                         }
396                         // remove all the old initial positions from the place where
397                         // we started doing the highlighting right up through the last
398                         // bit of text we touched.
399                         workingIt =
400                             fIniPositions.subSet(dpStart, dpEnd).iterator();
401                         while (workingIt.hasNext())
402                         {
403                             workingIt.next();
404                             workingIt.remove();
405                         }
406                         // Remove all the positions that are after the end of the file.:
407                         workingIt =
408                             fIniPositions
409                                 .tailSet(new DocPosition(document.getLength()))
410                                 .iterator();
411                         while (workingIt.hasNext())
412                         {
413                             workingIt.next();
414                             workingIt.remove();
415                         }
416                         // and put the new initial positions that we have found on the list.
417                         fIniPositions.addAll(newPositions);
418                         newPositions.clear();
419                     }
420                     catch (IOException x)
421                     {
422                     }
423                     synchronized (fDoclock)
424                     {
425                         lastPosition = -1;
426                         change = 0;
427                     }
428                     // since we did something, we should check that there is
429                     // nothing else to do before going back to sleep.
430                     tryAgain = true;
431                 }
432                 asleep = true;
433                 if (!tryAgain)
434                 {
435                     try
436                     {
437                         sleep(0xffffff);
438                     }
439                     catch (InterruptedException x)
440                     {
441                     }
442                 }
443                 asleep = false;
444             }
445         }
446     }
447     /***
448      * Color a section of the document.
449      * The actual coloring will start somewhere before the requested position
450      * and continue as long as needed.
451      * @param position the starting point for the coloring.
452      * @param adjustment amount of text inserted or removed at the starting point.
453      */
454     public static void color(int position, int adjustment)
455     {
456         colorer.color(position, adjustment);
457     }
458     /***
459      * retrieve the syle for the given type of text.
460      * @param styleName the label for the type of text ("tag" for example)
461      * or null if the styleName is not known.
462      * @return the style
463      */
464     private static SimpleAttributeSet getStyle(String styleName)
465     {
466         return ((SimpleAttributeSet) styles.get(styleName));
467     }
468     /***
469      * Create the styles and place them in the hash table.
470      */
471     private void initStyles()
472     {
473         SimpleAttributeSet style;
474         style = new SimpleAttributeSet();
475         StyleConstants.setFontFamily(style, "Monospaced");
476         StyleConstants.setFontSize(style, 11);
477         StyleConstants.setBackground(style, Color.white);
478         StyleConstants.setForeground(style, Color.blue);
479         StyleConstants.setBold(style, false);
480         StyleConstants.setItalic(style, false);
481         styles.put("reservedWord", style);
482         style = new SimpleAttributeSet();
483         StyleConstants.setFontFamily(style, "Monospaced");
484         StyleConstants.setFontSize(style, 11);
485         StyleConstants.setBackground(style, Color.white);
486         StyleConstants.setForeground(style, Color.black);
487         StyleConstants.setBold(style, false);
488         StyleConstants.setItalic(style, false);
489         styles.put("identifier", style);
490         style = new SimpleAttributeSet();
491         StyleConstants.setFontFamily(style, "Monospaced");
492         StyleConstants.setFontSize(style, 11);
493         StyleConstants.setBackground(style, Color.white);
494         StyleConstants.setForeground(style, new Color(0xB03060)        /*Color.maroon*/
495         );
496         StyleConstants.setBold(style, false);
497         StyleConstants.setItalic(style, false);
498         styles.put("literal", style);
499         style = new SimpleAttributeSet();
500         StyleConstants.setFontFamily(style, "Monospaced");
501         StyleConstants.setFontSize(style, 11);
502         StyleConstants.setBackground(style, Color.white);
503         StyleConstants.setForeground(style, new Color(0x000080) /*Color.navy*/
504         );
505         StyleConstants.setBold(style, false);
506         StyleConstants.setItalic(style, false);
507         styles.put("separator", style);
508         style = new SimpleAttributeSet();
509         StyleConstants.setFontFamily(style, "Monospaced");
510         StyleConstants.setFontSize(style, 11);
511         StyleConstants.setBackground(style, Color.white);
512         StyleConstants.setForeground(style, Color.black);
513         StyleConstants.setBold(style, true);
514         StyleConstants.setItalic(style, false);
515         styles.put("operator", style);
516         style = new SimpleAttributeSet();
517         StyleConstants.setFontFamily(style, "Monospaced");
518         StyleConstants.setFontSize(style, 11);
519         StyleConstants.setBackground(style, Color.white);
520         StyleConstants.setForeground(style, Color.green.darker());
521         StyleConstants.setBold(style, false);
522         StyleConstants.setItalic(style, false);
523         styles.put("comment", style);
524         style = new SimpleAttributeSet();
525         StyleConstants.setFontFamily(style, "Monospaced");
526         StyleConstants.setFontSize(style, 11);
527         StyleConstants.setBackground(style, Color.white);
528         StyleConstants.setForeground(style, Color.black);
529         StyleConstants.setBold(style, false);
530         StyleConstants.setItalic(style, false);
531         styles.put("whitespace", style);
532         style = new SimpleAttributeSet();
533         StyleConstants.setFontFamily(style, "Monospaced");
534         StyleConstants.setFontSize(style, 12);
535         StyleConstants.setBackground(style, Color.white);
536         StyleConstants.setForeground(style, Color.red);
537         StyleConstants.setBold(style, false);
538         StyleConstants.setItalic(style, false);
539         styles.put("error", style);
540         style = new SimpleAttributeSet();
541         StyleConstants.setFontFamily(style, "Monospaced");
542         StyleConstants.setFontSize(style, 11);
543         StyleConstants.setBackground(style, Color.white);
544         StyleConstants.setForeground(style, Color.orange);
545         StyleConstants.setBold(style, false);
546         StyleConstants.setItalic(style, false);
547         styles.put("unknown", style);
548     }
549     /***
550      * Just like a DefaultStyledDocument but intercepts inserts and removes
551      * to color them.
552      */
553     private class HighLightedDocument extends DefaultStyledDocument
554     {
555         public void insertString(int offs, String str, AttributeSet a)
556             throws BadLocationException
557         {
558             synchronized (fDoclock)
559             {
560                 super.insertString(offs, str, a);
561                 color(offs, str.length());
562                 documentReader.update(offs, str.length());
563             }
564         }
565         public void remove(int offs, int len) throws BadLocationException
566         {
567             synchronized (fDoclock)
568             {
569                 super.remove(offs, len);
570                 color(offs, -len);
571                 documentReader.update(offs, -len);
572             }
573         }
574     }
575 }
576 /***
577  * A wrapper for a position in a document appropriate for storing
578  * in a collection.
579  */
580 class DocPosition
581 {
582     // The actual position
583     private int position;
584     /***
585      * Get the position represented by this DocPosition
586      * @return the position
587      */
588     int getPosition()
589     {
590         return position;
591     }
592     /***
593      * Construct a DocPosition from the given offset into the document.
594      * @param position The position this DocObject will represent
595      */
596     public DocPosition(int pPosition)
597     {
598         this.position = pPosition;
599     }
600     /***
601      * Adjust this position.
602      * @param adjustment amount to adjust this position.
603      * @return the DocPosition, adjusted properly.
604      */
605     public DocPosition adjustPosition(int adjustment)
606     {
607         position += adjustment;
608         return this;
609     }
610     /***
611      * Two DocPositions are equal iff they have the same internal position.
612      *  @param obj the object
613      * @return if this DocPosition represents the same position as another.
614      */
615     public final boolean equals(final Object obj)
616     {
617         if (obj instanceof DocPosition)
618         {
619             DocPosition d = (DocPosition) (obj);
620             return this.position == d.position;
621         }
622         else
623         {
624             return false;
625         }
626     }
627     /***
628      * A string representation useful for debugging.
629      * @return A string representing the position.
630      */
631     public String toString()
632     {
633         return "" + position;
634     }
635 }
636 /***
637  * A comparator appropriate for use with Collections of
638  * DocPositions.
639  */
640 class DocPositionComparator implements Comparator
641 {
642     /***
643      * Comparator
644      * @param object to compare
645      * @return true for DocPositionComparators, false otherwise.
646      */
647     public boolean equals(Object obj)
648     {
649         return obj instanceof DocPositionComparator;
650     }
651     /***
652      * Compare two DocPositions
653      * @param o1 first DocPosition
654      * @param o2 second DocPosition
655      * @return negative,0 or positive
656      */
657     public int compare(final Object o1, final Object o2)
658     {
659         if (o1 instanceof DocPosition && o2 instanceof DocPosition)
660         {
661             DocPosition d1 = (DocPosition) (o1);
662             DocPosition d2 = (DocPosition) (o2);
663             return (d1.getPosition() - d2.getPosition());
664         }
665         else if (o1 instanceof DocPosition)
666         {
667             return -1;
668         }
669         else if (o2 instanceof DocPosition)
670         {
671             return 1;
672         }
673         else if (o1.hashCode() < o2.hashCode())
674         {
675             return -1;
676         }
677         else if (o2.hashCode() > o1.hashCode())
678         {
679             return 1;
680         }
681         else
682         {
683             return 0;
684         }
685     }
686 }
687 /***
688  * A reader interface for an abstract document.  Since the syntax highlighting
689  * packages only accept Stings and Readers, this must be used.
690  * Since the close() method does nothing and a seek() method has been added,
691  * this allows us to get some performance improvements through reuse.
692  * It can be used even after the lexer explicitly closes it by seeking to the
693  * place that we want to read next, and reseting the lexer.
694  */
695 class DocumentReader extends Reader
696 {
697     /*** Current position in the document */
698     private long position = 0;
699     /*** Saved position used in the mark and reset method */
700     private long mark = -1;
701     /*** The document that we are working with */
702     private AbstractDocument document;
703     /***
704      * Alerting the reader
705      * @param pPosition position
706      * @param adjustment adjustement
707      */
708     public void update(int pPosition, int adjustment)
709     {
710         if (pPosition < this.position)
711         {
712             if (this.position < pPosition - adjustment)
713             {
714                 this.position = pPosition;
715             }
716             else
717             {
718                 this.position += adjustment;
719             }
720         }
721     }
722     /***
723      * Construct a reader on the given document.
724      * @param document the document to be read.
725      */
726     public DocumentReader(AbstractDocument pDocument)
727     {
728         this.document = pDocument;
729     }
730     /***
731      * Has no effect.  This reader can be used even after it has been closed.
732      */
733     public void close()
734     {
735     }
736     /***
737      * Save a position for reset.
738      * @param readAheadLimit ignored.
739      */
740     public void mark(int readAheadLimit)
741     {
742         mark = position;
743     }
744     /***
745      * This reader support mark and reset.
746      * @return true
747      */
748     public boolean markSupported()
749     {
750         return true;
751     }
752     /***
753      * Read a single character.
754      * @return the character or -1 if the end of the document has been reached.
755      */
756     public int read()
757     {
758         if (position < document.getLength())
759         {
760             try
761             {
762                 char c = document.getText((int) position, 1).charAt(0);
763                 position++;
764                 return c;
765             }
766             catch (BadLocationException x)
767             {
768                 return -1;
769             }
770         }
771         else
772         {
773             return -1;
774         }
775     }
776     /***
777      * Read and fill the buffer.
778      * @param cbuf the buffer to fill.
779      * @return the number of characters read
780      */
781     public int read(char[] cbuf)
782     {
783         return read(cbuf, 0, cbuf.length);
784     }
785     /***
786      * Read and fill the buffer.
787      * @param cbuf the buffer to fill.
788      * @param off offset into the buffer to begin the fill.
789      * @param len maximum number of characters to put in the buffer.
790      * @return the number of characters read
791      */
792     public int read(char[] cbuf, int off, int len)
793     {
794         if (position < document.getLength())
795         {
796             int length = len;
797             if (position + length >= document.getLength())
798             {
799                 length = document.getLength() - (int) position;
800             }
801             if (off + length >= cbuf.length)
802             {
803                 length = cbuf.length - off;
804             }
805             try
806             {
807                 String s = document.getText((int) position, length);
808                 position += length;
809                 for (int i = 0; i < length; i++)
810                 {
811                     cbuf[off + i] = s.charAt(i);
812                 }
813                 return length;
814             }
815             catch (BadLocationException x)
816             {
817                 return -1;
818             }
819         }
820         else
821         {
822             return -1;
823         }
824     }
825     /***
826      * @return true
827      */
828     public boolean ready()
829     {
830         return true;
831     }
832     /***
833      * Reset this reader to the last mark, or the beginning of the document
834      * if a mark has not been set.
835      */
836     public void reset()
837     {
838         if (mark == -1)
839         {
840             position = 0;
841         }
842         else
843         {
844             position = mark;
845         }
846         mark = -1;
847     }
848     /***
849      * Skip characters of input.
850      * @param n number of characters to skip.
851      * @return the actual number of characters skipped.
852      */
853     public long skip(long n)
854     {
855         if (position + n <= document.getLength())
856         {
857             position += n;
858             return n;
859         }
860         else
861         {
862             long oldPos = position;
863             position = document.getLength();
864             return (document.getLength() - oldPos);
865         }
866     }
867     /***
868      * Seek to the given position in the document
869      * @param n the offset to which to seek.
870      */
871     public final void seek(final long n)
872     {
873         if (n <= document.getLength())
874         {
875             position = n;
876         }
877         else
878         {
879             position = document.getLength();
880         }
881     }
882 }