/*---------------------------------------------------------------------------*\
  $Id: ea3f75ed5546fc41b57f1f1307cda7f557579135 $
  ---------------------------------------------------------------------------
  This software is released under a BSD-style license:

  Copyright (c) 2004-2007 Brian M. Clapper. All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

  1.  Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

  2.  The end-user documentation included with the redistribution, if any,
      must include the following acknowlegement:

        "This product includes software developed by Brian M. Clapper
        (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
        copyright (c) 2004-2007 Brian M. Clapper."

      Alternately, this acknowlegement may appear in the software itself,
      if wherever such third-party acknowlegements normally appear.

  3.  Neither the names "clapper.org", "clapper.org Java Utility Library",
      nor any of the names of the project contributors may be used to
      endorse or promote products derived from this software without prior
      written permission. For written permission, please contact
      bmc@clapper.org.

  4.  Products derived from this software may not be called "clapper.org
      Java Utility Library", nor may "clapper.org" appear in their names
      without prior written permission of Brian M. Clapper.

  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  NO EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\*---------------------------------------------------------------------------*/

package org.clapper.util.regex;

import org.clapper.util.misc.LRUMap;
import org.clapper.util.text.TextUtil;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * 

This is a utility class implementing some common regular * expression-based operations, using the java.util.regex classes. * The various operations are briefly described here; see the individual * methods for full details.

* *

Substitution

* *

