[Top ページ] [他のサンプル]

RegexTextEditor.java


import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
 
import javax.swing.JTextPane;
import javax.swing.SizeRequirements;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.ParagraphView;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.undo.CompoundEdit;
 
@SuppressWarnings("serial")
public class RegexTextEditor extends JTextPane {
	private DummyAttributeSet inputAttributes_ = new DummyAttributeSet();
	
	public RegexTextEditor() {
		setEditorKit(new NoWrapEditorKit());
		setDocument(new RegexDocument());
		setFont(new Font("Monospaced", Font.PLAIN, 12));
	}
	
	/**
	 * 正規表現の強調表示をおこなうかどうかを設定します。
	 */
	public void setRegexMode(boolean flag) {
		((RegexDocument)getDocument()).setRegexMode(flag);
	}
	
	/**
	 * 正規表現の強調表示をおこなうかどうかを取得します。
	 */
	public boolean isRegexMode() {
		return ((RegexDocument)getDocument()).isRegexMode();
	}
	
	/**
	 * 使用するフォントの種類と大きさを指定します。
	 * (他の属性は使用されません)
	 */
	public void setFont(Font font) {
		StyledDocument doc = getStyledDocument();
		if (doc instanceof RegexDocument) {
			SimpleAttributeSet attribSet = new SimpleAttributeSet();
			attribSet.addAttribute(StyleConstants.FontFamily, font.getFamily());
			attribSet.addAttribute(StyleConstants.FontSize, font.getSize());
			((RegexDocument)doc).setBaseAttribute(attribSet);
		}
	}
	
	@Override
	public MutableAttributeSet getInputAttributes() {
		return inputAttributes_;
	}
	
	/**
	 * 折り返ししないテキストボックスのための EditorKit
	 */
	private static class NoWrapEditorKit extends StyledEditorKit {
		public ViewFactory getViewFactory() {
			return new NoWrapViewFactory();
		}
		
		private static class NoWrapParagraphView extends ParagraphView {
			public NoWrapParagraphView(Element elem) {
				super(elem);
			}
			protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
				SizeRequirements req = super.calculateMinorAxisRequirements(axis, r);
				req.minimum = req.preferred;
				return req;
			}
			public int getFlowSpan(int index) {
				return Integer.MAX_VALUE;
			}
		}
		
