LiveParenMatcher.java Source code

Java tutorial

Introduction

Here is the source code for LiveParenMatcher.java

Source

/*
Java Swing, 2nd Edition
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole
ISBN: 0-596-00408-7
Publisher: O'Reilly 
*/
// LiveParenMatcher.java
//Like ParenMatcher but continuously colors as the user edits the document.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import javax.swing.text.Segment;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

public class LiveParenMatcher extends ParenMatcher implements DocumentListener {

    public LiveParenMatcher() {
        super();
        getDocument().addDocumentListener(this);
    }

    public void changedUpdate(DocumentEvent de) {
        // no insertion or deletion, so do nothing
    }

    public void insertUpdate(DocumentEvent de) {
        SwingUtilities.invokeLater(this); // will call run()
    }

    public void removeUpdate(DocumentEvent de) {
        SwingUtilities.invokeLater(this); // will call run()
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("LiveParenMatcher");
        frame.setContentPane(new JScrollPane(new LiveParenMatcher()));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 200);
        frame.setVisible(true);
    }

    // ---- finished example from "The DocumentListener Interface" ----

    // ---- begin example from "The DocumentEvent Interface" ----
    //      (method renamed to insertUpdate_2)

    public void insertUpdate_2(DocumentEvent de) {
        Document doc = de.getDocument();
        int offset = de.getOffset();
        int length = de.getLength();
        String inserted = "";
        try {
            inserted = doc.getText(offset, length);
        } catch (BadLocationException ble) {
        }

        for (int j = 0; j < inserted.length(); j += 1) {
            char ch = inserted.charAt(j);
            if (ch == '(' || ch == '[' || ch == '{' || ch == ')' || ch == ']' || ch == '}') {
                SwingUtilities.invokeLater(this); // will call run()
                return; // no need to check further
            }
        }
    }

    // ---- begin example from "The Segment Class" ----
    //      (method renamed to insertUpdate_3)

    public void insertUpdate_3(DocumentEvent de) {
        Document doc = de.getDocument();
        int offset = de.getOffset();
        int length = de.getLength();
        Segment seg = new Segment();
        try {
            doc.getText(offset, length, seg); // text placed in Segment
        } catch (BadLocationException ble) {
        }

        // iterate through the Segment
        for (char ch = seg.first(); ch != seg.DONE; ch = seg.next())
            if (ch == '(' || ch == '[' || ch == '{' || ch == ')' || ch == ']' || ch == '}') {
                SwingUtilities.invokeLater(this); // will call run()
                return; // no need to check further
            }
    }

    // ---- begin example from "The ElementIterator Class" ----
    //      (method renamed to removeUpdate_2)

    public void removeUpdate_2(DocumentEvent de) {
        // print some debugging information before matching the parens
        ElementIterator iter = new ElementIterator(de.getDocument());

        for (Element elem = iter.first(); elem != null; elem = iter.next()) {
            DocumentEvent.ElementChange change = de.getChange(elem);
            if (change != null) { // null means there was no change in elem
                System.out.println("Element " + elem.getName() + " (depth " + iter.depth()
                        + ") changed its children: " + change.getChildrenRemoved().length + " children removed, "
                        + change.getChildrenAdded().length + " children added.\n");
            }
        }
        SwingUtilities.invokeLater(this); // will call run()
    }

}

class ParenMatcher extends JTextPane implements Runnable {

    public static Color[] matchColor = { Color.blue, Color.magenta, Color.green };

    public static Color badColor = Color.red;

    private AttributeSet[] matchAttrSet;

    private AttributeSet badAttrSet;

    public ParenMatcher() {
        // create an array of AttributeSets from the array of Colors
        StyleContext sc = StyleContext.getDefaultStyleContext();
        badAttrSet = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, badColor);
        matchAttrSet = new AttributeSet[matchColor.length];
        for (int j = 0; j < matchColor.length; j += 1)
            matchAttrSet[j] = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, matchColor[j]);
    }

    // match and color the parens/brackets/braces
    public void run() {
        StyledDocument doc = getStyledDocument();
        String text = "";
        int len = doc.getLength();
        try {
            text = doc.getText(0, len);
        } catch (BadLocationException ble) {
        }
        java.util.Stack stack = new java.util.Stack();
        for (int j = 0; j < text.length(); j += 1) {
            char ch = text.charAt(j);
            if (ch == '(' || ch == '[' || ch == '{') {
                int depth = stack.size();
                stack.push("" + ch + j); // push a String containg the char and
                // the offset
                AttributeSet aset = matchAttrSet[depth % matchAttrSet.length];
                doc.setCharacterAttributes(j, 1, aset, false);
            }
            if (ch == ')' || ch == ']' || ch == '}') {
                String peek = stack.empty() ? "." : (String) stack.peek();
                if (matches(peek.charAt(0), ch)) { // does it match?
                    stack.pop();
                    int depth = stack.size();
                    AttributeSet aset = matchAttrSet[depth % matchAttrSet.length];
                    doc.setCharacterAttributes(j, 1, aset, false);
                } else { // mismatch
                    doc.setCharacterAttributes(j, 1, badAttrSet, false);
                }
            }
        }

        while (!stack.empty()) { // anything left in the stack is a mismatch
            String pop = (String) stack.pop();
            int offset = Integer.parseInt(pop.substring(1));
            doc.setCharacterAttributes(offset, 1, badAttrSet, false);
        }
    }

    // unset the foreground color (if any) whenever the user enters text
    // (if not for this, text entered after a paren would catch the paren's
    // color)
    public void replaceSelection(String content) {
        getInputAttributes().removeAttribute(StyleConstants.Foreground);
        super.replaceSelection(content);
    }

    // return true if 'left' and 'right' are matching parens/brackets/braces
    public static boolean matches(char left, char right) {
        if (left == '(')
            return (right == ')');
        if (left == '[')
            return (right == ']');
        if (left == '{')
            return (right == '}');
        return false;
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("ParenMatcher");

        final ParenMatcher matcher = new ParenMatcher();
        matcher.setText("int fact(int n) {\n" + "  if (n <= 1) return 1;\n" + "  return(n * fact(n-1));\n" + "}\n");
        frame.getContentPane().add(new JScrollPane(matcher), BorderLayout.CENTER);

        JButton matchButton = new JButton("match parens");
        matchButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                matcher.run();
            }
        });
        frame.getContentPane().add(matchButton, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 150);
        frame.setVisible(true);
    }
}