Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright 2003-2007 the original author or authors.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package groovy.ui.text;
 
 import java.util.List;
 import java.util.Map;

Author(s):
Evan "Hippy" Slatis
 
 public class StructuredSyntaxDocumentFilter extends DocumentFilter {
     
     public static final String TAB_REPLACEMENT = "    ";
     
     private static final MLComparator ML_COMPARATOR = new MLComparator();

    
The root of the lexical parsing tree.
 
     protected LexerNode lexer = new LexerNode(true);
     
     // The styled document the filter parses
     protected DefaultStyledDocument styledDocument
     
     // the document buffer and segment
     private Segment segment = new Segment();
     private CharBuffer buffer;
    
    
The position tree of multi-line comments.
  
     protected SortedSet mlTextRunSet = new TreeSet();
     
     // Ensures not adding any regexp with capturing groups
     private static void checkRegexp(String regexp) {
         String checking = regexp.replaceAll("\\\\\\(""X").replaceAll("\\(\\?""X");
         int checked = checking.indexOf('(');
         if (checked > -1) {
             String msg = "Only non-capturing groups allowed:\r\n" +
                          regexp + "\r\n";
             for (int i = 0; i < checkedi++) {
                 msg += " ";
             }
             msg += "^";
             throw new IllegalArgumentException(msg);
         }
     }
    
    
Creates a new instance of StructuredSyntaxDocumentFilter

Parameters:
document the styled document to parse
 
         this. = document;
     }
     
     private int calcBeginParse(int offset) {
         MultiLineRun mlr = getMultiLineRun(offset);
         if (mlr != null) {
             // means we're in middle of mlr, so start at beginning of mlr
             offset = mlr.start();
         }
         else {
             // otherwise, earliest position in line not part of mlr
             offset = .getParagraphElement(offset).getStartOffset();
             mlr = getMultiLineRun(offset);
            offset = mlr == null ? offset : mlr.end() + 1;
        }
        
        return offset;
    }
    
    private int calcEndParse(int offset) {
        MultiLineRun mlr = getMultiLineRun(offset);
        if (mlr != null) {
            // means we're in middle of mlr, so end is at end of mlr
            offset = mlr.end();
        }
        else {
            // otherwise, latest position in line not part of mlr
            offset = .getParagraphElement(offset).getEndOffset();
            mlr = getMultiLineRun(offset);
            offset = mlr == null ? offset : mlr.end();
        }
        
        return offset;
    }
    
    
Create a new LexerNode for adding to root.

Returns:
a new LexerNode
    public LexerNode createLexerNode() {
        return new LexerNode(false);
    }
    
    // given an offset, return the mlr it resides in
    private MultiLineRun getMultiLineRun(int offset) {
        MultiLineRun ml = null;
        if (offset > 0) {
            Integer os = Integer.valueOf(offset);
            SortedSet set = .headSet(os);
            if (!set.isEmpty()) {
                ml = (MultiLineRun)set.last();
                ml = ml.end() >= offset ? ml : null;
            }
        }
        return ml;
    }
    
    
Get the root node for lexing the document. Children can be added such that matching patterns can be further parsed if required.

Returns:
the root lexing node.
    public LexerNode getRootNode() {
        return ;
    }
    
    
Insert a string into the document, and then parse it if the parser has been set.

Parameters:
fb
offset
text
attrs
Throws:
javax.swing.text.BadLocationException
    
    public void insertString(DocumentFilter.FilterBypass fbint offset,
                             String textAttributeSet attrs)
        throws BadLocationException {
        // remove problem meta characters returns
        text = replaceMetaCharacters(text);
        
        fb.insertString(offsettextattrs);
        
        // start on the string that was inserted
        parseDocument(offsettext.length());
    }
    
    
Parse the Document to update the character styles given an initial start position. Called by the filter after it has updated the text.

