Customizing a Text Editor

/*
 * @(#)JavaEditorKit.java    1.2 98/05/04
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 
*/


import  javax.swing.text.DefaultEditorKit;
import  javax.swing.text.Document;
import  javax.swing.text.ViewFactory;

/**
 * This kit supports a fairly minimal handling of
 * editing java text content.  It supports syntax 
 * highlighting and produces the lexical structure
 * of the document as best it can.
 *
 * 
@author  Timothy Prinzing
 * 
@version 1.2 05/04/98
 
*/

public   class  JavaEditorKit  extends  DefaultEditorKit  {

    
public JavaEditorKit() {
    
super();
    }


    
public JavaContext getStylePreferences() {
    
if (preferences == null{
        preferences 
= new JavaContext();
    }

    
return preferences;
    }


    
public void setStylePreferences(JavaContext prefs) {
    preferences 
= prefs;
    }


    
// --- EditorKit methods -------------------------

    
/**
     * Get the MIME type of the data that this
     * kit represents support for.  This kit supports
     * the type <code>text/java</code>.
     
*/

    
public String getContentType() {
    
return "text/java";
    }


    
/**
     * Create a copy of the editor kit.  This
     * allows an implementation to serve as a prototype
     * for others, so that they can be quickly created.
     
*/

    
public Object clone() {
    JavaEditorKit kit 
= new JavaEditorKit();
    kit.preferences 
= preferences;
    
return kit;
    }


    
/**
     * Creates an uninitialized text storage model
     * that is appropriate for this type of editor.
     *
     * 
@return the model
     
*/

    
public Document createDefaultDocument() {
    
return new JavaDocument();
    }


    
/**
     * Fetches a factory that is suitable for producing 
     * views of any models that are produced by this
     * kit.  The default is to have the UI produce the
     * factory, so this method has no implementation.
     *
     * 
@return the view factory
     
*/

    
public final ViewFactory getViewFactory() {
    
return getStylePreferences();
    }


    JavaContext preferences;
}









import  java.io.IOException;
import  java.io.InputStream;

import  javax.swing.event.DocumentEvent;
import  javax.swing.text.AttributeSet;
import  javax.swing.text.BadLocationException;
import  javax.swing.text.Element;
import  javax.swing.text.GapContent;
import  javax.swing.text.MutableAttributeSet;
import  javax.swing.text.PlainDocument;
import  javax.swing.text.Segment;

public   class  JavaDocument  extends  PlainDocument  {

    
public JavaDocument() {
    
super(new GapContent(1024));
    }


    
/**
     * Create a lexical analyzer for this document.
     
*/

    
public Scanner createScanner() {
    Scanner s;
    
try {
        s 
= new Scanner();
    }
 catch (IOException e) {
        s 
= null;
    }

    
return s;
    }


    
/**
     * Fetch a reasonable location to start scanning
     * given the desired start location.  This allows
     * for adjustments needed to accomodate multiline
     * comments.
     
*/

    
public int getScannerStart(int p) {
    Element elem 
= getDefaultRootElement();
    
int lineNum = elem.getElementIndex(p);
    Element line 
= elem.getElement(lineNum);
    AttributeSet a 
= line.getAttributes();
    
while (a.isDefined(CommentAttribute) && lineNum > 0{
        lineNum 
-= 1;
        line 
= elem.getElement(lineNum);
        a 
= line.getAttributes();
    }

    
return line.getStartOffset();
    }


    
// --- AbstractDocument methods ----------------------------

    
/**
     * Updates document structure as a result of text insertion.  This
     * will happen within a write lock.  The superclass behavior of
     * updating the line map is executed followed by marking any comment
     * areas that should backtracked before scanning.
     *
     * 
@param chng the change event
     * 
@param attr the set of attributes
     
*/

    
protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
    
super.insertUpdate(chng, attr);
    
    
// update comment marks
    Element root = getDefaultRootElement();
    DocumentEvent.ElementChange ec 
= chng.getChange(root);
    
if (ec != null{
        Element[] added 
= ec.getChildrenAdded();
        
boolean inComment = false;
        
for (int i = 0; i < added.length; i++{
        Element elem 
= added[i];
        
int p0 = elem.getStartOffset();
        
int p1 = elem.getEndOffset();
        String s;
        
try {
            s 
= getText(p0, p1 - p0);
        }
 catch (BadLocationException bl) {
            s 
= null
        }

        
if (inComment) {
            MutableAttributeSet a 
= (MutableAttributeSet) elem.getAttributes();
            a.addAttribute(CommentAttribute, CommentAttribute);
            
int index = s.indexOf("*/");
            
if (index >= 0{
            
// found an end of comment, turn off marks
            inComment = false;
            }

        }
 else {
            
// scan for multiline comment
            int index = s.indexOf("/*");
            
if (index >= 0{
            
// found a start of comment, see if it spans lines
            index = s.indexOf("*/", index);
            
if (index < 0{
                
// it spans lines
                inComment = true;
            }

            }

        }

        }

    }

    }


    
/**
     * Updates any document structure as a result of text removal.
     * This will happen within a write lock.  The superclass behavior of
     * updating the line map is executed followed by placing a lexical
     * update command on the analyzer queue.
     *
     * 
@param chng the change event
     
*/

    
protected void removeUpdate(DefaultDocumentEvent chng) {
    
super.removeUpdate(chng);
    
    
// update comment marks
    }


    
// --- variables ------------------------------------------------

    
/**
     * Key to be used in AttributeSet's holding a value of Token.
     
*/

    
static final Object CommentAttribute = new AttributeKey();

    
static class AttributeKey {

        
private AttributeKey() {
    }


        
public String toString() {
        
return "comment";
    }


    }



    
    
public class Scanner extends sun.tools.java.Scanner {

        Scanner() 
throws IOException {
        
super(new LocalEnvironment(), new DocumentInputStream(0, getLength()));
        scanComments 
= true;
    }


    
/**
     * Sets the range of the scanner.  This should be called
     * to reinitialize the scanner to the desired range of
     * coverage.
     
*/

        
public void setRange(int p0, int p1) throws IOException {
        useInputStream(
new DocumentInputStream(p0, p1));
        
this.p0 = p0;
    }


    
/**
     * This fetches the starting location of the current
     * token in the document.
     
*/

        
public final int getStartOffset() {
        
int begOffs = (int) (pos & MAXFILESIZE);
        
return p0 + begOffs;
    }


    
/**
     * This fetches the ending location of the current
     * token in the document.
     
*/

        
public final int getEndOffset() {
        
int endOffs = (int) (getEndPos() & MAXFILESIZE);
        
return p0 + endOffs;
    }


    
int p0;
    }


    
/**
     * Class to provide InputStream functionality from a portion of a
     * Document.  This really should be a Reader, but not enough
     * things use it yet.
     
*/

    
class DocumentInputStream extends InputStream {

        
public DocumentInputStream(int p0, int p1) {
        
this.segment = new Segment();
        
this.p0 = p0;
        
this.p1 = Math.min(getLength(), p1);
        pos 
= p0;
        
try {
        loadSegment();
        }
 catch (IOException ioe) {
        
throw new Error("unexpected: " + ioe);
        }

    }


    
/**
     * Reads the next byte of data from this input stream. The value 
     * byte is returned as an <code>int</code> in the range 
     * <code>0</code> to <code>255</code>. If no byte is available 
     * because the end of the stream has been reached, the value 
     * <code>-1</code> is returned. This method blocks until input data 
     * is available, the end of the stream is detected, or an exception 
     * is thrown. 
     * <p>
     * A subclass must provide an implementation of this method. 
     *
     * 
@return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * 
@exception  IOException  if an I/O error occurs.
     * 
@since      JDK1.0
     
*/

        
public int read() throws IOException {
        
if (index >= segment.offset + segment.count) {
        
if (pos >= p1) {
            
// no more data
            return -1;
        }

        loadSegment();
        }

        
return segment.array[index++];
    }


    
void loadSegment() throws IOException {
        
try {
        
int n = Math.min(1024, p1 - pos);
        getText(pos, n, segment);
        pos 
+= n;
        index 
= segment.offset;
        }
 catch (BadLocationException e) {
        
throw new IOException("Bad location");
        }

    }

    
    Segment segment;
    
int p0;    // start position
    int p1;    // end position
    int pos;   // pos in document
    int index; // index into array of the segment
    }


    
static class LocalEnvironment extends sun.tools.java.Environment {

        
public void error(Object source, int where, String err, 
              Object arg1, Object arg2, Object arg3) 
{
        
// should do something useful... 
        System.err.println(err);
        System.err.println(
"location: " + where);
    }

    }


}


/*
 * @(#)JavaContext.java    1.2 98/05/04
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 
*/


import  java.awt.Color;
import  java.awt.Font;
import  java.awt.Graphics;
import  java.awt.Shape;

import  javax.swing.text.BadLocationException;
import  javax.swing.text.Document;
import  javax.swing.text.Element;
import  javax.swing.text.Segment;
import  javax.swing.text.Style;
import  javax.swing.text.StyleConstants;
import  javax.swing.text.StyleContext;
import  javax.swing.text.Utilities;
import  javax.swing.text.View;
import  javax.swing.text.ViewFactory;
import  javax.swing.text.WrappedPlainView;

/**
 * A collection of styles used to render java text.  
 * This class also acts as a factory for the views used 
 * to represent the java documents.  Since the rendering 
 * styles are based upon view preferences, the views need
 * a way to gain access to the style settings which is 
 * facilitated by implementing the factory in the style 
 * storage.  Both functionalities can be widely shared across
 * java document views.
 *
 * 
@author   Timothy Prinzing
 * 
@version  1.2 05/04/98
 
*/

public   class  JavaContext  extends  StyleContext  implements  ViewFactory  {

    
/**
     * Constructs a set of styles to represent java lexical 
     * tokens.  By default there are no colors or fonts specified.
     
*/

    
public JavaContext() {
    
super();
    Style root 
= getStyle(DEFAULT_STYLE);
    tokenStyles 
= new Style[Token.MaximumScanValue + 1];
    Token[] tokens 
= Token.all;
    
int n = tokens.length;
    
for (int i = 0; i < n; i++{
        Token t 
= tokens[i];
        Style parent 
= getStyle(t.getCategory());
        
if (parent == null{
        parent 
= addStyle(t.getCategory(), root);
        }

        Style s 
= addStyle(null, parent);
        s.addAttribute(Token.TokenAttribute, t);
        tokenStyles[t.getScanValue()] 
= s;

    }

    }


    
/**
     * Fetch the foreground color to use for a lexical
     * token with the given value.
     * 
     * 
@param attr attribute set from a token element
     *  that has a Token in the set.
     
*/

    
public Color getForeground(int code) {
    
if (tokenColors == null{
        tokenColors 
= new Color[Token.MaximumScanValue + 1];
    }

    
if ((code >= 0&& (code < tokenColors.length)) {
        Color c 
= tokenColors[code];
        
if (c == null{
        Style s 
= tokenStyles[code];
        c 
= StyleConstants.getForeground(s);
        }

        
return c;
    }

    
return Color.black;
    }


    
/**
     * Fetch the font to use for a lexical
     * token with the given scan value.
     
*/

    
public Font getFont(int code) {
    
if (tokenFonts == null{
        tokenFonts 
= new Font[Token.MaximumScanValue + 1];
    }

    
if (code < tokenFonts.length) {
        Font f 
= tokenFonts[code];
        
if (f == null{
        Style s 
= tokenStyles[code];
        f 
= getFont(s);
        }

        
return f;
    }

    
return null;
    }


    
/**
     * Fetches the attribute set to use for the given
     * scan code.  The set is stored in a table to
     * facilitate relatively fast access to use in 
     * conjunction with the scanner.
     
*/

    
public Style getStyleForScanValue(int code) {
    
if (code < tokenStyles.length) {
        
return tokenStyles[code];
    }

    
return null;
    }


    
// --- ViewFactory methods -------------------------------------

    
public View create(Element elem) {
    
return new JavaView(elem);
    }


    
// --- variables -----------------------------------------------

    
/**
     * The styles representing the actual token types.
     
*/

    Style[] tokenStyles;

    
/**
     * Cache of foreground colors to represent the 
     * various tokens.
     
*/

    
transient Color[] tokenColors;

    
/**
     * Cache of fonts to represent the various tokens.
     
*/

    
transient Font[] tokenFonts;

    
/**
     * View that uses the lexical information to determine the
     * style characteristics of the text that it renders.  This
     * simply colorizes the various tokens and assumes a constant
     * font family and size.
     
*/

    
class JavaView extends WrappedPlainView {

    
/**
     * Construct a simple colorized view of java
     * text.
     
*/

    JavaView(Element elem) 
{
        
super(elem);
        JavaDocument doc 
= (JavaDocument) getDocument();
        lexer 
= doc.createScanner();
        lexerValid 
= false;
    }


    
/**
     * Renders using the given rendering surface and area 
     * on that surface.  This is implemented to invalidate
     * the lexical scanner after rendering so that the next
     * request to drawUnselectedText will set a new range
     * for the scanner.
     *
     * 
@param g the rendering surface to use
     * 
@param a the allocated region to render into
     *
     * 
@see View#paint
     
*/

        
public void paint(Graphics g, Shape a) {
        
super.paint(g, a);
        lexerValid 
= false;
    }


    
/**
     * Renders the given range in the model as normal unselected
     * text.  This is implemented to paint colors based upon the
     * token-to-color translations.  To reduce the number of calls
     * to the Graphics object, text is batched up until a color
     * change is detected or the entire requested range has been
     * reached.
     *
     * 
@param g the graphics context
     * 
@param x the starting X coordinate
     * 
@param y the starting Y coordinate
     * 
@param p0 the beginning position in the model
     * 
@param p1 the ending position in the model
     * @returns the location of the end of the range
     * 
@exception BadLocationException if the range is invalid
     
*/

        
protected int drawUnselectedText(Graphics g, int x, int y, 
                     
int p0, int p1) throws BadLocationException {
        Document doc 
= getDocument();
        Color last 
= null;
        
int mark = p0;
        
for (; p0 < p1; ) {
        updateScanner(p0);
        
int p = Math.min(lexer.getEndOffset(), p1);
        p 
= (p <= p0) ? p1 : p;
        Color fg 
= getForeground(lexer.token);
        
if (fg != last && last != null{
            
// color change, flush what we have
            g.setColor(last);
            Segment text 
= getLineBuffer();
            doc.getText(mark, p0 
- mark, text);
            x 
= Utilities.drawTabbedText(text, x, y, g, this, mark);
            mark 
= p0;
        }

        last 
= fg;
        p0 
= p;
        }

        
// flush remaining
        g.setColor(last);
        Segment text 
= getLineBuffer();
        doc.getText(mark, p1 
- mark, text);
        x 
= Utilities.drawTabbedText(text, x, y, g, this, mark);
        
return x;
    }


    
/**
     * Update the scanner (if necessary) to point to the appropriate
     * token for the given start position needed for rendering.
     
*/

    
void updateScanner(int p) {
        
try {
        
if (! lexerValid) {
            JavaDocument doc 
= (JavaDocument) getDocument();
            lexer.setRange(doc.getScannerStart(p), doc.getLength());
            lexerValid 
= true;
        }

        
while (lexer.getEndOffset() <= p) {
            lexer.scan();
        }

        }
 catch (Throwable e) {
        
// can't adjust scanner... calling logic
        
// will simply render the remaining text.
        e.printStackTrace();
        }

    }

    
    JavaDocument.Scanner lexer;
    
boolean lexerValid;
    }


}



/*
 * @(#)Token.java    1.2 98/05/04
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 
*/


import  java.io.Serializable;
import  sun.tools.java.Constants;

/**
 * Simple class to represent a lexical token.  This
 * wraps the Constants used by the scanner to provide
 * a convenient class that can be stored as a attribute
 * value.
 *
 * 
@author  Timothy Prinzing
 * 
@version 1.2 05/04/98
 
*/

public   class  Token  implements  Serializable  {

    Token(String representation, 
int scanValue) {
    
this.representation = representation;
    
this.scanValue = scanValue;
    }

    
    
/**
     * A human presentable form of the token, useful
     * for things like lists, debugging, etc.
     
*/

    
public String toString() {
    
return representation;
    }


    
/**
     * Numeric value of this token.  This is the value
     * returned by the scanner and is the tie between 
     * the lexical scanner and the tokens.
     
*/

    
public int getScanValue() {
    
return scanValue;
    }


    
/**
     * Specifies the category of the token as a 
     * string that can be used as a label.
     
*/

    
public String getCategory() {
    String nm 
= getClass().getName();
    
int nmStart = nm.lastIndexOf('.'+ 1// not found results in 0
    return nm.substring(nmStart, nm.length());
    }


    
/**
     * Returns a hashcode for this set of attributes.
     * 
@return     a hashcode value for this set of attributes.
     
*/

    
public final int hashCode() {
    
return scanValue;
    }


    
/**
     * Compares this object to the specifed object.
     * The result is <code>true</code> if and only if the argument is not 
     * <code>null</code> and is a <code>Font</code> object with the same 
     * name, style, and point size as this font. 
     * 
@param     obj   the object to compare this font with.
     * 
@return    <code>true</code> if the objects are equal; 
     *            <code>false</code> otherwise.
     
*/

    
public final boolean equals(Object obj) {
    
if (obj instanceof Token) {
        Token t 
= (Token) obj;
        
return (scanValue == t.scanValue);
    }

    
return false;
    }


    
// --- variables -------------------------------------

    
public static final int MaximumScanValue = Constants.INLINENEWINSTANCE + 1;

    
/**
     * Key to be used in AttributeSet's holding a value of Token.
     
*/

    
public static final Object TokenAttribute = new AttributeKey();

    String representation;
    
int scanValue;

    
public static class Operator extends Token {

    Operator(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }


    }

    
    
public static class Value extends Token {

    Value(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }


    }

    
    
public static class Type extends Token {

    Type(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Expression extends Token {

    Expression(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Statement extends Token {

    Statement(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Declaration extends Token {

    Declaration(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Modifier extends Token {

    Modifier(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Punctuation extends Token {

    Punctuation(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
public static class Special extends Token {

    Special(String representation, 
int scanValue) {
        
super(representation, scanValue);
    }

    }

    
    
static class AttributeKey {

        
private AttributeKey() {
    }


        
public String toString() {
        
return "token";
    }


    }


    
/*
     * Operators
     
*/

    
public static final Token COMMA =       new Operator(Constants.opNames[Constants.COMMA], 
                             Constants.COMMA);
    
public static final Token ASSIGN =      new Operator(Constants.opNames[Constants.ASSIGN],
                             Constants.ASSIGN);
    
public static final Token ASGMUL =      new Operator(Constants.opNames[Constants.ASGMUL],
                             Constants.ASGMUL);
    
public static final Token ASGDIV =      new Operator(Constants.opNames[Constants.ASGDIV],
                             Constants.ASGDIV);
    
public static final Token ASGREM =      new Operator(Constants.opNames[Constants.ASGREM],
                             Constants.ASGREM);
    
public static final Token ASGADD =      new Operator(Constants.opNames[Constants.ASGADD],
                             Constants.ASGADD);
    
public static final Token ASGSUB =      new Operator(Constants.opNames[Constants.ASGSUB],
                             Constants.ASGSUB);
    
public static final Token ASGLSHIFT =   new Operator(Constants.opNames[Constants.ASGLSHIFT],
                             Constants.ASGLSHIFT);
    
public static final Token ASGRSHIFT =   new Operator(Constants.opNames[Constants.ASGRSHIFT],
                             Constants.ASGRSHIFT);
    
public static final Token ASGURSHIFT =  new Operator(Constants.opNames[Constants.ASGURSHIFT],
                             Constants.ASGURSHIFT);
    
public static final Token ASGBITAND =   new Operator(Constants.opNames[Constants.ASGBITAND],
                             Constants.ASGBITAND);
    
public static final Token ASGBITOR =    new Operator(Constants.opNames[Constants.ASGBITOR],
                             Constants.ASGBITOR);
    
public static final Token ASGBITXOR =   new Operator(Constants.opNames[Constants.ASGBITOR],
                             Constants.ASGBITOR);
    
public static final Token COND =        new Operator(Constants.opNames[Constants.COND],
                             Constants.COND);
    
public static final Token OR =          new Operator(Constants.opNames[Constants.OR],
                             Constants.OR);
    
public static final Token AND =         new Operator(Constants.opNames[Constants.AND],
                             Constants.AND);
    
public static final Token BITOR =       new Operator(Constants.opNames[Constants.BITOR],
                             Constants.BITOR);
    
public static final Token BITXOR =      new Operator(Constants.opNames[Constants.BITXOR],
                             Constants.BITXOR);
    
public static final Token BITAND =      new Operator(Constants.opNames[Constants.BITAND],
                             Constants.BITAND);
    
public static final Token NE =          new Operator(Constants.opNames[Constants.NE],
                             Constants.NE);
    
public static final Token EQ =          new Operator(Constants.opNames[Constants.EQ],
                             Constants.EQ);
    
public static final Token GE =          new Operator(Constants.opNames[Constants.GE],
                             Constants.GE);
    
public static final Token GT =          new Operator(Constants.opNames[Constants.GT],
                             Constants.GT);
    
public static final Token LE =          new Operator(Constants.opNames[Constants.LE],
                             Constants.LE);
    
public static final Token LT =          new Operator(Constants.opNames[Constants.LT],
                             Constants.LT);
    
public static final Token INSTANCEOF =  new Operator(Constants.opNames[Constants.INSTANCEOF],
                             Constants.INSTANCEOF);
    
public static final Token LSHIFT =      new Operator(Constants.opNames[Constants.LSHIFT],
                             Constants.LSHIFT);
    
public static final Token RSHIFT =      new Operator(Constants.opNames[Constants.RSHIFT],
                             Constants.RSHIFT);
    
public static final Token URSHIFT =     new Operator(Constants.opNames[Constants.URSHIFT],
                             Constants.URSHIFT);
    
public static final Token ADD =         new Operator(Constants.opNames[Constants.ADD],
                             Constants.ADD);
    
public static final Token SUB =         new Operator(Constants.opNames[Constants.SUB],
                             Constants.SUB);
    
public static final Token DIV =         new Operator(Constants.opNames[Constants.DIV],
                             Constants.DIV);
    
public static final Token REM =         new Operator(Constants.opNames[Constants.REM],
                             Constants.REM);
    
public static final Token MUL =         new Operator(Constants.opNames[Constants.MUL],
                             Constants.MUL);
    
public static final Token CAST =        new Operator(Constants.opNames[Constants.CAST],
                             Constants.CAST);
    
public static final Token POS =         new Operator(Constants.opNames[Constants.POS],
                             Constants.POS);
    
public static final Token NEG =         new Operator(Constants.opNames[Constants.NEG],
                             Constants.NEG);
    
public static final Token NOT =         new Operator(Constants.opNames[Constants.NOT],
                             Constants.NOT);
    
public static final Token BITNOT =      new Operator(Constants.opNames[Constants.BITNOT],
                             Constants.BITNOT);
    
public static final Token PREINC =      new Operator(Constants.opNames[Constants.PREINC],
                             Constants.PREINC);
    
public static final Token PREDEC =      new Operator(Constants.opNames[Constants.PREDEC],
                             Constants.PREDEC);
    
public static final Token NEWARRAY =    new Operator(Constants.opNames[Constants.NEWARRAY],
                             Constants.NEWARRAY);
    
public static final Token NEWINSTANCE = new Operator(Constants.opNames[Constants.NEWINSTANCE],
                             Constants.NEWINSTANCE);
    
public static final Token NEWFROMNAME = new Operator(Constants.opNames[Constants.NEWFROMNAME],
                             Constants.NEWFROMNAME);
    
public static final Token POSTINC =     new Operator(Constants.opNames[Constants.POSTINC],
                             Constants.POSTINC);
    
public static final Token POSTDEC =     new Operator(Constants.opNames[Constants.POSTDEC],
                             Constants.POSTDEC);
    
public static final Token FIELD =       new Operator(Constants.opNames[Constants.FIELD],
                             Constants.FIELD);
    
public static final Token METHOD =      new Operator(Constants.opNames[Constants.METHOD],
                             Constants.METHOD);
    
public static final Token ARRAYACCESS = new Operator(Constants.opNames[Constants.ARRAYACCESS],
                             Constants.ARRAYACCESS);
    
public static final Token NEW =         new Operator(Constants.opNames[Constants.NEW],
                             Constants.NEW);
    
public static final Token INC =         new Operator(Constants.opNames[Constants.INC],
                             Constants.INC);
    
public static final Token DEC =         new Operator(Constants.opNames[Constants.DEC],
                             Constants.DEC);
    
public static final Token CONVERT =     new Operator(Constants.opNames[Constants.CONVERT],
                             Constants.CONVERT);
    
public static final Token EXPR =        new Operator(Constants.opNames[Constants.EXPR],
                             Constants.EXPR);
    
public static final Token ARRAY =       new Operator(Constants.opNames[Constants.ARRAY],
                             Constants.ARRAY);
    
public static final Token GOTO =        new Operator(Constants.opNames[Constants.GOTO],
                             Constants.GOTO);
    
/*
     * Value tokens
     
*/

    
public static final Token IDENT =       new Value(Constants.opNames[Constants.IDENT],
                              Constants.IDENT);
    
public static final Token BOOLEANVAL =  new Value(Constants.opNames[Constants.BOOLEANVAL],
                              Constants.BOOLEANVAL);
    
public static final Token BYTEVAL =     new Value(Constants.opNames[Constants.BYTEVAL],
                              Constants.BYTEVAL);
    
public static final Token CHARVAL =     new Value(Constants.opNames[Constants.CHARVAL],
                              Constants.CHARVAL);
    
public static final Token SHORTVAL =    new Value(Constants.opNames[Constants.SHORTVAL],
                              Constants.SHORTVAL);
    
public static final Token INTVAL =      new Value(Constants.opNames[Constants.INTVAL],
                              Constants.INTVAL);
    
public static final Token LONGVAL =     new Value(Constants.opNames[Constants.LONGVAL],
                              Constants.LONGVAL);
    
public static final Token FLOATVAL =    new Value(Constants.opNames[Constants.FLOATVAL],
                              Constants.FLOATVAL);
    
public static final Token DOUBLEVAL =   new Value(Constants.opNames[Constants.DOUBLEVAL],
                              Constants.DOUBLEVAL);
    
public static final Token STRINGVAL =   new Value(Constants.opNames[Constants.STRINGVAL],
                              Constants.STRINGVAL);
    
/*
     * Type keywords
     
*/

    
public static final Token BYTE =        new Type(Constants.opNames[Constants.BYTE],
                             Constants.BYTE);
    
public static final Token CHAR =        new Type(Constants.opNames[Constants.CHAR],
                             Constants.CHAR);
    
public static final Token SHORT =       new Type(Constants.opNames[Constants.SHORT],
                             Constants.SHORT);
    
public static final Token INT =         new Type(Constants.opNames[Constants.INT],
                             Constants.INT);
    
public static final Token LONG =        new Type(Constants.opNames[Constants.LONG],
                             Constants.LONG);
    
public static final Token FLOAT =       new Type(Constants.opNames[Constants.FLOAT],
                             Constants.FLOAT);
    
public static final Token DOUBLE =      new Type(Constants.opNames[Constants.DOUBLE],
                             Constants.DOUBLE);
    
public static final Token VOID =        new Type(Constants.opNames[Constants.VOID],
                             Constants.VOID);
    
public static final Token BOOLEAN =     new Type(Constants.opNames[Constants.BOOLEAN],
                             Constants.BOOLEAN);
    
/*
     * Expression keywords
     
*/

    
public static final Token TRUE =        new Expression(Constants.opNames[Constants.TRUE],
                               Constants.TRUE);
    
public static final Token FALSE =       new Expression(Constants.opNames[Constants.FALSE],
                               Constants.FALSE);
    
public static final Token THIS =        new Expression(Constants.opNames[Constants.THIS],
                               Constants.THIS);
    
public static final Token SUPER =       new Expression(Constants.opNames[Constants.SUPER],
                               Constants.SUPER);
    
public static final Token NULL =        new Expression(Constants.opNames[Constants.NULL],
                               Constants.NULL);
    
/*
     * Statement keywords
     
*/

    
public static final Token IF =             new Statement(Constants.opNames[Constants.IF],
                                 Constants.IF);
    
public static final Token ELSE =           new Statement(Constants.opNames[Constants.ELSE],
                                 Constants.ELSE);
    
public static final Token FOR =            new Statement(Constants.opNames[Constants.FOR],
                                 Constants.FOR);
    
public static final Token WHILE =          new Statement(Constants.opNames[Constants.WHILE],
                                 Constants.WHILE);
    
public static final Token DO =             new Statement(Constants.opNames[Constants.DO],
                                 Constants.DO);
    
public static final Token SWITCH =         new Statement(Constants.opNames[Constants.SWITCH],
                                 Constants.SWITCH);
    
public static final Token CASE =           new Statement(Constants.opNames[Constants.CASE],
                                 Constants.CASE);
    
public static final Token DEFAULT =        new Statement(Constants.opNames[Constants.DEFAULT],
                                 Constants.DEFAULT);
    
public static final Token BREAK =          new Statement(Constants.opNames[Constants.BREAK],
                                 Constants.BREAK);
    
public static final Token CONTINUE =       new Statement(Constants.opNames[Constants.CONTINUE],
                                 Constants.CONTINUE);
    
public static final Token RETURN =         new Statement(Constants.opNames[Constants.RETURN],
                                 Constants.RETURN);
    
public static final Token TRY =            new Statement(Constants.opNames[Constants.TRY],
                                 Constants.TRY);
    
public static final Token CATCH =          new Statement(Constants.opNames[Constants.CATCH],
                                 Constants.CATCH);
    
public static final Token FINALLY =        new Statement(Constants.opNames[Constants.FINALLY],
                                 Constants.FINALLY);
    
public static final Token THROW =          new Statement(Constants.opNames[Constants.THROW],
                                 Constants.THROW);
    
public static final Token STAT =           new Statement(Constants.opNames[Constants.STAT],
                                 Constants.STAT);
    
public static final Token EXPRESSION =     new Statement(Constants.opNames[Constants.EXPRESSION],
                                 Constants.EXPRESSION);
    
public static final Token DECLARATION =    new Statement(Constants.opNames[Constants.DECLARATION],
                                 Constants.DECLARATION);
    
public static final Token VARDECLARATION = new Statement(Constants.opNames[Constants.VARDECLARATION],
                                 Constants.VARDECLARATION);
    
/*
     * Declaration keywords
     
*/

    
public static final Token IMPORT =         new Declaration(Constants.opNames[Constants.IMPORT],
                                   Constants.IMPORT);
    
public static final Token CLASS =          new Declaration(Constants.opNames[Constants.CLASS],
                                   Constants.CLASS);
    
public static final Token EXTENDS =        new Declaration(Constants.opNames[Constants.EXTENDS],
                                   Constants.EXTENDS);
    
public static final Token IMPLEMENTS =     new Declaration(Constants.opNames[Constants.IMPLEMENTS],
                                   Constants.IMPLEMENTS);
    
public static final Token INTERFACE =      new Declaration(Constants.opNames[Constants.INTERFACE],
                                   Constants.INTERFACE);
    
public static final Token PACKAGE =        new Declaration(Constants.opNames[Constants.PACKAGE],
                                   Constants.PACKAGE);
    
/*
     * Modifier keywords
     
*/

    
public static final Token PRIVATE =        new Modifier(Constants.opNames[Constants.PRIVATE],
                                Constants.PRIVATE);
    
public static final Token PUBLIC =         new Modifier(Constants.opNames[Constants.PUBLIC],
                                Constants.PUBLIC);
    
public static final Token PROTECTED =      new Modifier(Constants.opNames[Constants.PROTECTED],
                                Constants.PROTECTED);
    
public static final Token CONST =          new Modifier(Constants.opNames[Constants.CONST],
                                Constants.CONST);
    
public static final Token STATIC =         new Modifier(Constants.opNames[Constants.STATIC],
                                Constants.STATIC);
    
public static final Token TRANSIENT =      new Modifier(Constants.opNames[Constants.TRANSIENT],
                                Constants.TRANSIENT);
    
public static final Token SYNCHRONIZED =   new Modifier(Constants.opNames[Constants.SYNCHRONIZED],
                                Constants.SYNCHRONIZED);
    
public static final Token NATIVE =         new Modifier(Constants.opNames[Constants.NATIVE],
                                Constants.NATIVE);
    
public static final Token FINAL =          new Modifier(Constants.opNames[Constants.FINAL],
                                Constants.FINAL);
    
public static final Token VOLATILE =       new Modifier(Constants.opNames[Constants.VOLATILE],
                                Constants.VOLATILE);
    
public static final Token ABSTRACT =       new Modifier(Constants.opNames[Constants.ABSTRACT],
                                Constants.ABSTRACT);

    
/*
     * Punctuation
     
*/

    
public static final Token SEMICOLON =      new Punctuation(Constants.opNames[Constants.SEMICOLON],
                                   Constants.SEMICOLON);
    
public static final Token COLON =          new Punctuation(Constants.opNames[Constants.COLON],
                                   Constants.COLON);
    
public static final Token QUESTIONMARK =   new Punctuation(Constants.opNames[Constants.QUESTIONMARK],
                                   Constants.QUESTIONMARK);
    
public static final Token LBRACE =         new Punctuation(Constants.opNames[Constants.LBRACE],
                                   Constants.LBRACE);
    
public static final Token RBRACE =         new Punctuation(Constants.opNames[Constants.RBRACE],
                                   Constants.RBRACE);
    
public static final Token LPAREN =         new Punctuation(Constants.opNames[Constants.LPAREN],
                                   Constants.LPAREN);
    
public static final Token RPAREN =         new Punctuation(Constants.opNames[Constants.RPAREN],
                                   Constants.RPAREN);
    
public static final Token LSQBRACKET =     new Punctuation(Constants.opNames[Constants.LSQBRACKET],
                                   Constants.LSQBRACKET);
    
public static final Token RSQBRACKET =     new Punctuation(Constants.opNames[Constants.RSQBRACKET],
                                   Constants.RSQBRACKET);
    
public static final Token THROWS =         new Punctuation(Constants.opNames[Constants.THROWS],
                                   Constants.THROWS);

    
/*
     * Special tokens
     
*/

    
public static final Token ERROR =             new Special(Constants.opNames[Constants.ERROR],
                                  Constants.ERROR);
    
public static final Token COMMENT =           new Special(Constants.opNames[Constants.COMMENT],
                                  Constants.COMMENT);
    
public static final Token TYPE =              new Special(Constants.opNames[Constants.TYPE],
                                  Constants.TYPE);
    
public static final Token LENGTH =            new Special(Constants.opNames[Constants.LENGTH],
                                  Constants.LENGTH);
    
public static final Token INLINERETURN =      new Special(Constants.opNames[Constants.INLINERETURN],
                                  Constants.INLINERETURN);
    
public static final Token INLINEMETHOD =      new Special(Constants.opNames[Constants.INLINEMETHOD],
                                  Constants.INLINEMETHOD);
    
public static final Token INLINENEWINSTANCE = new Special(Constants.opNames[Constants.INLINENEWINSTANCE],
                                  Constants.INLINENEWINSTANCE);
    
public static final Token UNSCANNED =         new Special("unscanned", MaximumScanValue);

    
static Token[] operators = {
    COMMA, ASSIGN, ASGMUL, ASGDIV, ASGREM, ASGADD, ASGSUB, ASGLSHIFT,
    ASGRSHIFT, ASGURSHIFT, ASGBITAND, ASGBITOR, ASGBITXOR, COND, OR, AND,
    BITOR, BITXOR, BITAND, NE, EQ, GE, GT, LE, LT, INSTANCEOF, LSHIFT, 
    RSHIFT, URSHIFT, ADD, SUB, DIV, REM, MUL, CAST, POS, NEG, NOT, BITNOT,
    PREINC, PREDEC, NEWARRAY, NEWINSTANCE, NEWFROMNAME, POSTINC, POSTDEC,
    FIELD, METHOD, ARRAYACCESS, NEW, INC, DEC, CONVERT, EXPR, ARRAY, GOTO
    }
;

    
static Token[] values = {
    IDENT, BOOLEANVAL, BYTEVAL, CHARVAL, SHORTVAL, INTVAL, LONGVAL, 
    FLOATVAL, DOUBLEVAL, STRINGVAL
    }
;

    
static Token[] types = {
    BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, VOID, BOOLEAN
    }
;

    
static Token[] expressions = {
    TRUE, FALSE, THIS, SUPER, NULL
    }
;

    
static Token[] statements = {
    IF, ELSE, FOR, WHILE, DO, SWITCH, CASE, DEFAULT, BREAK,
    CONTINUE, RETURN, TRY, CATCH, FINALLY, THROW, STAT, EXPRESSION, 
    DECLARATION, VARDECLARATION
    }
;

    
static Token[] declarations = {
    IMPORT, CLASS, EXTENDS, IMPLEMENTS, INTERFACE, PACKAGE
    }
;

    
static Token[] modifiers = {
    PRIVATE, PUBLIC, PROTECTED, CONST, STATIC, TRANSIENT, SYNCHRONIZED,
    NATIVE, FINAL, VOLATILE, ABSTRACT
    }
;

    
static Token[] punctuations = {
    SEMICOLON, COLON, QUESTIONMARK, LBRACE, RBRACE, LPAREN, 
    RPAREN, LSQBRACKET, RSQBRACKET, THROWS
    }
;

    
static Token[] specials = {
    ERROR, COMMENT, TYPE, LENGTH, INLINERETURN, INLINEMETHOD, INLINENEWINSTANCE, UNSCANNED
    }
;

    
static Token[] all = {
    COMMA, ASSIGN, ASGMUL, ASGDIV, ASGREM, ASGADD, ASGSUB, ASGLSHIFT,
    ASGRSHIFT, ASGURSHIFT, ASGBITAND, ASGBITOR, ASGBITXOR, COND, OR, AND,
    BITOR, BITXOR, BITAND, NE, EQ, GE, GT, LE, LT, INSTANCEOF, LSHIFT, 
    RSHIFT, URSHIFT, ADD, SUB, DIV, REM, MUL, CAST, POS, NEG, NOT, BITNOT,
    PREINC, PREDEC, NEWARRAY, NEWINSTANCE, NEWFROMNAME, POSTINC, POSTDEC,
    FIELD, METHOD, ARRAYACCESS, NEW, INC, DEC, CONVERT, EXPR, ARRAY, GOTO, 
    IDENT, BOOLEANVAL, BYTEVAL, CHARVAL, SHORTVAL, INTVAL, LONGVAL, 
    FLOATVAL, DOUBLEVAL, STRINGVAL,
    BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, VOID, BOOLEAN,
    TRUE, FALSE, THIS, SUPER, NULL,
    IF, ELSE, FOR, WHILE, DO, SWITCH, CASE, DEFAULT, BREAK,
    CONTINUE, RETURN, TRY, CATCH, FINALLY, THROW, STAT, EXPRESSION, 
    DECLARATION, VARDECLARATION,
    IMPORT, CLASS, EXTENDS, IMPLEMENTS, INTERFACE, PACKAGE,
    PRIVATE, PUBLIC, PROTECTED, CONST, STATIC, TRANSIENT, SYNCHRONIZED,
    NATIVE, FINAL, VOLATILE, ABSTRACT,
    SEMICOLON, COLON, QUESTIONMARK, LBRACE, RBRACE, LPAREN, 
    RPAREN, LSQBRACKET, RSQBRACKET, THROWS,
    ERROR, COMMENT, TYPE, LENGTH, INLINERETURN, INLINEMETHOD, INLINENEWINSTANCE, UNSCANNED
    }
;

}




import  java.io.Serializable;
import  java.util.Vector;

import  javax.swing.text.AbstractDocument;
import  javax.swing.text.BadLocationException;
import  javax.swing.text.Position;
import  javax.swing.text.Segment;
import  javax.swing.undo.UndoableEdit;


public   final   class  GapContent  implements  AbstractDocument.Content, Serializable  {


    
/**
     * Creates a new GapContent object.  Initial size defaults to 10.
     
*/

    
public GapContent() {
    
this(10);
    }


    
/**
     * Creates a new GapContent object, with the initial
     * size specified.
     *
     * 
@param initialLength the initial size
     
*/

    
public GapContent(int initialLength) {
    array 
= new char[initialLength];
    array[
0= ' ';
    g0 
= 1;
    g1 
= initialLength;
    }


    
// --- AbstractDocument.Content methods -------------------------

    
/**
     * Returns the length of the content.
     *
     * 
@return the length >= 1
     * 
@see AbstractDocument.Content#length
     
*/

    
public int length() {
    
int len = array.length - (g1 - g0);
    
return len;
    }


    
/**
     * Inserts a string into the content.
     *
     * 
@param where the starting position >= 0, < length()
     * 
@param str the non-null string to insert
     * 
@return an UndoableEdit object for undoing
     * 
@exception BadLocationException if the specified position is invalid
     * 
@see AbstractDocument.Content#insertString
     
*/

    
public UndoableEdit insertString(int where, String str) throws BadLocationException {
    
if (where >= length()) {
        
throw new BadLocationException("Invalid insert", length());
    }

    
char[] chars = str.toCharArray();
    replace(where, 
0, chars);
    
return null;
    }


    
/**
     * Removes part of the content.
     *
     * 
@param where the starting position >= 0, where + nitems < length()
     * 
@param nitems the number of characters to remove >= 0
     * 
@return an UndoableEdit object for undoing
     * 
@exception BadLocationException if the specified position is invalid
     * 
@see AbstractDocument.Content#remove
     
*/

    
public UndoableEdit remove(int where, int nitems) throws BadLocationException {
    
if (where + nitems >= length()) {
        
throw new BadLocationException("Invalid insert", length() + 1);
    }

    replace(where, nitems, empty);
    
return null;
    
    }


    
/**
     * Retrieves a portion of the content.
     *
     * 
@param where the starting position >= 0
     * 
@param len the length to retrieve >= 0
     * 
@return a string representing the content
     * 
@exception BadLocationException if the specified position is invalid
     * 
@see AbstractDocument.Content#getString
     
*/

    
public String getString(int where, int len) throws BadLocationException {
    Segment s 
= new Segment();
    getChars(where, len, s);
    
return new String(s.array, s.offset, s.count);
    }


    
/**
     * Retrieves a portion of the content.  If the desired content spans
     * the gap, we copy the content.  If the desired content does not
     * span the gap, the actual store is returned to avoid the copy since
     * it is contiguous.
     *
     * 
@param where the starting position >= 0, where + len <= length()
     * 
@param len the number of characters to retrieve >= 0
     * 
@param chars the Segment object to return the characters in
     * 
@exception BadLocationException if the specified position is invalid
     * 
@see AbstractDocument.Content#getChars
     
*/

    
public void getChars(int where, int len, Segment chars) throws BadLocationException {
    
if (where < 0{
        
throw new BadLocationException("Invalid location"-1);
    }

    
if ((where + len) > length()) {
        
throw new BadLocationException("Invalid location", length() + 1);
    }

    
if ((where + len) <= g0) {
        
// below gap
        chars.array = array;
        chars.offset 
= where;
    }
 else if (where >= g0) {
        
// above gap
        chars.array = array;
        chars.offset 
= g1 + where - g0;
    }
 else {
        
// spans the gap, must copy
        chars.array = new char[len];
        chars.offset 
= 0;
        
int before = g0 - where;
        System.arraycopy(array, where, chars.array, 
0, before);
        System.arraycopy(array, g1, chars.array, before, len 
- before);
    }

    chars.count 
= len;
    }


    
/**
     * Creates a position within the content that will
     * track change as the content is mutated.
     *
     * 
@param offset the offset to track >= 0
     * 
@return the position
     * 
@exception BadLocationException if the specified position is invalid
     
*/

    
public Position createPosition(int offset) throws BadLocationException {
    
if (marks == null{
        marks 
= new Vector();
        search 
= new MarkData(0);
    }

    
if (unusedMarks > Math.max(5, (marks.size() / 10))) {
        removeUnusedMarks();
    }

    
int index = (offset < g0) ? offset : offset + (g1 - g0);
    MarkData m 
= new MarkData(index);
    
int sortIndex = findSortIndex(m);
    marks.insertElementAt(m, sortIndex);
    
return new StickyPosition(m);
    }


    
/**
     * Holds the data for a mark... seperately from
     * the real mark so that the real mark (Position
     * that the caller of createPosition holds) can be 
     * collected if there are no more references to
     * it.  The update table holds only a reference
     * to this data.
     
*/

    
final class MarkData {

    MarkData(
int index) {
        
this.index = index;
    }


    
/**
     * Fetch the location in the contiguous sequence
     * being modeled.  The index in the gap array
     * is held by the mark, so it is adjusted according
     * to it's relationship to the gap.
     
*/

        
public final int getOffset() {
        
int offs = (index < g0) ? index : index - (g1 - g0);
        
return Math.max(offs, 0);
    }


        
public final void dispose() {
        unused 
= true;
        unusedMarks 
+= 1;
    }


    
int index;
    
boolean unused;
    }


    
/**
     * This really wants to be a weak reference but
     * in 1.1 we don't have a 100% pure solution for
     * this... so this class trys to hack a solution 
     * to causing the marks to be collected.
     
*/

    
final class StickyPosition implements Position {

    StickyPosition(MarkData mark) 
{
        
this.mark = mark;
    }


        
public final int getOffset() {
        
return mark.getOffset();
    }


    
protected void finalize() throws Throwable {
        
// schedule the record to be removed later
        
// on another thread.
        mark.dispose();
    }


        
public String toString() {
        
return Integer.toString(getOffset());
    }


    MarkData mark;
    }


    
// --- variables --------------------------------------

    
private static final char[] empty = new char[0];
    
private transient Vector marks;

    
/**
     * Record used for searching for the place to
     * start updating mark indexs when the gap 
     * boundries are moved.
     
*/

    
private transient MarkData search;

    
/**
     * The number of unused mark entries
     
*/

    
private transient int unusedMarks;

    
/**
     * The array of unicode characters that store 
     * the content.
     
*/

    
char[] array;

    
/**
     * start of gap in the array
     
*/

    
int g0;

    
/**
     * end of gap in the array
     
*/

    
int g1;


    
// --- gap management -------------------------------

    
/**
     * Replace the given logical position in the storage with
     * the given new items.  This will move the gap to the area
     * being changed if the gap is not currently located at the
     * change location.
     *
     * 
@param position the location to make the replacement.  This
     *  is not the location in the underlying storage array, but
     *  the location in the contiguous space being modeled.
     * 
@param rmSize the number of items to remove
     * 
@param addItems the new items to place in storage.
     
*/

    
void replace(int position, int rmSize, char[] addItems) {
    
int addSize = addItems.length;
    
int addOffset = 0;
    
if (addSize == 0{
        close(position, rmSize);
        
return;
    }
 else if (rmSize > addSize) {
        
/* Shrink the end. */
        close(position
+addSize, rmSize-addSize);
    }
 else {
        
/* Grow the end, do two chunks. */
        
int endSize = addSize - rmSize;
        
int end = open(position + rmSize, endSize);
        System.arraycopy(addItems, rmSize, array, end, endSize);
        addSize 
= rmSize;
    }

    System.arraycopy(addItems, addOffset, array, position, addSize);
    }


    
/** 
     * Delete nItems at position.  Squeezes any marks 
     * within the deleted area to position.  This moves
     * the gap to the best place by minimizing it's 
     * overall movement.  The gap must intersect the
     * target block.
     
*/

    
void close(int position, int nItems) {
    
if (nItems == 0)  return;

    
int end = position + nItems;
    
int new_gs = (g1 - g0) + nItems;
    
if (end <= g0) {
        
// Move gap to end of block.
        if (g0 != end) {
        shiftGap(end);
        }

        
// Adjust g0.
        shiftGapStartDown(g0 - nItems);
    }
 else if (position >= g0) {
        
// Move gap to beginning of block.
        if (g0 != position) {
        shiftGap(position);
        }

        
// Adjust g1. 
        shiftGapEndUp(g0 + new_gs);
    }
 else {
        
// The gap is properly inside the target block.
        
// No data movement necessary, simply move both gap pointers.
        shiftGapStartDown(position);
        shiftGapEndUp(g0 
+ new_gs);
    }

    }


    
/**
     * Make space for the given number of items at the given
     * location.  
     *
     * @returns the location that the caller should fill in.
     
*/

    
int open(int position, int nItems) {
    
int gapSize = g1 - g0;
    
if (nItems == 0{
        
if (position > g0)  
        position 
+= gapSize;
        
return position;
    }


    
// Expand the array if the gap is too small.
    shiftGap(position);
    
if (nItems >= gapSize) {
        
// Pre-shift the gap, to reduce total movement.
        shiftEnd(array.length - gapSize + nItems);
        gapSize 
= g1 - g0;
    }


    g0 
= g0 + nItems;
    
return position;
    }


    
/** 
     * resize the underlying storage array to the 
     * given new size
     
*/

    
void resize(int nsize) {
    
char[] narray = new char[nsize];
    System.arraycopy(array, 
0, narray, 0, Math.min(nsize, array.length));
    array 
= narray;
    }


    
/**
     * Make the gap bigger, moving any necessary data and updating 
     * the appropriate marks
     
*/

    
void shiftEnd(int newSize) {
    
int oldSize = array.length;
    
int oldGapEnd = g1;
    
int upperSize = oldSize - oldGapEnd;
    
int newGapEnd;
    
long dg;

    
if (newSize < oldSize) {
        
if (oldSize <= array.length) {
        
// No more downsizing.
        return;
        }

        
if (upperSize > 0{
        
/* When contracting, move vector contents to front. */
        shiftGap(oldSize 
- (g1 - g0));
        oldGapEnd 
= oldSize;
        upperSize 
= 0;
        }

    }


    resize(newSize);
    newGapEnd 
= array.length - upperSize;
    g1 
= newGapEnd;
    dg 
= newGapEnd - oldGapEnd;

    
// Adjust marks.
    int adjustIndex = findMarkAdjustIndex(oldGapEnd);
    
int n = marks.size();
    
for (int i = adjustIndex; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        mark.index 
+= dg;
    }

    
    
if (upperSize != 0{
        
// Copy array items to new end of array.
        System.arraycopy(array, oldGapEnd, array, newGapEnd, upperSize);
    }

    }


    
/**
     * Move the start of the gap to a new location,
     * without changing the size of the gap.  This 
     * moves the data in the array and updates the
     * marks accordingly.
     
*/

    
void shiftGap(int newGapStart) {
    
if (newGapStart == g0) {
        
return;
    }

    
int oldGapStart = g0;
    
int dg = newGapStart - oldGapStart;
    
int oldGapEnd = g1;
    
int newGapEnd = oldGapEnd + dg;
    
int gapSize = oldGapEnd - oldGapStart;

    g0 
= newGapStart;
    g1 
= newGapEnd;
    
if (dg > 0{
        
// Move gap up, move data and marks down.
        int adjustIndex = findMarkAdjustIndex(oldGapStart);
        
int n = marks.size();
        
for (int i = adjustIndex; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        
if (mark.index >= newGapEnd) {
            
break;
        }

        mark.index 
-= gapSize;
        }

        System.arraycopy(array, oldGapEnd, array, oldGapStart, dg);
    }
 else if (dg < 0{
        
// Move gap down, move data and marks up.
        int adjustIndex = findMarkAdjustIndex(newGapStart);
        
int n = marks.size();
        
for (int i = adjustIndex; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        
if (mark.index >= oldGapEnd) {
            
break;
        }

        mark.index 
+= gapSize;
        }

        System.arraycopy(array, newGapStart, array, newGapEnd, 
-dg);
    }

    }


    
/**
     * Adjust the gap end downward.  This doesn't move
     * any data, but it does update any marks affected 
     * by the boundry change.  All marks from the old
     * gap start down to the new gap start are squeezed
     * to the end of the gap (their location has been
     * removed).
     
*/

    
void shiftGapStartDown(int newGapStart) {
    
// Push aside all marks from oldGapStart down to newGapStart.
    int adjustIndex = findMarkAdjustIndex(newGapStart);
    
int n = marks.size();
    
for (int i = adjustIndex; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        
if (mark.index > g0) {
        
// no more marks to adjust
        break;
        }

        mark.index 
= g1;
    }

    g0 
= newGapStart;
    }


    
/**
     * Adjust the gap end upward.  This doesn't move
     * any data, but it does update any marks affected 
     * by the boundry change. All marks from the old
     * gap end up to the new gap end are squeezed
     * to the end of the gap (their location has been
     * removed).
     
*/

    
void shiftGapEndUp(int newGapEnd) {
    
int adjustIndex = findMarkAdjustIndex(g1);
    
int n = marks.size();
    
for (int i = adjustIndex; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        
if (mark.index >= newGapEnd) {
        
break;
        }

        mark.index 
= newGapEnd;
    }

    g1 
= newGapEnd;
    }


    
/**
     * Compares two marks.
     *
     * 
@param o1 the first object
     * 
@param o2 the second object
     * 
@return < 0 if o1 < o2, 0 if the same, > 0 if o1 > o2
     
*/

    
final int compare(MarkData o1, MarkData o2) {
    
if (o1.index < o2.index) {
        
return -1;
    }
 else if (o1.index > o2.index) {
        
return 1;
    }
 else {
        
return 0;
    }

    }


    
/**
     * Finds the index to start mark adjustments given
     * some search index.
     
*/

    
final int findMarkAdjustIndex(int searchIndex) {
    search.index 
= Math.max(searchIndex, 1);
    
int index = findSortIndex(search);

    
// return the first in the series
    
// (ie. there may be duplicates).
    for (int i = index - 1; i >= 0; i--{
        MarkData d 
= (MarkData) marks.elementAt(i);
        
if (d.index != search.index) {
        
break;
        }

        index 
-= 1;
    }

    
return index;
    }


    
/**
     * Finds the index of where to insert a new mark.
     *
     * 
@param o the mark to insert
     * 
@return the index
     
*/

    
final int findSortIndex(MarkData o) {
    
int lower = 0
    
int upper = marks.size() - 1;
    
int mid = 0;
    
    
if (upper == -1{
        
return 0;
    }


    
int cmp = 0;
    MarkData last 
= (MarkData) marks.elementAt(upper);
    cmp 
= compare(o, last);
    
if (cmp > 0)
        
return upper + 1;
    
    
while (lower <= upper) {
        mid 
= lower + ((upper - lower) / 2);
        MarkData entry 
= (MarkData) marks.elementAt(mid);
        cmp 
= compare(o, entry);

        
if (cmp == 0{
        
// found a match
        return mid;
        }
 else if (cmp < 0{        
        upper 
= mid - 1;
        }
 else {
        lower 
= mid + 1;
        }

    }


    
// didn't find it, but we indicate the index of where it would belong.
    return (cmp < 0? mid : mid + 1;
    }


    
/**
     * Remove all unused marks out of the sorted collection
     * of marks.  
     
*/

    
final void removeUnusedMarks() {
    
int n = marks.size();
    Vector cleaned 
= new Vector(n);
    
for (int i = 0; i < n; i++{
        MarkData mark 
= (MarkData) marks.elementAt(i);
        
if (mark.unused == false{
        cleaned.addElement(mark);
        }

    }

    marks 
= cleaned;
    unusedMarks 
= 0;
    }


}



This article shows how to add customized features to the ready-made text-editor kits that are supplied with Swing.

More specifically, it shows how to create a "plug-in" for JEditorPane that can handle different kinds of content are not handled by default.

To show you how to create plug-ins for Swing text-editor kits, we supply all the source files you'll need to create, compile, and execute a plug-in that adds color highlighting capabilities to a text editor.

Once you know how to do that, you can use similar techniques to provide text editors with other kinds of custom capabilities.

This article is designed to accompany a longer and more comprehensive article, titled "The Swing Text Package," that is also presented in this issue. Before you start working with the sample program we'll be presenting here, we recommend that you read "The Swing Text Package."

Once you learn the techniques outlined in this article, you can use them to add any kind of special capabilities you like to any text editor that's compatible with the Swing text API.

The major topics we'll be covering are:

 

Introducing the JavaEditorKit

To create the customized source-code editor demonstrated in this article, we created a customized implementation of the Swing EditorKit class and used it to add color highlighting to the Swing JEditorPane class.

The new editor kit that we created is designed for editing source code written in the Java programming language, so we've named it the JavaEditorKit.

Later in this article, you find links to the all source-code files that we used to create the JavaEditorKit. You can download the source code, study it, and modify it to create any kind of customized text editor you'd like to build.

 

The JEditorPane class

JEditorPane -- the foundation class for the customized editor that we'll create in this article -- separates the management of look-and-feel features from the management of document content.

By default, JEditorPane supports plain text, HTML text, and RTF text. To create the JavaEditorKit that we built for this article, we added some specialized support for text written in the Java programming language.

When you learn how the JavaEditorKit example works, you know all you need to know to add just about any kind of support for new kinds of content to a Swing text editor.

 

A preview of the JavaEditorKit program

When you complete the JavaEditorKit example and execute it, the program displays a window like the one shown below. As you can see, the JavaEditorKit provides color text highlighting that is specially designed for use with the Java programming language.

Editor kit screen shot

 

Planning a strategy

When you want to customize a Swing text editor, the first thing you should do is plan a strategy for the kind of support that your new editor kit will provide. In the example that we'll present in this article, we'' provide the bare bones of what a programmer would need to edit code written in the Java programming language.

At this level, we won’t be dealing with the overall environment of an IDE; that would be, a higher-level task. Instead, we'll just create a plug-in that provides some basic services. By building on that foundation, we could easily create a higher-level environment. There are many directions we could go in, but for now, we'll keep it simple by dealing primarily with syntax highlighting.

To perform the task of syntax highlighting, it might be tempting to use the styled-text support that Swing provides. But we'll be building a source-code editor, and in terms of modeling, source code is really oriented more towards plain text than towards styled text. Furthermore, different programmers may want to view the documents using different style settings.

Also, if the syntax of a program gets too broken it can always be treated as plain text. So embedding styles in documents is not very desirable -- at least not in the kind of editor we are building.

The JavaEditorKit example extends a plain-text document, forming semi-styled views (in color) over it. To accomplish this task, we could use a model that maintains a lexical structure of the program in addition to the line-mapping services already provided by the PlainDocument class. The lexical structure provided by PlainDocument is designed primarily to provide a beautification service that is nice but not essential, so we could build and store a lexical structure on a separate lower-priority thread in such a way that it would not get in the way of any editing or text viewing. If an application opened a large document, it could be used as soon as the plain-text part Became available. This strategy would free the user from having to wait for the scanner to finish -- an operation that could take a while if the region to be "lexed" was a large one.

Alternatively, we could "lex" the region being displayed and not store any lexical information. This is the approach we have taken --because it reduces the complexity of the example and avoids storing a lot of extra information that we do not use in our JavaEditorKit example.

 

Implementation

As noted in the article title "The Swing Text Package," the EditorKit class is the primary mechanism for extending Swing text components. To create an EditorKit, you register it with a JEditorPane object.

When you start building a new editor kit, the first thing you need to do is create an EditorKit implementation -- which, in turn, forms the plug-in for whatever new type of content you want to support.

In the JavaEditorKit example, we create a content type named TEXT/JAVA -- and, as noted previously, we call our EditorKit implementation JavaEditorKit. So JavaEditorKit is the class name that we register with JEditorPane, and is also the class name that we use for all requests that we issue to edit Java text.

The two primary duties of the EditorKit class (beyond the duties provided by the superclass) are to hand out Java programming language documents that model the text with optional lexical information, and to produce views that display those models.

The class that we use to model text is called JavaDocument. It extends the PlainDocument class because it fundamentally deals with plain text. In the JavaEditorKit example, we add to the PlainDocument class the ability to produce lexical tokens for some given range of the document.

To produce lexical information, the JavaEditorKit example simply uses the freely available scanner found in the JDK. To feed the scanner, we implement a simple nested class DocumentInputStream to give the scanner a stream-based access to the content storage of the model. The document also provides a crude mechanism for marking multi-line comment areas so scanning can start at a reasonable location.

This mechanism would not be robust enough for use in a real-world application, but it serves well as an example. The comment marking is performed in the insertUpdate method, which gives the document an opportunity to update its structure after an insertion takes place. Because the insertUpdate method occurs under the protection of a write lock, attributes can be freely modified.

One other fundamental class is used in the JavaEditorKit example: a view object that highlights in color the various tokens used in the Java programming language.

The view object used in JavaEditorKit is an implementation of the View class. The Swing text package provides a PlainView class that can be used to represent a PlainDocument. In the JavaEditorKit example, we extend PlainDocument's rendering methods to take into account the lexical structure of the document.

JavaEditorKit produces views for Swing text using an implementation of the ViewFactory interface. This interface is implemented by the JavaContext class to produce a nested class JavaView. The rendering method uses the scan codes of the lexical tokens to reference into a lookup table for the color to use for the token. Making the JavaView class a nested class ensures it easy access to the style information it will use for it’s rendering operations.

To manage styles conveniently, the JavaContext class extends the StyleContext class, mapping a policy over the set of styles contained. The JavaEditorKit provides a pair of methods to set and get the JavaContext to use for building the views. This capabilities enables the developer to use serialization for saving and restoring the settings, and to have this set of settings shared for all of the views produced by it.

The names of the styles are based upon the categories of lexical tokens and actual lexical token names. This convention allows attributes to be set for different categories of tokens, and allows specific settings for particular tokens. In other words, you can use it to build an independent customization interface to configure the EditorKit. To perform this class properly, one would create a BeanInfo class for the appropriate Bean and name the customization class. Those kinds of operations are beyond the scope of this particular article, but may be examined in more detail in a future article in The Swing Connection.

The JavaContext can also serve other purposes. For example, you can use it to produce a styled document. In that case, you could export the styled views (using style preferences specified in the JavaContext) into other formats such as HTML and RTF.

The Token class represents the various possible token types that can be produced by the lexical process. These are implemented as the set of possible scan codes produced by the freely available scanner in the JDK, along with a couple of additional values. The Token class wraps up set of possible tokens as objects so that additional things can be done with them -- for example, to make the lexical information more useful. This feature makes tokens easier to manage when styles are being set up, and allows them to be used as attributes.

 

Using the JavaEditorKit Example

These are the source files that implement the JavaEditorKit application. Just click on their links to download them:

When you have compiled the application, you can execute it using a helper program named JavaKitTest, which you can download by clicking this link.

The JavaKitTest program creates a JEditorPane component and installs the JavaEditorKit into it. The component is then used to load a file named as an argument to the method EditorKitTest.main(). The example plugs some values into the JavaContext to illustrate color highlighting.

 
/*
 * %W% %E%
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 
*/


import  java.awt.BorderLayout;
import  java.awt.Color;
import  java.awt.Font;
import  java.io.File;
import  java.io.FileReader;

import  javax.swing.JEditorPane;
import  javax.swing.JFrame;
import  javax.swing.JScrollPane;
import  javax.swing.JViewport;
import  javax.swing.text.Style;
import  javax.swing.text.StyleConstants;

/**
 * Simple wrapper around JEditorPane to browse java text
 * using the JavaEditorKit plug-in.
 *
 * java JavaKitTest filename
 
*/

public   class  JavaKitTest  {

    
public static void main(String[] args) {
    
if (args.length != 1{
//        System.err.println("need filename argument");
//        System.exit(1);
        args = new String[]{"test.xml"};
    }

    
try {
        JEditorPane editor 
= new JEditorPane();
        JavaEditorKit kit 
= new JavaEditorKit();
        editor.setEditorKitForContentType(
"text/java", kit);
        editor.setContentType(
"text/java");
        editor.setBackground(Color.white);
        editor.setFont(
new Font("Courier"012));
        editor.setEditable(
true);

        
// PENDING(prinz) This should have a customizer and
        
// be serialized.  This is a bogus initialization.
        JavaContext styles = kit.getStylePreferences();
        Style s;
        s 
= styles.getStyleForScanValue(Token.COMMENT.getScanValue());
        StyleConstants.setForeground(s, 
new Color(102153153));
        s 
= styles.getStyleForScanValue(Token.STRINGVAL.getScanValue());
        StyleConstants.setForeground(s, 
new Color(102153102));
        Color keyword 
= new Color(102102255);
        
for (int code = 70; code <= 130; code++{
        s 
= styles.getStyleForScanValue(code);
        
if (s != null{
            StyleConstants.setForeground(s, keyword);
        }

        }


        File file 
= new File(args[0]);
        editor.read(
new FileReader(file), file);
        JScrollPane scroller 
= new JScrollPane();
        JViewport vp 
= scroller.getViewport();
        vp.add(editor);
        vp.setBackingStoreEnabled(
true);

        JFrame f 
= new JFrame("JavaEditorKit: " + args[0]);
        f.getContentPane().setLayout(
new BorderLayout());
        f.getContentPane().add(
"Center", scroller);
        f.pack();
        f.setSize(
600600);
        f.setVisible(
true);
    }
 catch (Throwable e) {
        e.printStackTrace();
        System.exit(
1);
    }

    }


}

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值