HighlightedDocument.java

/*
 * This file is part of the programmer editor demo
 * Copyright (C) 2001-2005 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Syntax+Highlighting
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * See COPYING.TXT for details.
 */
package com.jsql.view.swing.sql.lexer;

import com.jsql.util.LogLevelUtil;
import com.jsql.view.swing.sql.lexer.syntax.JavaScriptLexer;
import com.jsql.view.swing.sql.lexer.syntax.Lexer;
import com.jsql.view.swing.sql.lexer.syntax.SQLLexer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * A <a href="http://ostermiller.org/syntax/editor.html">demonstration text
 * editor</a> that uses syntax highlighting.
 */
public class HighlightedDocument extends DefaultStyledDocument {
    
    /**
     * Log4j logger sent to view.
     */
    private static final Logger LOGGER = LogManager.getRootLogger();
    
    public static final Object SQL_STYLE = SQLLexer.class;
    public static final Object JAVASCRIPT_STYLE = JavaScriptLexer.class;
    
    public static final Object GRAYED_OUT_STYLE = new Object();

    /**
     * A reader wrapped around the document so that the document can be fed into
     * the lexer.
     */
    private final transient DocumentReader documentReader;
    
    /** If non-null, all is drawn with this style (no lexing). */
    private transient AttributeSet globalStyle = null;

    /**
     * The lexer that tells us what colors different words should be.
     */
    private transient Lexer syntaxLexer;

    /**
     * A thread that handles the actual coloring.
     */
    private final transient Colorer colorer;

    /**
     * A lock for modifying the document, or for actions that depend on the
     * document not being modified.
     */
    private final transient Object docLock = new Object();

    /**
     * Create a new Demo
     */
    public HighlightedDocument(Object l) {

        // Start the thread that does the coloring
        this.colorer = new Colorer(this);
        this.colorer.start();

        // create the new document.
        this.documentReader = new DocumentReader(this);
        
        if (l == SQL_STYLE) {
            this.syntaxLexer = new SQLLexer(this.documentReader);
        } else {
            this.syntaxLexer = new JavaScriptLexer(this.documentReader);
        }
    }

    /**
     * Color or recolor the entire document
     */
    public void colorAll() {
        this.color(0, this.getLength());
    }

    /**
     * Color a section of the document. The actual coloring will start somewhere
     * before the requested position and continue as long as needed.
     * 
     * @param position
     *            the starting point for the coloring.
     * @param adjustment
     *            amount of text inserted or removed at the starting point.
     */
    public void color(int position, int adjustment) {
        this.colorer.color(position, adjustment);
    }
    
    public void setGlobalStyle(AttributeSet value) {
        
        this.globalStyle = value;
        this.colorAll();
    }

    public void setHighlightStyle(Object valueSource) {
        
        Object value = valueSource;
        
        if (value == HighlightedDocument.GRAYED_OUT_STYLE) {
            
            this.setGlobalStyle(TokenStyles.getStyle("grayedOut"));
            return;
        }

        if (!(value instanceof Class)) {
            value = HighlightedDocument.SQL_STYLE;
        }
        
        Class<?> source = (Class<?>) value;
        Class<?>[] parms = { Reader.class };
        Object[] args = { this.documentReader };
        
        try {
            Constructor<?> cons = source.getConstructor(parms);
            this.syntaxLexer = (Lexer) cons.newInstance(args);
            this.globalStyle = null;
            this.colorAll();
        } catch (
            SecurityException
            | NoSuchMethodException
            | InstantiationException
            | IllegalAccessException
            | IllegalArgumentException
            | InvocationTargetException e
        ) {
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
        }
    }
    
    //
    // Intercept inserts and removes to color them.
    //
    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
        
        synchronized (this.docLock) {
            
            super.insertString(offs, str, a);
            this.color(offs, str.length());
            this.documentReader.update(offs, str.length());
        }
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        
        synchronized (this.docLock) {
            
            super.remove(offs, len);
            this.color(offs, -len);
            this.documentReader.update(offs, -len);
        }
    }

    // methods for Colorer to retrieve information
    DocumentReader getDocumentReader() { return this.documentReader; }
    Object getDocumentLock() { return this.docLock; }
    Lexer getSyntaxLexer() { return this.syntaxLexer; }
    AttributeSet getGlobalStyle() { return this.globalStyle; }

    /**
     * Deactivate the colorer to end the backend thread.
     */
    public void stopColorer() {
        this.colorer.stopThread();
    }
}