ro.nextreports.designer.ui.sqleditor.syntax.SyntaxDocument.java Source code

Java tutorial

Introduction

Here is the source code for ro.nextreports.designer.ui.sqleditor.syntax.SyntaxDocument.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 ro.nextreports.designer.ui.sqleditor.syntax;

import java.io.CharArrayReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import javax.swing.text.Segment;
import javax.swing.undo.UndoManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Decebal Suiu
 */
public class SyntaxDocument extends PlainDocument {

    private static final Log LOG = LogFactory.getLog(SyntaxDocument.class);

    protected Lexer lexer;
    protected List<Token> tokens;
    protected UndoManager undo = new CompoundUndoManager();

    public SyntaxDocument(Lexer lexer) {
        super();
        putProperty(PlainDocument.tabSizeAttribute, 4); // outside ?!
        this.lexer = lexer;

        // Listen for undo and redo events
        addUndoableEditListener(new UndoableEditListener() {

            public void undoableEditHappened(UndoableEditEvent event) {
                if (event.getEdit().isSignificant()) {
                    undo.addEdit(event.getEdit());
                }
            }

        });
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
        super.insertString(offs, str, a);
        parse();
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        super.remove(offs, len);
        parse();
    }

    @Override
    public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
        super.replace(offset, length, text, attrs);
        parse();
    }

    /**
     * Find the token at a given position.  May return null if no token is
     * found (whitespace skipped) or if the position is out of range.
     * 
     * @param pos
     * @return
     */
    public Token getTokenAt(int pos) {
        if (tokens == null || tokens.isEmpty() || pos > getLength()) {
            return null;
        }

        Token token = null;
        Token tKey = new Token(TokenType.DEFAULT, pos, 1);
        @SuppressWarnings("unchecked")
        int ndx = Collections.binarySearch((List) tokens, tKey);
        if (ndx < 0) {
            // so, start from one before the token where we should be...
            // -1 to get the location, and another -1 to go back..
            ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1);
            Token t = tokens.get(ndx);
            if ((t.start <= pos) && (pos <= (t.start + t.length))) {
                token = t;
            }
        } else {
            token = tokens.get(ndx);
        }

        return token;
    }

    /**
     * Perform an undo action, if possible
     */
    public void doUndo() {
        if (undo.canUndo()) {
            undo.undo();
            parse();
        }
    }

    /**
     * Perform a redo action, if possible.
     */
    public void doRedo() {
        if (undo.canRedo()) {
            undo.redo();
            parse();
        }
    }

    /**
     * This will discard all undoable edits
     */
    public void clearUndos() {
        undo.discardAllEdits();
    }

    /**
     * Return an iterator of tokens between p0 and p1.
     * 
     * @param start
     * @param end
     * @return
     */
    protected Iterator<Token> getTokens(int start, int end) {
        return new TokenIterator(start, end);
    }

    /**
     * Parse the entire document and return list of tokens that do not already
     * exist in the tokens list. There may be overlaps, and replacements, 
     * which we will cleanup later.
     * 
     * @return list of tokens that do not exist in the tokens field 
     */
    private void parse() {
        // if we have no lexer, then we must have no tokens...
        if (lexer == null) {
            tokens = null;
            return;
        }

        List<Token> tokens = new ArrayList<Token>(getLength() / 10);
        long time = System.nanoTime();
        int length = getLength();
        try {
            Segment segment = new Segment();
            getText(0, getLength(), segment);
            CharArrayReader reader = new CharArrayReader(segment.array, segment.offset, segment.count);
            lexer.yyreset(reader);
            Token token;
            while ((token = lexer.yylex()) != null) {
                tokens.add(token);
            }
        } catch (BadLocationException e) {
            LOG.error(e.getMessage(), e);
        } catch (IOException e) {
            // This will not be thrown from the Lexer
            LOG.error(e.getMessage(), e);
        } finally {
            // Benchmarks:
            // Parsed 574038 chars in 81 ms, giving 74584 tokens
            //                System.out.printf("Parsed %d in %d ms, giving %d tokens",
            //                        len, (System.nanoTime() - ts) / 1000000, toks.size());
            if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("Parsed %d in %d ms, giving %d tokens", length,
                        (System.nanoTime() - time) / 1000000, tokens.size()));
            }
            this.tokens = tokens;
        }
    }

    /**
     * This class is used to iterate over tokens between two positions.
     */
    class TokenIterator implements Iterator<Token> {

        int start;
        int end;
        int ndx = 0;

        @SuppressWarnings("unchecked")
        private TokenIterator(int start, int end) {
            this.start = start;
            this.end = end;
            if (tokens != null && !tokens.isEmpty()) {
                Token token = new Token(TokenType.COMMENT, start, end - start);
                ndx = Collections.binarySearch((List) tokens, token);
                // we will probably not find the exact token...
                if (ndx < 0) {
                    // so, start from one before the token where we should be...
                    // -1 to get the location, and another -1 to go back..
                    ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1);
                    Token t = tokens.get(ndx);
                    // if the prev token does not overlap, then advance one
                    if (t.start + t.length <= start) {
                        ndx++;
                    }

                }
            }
        }

        public boolean hasNext() {
            if (tokens == null) {
                return false;
            }
            if (ndx >= tokens.size()) {
                return false;
            }
            Token t = tokens.get(ndx);
            if (t.start >= end) {
                return false;
            }

            return true;
        }

        public Token next() {
            return tokens.get(ndx++);
        }

        public void remove() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

    }

}