		private static class NoWrapViewFactory implements ViewFactory {
			public View create(Element elem) {
				String kind = elem.getName();
				if(kind != null) {
					if (kind.equals(AbstractDocument.ContentElementName)) {
						return new LabelView(elem);
					} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
						return new NoWrapParagraphView(elem);
					} else if (kind.equals(AbstractDocument.SectionElementName)) {
						return new BoxView(elem, View.Y_AXIS);
					} else if (kind.equals(StyleConstants.ComponentElementName)) {
						return new ComponentView(elem);
					} else if(kind.equals(StyleConstants.IconElementName)) {
						return new IconView(elem);
					}
				}
				return new LabelView(elem);
			}
		}
	}
	
	/**
	 * 正規表現の強調表示を自動でおこなう Document
	 */
	private static class RegexDocument extends DefaultStyledDocument implements UndoableEditListener {
		private ArrayList undoableEditListeners_ = new ArrayList();
		private boolean regexMode_ = true;
		private boolean isUpdateRequested_ = false;
		private CompoundEdit compoundEdit_ = null;
		private enum TokenKind {
			Normal,
			Repeat,			// +, *, {n,m}
			RepeatAttrib,	// ?, +
			CharClass,		// \p
			BlockStart,		// (
			Or,				// |
			None,
		};
		
		public RegexDocument() {
			// Undo 対策
			super.addUndoableEditListener(this);

			addDocumentListener(new DocumentListener() {
				public void removeUpdate(DocumentEvent e) {
					requestUpdateAttribute();
				}
				public void insertUpdate(DocumentEvent e) {
					requestUpdateAttribute();
				}
				public void changedUpdate(DocumentEvent e) {}
			});
		}
		
		public void setBaseAttribute(AttributeSet attribSet) {
			normalAttributeSet_ = attribSet;
		}
		
		public void setRegexMode(boolean flag) {
			if (flag == regexMode_) { return; }
			regexMode_ = flag;
			requestUpdateAttribute();
		}
		
		public boolean isRegexMode() { return regexMode_; }

		@Override
		public void addUndoableEditListener(UndoableEditListener listener) {
			undoableEditListeners_.add(listener);
		}
		
		@Override
		public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
			if (str.indexOf('\n') >= 0) { str = str.replace("\n", ""); }
			super.insertString(offs, str, a);
		}
		
		@Override
		public void remove(int offs, int len) throws BadLocationException {
			super.remove(offs, len);
		}
		
		private void requestUpdateAttribute() {
			if (isUpdateRequested_) { return; }
			
			isUpdateRequested_ = true;
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					updateAttribute();
					isUpdateRequested_ = false;
				}
			});
		}
		
		private void updateAttribute() {
			// かな漢字変換で確定前の状態かどうかのチェック
			Element paragraphElem = getParagraphElement(0);
			int elemCount = paragraphElem.getElementCount();
			boolean isExistComposedAttrib = false;
			for (int i = 0; i < elemCount; i++) {
				Element elem = paragraphElem.getElement(i);
				AttributeSet attribSet = elem.getAttributes();
				if (attribSet == null) { continue; }
				Object attrib = attribSet.getAttribute(StyleConstants.ComposedTextAttribute);
				if (attrib != null) {
					SimpleAttributeSet newAttribSet = new SimpleAttributeSet();
					newAttribSet.addAttribute(StyleConstants.ComposedTextAttribute, attrib);
					setCharacterAttributes(elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset(), newAttribSet, true);
					isExistComposedAttrib = true;
				}
			}
			if (isExistComposedAttrib) { return; } // かな漢字変換で確定前の状態であれば何もしない
			
			// 文字の属性を変更は Undo時に一括でUndoされるようにする。
			compoundEdit_ = new CompoundEdit() {
				@Override
				public boolean isSignificant() { return false; }
			};
			
			try {
				int length = getLength();
				String str = getText(0, length);
				setCharacterAttributes(0, length, normalAttributeSet_, true);
				if (!regexMode_) {
					return;
				}
				
				boolean quoted         = false;
				boolean escaped        = false;    // '\' でエスケープされた状態
				int charRangeLevel     = 0;        // [] の中の場合の何階層目にいるか
				int charRangeState     = 0;        // [] 内の状態 (0:[]の先頭, 1:[]の先頭'^'の直後, 2:その他)
				int nestLevel = 0;
				TokenKind prevToken = TokenKind.None;
				String specialChars = "*+.|?{}$^";
				
				for (int i = 0; i < length; i++) {
					char c = str.charAt(i);
					char nextC = i < length - 1 ? str.charAt(i + 1) : 0;
					
					if (quoted) {
						if (c == '\\' && nextC == 'E') {
							quoted = false;
							setCharacterAttributes(i, 2, escapeAttributeSet_, false);
							prevToken = TokenKind.Normal;
							i++;
						}
						continue;
					}
					
					// '\' の直後
					if (escaped) {
						escaped = false;
						if (c == 'Q') { // \Q
							setCharacterAttributes(i - 1, 2, escapeAttributeSet_, false);
							quoted = true;
							continue;
						}
						if ("dDsSwW".indexOf(c) >= 0) { // \d, \D, \S, \s, \w, \W
							setCharacterAttributes(i - 1, 2, specialCharAttributeSet_, false);
						}
						c = '\\';
					} else if (c == '\\') {
						escaped = true;
					}
					
					// ■ [] の中の場合の処理...
					if (charRangeLevel > 0) {
						if (charRangeState == 0) {
							charRangeState = 2;
							if (c == '^') {
								setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
								charRangeState = 1;
							}
						} else if (charRangeState == 1) {
							charRangeState = 2;
						} else if (c == ']') {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							charRangeLevel--;
							if (charRangeLevel == 0) {
								nestLevel--;
							}
						}
						if (c == '&' && str.charAt(i - 1) == '&' && nextC != '&' && (i < 2 || str.charAt(i - 2) != '&')) {
							setCharacterAttributes(i - 1, 2, specialCharAttributeSet_, false);
						}
						if (c == '[') {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							charRangeLevel++;
							charRangeState = 0;
						}
						
						if (charRangeLevel > 0) {
							setCharacterAttributes(i, 1, rangeAttributeSet_, false);
						}
						prevToken = TokenKind.Normal;
						continue;
					}
					
					// ■ [] の中でない場合の処理...
					TokenKind tokenKind = TokenKind.Normal;
					if (c == '*') {
						if (prevToken == TokenKind.Normal) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.Repeat;
						} else {
							setCharacterAttributes(i, 1, errorAttributeSet_, false);
						}
					} else if (c == '+') {
						if (prevToken == TokenKind.Normal) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.Repeat;
						} else if (prevToken == TokenKind.Repeat) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.RepeatAttrib;
						} else {
							setCharacterAttributes(i, 1, errorAttributeSet_, false);
						}
					} else if (c == '?') {
						if (prevToken == TokenKind.Normal) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.Repeat;
						} else if (prevToken == TokenKind.Repeat) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.RepeatAttrib;
						} else if (prevToken == TokenKind.BlockStart) {
							setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
							tokenKind = TokenKind.Normal; // ***
						} else {
							setCharacterAttributes(i, 1, errorAttributeSet_, false);
						}
					} else if (c == '|') {
						setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
						tokenKind = TokenKind.Or;
					} else if (specialChars.indexOf(c) >= 0) {
						setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
					} else if (c == '(') {
						setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
						nestLevel++;
						tokenKind = TokenKind.BlockStart;
					} else if (c == '[') {
						setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
						charRangeState = 0;
						charRangeLevel++;
						nestLevel++;
					} else if (c == ')') {
						setCharacterAttributes(i, 1, specialCharAttributeSet_, false);
						if (nestLevel > 0) {
							nestLevel--;
						} else {
							setCharacterAttributes(i, 1, errorAttributeSet_, false);
						}
						tokenKind = TokenKind.Normal;
					}
					
					prevToken = tokenKind;
				}
			} catch(BadLocationException e) {
				e.printStackTrace();
			} finally {
				compoundEdit_.end();
				UndoableEditEvent editEvent = new UndoableEditEvent(this, compoundEdit_);
				compoundEdit_ = null;
				undoableEditHappened(editEvent);
			}
		}
		
		private AttributeSet normalAttributeSet_ = new SimpleAttributeSet();
		private static SimpleAttributeSet specialCharAttributeSet_ = new SimpleAttributeSet();
		private static SimpleAttributeSet rangeAttributeSet_ = new SimpleAttributeSet();
		private static SimpleAttributeSet escapeAttributeSet_ = new SimpleAttributeSet();
		private static SimpleAttributeSet errorAttributeSet_ = new SimpleAttributeSet();
		static {
			specialCharAttributeSet_.addAttribute(StyleConstants.Foreground, new Color(0, 128, 0));
			specialCharAttributeSet_.addAttribute(StyleConstants.Bold, true);
			
			rangeAttributeSet_.addAttribute(StyleConstants.Background, new Color(255, 255, 220));
			
			escapeAttributeSet_.addAttribute(StyleConstants.Foreground, new Color(0, 0, 255));
			
			errorAttributeSet_.addAttribute(StyleConstants.Foreground, Color.RED);
		}
		
		public void undoableEditHappened(UndoableEditEvent e) {
			if (compoundEdit_ == null) {
				for (UndoableEditListener listener : undoableEditListeners_) {
					listener.undoableEditHappened(e);
				}
			} else {
				compoundEdit_.addEdit(e.getEdit());
			}
		}
	}
	
	private static class DummyAttributeSet extends SimpleAttributeSet {
		@Override
		public void addAttribute(Object name, Object value) {}
		@Override
		public void addAttributes(AttributeSet attributeSet) {}
	}

}