Java tutorial
/* 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); } }