Parameters:
offset
length
Throws:
javax.swing.text.BadLocationException
    protected void parseDocument(int offsetint lengththrows BadLocationException {
        // intialize the segment with the complete document so the segment doesn't
        // have an underlying gap in the buffer
        
         = CharBuffer.wrap(.).asReadOnlyBuffer();
        
        // initialize the lexer if necessary
        if (!.isInitialized()) {
            // prime the parser and reparse whole document
            .initialize();
            offset = 0;
            length = .getLength();
        }
        else {
            int end = offset + length;
            offset = calcBeginParse(offset);
            length = calcEndParse(end) - offset;
            
            // clean the tree by ensuring multi line styles are reset in area
            // of parsing
            SortedSet set = .subSet(Integer.valueOf(offset),
                                                Integer.valueOf(offset + length));
            if (set != null) {
                set.clear();
            }
        }
        
        // parse the document
        .parse(offsetlength);
    }

    
Remove a string from the document, and then parse it if the parser has been set.

Parameters:
fb
offset
length
Throws:
javax.swing.text.BadLocationException
    
    public void remove(DocumentFilter.FilterBypass fbint offsetint length)
        throws BadLocationException {
        // FRICKIN' HACK!!!!! For some reason, deleting a string at offset 0
        // does not get done properly, so first replace and remove after parsing
        if (offset == 0 && length != fb.getDocument().getLength()) {
            fb.replace(0, length"\n".);
            
            // start on either side of the removed text
            parseDocument(offset, 2);
            fb.remove(offset, 1);
        }
        else {
            fb.remove(offsetlength);
            
            // start on either side of the removed text
            if (offset + 1 < fb.getDocument().getLength()) {
                parseDocument(offset, 1);
            }
            else if (offset - 1 > 0) {
                parseDocument(offset - 1, 1);
            }
            else {
                // empty text
                .clear();
            }
        }
    }

    
Replace a string in the document, and then parse it if the parser has been set.

Parameters:
fb
offset
length
text
attrs
Throws:
javax.swing.text.BadLocationException
    
    public void replace(DocumentFilter.FilterBypass fbint offset
                        int lengthString textAttributeSet attrs)
        throws BadLocationException {
        // remove problem meta characters returns
        text = replaceMetaCharacters(text);
        
        fb.replace(offsetlengthtextattrs);
        
        // start on the text that was replaced
        parseDocument(offsettext.length());
    }
    
    // tabs with spaces (I hate tabs)
    private String replaceMetaCharacters(String string) {
        // just in case remove carriage returns
        string = string.replaceAll("\\t");
        return string;
    }
    
    public final class LexerNode {
        
        private Style defaultStyle;
    
        private Map styleMap = new LinkedHashMap();
        private Map children = new HashMap();
        private Matcher matcher;
        private List groupList = new ArrayList();
        
        private boolean initialized;
        
        private CharBuffer lastBuffer;
        /*
         * Creates a new instance of LexerNode 
         */
        LexerNode(boolean isParent) {
            StyleContext sc = StyleContext.getDefaultStyleContext();
             = sc.getStyle(.);
        }
    
        private String buildRegexp(String[] regexps) {
            String regexp = "";
            for (int i = 0; i < regexps.lengthi++) {
                regexp += "|" + regexps[i];
            }
            // ensure leading '|' is removed
            return regexp.substring(1);
        }
        
        public Style getDefaultStyle() {
            return ;
        }
        private void initialize() {
             = null;
            .clear();
            .add(null);
            
            Iterator iter = .keySet().iterator();
            String regexp = "";
            while (iter.hasNext()) {
                String nextRegexp = (String)iter.next();
                regexp += "|(" + nextRegexp + ")";
                // have to compile regexp first so that it will match
                .add(Pattern.compile(nextRegexp).pattern());
            }
            if (!regexp.equals("")) {
                 = Pattern.compile(regexp.substring(1)).matcher("");
                
                iter = .values().iterator();
                while (iter.hasNext()) {
                    ((LexerNode)iter.next()).initialize();
                }
            }
             = true;
        }
        
        

Returns:
true if initialised
        
        public boolean isInitialized() {
            return ;
        }

        

Parameters:
buffer
offset
length
Throws:
javax.swing.text.BadLocationException
        
        public void parse(CharBuffer bufferint offsetint length)
            throws BadLocationException {
            // get the index of where we can start to look for an exit:
            // i.e. after the end of the length of the segment, when we find 
            // that text in question already is set properly, we can stop
            // parsing
            int checkPoint = offset + length;
            
            // reset the matcher and start parsing string
            if ( != buffer) {
                .reset(buffer);
                 = buffer;
            }
            
            // the start and end indices of a match in the Matcher looking
            int matchEnd = offset;
            Style style = null;
            while (matchEnd < checkPoint && .find(offset)) {
                // when we get something other than -1, we know which regexp
                // matched; the 0 group is the complete expression of the 
                // matcher, which would always return a hit based on the above
                // while condition
                int groupNum = 0;
                while ((offset = .start(++groupNum)) == -1){
                }
                
                // if the matching offset is not the same as the end of the 
                // previous match, we have extra text not matched, so set to 
                // the default style of this lexer node
                if (offset != matchEnd) {
                    offset = offset > checkPoint ? checkPoint : offset
                    .setCharacterAttributes(matchEnd,
                                                          offset - matchEnd,
                                                          ,
                                                          true);
                    if (offset >= checkPoint) {
                        return;
                    }
                }
                // track the end of the matching string 
                matchEnd = .end(groupNum);
                // retrieve the proper style from groupNum of the groupList and
                // styleMap, then set the attributes of the matching string
                style = (Style).get((String).get(groupNum));
                .setCharacterAttributes(offset,
                                                      matchEnd - offset,
                                                      styletrue);
                // if the match was multiline, which we'll know if they span
                // multiple paragraph elements, the mark it (this list was cleaned
                // above in parseDocument())
                if (.getParagraphElement(offset).getStartOffset() !=
                    .getParagraphElement(matchEnd).getStartOffset()) {
                    // mark a ml run
                    MultiLineRun mlr = new MultiLineRun(offsetmatchEnd);
                    .add(mlr);
                }
                
                // parse the child regexps, if any, within a matched block
                LexerNode node = (LexerNode).get(.get(groupNum));
                if (node != null) {
                    node.parse(bufferoffsetmatchEnd - offset);
                }
                
                // set the offset to start where we left off
                offset = matchEnd;
            }
            if (matchEnd < checkPoint) {
                // if we finished before hitting the end of the checkpoint from
                // no mroe matches, then set ensure the text is reset to the
                // defaultStyle
                .setCharacterAttributes(matchEnd,
                                                      checkPoint - matchEnd,
                                                      ,
                                                      true);
            }
        }

        

Parameters:
regexp
node
        
        public void putChild(String regexpLexerNode node) {
            node.defaultStyle = (Style).get(regexp);
            
            // have to compile regexp first so that it will match
            .put(Pattern.compile(regexp).pattern(), node);
             = false;
        }

        

Parameters:
regexps
node
        
        public void putChild(String[] regexpsLexerNode node) {
            putChild(buildRegexp(regexps), node);
        }

        

Parameters:
regexp
style
        
        public void putStyle(String regexpStyle style) {
            checkRegexp(regexp);
            .put(regexpstyle);
             = false;
        }

        

Parameters:
regexps
style
        
        public void putStyle(String regexps[], Style style) {
            putStyle(buildRegexp(regexps), style);
        }

        

Parameters:
regexp
        
        public void removeChild(String regexp) {
            .remove(regexp);
        }

        

Parameters:
regexp
        
        public void removeStyle(String regexp) {
            .remove(regexp);
            .remove(regexp);
        }

        

Parameters:
regexps
        
        public void removeStyle(String regexps[]) {
            removeStyle(buildRegexp(regexps));
        }
        
        public void setDefaultStyle(Style style) {
             = style;
        }
    }
    
    protected class MultiLineRun {
        
        private Position start;
        private Position end;
        private int delimeterSize;
        
        public MultiLineRun(int startint endthrows BadLocationException {
            this(startend, 2);
        }
        
        public MultiLineRun(int startint endint delimeterSizethrows BadLocationException {
            if (start > end) {
                String msg = "Start offset is after end: ";
                throw new BadLocationException(msgstart);
            }
            if (delimeterSize < 1) {
                String msg = "Delimiters be at least size 1: " + 
                              delimeterSize;
                throw new IllegalArgumentException(msg);
            }
            this. = .createPosition(start);
            this. = .createPosition(end);
            this. = delimeterSize;
        }
        
        public int getDelimeterSize() {
            return ;
        }
        
        public int end() {
            return .getOffset();
        }
        
        public int length() {
            return .getOffset() - .getOffset();
        }
        
        public int start() {
            return .getOffset();
        }
        
        public String toString() {
            return .toString() + " " + .toString();
        }
        
    }
    private static class MLComparator implements Comparator {
        
        public int compare(Object objObject obj1) {
            return valueOf(obj) - valueOf(obj1);
        }
        
        private int valueOf(Object obj) {
            return obj instanceof Integer ? 
                    ((Integer)obj).intValue() : 
                    (obj instanceof MultiLineRun) ?
                        ((MultiLineRun)obj).start() :
                        ((Position)obj).getOffset();
        }
    }
New to GrepCode? Check out our FAQ X