/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.framework.uitools; // JDK import java.util.Hashtable; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.PlainDocument; /** * This class allows the document to use a regular expression. With the regular * expression, only specific string will be inserted or deleted. Plain text area * and text field can use this document to store the text. This document also * has a back pointer to the owner. * * @version 10.0.3 * @author Pascal Filion */ public class RegexpDocument extends PlainDocument { /** * The regular expression used to check the insertion or deletion of string. */ private Pattern regularExpression; /** * Caches the created <code>Pattern</code>s for a regular expressions. */ private static final Hashtable cachedRegularExpressions = new Hashtable(); /** Regular expression defined for class name without the package. */ public static final int RE_CLASS = 2; /** Regular expression defined for field name. */ public static final int RE_FIELD = 6; /** Regular expression defined for class with package name. */ public static final int RE_FULLY_QUALIFIED_CLASS_NAME = 3; /** Regular expression defined for interface name. */ public static final int RE_INTERFACE = 4; /** Regular expression defined for method name. */ public static final int RE_METHOD = 5; /** If no regular expression is needed for this document. */ public static final int RE_NONE = 0; /** Regular expression defined for integer [-infinite.0, +infinite.0]. */ public static final int RE_NUMERIC_DECIMAL = 10; /** Regular expression defined for integer [-infinite.0, -1.0]. */ public static final int RE_NUMERIC_DECIMAL_NEGATIVE = 12; /** Regular expression defined for integer [0.0, +infinite.0]. */ public static final int RE_NUMERIC_DECIMAL_POSITIVE = 11; /** Regular expression defined for integer [0.0, +infinite.0]. */ public static final int RE_NUMERIC_DECIMAL_STRICT = 15; /** Regular expression defined for integer [-infinite, +infinite]. */ public static final int RE_NUMERIC_INTEGER = 7; /** Regular expression defined for integer [-infinite, -1]. */ public static final int RE_NUMERIC_INTEGER_NEGATIVE = 9; /** Regular expression defined for integer [0, +infinite]. */ public static final int RE_NUMERIC_INTEGER_POSITIVE = 8; /** Regular expression defined for integer [0, +infinite]. */ public static final int RE_NUMERIC_INTEGER_STRICT = 16; /** Regular expression defined by the user. */ public static final int RE_OTHER = 14; /** Regular expression defined for package. */ public static final int RE_PACKAGE = 1; /** Regular expression defined for SQL related string. */ public static final int RE_SQL_RELATED = 13; /** * Regular expression that accepts only short class name. A class name is a * string begining with a letter or with '_' and followed by zero or more * alphanumeric character (including '_'). For inner classes, it is possible * to use '$'. If '.' needs to be used, then <code>RE_SYNTAX_FULLY_QUALIFIED_CLASS_NAME</code> * will have to be used instead. */ public static final String RE_SYNTAX_CLASS = "([\\p{L}_][\\p{L}\\p{N}_]*(\\$[\\p{L}_][\\p{L}\\p{N}_]+)*(\\$\\p{N}+)?(\\$\\z)?)*"; /** * Regular expression that accepts only package name and fully qualified * class name. A package name is an identifier that can be followed zero or * more identifier separator by '.'. For inner classes, it is possible to use * '$'. */ public static final String RE_SYNTAX_FULLY_QUALIFIED_CLASS_NAME = "([\\p{L}_][\\p{L}\\p{N}_]*(\\.[\\p{L}_][\\p{L}\\p{N}_]*)*(\\$[\\p{L}_][\\p{L}\\p{N}_]+)*(\\$\\p{N}+)?((\\.\\z)|(\\$\\z))?)*"; /** * Regular expression that accepts only identifier. An identifier is a string * begining with a letter or '_' and followed by zero or more alphanumeric * character (including '_'). */ public static final String RE_SYNTAX_IDENTIFIER = "([\\p{L}_][\\p{L}\\p{N}_]*)*"; /** * Regular expression that accepts any type of expression. */ public static final String RE_SYNTAX_NONE = ".*"; /** * Regular expression that accepts only positive decimal value. An decimal * value is a positive or negative number that can have a decimal part. */ public static final String RE_SYNTAX_NUMERIC_DECIMAL = "[\\+-]?\\d*\\.?\\d*"; /** * Regular expression that accepts only negative decimal value. */ public static final String RE_SYNTAX_NUMERIC_DECIMAL_NEGATIVE = "-?\\d*\\.?\\d*"; /** * Regular expression that accepts only positive decimal value. */ public static final String RE_SYNTAX_NUMERIC_DECIMAL_POSITIVE = "\\+?\\d*\\.?\\d*"; /** * Regular expression that accepts only positive decimal value. An decimal * value is a positive or negative number that can have a decimal part. */ public static final String RE_SYNTAX_NUMERIC_DECIMAL_STRICT = "\\d*\\.?\\d*"; /** * Regular expression that accepts only integer value. An integer value is a * positive or negative number that does not have a decimal part. */ public static final String RE_SYNTAX_NUMERIC_INTEGER = "[\\+-]?\\d*"; /** * Regular expression that accepts only negative integer value. */ public static final String RE_SYNTAX_NUMERIC_INTEGER_NEGATIVE = "-?\\d*"; /** * Regular expression that accepts only positive integer value. */ public static final String RE_SYNTAX_NUMERIC_INTEGER_POSITIVE = "\\+?\\d*"; /** * Regular expression that accepts only integer value without + or -. */ public static final String RE_SYNTAX_NUMERIC_INTEGER_STRICT = "\\d*"; /** * Regular expression that accepts only package name and fully qualified * class name. A package name is an identifier that can be followed zero or * more identifier separator by '.'. */ public static final String RE_SYNTAX_PACKAGE = "([\\p{L}_][\\p{L}\\p{N}_]*(\\.[\\p{L}_][\\p{L}\\p{N}_]*)*(\\.\\z)?)*"; /** * Creates a new <code>RegexpDocument</code> with no regular expression. */ public RegexpDocument() { this(RE_NONE); } /** * Creates a new <code>RegexpDocument</code> and use a predefined regular * expression. * * @param regularExpressionType One of the predefined regular expression type */ public RegexpDocument(int regularExpressionType) { this(buildRegularExpression(regularExpressionType)); } /** * Creates a new <code>RegexpDocument</code> and set the regular expression. * * @param regularExpression The regular expression use to parse the text */ public RegexpDocument(Pattern regularExpression) { super(); initialize(regularExpression); } /** * Creates a new <code>RegexpDocument</code> and set the regular expression. * * @param regularExpression The regular expression use to parse the text */ public RegexpDocument(String regularExpression) { this(buildRegularExpression(regularExpression)); } /** * Creates a new <code>RegexpDocument</code> and use a predefined regular * expression. * * @param regularExpressionType One of the predefined regular expression type * @return A new <code>RegexpDocument</code> */ public static Document buildDocument(int regularExpressionType) { return new RegexpDocument(regularExpressionType); } /** * Creates a new <code>RegexpDocument</code> and set the regular expression. * * @param regularExpression The regular expression use to parse the text * @return A new <code>RegexpDocument</code> */ public static Document buildDocument(Pattern regularExpression) { return new RegexpDocument(regularExpression); } /** * Creates a new <code>RegexpDocument</code> and set the regular expression. * * @param regularExpression The regular expression use to parse the text * @return A new <code>RegexpDocument</code> */ public static Document buildDocument(String regularExpression) { return new RegexpDocument(regularExpression); } /** * Returns the predefined regular expression associated with the * regular expression type. If the given value is not a predefined * type, then the regular expression returned is RE_SYNTAX_NONE. * If an exception is thrown when creating the regular expression, * <code>null</code> is returned. * * @param expressionExpressionType A predefined type of regular expression * @return The regular expression object that represents the regular * expression type */ public static Pattern buildRegularExpression(int expressionExpressionType) { switch (expressionExpressionType) { case RE_CLASS: return buildRegularExpression(RE_SYNTAX_CLASS); case RE_FULLY_QUALIFIED_CLASS_NAME: return buildRegularExpression(RE_SYNTAX_FULLY_QUALIFIED_CLASS_NAME); case RE_FIELD: case RE_INTERFACE: case RE_METHOD: case RE_SQL_RELATED: return buildRegularExpression(RE_SYNTAX_IDENTIFIER); case RE_NUMERIC_INTEGER: return buildRegularExpression(RE_SYNTAX_NUMERIC_INTEGER); case RE_NUMERIC_INTEGER_NEGATIVE: return buildRegularExpression(RE_SYNTAX_NUMERIC_INTEGER_NEGATIVE); case RE_NUMERIC_INTEGER_POSITIVE: return buildRegularExpression(RE_SYNTAX_NUMERIC_INTEGER_POSITIVE); case RE_NUMERIC_DECIMAL: return buildRegularExpression(RE_SYNTAX_NUMERIC_DECIMAL); case RE_NUMERIC_DECIMAL_NEGATIVE: return buildRegularExpression(RE_SYNTAX_NUMERIC_DECIMAL_NEGATIVE); case RE_NUMERIC_DECIMAL_POSITIVE: return buildRegularExpression(RE_SYNTAX_NUMERIC_DECIMAL_POSITIVE); case RE_PACKAGE: return buildRegularExpression(RE_SYNTAX_PACKAGE); default: return null; } } /** * Creates the compiled <code>Pattern</code> for the given regular expression. * * @param regularExpression The pattern used to create a new regular expression */ public static Pattern buildRegularExpression(String regularExpression) { return buildRegularExpression(regularExpression, Pattern.UNICODE_CASE); } /** * Creates the compiled <code>Pattern</code> for the given regular expression. * * @param regularExpression The pattern used to create a new regular expression * @param flags The flags to be used with the regular expression */ public static Pattern buildRegularExpression(String regularExpression, int flags) { Pattern pattern = (Pattern) cachedRegularExpressions.get(regularExpression); if (pattern == null) { pattern = Pattern.compile(regularExpression, flags); cachedRegularExpressions.put(regularExpression, pattern); } return pattern; } /** * Returns the regular expression used for this document. * * @return The regular expression used for this document * @see #setRegularExpression */ protected Pattern getRegularExpression() { return regularExpression; } /** * Initializes this <code>RegexpDocument</code>. * * @param regularExpression The regular expression use to parse the text or * <code>null</code> if anything is accepted */ protected void initialize(Pattern regularExpression) { this.regularExpression = regularExpression; } /** * Inserts a string of content. This will cause a DocumentEvent of type * DocumentEvent.EventType.INSERT to be sent to the registered * DocumentListers, unless an exception is thrown. The DocumentEvent will be * delivered by calling the insertUpdate method on the DocumentListener. The * offset and length of the generated DocumentEvent will indicate what change * was actually made to the Document. * <p> * If the Document structure changed as result of the insertion, the details * of what Elements were inserted and removed in response to the change will * also be contained in the generated DocumentEvent. It is up to the * implementation of a Document to decide how the structure should change in * response to an insertion. * <p> * If the Document supports undo/redo, an UndoableEditEvent will also be * generated. * * @param startingOffset The offset into the document to insert the content * >= 0. All positions that track change at or after the given location will * move * @param text The string to insert * @param attributeSet The attributes to associate with the inserted content. * This may be null if there are no attributes * @exception BadLocationException the given insert position is not a valid * position within the document */ public void insertString(int startingOffset, String text, AttributeSet attributeSet) throws BadLocationException { if (regularExpression != null) { StringBuffer upcomingString = new StringBuffer(getText(0, getLength())); upcomingString.insert(startingOffset, text); String upcoming = upcomingString.toString(); Matcher matcher = regularExpression.matcher(upcoming); if (!matcher.matches()) return; } super.insertString(startingOffset, text, attributeSet); } /** * Removes a portion of the content of the document. This will cause a * DocumentEvent of type DocumentEvent.EventType.REMOVE to be sent to the * registered DocumentListeners, unless an exception is thrown. The * notification will be sent to the listeners by calling the removeUpdate * method on the DocumentListeners. * <p> * To ensure reasonable behavior in the face of concurrency, the event is * dispatched after the mutation has occurred. This means that by the time a * notification of removal is dispatched, the document has already been * updated and any marks created by createPosition have already changed. * <p> * For a removal, the end of the removal range is collapsed down to the start * of the range, and any marks in the removal range are collapsed down to the * start of the range. * <p> * If the Document structure changed as result of the removal, the details of * what Elements were inserted and removed in response to the change will also * be contained in the generated DocumentEvent. It is up to the implementation * of a Document to decide how the structure should change in response to a * remove. * <p> * If the Document supports undo/redo, an UndoableEditEvent will also be * generated. * * @param startingOffset The offset from the begining >= 0 * @param length The number of characters to remove >= 0 * @exception BadLocationException some portion of the removal range was not a * valid part of the document. The location in the exception is the first bad * position encountered */ public void remove(int startingOffset, int length) throws BadLocationException { if (regularExpression != null) { StringBuffer upcomingString = new StringBuffer(getText(0, getLength())); upcomingString = upcomingString.delete(startingOffset, startingOffset + length); String upcoming = upcomingString.toString(); Matcher matcher = regularExpression.matcher(upcoming); if (!matcher.matches()) return; } super.remove(startingOffset, length); } }