The {@link #substitute} method implements Perl-like regular expression * substitution. It takes an edit string representing the substitution, * and a string to be edited. It returns the possibly edited string. The * substitution syntax is similar to Perl:

* *
s/regex/replacement/[g][i][m][o][x]
* *

The regular expressions compiled once, and the compiled versions are * cached in an internal LRU buffer. The buffer's size is fixed at the time * of instantiation.

* *

See the documentation for the {@link #substitute} method for full * details.

*/ public class RegexUtil { /*----------------------------------------------------------------------*\ Public Constants \*----------------------------------------------------------------------*/ /** * Default size of the internal LRU buffer that is used to hold * compiled regular expressions. The buffer will never contain any more * than this many compiled regular expressions. Once the buffer is * full, any newly compiled regular expression (e.g., as a result of a * {@link #substitute} call made with a new expression) will replace * the oldest (least recently used) item in the buffer. */ public static final int DEFAULT_LRU_BUFFER_SIZE = 20; /*----------------------------------------------------------------------*\ Inner Classes \*----------------------------------------------------------------------*/ private class Substitution { int flags = 0; // java.util.regex.Pattern.compile() flags boolean replaceAll = false; // global or once-only String regex = null; int hc = -1; Substitution (String regex) { this.regex = regex; } public synchronized int hashCode() { if (hc == -1) { StringBuffer buf = new StringBuffer(); buf.append (regex); buf.append (String.valueOf (replaceAll)); buf.append (String.valueOf (flags)); hc = buf.toString().hashCode(); } return hc; } public boolean equals (Object o) { boolean eq = false; if (o instanceof Substitution) { eq = (regex.equals (((Substitution) o).regex)) && (flags == ((Substitution) o).flags) && (replaceAll == ((Substitution) o).replaceAll); } return eq; } } /*----------------------------------------------------------------------*\ Private Data Items \*----------------------------------------------------------------------*/ private LRUMap compiledRegexps = new LRUMap (100); /*----------------------------------------------------------------------*\ Constructors \*----------------------------------------------------------------------*/ /** * Allocate a new RegexUtil object. The object's internal * LRU buffer will cache up to 100 substitution regular expressions. */ public RegexUtil() { // Nothing to do } /*----------------------------------------------------------------------*\ Public Methods \*----------------------------------------------------------------------*/ /** * This method implements Perl-like regular expression substitution. It * takes an edit string representing the substitution, and a string to * be edited. It returns the possibly edited string. The substitution * syntax is similar to Perl:

* *
s/regex/replacement/[g][i][m][o][x]
* *

The regular expressions compiled once, and the compiled versions are * cached in an internal LRU buffer. The buffer's size is fixed at the time * of instantiation.

* *

Any non-alphabetic, printing character may be used in place of the * slashes. The modifiers generally have the same meanings as in Perl, * though some of them aren't actually supported (but are present solely * for syntactical compatibility).

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ModifierMeaning
gSubstitute for all occurrences of the regular expression. * not just the first one.
iDo case-insensitive pattern matching. This modifier corresponds * to the java.util.regex.Pattern.CASE_INSENSITIVE flag. *
mTreat the string is consisting of multiple lines. This modifier * corresponds to the java.util.regex.Pattern.MULTILINE * flag. It changes the meaning of "^" and "$" so that they * match just after or just before, respectively, a line * terminator or the end of the input sequence. By default * these expressions only match at the beginning and the end of * the entire input sequence. *
oCompile once. This modifier is ignored, since regular * expressions are always compiled once and stored in the * internal LRU buffer.
uEnables Unicode-aware case folding. This modifier corresponds * to the java.util.regex.UNICODE_CASE flag. When this * modifier is specified, case-insensitive matching, when * enabled by the CASE_INSENSITIVE flag, is done in a manner * consistent with the Unicode Standard. By default, * case-insensitive matching assumes that only characters in * the US-ASCII charset are being matched. Specifying this flag * may impose a performance penalty. *
xPermits whitespace and comments in a pattern. This modifier * corresponds to the java.util.regex.Pattern.COMMENTS * flag. When this mode is active, whitespace is ignored, and * embedded comments starting with # are ignored until the end * of a line. *
* * @param substitutionCommand the "s///" substitution command * @param s string to edit * * @return the possibly edited string * * @throws RegexException bad expression, bad regular expression, etc. */ public String substitute(String substitutionCommand, String s) throws RegexException { // Minimum size: 5 (s/a//) if (substitutionCommand.length() < 5) { throw new RegexException(Package.BUNDLE_NAME, "RegexUtil.substitutionCommandTooShort", "Substitution command \"{0}\" is too " + "short.", new Object[] {substitutionCommand}); } if (substitutionCommand.charAt(0) != 's') { throw new RegexException(Package.BUNDLE_NAME, "RegexUtil.badSubstitutionSyntax", "\"{0}\" is a syntactically incorrect " + "substitution command.", new Object[] {substitutionCommand}); } char delim = substitutionCommand.charAt(1); if (Character.isWhitespace(delim) || (Character.isLetter(delim))) { throw new RegexException(Package.BUNDLE_NAME, "RegexUtil.badSubstitutionDelim", "Substitution command \"{0}\" uses " + "alphabetic or white-space delimiter " + "\"{1}\".", new Object[] { substitutionCommand, String.valueOf (delim) }); } String[] fields = TextUtil.split(substitutionCommand, delim, true); if ((fields.length != 3) && (fields.length != 4)) { throw new RegexException(Package.BUNDLE_NAME, "RegexUtil.badSubstitutionSyntax", "\"{0}\" is a syntactically incorrect " + "substitution command.", new Object[] {substitutionCommand}); } assert ((fields[0].length() == 1) && (fields[0].charAt(0) == 's')); String regex = fields[1]; String replacement = fields[2]; Substitution sub = new Substitution(regex); if (fields.length == 4) getSubstitutionFlags(substitutionCommand, fields[3], sub); Pattern pattern = (Pattern) compiledRegexps.get(sub); if (pattern == null) { try { pattern = Pattern.compile(regex, sub.flags); compiledRegexps.put(sub, pattern); } catch (PatternSyntaxException ex) { throw new RegexException (ex); } } String result = s; Matcher matcher = pattern.matcher(s); // Note that Matcher.replaceFirst() and Matcher.replaceAll() handle // group replacement tokens $1, $2, etc. if (sub.replaceAll) result = matcher.replaceAll(replacement); else result = matcher.replaceFirst(replacement); return result; } /*----------------------------------------------------------------------*\ Private Methods \*----------------------------------------------------------------------*/ private void getSubstitutionFlags (String substitutionCommand, String sFlags, Substitution sub) throws RegexException { char[] modifiers = sFlags.toCharArray(); for (int i = 0; i < modifiers.length; i++) { char mod = modifiers[i]; switch (mod) { case 'g': sub.replaceAll = true; break; case 'i': sub.flags |= Pattern.CASE_INSENSITIVE; break; case 'm': sub.flags |= Pattern.MULTILINE; break; case 'o': // N/A break; case 'x': sub.flags |= Pattern.COMMENTS; break; default: throw new RegexException (Package.BUNDLE_NAME, "RegexUtil.badSubstitutionModifier", "Substitution command \"{0}\" has unknown modifier " + "character\"{1}\".", new Object[] { substitutionCommand, String.valueOf (mod) }); } } } }

Ads help maintain this website.