diff options
Diffstat (limited to 'libjava/classpath/gnu/java/util')
35 files changed, 7548 insertions, 1 deletions
diff --git a/libjava/classpath/gnu/java/util/prefs/EventDispatcher.java b/libjava/classpath/gnu/java/util/prefs/EventDispatcher.java index feabe4dce51..ecddd3a55f2 100644 --- a/libjava/classpath/gnu/java/util/prefs/EventDispatcher.java +++ b/libjava/classpath/gnu/java/util/prefs/EventDispatcher.java @@ -74,7 +74,7 @@ public class EventDispatcher extends Thread { try { - wait(); + queue.wait(); } catch (InterruptedException _) { @@ -107,6 +107,7 @@ public class EventDispatcher extends Thread synchronized (queue) { queue.add(runner); + queue.notify(); } } } diff --git a/libjava/classpath/gnu/java/util/prefs/GConfBasedFactory.java b/libjava/classpath/gnu/java/util/prefs/GConfBasedFactory.java new file mode 100644 index 00000000000..ae734b60985 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/GConfBasedFactory.java @@ -0,0 +1,78 @@ +/* GConfBasedFactory.java -- GConf based PreferencesFactory implementation + Copyright (C) 2006 Free Software Foundation, Inc. + + This file is part of GNU Classpath. + + GNU Classpath is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GNU Classpath is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Classpath; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +package gnu.java.util.prefs; + +import java.util.prefs.Preferences; +import java.util.prefs.PreferencesFactory; + +/** + * Factory object that generates a Preferences nodes that are read from a GConf + * daemon. + * + * @author Mario Torre <neugens@limasoftware.net> + */ +public class GConfBasedFactory implements PreferencesFactory +{ + /** System preference root. */ + private static final Preferences systemPreferences + = new GConfBasedPreferences(null, "", false); + + /** User preference root. */ + private static final Preferences userPreferences + = new GConfBasedPreferences(null, "", true); + + /** + * Returns the system root preference node. + * + * @see java.util.prefs.PreferencesFactory#systemRoot() + */ + public Preferences systemRoot() + { + return systemPreferences; + } + + /** + * Returns the user root preference node corresponding to the calling user. + * + * @see java.util.prefs.PreferencesFactory#userRoot() + */ + public Preferences userRoot() + { + return userPreferences; + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/GConfBasedPreferences.java b/libjava/classpath/gnu/java/util/prefs/GConfBasedPreferences.java new file mode 100644 index 00000000000..5702751cf5c --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/GConfBasedPreferences.java @@ -0,0 +1,412 @@ +/* GConfBasedPreferences.java -- GConf based Preferences implementation + Copyright (C) 2006 Free Software Foundation, Inc. + + This file is part of GNU Classpath. + + GNU Classpath is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GNU Classpath is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Classpath; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +package gnu.java.util.prefs; + +import gnu.java.util.prefs.gconf.GConfNativePeer; + +import java.security.Permission; + +import java.util.Iterator; +import java.util.List; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; + +/** + * This is a GConf based preference implementation which writes the preferences + * as GConf key-value pairs. System Root is defined to be the + * <code>"/system"</code> directory of GConf for the current user, while User + * Root is <code>"/apps/java"</code>. These defaults can be modified by + * defining two system properties:<br /> + * <br /> + * User Root:<br /> + * <br /> + * + * <pre> + * gnu.java.util.prefs.gconf.user_root + * </pre> + * + * <br /> + * <br /> + * and System Root:<br /> + * <br /> + * + * <pre> + * gnu.java.util.prefs.gconf.system_root + * </pre> + * + * <br /> + * + * @author Mario Torre <neugens@limasoftware.net> + * @version 1.0.1 + */ +public class GConfBasedPreferences + extends AbstractPreferences +{ + /** Get access to Runtime permission */ + private static final Permission PERMISSION + = new RuntimePermission("preferences"); + + /** CGonf client backend */ + private static GConfNativePeer backend = new GConfNativePeer(); + + /** Default user root path */ + private static final String DEFAULT_USER_ROOT = "/apps/classpath"; + + /** Default system root path */ + private static final String DEFAULT_SYSTEM_ROOT = "/system"; + + /** current node full path */ + private String node = ""; + + /** True if this is a preference node in the user tree, false otherwise. */ + private final boolean isUser; + + /** + * Creates a preference root user node. + */ + public GConfBasedPreferences() + { + this(true); + } + + /** + * Creates a preference root node. When <code>isUser</code> is true it will + * be user node otherwise it will be a system node. + */ + public GConfBasedPreferences(boolean isUser) + { + this(null, "", isUser); + } + + /** + * Creates a new preference node given a parent node and a name, which has to + * be relative to its parent. When <code>isUser</code> is true it will be user + * node otherwise it will be a system node. + * + * @param parent The parent node of this newly created node. + * @param name A name relative to the parent node. + * @param isUser Set to <code>true</code> initializes this node to be + * a user node, <code>false</code> initialize it to be a system node. + */ + public GConfBasedPreferences(AbstractPreferences parent, String name, + boolean isUser) + { + super(parent, name); + this.isUser = isUser; + + // stores the fully qualified name of this node + String absolutePath = this.absolutePath(); + if (absolutePath != null && absolutePath.endsWith("/")) + { + absolutePath = absolutePath.substring(0, absolutePath.length() - 1); + } + + this.node = this.getRealRoot(isUser) + absolutePath; + + boolean nodeExist = backend.nodeExist(this.node); + + this.newNode = !nodeExist; + backend.startWatchingNode(this.node); + } + + /** + * Returns a child node with the given name. + * If the child node does not exists, it will be created. + * + * @param name The name of the requested node. + * @return A new reference to the node, creating the node if it is necessary. + */ + protected AbstractPreferences childSpi(String name) + { + // we don't check anything here, if the node is a new node this will be + // detected in the constructor, so we simply return a new reference to + // the requested node. + return new GConfBasedPreferences(this, name, this.isUser); + } + + /** + * Returns an array of names of the children of this preference node. + * If the current node does not have children, the returned array will be + * of <code>size</code> 0 (that is, not <code>null</code>). + * + * @return A <code>String</code> array of names of children of the current + * node. + * @throws BackingStoreException if this operation cannot be completed. + */ + protected String[] childrenNamesSpi() throws BackingStoreException + { + List nodeList = backend.getChildrenNodes(this.node); + String[] nodes = new String[nodeList.size()]; + nodeList.toArray(nodes); + + return nodes; + } + + /** + * Suggest a flush to the backend. Actually, this is only a suggestion as + * GConf handles this for us asynchronously. More over, both sync and flush + * have the same meaning in this class, so calling sync has exactly the same + * effect. + * + * @see #sync + * @throws BackingStoreException if this operation cannot be completed. + */ + public void flush() throws BackingStoreException + { + backend.suggestSync(); + } + + /** + * Request a flush. + * + * @see #flush + * @throws BackingStoreException if this operation cannot be completed. + */ + protected void flushSpi() throws BackingStoreException + { + this.flush(); + } + + /** + * Returns all of the key in this preference node. + * If the current node does not have preferences, the returned array will be + * of size zero. + * + * @return A <code>String</code> array of keys stored under the current + * node. + * @throws BackingStoreException if this operation cannot be completed. + */ + protected String[] keysSpi() throws BackingStoreException + { + List keyList = backend.getKeys(this.node); + String[] keys = new String[keyList.size()]; + keyList.toArray(keys); + + return keys; + } + + /** + * Does a recursive postorder traversal of the preference tree, starting from + * the given directory invalidating every preference found in the node. + * + * @param directory The name of the starting directory (node) + */ + private void postorderRemove(String directory) + { + try + { + // gets the listing of directories in this node + List dirs = backend.getChildrenNodes(directory); + + if (dirs.size() != 0) + { + String currentDir = null; + + for (Iterator itr = dirs.iterator(); itr.hasNext();) + { + currentDir = (String) itr.next(); + + // recursive search inside this directory + postorderRemove(currentDir); + } + } + + // remove all the keys associated to this directory + List entries = backend.getKeys(directory); + + if (entries.size() != 0) + { + String key = null; + + for (Iterator keys = entries.iterator(); keys.hasNext();) + { + key = (String) keys.next(); + this.removeSpi(key); + } + } + } + catch (BackingStoreException ex) + { + /* ignore */ + } + } + + /** + * Stores the given key-value pair into this preference node. + * + * @param key The key of this preference. + * @param value The value of this preference. + */ + protected void putSpi(String key, String value) + { + backend.setString(this.getGConfKey(key), value); + } + + /** + * Removes this preference node, including all its children. + * Also removes the preferences associated. + */ + protected void removeNodeSpi() throws BackingStoreException + { + this.postorderRemove(this.node); + this.flush(); + } + + /** + * Removes the given key from this preference node. + * If the key does not exist, no operation is performed. + * + * @param key The key to remove. + */ + protected void removeSpi(String key) + { + backend.unset(this.getGConfKey(key)); + } + + /** + * Suggest a sync to the backend. Actually, this is only a suggestion as GConf + * handles this for us asynchronously. More over, both sync and flush have the + * same meaning in this class, so calling flush has exactly the same effect. + * + * @see #flush + * @throws BackingStoreException if this operation cannot be completed due to + * a failure in the backing store, or inability to communicate with + * it. + */ + public void sync() throws BackingStoreException + { + this.flush(); + } + + /** + * Request a sync. + * + * @see #sync + * @throws BackingStoreException if this operation cannot be completed due to + * a failure in the backing store, or inability to communicate with + * it. + */ + protected void syncSpi() throws BackingStoreException + { + this.sync(); + } + + /** + * Returns the value of the given key. + * If the keys does not have a value, or there is an error in the backing + * store, <code>null</code> is returned instead. + * + * @param key The key to retrieve. + * @return The value associated with the given key. + */ + protected String getSpi(String key) + { + return backend.getKey(this.getGConfKey(key)); + } + + /** + * Returns <code>true</code> if this preference node is a user node, + * <code>false</code> if is a system preference node. + * + * @return <code>true</code> if this preference node is a user node, + * <code>false</code> if is a system preference node. + */ + public boolean isUserNode() + { + return this.isUser; + } + + /* + * PRIVATE METHODS + */ + + /** + * Builds a GConf key string suitable for operations on the backend. + * + * @param key The key to convert into a valid GConf key. + * @return A valid Gconf key. + */ + private String getGConfKey(String key) + { + String nodeName = ""; + + if (this.node.endsWith("/")) + { + nodeName = this.node + key; + } + else + { + nodeName = this.node + "/" + key; + } + + return nodeName; + } + + /** + * Builds the root node to use for this preference. + * + * @param isUser Defines if this node is a user (<code>true</code>) or system + * (<code>false</code>) node. + * @return The real root of this preference tree. + */ + private String getRealRoot(boolean isUser) + { + // not sure about this, we should have already these permissions... + SecurityManager security = System.getSecurityManager(); + + if (security != null) + { + security.checkPermission(PERMISSION); + } + + String root = null; + + if (isUser) + { + root = System.getProperty("gnu.java.util.prefs.gconf.user_root", + DEFAULT_USER_ROOT); + } + else + { + root = System.getProperty("gnu.java.util.prefs.gconf.system_root", + DEFAULT_SYSTEM_ROOT); + } + + return root; + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/gconf/GConfNativePeer.java b/libjava/classpath/gnu/java/util/prefs/gconf/GConfNativePeer.java new file mode 100644 index 00000000000..f1cb6278767 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/gconf/GConfNativePeer.java @@ -0,0 +1,298 @@ +/* GConfNativePeer.java -- GConf based preference peer for native methods + Copyright (C) 2006 Free Software Foundation, Inc. + + This file is part of GNU Classpath. + + GNU Classpath is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GNU Classpath is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Classpath; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + + +package gnu.java.util.prefs.gconf; + +import java.util.List; +import java.util.prefs.BackingStoreException; + +/** + * Native peer for GConf based preference backend. + * + * @author Mario Torre <neugens@limasoftware.net> + * @version 1.0.1 + */ +public final class GConfNativePeer +{ + /** + * Object to achieve locks for methods that need to be synchronized. + */ + private static final Object[] semaphore = new Object[0]; + + /** + * Creates a new instance of GConfNativePeer + */ + public GConfNativePeer() + { + synchronized (semaphore) + { + init_class(); + } + } + + /** + * Queries whether the node <code>node</code> exists in theGConf database. + * Returns <code>true</code> or <code>false</code>. + * + * @param node the node to check. + */ + public boolean nodeExist(String node) + { + return gconf_client_dir_exists(node); + } + + /** + * Add the node <code>node</code> to the list of nodes the GConf will watch. + * An event is raised everytime this node is changed. You can add a node + * multiple times. + * + * @param node the node to track. + */ + public void startWatchingNode(String node) + { + gconf_client_add_dir(node); + } + + /** + * Remove the node <code>node</code> to the list of nodes the GConf is + * watching. Note that if a node has been added multiple times, you must + * remove it the same number of times before the remove takes effect. + * + * @param node the node you don't want to track anymore. + */ + public void stopWatchingNode(String node) + { + gconf_client_remove_dir(node); + } + + /** + * Change the value of key to val. Automatically creates the key if it didn't + * exist before (ie it was unset or it only had a default value). + * Key names must be valid GConf key names, that is, there can be more + * restrictions than for normal Preference Backend. + * + * @param key the key to alter (or add). + * @param value the new value for this key. + * @return true if the key was updated, false otherwise. + */ + public boolean setString(String key, String value) + { + return gconf_client_set_string(key, value); + } + + /** + * Unsets the value of key; if key is already unset, has no effect. Depending + * on the GConf daemon, unsetting a key may have the side effect to remove it + * completely form the database. + * + * @param key the key to unset. + * @return true on success, false if the key was not updated. + */ + public boolean unset(String key) + { + return gconf_client_unset(key); + } + + /** + * Gets the value of a configuration key. + * + * @param key the configuration key. + * @return the values of this key, null if the key is not valid. + */ + public String getKey(String key) + { + return gconf_client_get_string(key); + } + + /** + * Lists the key in the given node. Does not list subnodes. Keys names are the + * stripped names (name relative to the current node) of the keys stored in + * this node. + * + * @param node the node where keys are stored. + * @return a java.util.List of keys. If there are no keys in the given node, a + * list of size 0 is returned. + */ + public List getKeys(String node) throws BackingStoreException + { + return gconf_client_gconf_client_all_keys(node); + } + + /** + * Lists the subnodes in <code>node</code>. The returned list contains + * allocated strings. Each string is the name relative tho the given node. + * + * @param node the node to get subnodes from. If there are no subnodes in the + * given node, a list of size 0 is returned. + */ + public List getChildrenNodes(String node) throws BackingStoreException + { + return gconf_client_gconf_client_all_nodes(node); + } + + /** + * Suggest to the backend GConf daemon to synch with the database. + */ + public void suggestSync() throws BackingStoreException + { + gconf_client_suggest_sync(); + } + + protected void finalize() throws Throwable + { + try + { + synchronized (semaphore) + { + finalize_class(); + } + } + finally + { + super.finalize(); + } + } + + /* ***** native methods ***** */ + + /* + * Basicly, these are one to one mappings to GConfClient functions. + * GConfClient instances are handled by the native layer, and are hidden from + * the main java class. + */ + + /** + * Initialize the GConf native peer and enable the object cache. + * It is meant to be used by the static initializer. + */ + native static final private void init_id_cache(); + + /** + * Initialize the GConf native peer. This is meant to be used by the + * class constructor. + */ + native static final private void init_class(); + + /** + * Class finalizer. + */ + native static final private void finalize_class(); + + /** + * Queries the GConf database to see if the given node exists, returning + * true if the node exist, false otherwise. + * + * @param node the node to query for existence. + * @return true if the node exist, false otherwise. + */ + native static final protected boolean gconf_client_dir_exists(String node); + + /** + * Adds the given node to the list of nodes that GConf watches for + * changes. + * + * @param node the node to watch for changes. + */ + native static final protected void gconf_client_add_dir(String node); + + /** + * Removes the given node from the list of nodes that GConf watches for + * changes. + * + * @param node the node to remove from from the list of watched nodes. + */ + native static final protected void gconf_client_remove_dir(String node); + + /** + * Sets the given key/value pair into the GConf database. + * The key must be a valid GConf key. + * + * @param key the key to store in the GConf database + * @param value the value to associate to the given key. + * @return true if the change has effect, false otherwise. + */ + native static final protected boolean gconf_client_set_string(String key, + String value); + + /** + * Returns the key associated to the given key. Null is returned if the + * key is not valid. + * + * @param key the key to return the value of. + * @return The value associated to the given key, or null. + */ + native static final protected String gconf_client_get_string(String key); + + /** + * Usets the given key, removing the key from the database. + * + * @param key the key to remove. + * @return true if the operation success, false otherwise. + */ + native static final protected boolean gconf_client_unset(String key); + + /** + * Suggest to the GConf native peer a sync with the database. + * + */ + native static final protected void gconf_client_suggest_sync(); + + /** + * Returns a list of all nodes under the given node. + * + * @param node the source node. + * @return A list of nodes under the given source node. + */ + native + static final protected List gconf_client_gconf_client_all_nodes(String node); + + /** + * Returns a list of all keys stored in the given node. + * + * @param node the source node. + * @return A list of all keys stored in the given node. + */ + native + static final protected List gconf_client_gconf_client_all_keys(String node); + + static + { + System.loadLibrary("gconfpeer"); + init_id_cache(); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/BacktrackStack.java b/libjava/classpath/gnu/java/util/regex/BacktrackStack.java new file mode 100644 index 00000000000..d711945e43b --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/BacktrackStack.java @@ -0,0 +1,112 @@ +/* gnu/regexp/BacktrackStack.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +/** + * An instance of this class represents a stack + * used for backtracking. + * + * @author Ito Kazumitsu</A> + */ +final class BacktrackStack { + + /** A set of data to be used for backtracking. */ + static class Backtrack { + /** REToken to which to go back */ + REToken token; + /** CharIndexed on which matches are being searched for. */ + CharIndexed input; + /** REMatch to be used by the REToken token. */ + REMatch match; + /** Some parameter used by the token's backtrack method. */ + Object param; + Backtrack(REToken token, CharIndexed input, REMatch match, Object param) { + this.token = token; + this.input = input; + // REMatch may change before backtracking is needed. So we + // keep a clone of it. + this.match = (REMatch) match.clone(); + this.param = param; + } + } + + Backtrack[] stack; + private int size; + private int capacity; + private static final int INITIAL_CAPACITY = 32; + private static final int CAPACITY_INCREMENT = 16; + + BacktrackStack() { + stack = new Backtrack[INITIAL_CAPACITY]; + size = 0; + capacity = INITIAL_CAPACITY; + } + + boolean empty() { + return size == 0; + } + + Backtrack peek() { + return stack[size - 1]; + } + + Backtrack pop() { + Backtrack bt = stack[--size]; + stack[size] = null; + return bt; + } + + void clear() { + for (int i = 0; i < size; i++) { + stack[i] = null; + } + size = 0; + } + + void push(Backtrack bt) { + if (size >= capacity) { + capacity += CAPACITY_INCREMENT; + Backtrack[] newStack = new Backtrack[capacity]; + System.arraycopy(stack, 0, newStack, 0, size); + stack = newStack; + } + stack[size++] = bt; + } + +} diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexed.java b/libjava/classpath/gnu/java/util/regex/CharIndexed.java new file mode 100644 index 00000000000..6cd857e3bc0 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexed.java @@ -0,0 +1,116 @@ +/* gnu/regexp/CharIndexed.java + Copyright (C) 1998-2001, 2004, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * Defines the interface used internally so that different types of source + * text can be accessed in the same way. Built-in concrete classes provide + * support for String, StringBuffer, InputStream and char[] types. + * A class that is CharIndexed supports the notion of a cursor within a + * block of text. The cursor must be able to be advanced via the move() + * method. The charAt() method returns the character at the cursor position + * plus a given offset. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public interface CharIndexed { + /** + * Defines a constant (0xFFFF was somewhat arbitrarily chosen) + * that can be returned by the charAt() function indicating that + * the specified index is out of range. + */ + char OUT_OF_BOUNDS = '\uFFFF'; + + /** + * Returns the character at the given offset past the current cursor + * position in the input. The index of the current position is zero. + * It is possible for this method to be called with a negative index. + * This happens when using the '^' operator in multiline matching mode + * or the '\b' or '\<' word boundary operators. In any case, the lower + * bound is currently fixed at -2 (for '^' with a two-character newline). + * + * @param index the offset position in the character field to examine + * @return the character at the specified index, or the OUT_OF_BOUNDS + * character defined by this interface. + */ + char charAt(int index); + + /** + * Shifts the input buffer by a given number of positions. Returns + * true if the new cursor position is valid. + */ + boolean move(int index); + + /** + * Returns true if the most recent move() operation placed the cursor + * position at a valid position in the input. + */ + boolean isValid(); + + /** + * Returns another CharIndexed containing length characters to the left + * of the given index. The given length is an expected maximum and + * the returned CharIndexed may not necessarily contain so many characters. + */ + CharIndexed lookBehind(int index, int length); + + /** + * Returns the effective length of this CharIndexed + */ + int length(); + + /** + * Sets the REMatch last found on this input. + */ + void setLastMatch(REMatch match); + + /** + * Returns the REMatch last found on this input. + */ + REMatch getLastMatch(); + + /** + * Returns the anchor. + */ + int getAnchor(); + + /** + * Sets the anchor. + */ + void setAnchor(int anchor); +} diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexedCharArray.java b/libjava/classpath/gnu/java/util/regex/CharIndexedCharArray.java new file mode 100644 index 00000000000..6f74c992f8f --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedCharArray.java @@ -0,0 +1,46 @@ +/* gnu/regexp/CharIndexedCharArray.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.nio.CharBuffer; + +class CharIndexedCharArray extends CharIndexedCharSequence { + + CharIndexedCharArray(char[] str, int index) { + super(CharBuffer.wrap(str), index); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexedCharSequence.java b/libjava/classpath/gnu/java/util/regex/CharIndexedCharSequence.java new file mode 100644 index 00000000000..2eb753b0f5c --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedCharSequence.java @@ -0,0 +1,82 @@ +/* gnu/regexp/CharIndexedCharSequence.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.Serializable; + +class CharIndexedCharSequence implements CharIndexed, Serializable { + private CharSequence s; + private int anchor; + private int len; + + CharIndexedCharSequence(CharSequence s, int index) { + this.s = s; + len = s.length(); + anchor = index; + } + + public char charAt(int index) { + int pos = anchor + index; + return ((pos < len) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS; + } + + public boolean isValid() { + return (anchor < len); + } + + public boolean move(int index) { + return ((anchor += index) < len); + } + + public CharIndexed lookBehind(int index, int length) { + if (length > (anchor + index)) length = anchor + index; + return new CharIndexedCharSequence(s, anchor + index - length); + } + + public int length() { + return len - anchor; + } + + private REMatch lastMatch; + public void setLastMatch(REMatch match) { + lastMatch = (REMatch)match.clone(); + lastMatch.anchor = anchor; + } + public REMatch getLastMatch() { return lastMatch; } + public int getAnchor() { return anchor; } + public void setAnchor(int anchor) { this.anchor = anchor; } +} diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexedInputStream.java b/libjava/classpath/gnu/java/util/regex/CharIndexedInputStream.java new file mode 100644 index 00000000000..77cd1abd5cc --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedInputStream.java @@ -0,0 +1,181 @@ +/* gnu/regexp/CharIndexedInputStream.java + Copyright (C) 1998-2001, 2004, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +// TODO: move(x) shouldn't rely on calling next() x times + +class CharIndexedInputStream implements CharIndexed { + private static final int BUFFER_INCREMENT = 1024; + private static final int UNKNOWN = Integer.MAX_VALUE; // value for end + + private BufferedInputStream br; + + // so that we don't try to reset() right away + private int index = -1; + + private int bufsize = BUFFER_INCREMENT; + + private int end = UNKNOWN; + + private char cached = OUT_OF_BOUNDS; + + // Big enough for a \r\n pair + // lookBehind[0] = most recent + // lookBehind[1] = second most recent + private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; + + CharIndexedInputStream(InputStream str, int index) { + if (str instanceof BufferedInputStream) br = (BufferedInputStream) str; + else br = new BufferedInputStream(str,BUFFER_INCREMENT); + next(); + if (index > 0) move(index); + } + + private boolean next() { + if (end == 1) return false; + end--; // closer to end + + try { + if (index != -1) { + br.reset(); + } + int i = br.read(); + br.mark(bufsize); + if (i == -1) { + end = 1; + cached = OUT_OF_BOUNDS; + return false; + } + cached = (char) i; + index = 1; + } catch (IOException e) { + e.printStackTrace(); + cached = OUT_OF_BOUNDS; + return false; + } + return true; + } + + public char charAt(int index) { + if (index == 0) { + return cached; + } else if (index >= end) { + return OUT_OF_BOUNDS; + } else if (index == -1) { + return lookBehind[0]; + } else if (index == -2) { + return lookBehind[1]; + } else if (index < -2) { + return OUT_OF_BOUNDS; + } else if (index >= bufsize) { + // Allocate more space in the buffer. + try { + while (bufsize <= index) bufsize += BUFFER_INCREMENT; + br.reset(); + br.mark(bufsize); + br.skip(index-1); + } catch (IOException e) { } + } else if (this.index != index) { + try { + br.reset(); + br.skip(index-1); + } catch (IOException e) { } + } + char ch = OUT_OF_BOUNDS; + + try { + int i = br.read(); + this.index = index+1; // this.index is index of next pos relative to charAt(0) + if (i == -1) { + // set flag that next should fail next time? + end = index; + return ch; + } + ch = (char) i; + } catch (IOException ie) { } + + return ch; + } + + public boolean move(int index) { + // move read position [index] clicks from 'charAt(0)' + boolean retval = true; + while (retval && (index-- > 0)) retval = next(); + return retval; + } + + public boolean isValid() { + return (cached != OUT_OF_BOUNDS); + } + + public CharIndexed lookBehind(int index, int length) { + throw new UnsupportedOperationException( + "difficult to look behind for an input stream"); + } + + public int length() { + throw new UnsupportedOperationException( + "difficult to tell the length for an input stream"); + } + + public void setLastMatch(REMatch match) { + throw new UnsupportedOperationException( + "difficult to support setLastMatch for an input stream"); + } + + public REMatch getLastMatch() { + throw new UnsupportedOperationException( + "difficult to support getLastMatch for an input stream"); + } + + public int getAnchor() { + throw new UnsupportedOperationException( + "difficult to support getAnchor for an input stream"); + } + + public void setAnchor(int anchor) { + throw new UnsupportedOperationException( + "difficult to support setAnchor for an input stream"); + } + + +} + diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexedString.java b/libjava/classpath/gnu/java/util/regex/CharIndexedString.java new file mode 100644 index 00000000000..fab6d78360b --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedString.java @@ -0,0 +1,44 @@ +/* gnu/regexp/CharIndexedString.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +class CharIndexedString extends CharIndexedCharSequence { + CharIndexedString(String str, int index) { + super(str, index); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/CharIndexedStringBuffer.java b/libjava/classpath/gnu/java/util/regex/CharIndexedStringBuffer.java new file mode 100644 index 00000000000..10005b66831 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedStringBuffer.java @@ -0,0 +1,45 @@ +/* gnu/regexp/CharIndexedStringBuffer.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +class CharIndexedStringBuffer extends CharIndexedCharSequence { + + CharIndexedStringBuffer(StringBuffer str, int index) { + super(str, index); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RE.java b/libjava/classpath/gnu/java/util/regex/RE.java new file mode 100644 index 00000000000..1aab3b781a2 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RE.java @@ -0,0 +1,2128 @@ +/* gnu/regexp/RE.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.Stack; +import java.util.Vector; + +/** + * RE provides the user interface for compiling and matching regular + * expressions. + * <P> + * A regular expression object (class RE) is compiled by constructing it + * from a String, StringBuffer or character array, with optional + * compilation flags (below) + * and an optional syntax specification (see RESyntax; if not specified, + * <code>RESyntax.RE_SYNTAX_PERL5</code> is used). + * <P> + * Once compiled, a regular expression object is reusable as well as + * threadsafe: multiple threads can use the RE instance simultaneously + * to match against different input text. + * <P> + * Various methods attempt to match input text against a compiled + * regular expression. These methods are: + * <LI><code>isMatch</code>: returns true if the input text in its + * entirety matches the regular expression pattern. + * <LI><code>getMatch</code>: returns the first match found in the + * input text, or null if no match is found. + * <LI><code>getAllMatches</code>: returns an array of all + * non-overlapping matches found in the input text. If no matches are + * found, the array is zero-length. + * <LI><code>substitute</code>: substitute the first occurence of the + * pattern in the input text with a replacement string (which may + * include metacharacters $0-$9, see REMatch.substituteInto). + * <LI><code>substituteAll</code>: same as above, but repeat for each + * match before returning. + * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration + * object that allows iteration over the matches (see + * REMatchEnumeration for some reasons why you may want to do this + * instead of using <code>getAllMatches</code>. + * <P> + * + * These methods all have similar argument lists. The input can be a + * CharIndexed, String, a character array, a StringBuffer, or an + * InputStream of some sort. Note that when using an + * InputStream, the stream read position cannot be guaranteed after + * attempting a match (this is not a bug, but a consequence of the way + * regular expressions work). Using an REMatchEnumeration can + * eliminate most positioning problems. + * + * Although the input object can be of various types, it is recommended + * that it should be a CharIndexed because {@link CharIndexed#getLastMatch()} + * can show the last match found on this input, which helps the expression + * \G work as the end of the previous match. + * + * <P> + * + * The optional index argument specifies the offset from the beginning + * of the text at which the search should start (see the descriptions + * of some of the execution flags for how this can affect positional + * pattern operators). For an InputStream, this means an + * offset from the current read position, so subsequent calls with the + * same index argument on an InputStream will not + * necessarily access the same position on the stream, whereas + * repeated searches at a given index in a fixed string will return + * consistent results. + * + * <P> + * You can optionally affect the execution environment by using a + * combination of execution flags (constants listed below). + * + * <P> + * All operations on a regular expression are performed in a + * thread-safe manner. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @version 1.1.5-dev, to be released + */ + +public class RE extends REToken { + + private static final class IntPair implements Serializable { + public int first, second; + } + + private static final class CharUnit implements Serializable { + public char ch; + public boolean bk; + } + + // This String will be returned by getVersion() + private static final String VERSION = "1.1.5-dev"; + + // The localized strings are kept in a separate file + private static ResourceBundle messages = PropertyResourceBundle.getBundle("gnu/java/util/regex/MessagesBundle", Locale.getDefault()); + + // These are, respectively, the first and last tokens in our linked list + // If there is only one token, firstToken == lastToken + private REToken firstToken, lastToken; + + // This is the number of subexpressions in this regular expression, + // with a minimum value of zero. Returned by getNumSubs() + private int numSubs; + + /** Minimum length, in characters, of any possible match. */ + private int minimumLength; + private int maximumLength; + + /** + * Compilation flag. Do not differentiate case. Subsequent + * searches using this RE will be case insensitive. + */ + public static final int REG_ICASE = 0x02; + + /** + * Compilation flag. The match-any-character operator (dot) + * will match a newline character. When set this overrides the syntax + * bit RE_DOT_NEWLINE (see RESyntax for details). This is equivalent to + * the "/s" operator in Perl. + */ + public static final int REG_DOT_NEWLINE = 0x04; + + /** + * Compilation flag. Use multiline mode. In this mode, the ^ and $ + * anchors will match based on newlines within the input. This is + * equivalent to the "/m" operator in Perl. + */ + public static final int REG_MULTILINE = 0x08; + + /** + * Execution flag. + * The match-beginning operator (^) will not match at the beginning + * of the input string. Useful for matching on a substring when you + * know the context of the input is such that position zero of the + * input to the match test is not actually position zero of the text. + * <P> + * This example demonstrates the results of various ways of matching on + * a substring. + * <P> + * <CODE> + * String s = "food bar fool";<BR> + * RE exp = new RE("^foo.");<BR> + * REMatch m0 = exp.getMatch(s);<BR> + * REMatch m1 = exp.getMatch(s.substring(8));<BR> + * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR> + * REMatch m3 = exp.getMatch(s,8); <BR> + * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX); <BR> + * <P> + * // Results:<BR> + * // m0.toString(): "food"<BR> + * // m1.toString(): "fool"<BR> + * // m2.toString(): null<BR> + * // m3.toString(): null<BR> + * // m4.toString(): "fool"<BR> + * </CODE> + */ + public static final int REG_NOTBOL = 0x10; + + /** + * Execution flag. + * The match-end operator ($) does not match at the end + * of the input string. Useful for matching on substrings. + */ + public static final int REG_NOTEOL = 0x20; + + /** + * Execution flag. + * When a match method is invoked that starts matching at a non-zero + * index into the input, treat the input as if it begins at the index + * given. The effect of this flag is that the engine does not "see" + * any text in the input before the given index. This is useful so + * that the match-beginning operator (^) matches not at position 0 + * in the input string, but at the position the search started at + * (based on the index input given to the getMatch function). See + * the example under REG_NOTBOL. It also affects the use of the \< + * and \b operators. + */ + public static final int REG_ANCHORINDEX = 0x40; + + /** + * Execution flag. + * The substitute and substituteAll methods will not attempt to + * interpolate occurrences of $1-$9 in the replacement text with + * the corresponding subexpressions. For example, you may want to + * replace all matches of "one dollar" with "$1". + */ + public static final int REG_NO_INTERPOLATE = 0x80; + + /** + * Execution flag. + * Try to match the whole input string. An implicit match-end operator + * is added to this regexp. + */ + public static final int REG_TRY_ENTIRE_MATCH = 0x0100; + + /** + * Execution flag. + * The substitute and substituteAll methods will treat the + * character '\' in the replacement as an escape to a literal + * character. In this case "\n", "\$", "\\", "\x40" and "\012" + * will become "n", "$", "\", "x40" and "012" respectively. + * This flag has no effect if REG_NO_INTERPOLATE is set on. + */ + public static final int REG_REPLACE_USE_BACKSLASHESCAPE = 0x0200; + + /** + * Compilation flag. Allow whitespace and comments in pattern. + * This is equivalent to the "/x" operator in Perl. + */ + public static final int REG_X_COMMENTS = 0x0400; + + /** + * Compilation flag. If set, REG_ICASE is effective only for US-ASCII. + */ + public static final int REG_ICASE_USASCII = 0x0800; + + /** Returns a string representing the version of the gnu.regexp package. */ + public static final String version() { + return VERSION; + } + + // Retrieves a message from the ResourceBundle + static final String getLocalizedMessage(String key) { + return messages.getString(key); + } + + /** + * Constructs a regular expression pattern buffer without any compilation + * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer or char[]. Other input types will be converted to + * strings using the toString() method. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern) throws REException { + this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern, int cflags) throws REException { + this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and regular expression syntax. + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @param syntax The type of regular expression syntax to use. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern, int cflags, RESyntax syntax) throws REException { + this(pattern,cflags,syntax,0,0); + } + + // internal constructor used for alternation + private RE(REToken first, REToken last,int subs, int subIndex, int minLength, int maxLength) { + super(subIndex); + firstToken = first; + lastToken = last; + numSubs = subs; + minimumLength = minLength; + maximumLength = maxLength; + addToken(new RETokenEndSub(subIndex)); + } + + private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { + super(myIndex); // Subexpression index of this token. + initialize(patternObj, cflags, syntax, myIndex, nextSub); + } + + // For use by subclasses + protected RE() { super(0); } + + // The meat of construction + protected void initialize(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { + char[] pattern; + if (patternObj instanceof String) { + pattern = ((String) patternObj).toCharArray(); + } else if (patternObj instanceof char[]) { + pattern = (char[]) patternObj; + } else if (patternObj instanceof StringBuffer) { + pattern = new char [((StringBuffer) patternObj).length()]; + ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0); + } else { + pattern = patternObj.toString().toCharArray(); + } + + int pLength = pattern.length; + + numSubs = 0; // Number of subexpressions in this token. + Vector branches = null; + + // linked list of tokens (sort of -- some closed loops can exist) + firstToken = lastToken = null; + + // Precalculate these so we don't pay for the math every time we + // need to access them. + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + + // Parse pattern into tokens. Does anyone know if it's more efficient + // to use char[] than a String.charAt()? I'm assuming so. + + // index tracks the position in the char array + int index = 0; + + // this will be the current parse character (pattern[index]) + CharUnit unit = new CharUnit(); + + // This is used for {x,y} calculations + IntPair minMax = new IntPair(); + + // Buffer a token so we can create a TokenRepeated, etc. + REToken currentToken = null; + char ch; + boolean quot = false; + + // Saved syntax and flags. + RESyntax savedSyntax = null; + int savedCflags = 0; + boolean flagsSaved = false; + + while (index < pLength) { + // read the next character unit (including backslash escapes) + index = getCharUnit(pattern,index,unit,quot); + + if (unit.bk) + if (unit.ch == 'Q') { + quot = true; + continue; + } else if (unit.ch == 'E') { + quot = false; + continue; + } + if (quot) + unit.bk = false; + + if (((cflags & REG_X_COMMENTS) > 0) && (!unit.bk) && (!quot)) { + if (Character.isWhitespace(unit.ch)) { + continue; + } + if (unit.ch == '#') { + for (int i = index; i < pLength; i++) { + if (pattern[i] == '\n') { + index = i + 1; + continue; + } + else if (pattern[i] == '\r') { + if (i + 1 < pLength && pattern[i + 1] == '\n') { + index = i + 2; + } + else { + index = i + 1; + } + continue; + } + } + index = pLength; + continue; + } + } + + // ALTERNATION OPERATOR + // \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT) + // not available if RE_LIMITED_OPS is set + + // TODO: the '\n' literal here should be a test against REToken.newline, + // which unfortunately may be more than a single character. + if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ (unit.bk || quot))) + || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !(unit.bk || quot)) ) + && !syntax.get(RESyntax.RE_LIMITED_OPS)) { + // make everything up to here be a branch. create vector if nec. + addToken(currentToken); + RE theBranch = new RE(firstToken, lastToken, numSubs, subIndex, minimumLength, maximumLength); + minimumLength = 0; + maximumLength = 0; + if (branches == null) { + branches = new Vector(); + } + branches.addElement(theBranch); + firstToken = lastToken = currentToken = null; + } + + // INTERVAL OPERATOR: + // {x} | {x,} | {x,y} (RE_INTERVALS && RE_NO_BK_BRACES) + // \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES) + // + // OPEN QUESTION: + // what is proper interpretation of '{' at start of string? + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*){2,}", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ (unit.bk || quot))) { + int newIndex = getMinMax(pattern,index,minMax,syntax); + if (newIndex > index) { + if (minMax.first > minMax.second) + throw new REException(getLocalizedMessage("interval.order"),REException.REG_BADRPT,newIndex); + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,newIndex); + if (currentToken instanceof RETokenRepeated) + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,newIndex); + if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,newIndex); + index = newIndex; + currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); + } + else { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,unit.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + } + + // LIST OPERATOR: + // [...] | [^...] + + else if ((unit.ch == '[') && !(unit.bk || quot)) { + // Create a new RETokenOneOf + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index, pLength, cflags, syntax, 0); + addToken(currentToken); + currentToken = result.token; + index = result.index; + } + + // SUBEXPRESSIONS + // (...) | \(...\) depending on RE_NO_BK_PARENS + + else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) { + boolean pure = false; + boolean comment = false; + boolean lookAhead = false; + boolean lookBehind = false; + boolean independent = false; + boolean negativelh = false; + boolean negativelb = false; + if ((index+1 < pLength) && (pattern[index] == '?')) { + switch (pattern[index+1]) { + case '!': + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + negativelh = true; + lookAhead = true; + index += 2; + } + break; + case '=': + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + lookAhead = true; + index += 2; + } + break; + case '<': + // We assume that if the syntax supports look-ahead, + // it also supports look-behind. + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + index++; + switch (pattern[index +1]) { + case '!': + pure = true; + negativelb = true; + lookBehind = true; + index += 2; + break; + case '=': + pure = true; + lookBehind = true; + index += 2; + } + } + break; + case '>': + // We assume that if the syntax supports look-ahead, + // it also supports independent group. + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + independent = true; + index += 2; + } + break; + case 'i': + case 'd': + case 'm': + case 's': + case 'u': + case 'x': + case '-': + if (!syntax.get(RESyntax.RE_EMBEDDED_FLAGS)) break; + // Set or reset syntax flags. + int flagIndex = index + 1; + int endFlag = -1; + RESyntax newSyntax = new RESyntax(syntax); + int newCflags = cflags; + boolean negate = false; + while (flagIndex < pLength && endFlag < 0) { + switch(pattern[flagIndex]) { + case 'i': + if (negate) + newCflags &= ~REG_ICASE; + else + newCflags |= REG_ICASE; + flagIndex++; + break; + case 'd': + if (negate) + newSyntax.setLineSeparator(RESyntax.DEFAULT_LINE_SEPARATOR); + else + newSyntax.setLineSeparator("\n"); + flagIndex++; + break; + case 'm': + if (negate) + newCflags &= ~REG_MULTILINE; + else + newCflags |= REG_MULTILINE; + flagIndex++; + break; + case 's': + if (negate) + newCflags &= ~REG_DOT_NEWLINE; + else + newCflags |= REG_DOT_NEWLINE; + flagIndex++; + break; + case 'u': + if (negate) + newCflags |= REG_ICASE_USASCII; + else + newCflags &= ~REG_ICASE_USASCII; + flagIndex++; + break; + case 'x': + if (negate) + newCflags &= ~REG_X_COMMENTS; + else + newCflags |= REG_X_COMMENTS; + flagIndex++; + break; + case '-': + negate = true; + flagIndex++; + break; + case ':': + case ')': + endFlag = pattern[flagIndex]; + break; + default: + throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); + } + } + if (endFlag == ')') { + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + // This can be treated as though it were a comment. + comment = true; + index = flagIndex - 1; + break; + } + if (endFlag == ':') { + savedSyntax = syntax; + savedCflags = cflags; + flagsSaved = true; + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + index = flagIndex -1; + // Fall through to the next case. + } + else { + throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); + } + case ':': + if (syntax.get(RESyntax.RE_PURE_GROUPING)) { + pure = true; + index += 2; + } + break; + case '#': + if (syntax.get(RESyntax.RE_COMMENTS)) { + comment = true; + } + break; + default: + throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); + } + } + + if (index >= pLength) { + throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); + } + + // find end of subexpression + int endIndex = index; + int nextIndex = index; + int nested = 0; + + while ( ((nextIndex = getCharUnit(pattern,endIndex,unit,false)) > 0) + && !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) ) { + if ((endIndex = nextIndex) >= pLength) + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + else if ((unit.ch == '[') && !(unit.bk || quot)) { + // I hate to do something similar to the LIST OPERATOR matters + // above, but ... + int listIndex = nextIndex; + if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; + if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; + int listEndIndex = -1; + int listNest = 0; + while (listIndex < pLength && listEndIndex < 0) { + switch(pattern[listIndex++]) { + case '\\': + listIndex++; + break; + case '[': + // Sun's API document says that regexp like "[a-d[m-p]]" + // is legal. Even something like "[[[^]]]]" is accepted. + listNest++; + if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; + if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; + break; + case ']': + if (listNest == 0) + listEndIndex = listIndex; + listNest--; + break; + } + } + if (listEndIndex >= 0) { + nextIndex = listEndIndex; + if ((endIndex = nextIndex) >= pLength) + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + else + continue; + } + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + } + else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) + nested++; + else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) + nested--; + } + + // endIndex is now position at a ')','\)' + // nextIndex is end of string or position after ')' or '\)' + + if (comment) index = nextIndex; + else { // not a comment + // create RE subexpression as token. + addToken(currentToken); + if (!pure) { + numSubs++; + } + + int useIndex = (pure || lookAhead || lookBehind || independent) ? + 0 : nextSub + numSubs; + currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub + numSubs); + numSubs += ((RE) currentToken).getNumSubs(); + + if (lookAhead) { + currentToken = new RETokenLookAhead(currentToken,negativelh); + } + else if (lookBehind) { + currentToken = new RETokenLookBehind(currentToken,negativelb); + } + else if (independent) { + currentToken = new RETokenIndependent(currentToken); + } + + index = nextIndex; + if (flagsSaved) { + syntax = savedSyntax; + cflags = savedCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + flagsSaved = false; + } + } // not a comment + } // subexpression + + // UNMATCHED RIGHT PAREN + // ) or \) throw exception if + // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) + else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot)))) { + throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index); + } + + // START OF LINE OPERATOR + // ^ + + else if ((unit.ch == '^') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = null; + RETokenStart token = null; + if ((cflags & REG_MULTILINE) > 0) { + String sep = syntax.getLineSeparator(); + if (sep == null) { + token = new RETokenStart(subIndex, null, true); + } + else { + token = new RETokenStart(subIndex, sep); + } + } + else { + token = new RETokenStart(subIndex, null); + } + addToken(token); + } + + // END OF LINE OPERATOR + // $ + + else if ((unit.ch == '$') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = null; + RETokenEnd token = null; + if ((cflags & REG_MULTILINE) > 0) { + String sep = syntax.getLineSeparator(); + if (sep == null) { + token = new RETokenEnd(subIndex, null, true); + } + else { + token = new RETokenEnd(subIndex, sep); + } + } + else { + token = new RETokenEnd(subIndex, null); + } + addToken(token); + } + + // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null) + // . + + else if ((unit.ch == '.') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = new RETokenAny(subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL)); + } + + // ZERO-OR-MORE REPEAT OPERATOR + // * + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)*", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '*') && !(unit.bk || quot)) { + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + if (currentToken instanceof RETokenRepeated) + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index); + } + + // ONE-OR-MORE REPEAT OPERATOR / POSSESSIVE MATCHING OPERATOR + // + | \+ depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)+", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) { + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + + // Check for possessive matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) { + RETokenRepeated tokenRep = (RETokenRepeated)currentToken; + if (syntax.get(RESyntax.RE_POSSESSIVE_OPS) && !tokenRep.isPossessive() && !tokenRep.isStingy()) + tokenRep.makePossessive(); + else + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + + } + else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + else + currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index); + } + + // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR + // ? | \? depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // stingy matching if RE_STINGY_OPS is set and it follows a quantifier + + else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) { + if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + + // Check for stingy matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) { + RETokenRepeated tokenRep = (RETokenRepeated)currentToken; + if (syntax.get(RESyntax.RE_STINGY_OPS) && !tokenRep.isStingy() && !tokenRep.isPossessive()) + tokenRep.makeStingy(); + else + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + } + else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + else + currentToken = setRepeated(currentToken,0,1,index); + } + + // OCTAL CHARACTER + // \0377 + + else if (unit.bk && (unit.ch == '0') && syntax.get(RESyntax.RE_OCTAL_CHAR)) { + CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException("invalid octal character", REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken(currentToken); + currentToken = new RETokenChar(subIndex,ce.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // BACKREFERENCE OPERATOR + // \1 \2 ... \9 and \10 \11 \12 ... + // not available if RE_NO_BK_REFS is set + // Perl recognizes \10, \11, and so on only if enough number of + // parentheses have opened before it, otherwise they are treated + // as aliases of \010, \011, ... (octal characters). In case of + // Sun's JDK, octal character expression must always begin with \0. + // We will do as JDK does. But FIXME, take a look at "(a)(b)\29". + // JDK treats \2 as a back reference to the 2nd group because + // there are only two groups. But in our poor implementation, + // we cannot help but treat \29 as a back reference to the 29th group. + + else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) { + addToken(currentToken); + int numBegin = index - 1; + int numEnd = pLength; + for (int i = index; i < pLength; i++) { + if (! Character.isDigit(pattern[i])) { + numEnd = i; + break; + } + } + int num = parseInt(pattern, numBegin, numEnd-numBegin, 10); + + currentToken = new RETokenBackRef(subIndex,num,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + index = numEnd; + } + + // START OF STRING OPERATOR + // \A if RE_STRING_ANCHORS is set + + else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenStart(subIndex,null); + } + + // WORD BREAK OPERATOR + // \b if ???? + + else if (unit.bk && (unit.ch == 'b') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, false); + } + + // WORD BEGIN OPERATOR + // \< if ???? + else if (unit.bk && (unit.ch == '<')) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN, false); + } + + // WORD END OPERATOR + // \> if ???? + else if (unit.bk && (unit.ch == '>')) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.END, false); + } + + // NON-WORD BREAK OPERATOR + // \B if ???? + + else if (unit.bk && (unit.ch == 'B') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, true); + } + + + // DIGIT OPERATOR + // \d if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-DIGIT OPERATOR + // \D + + else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NEWLINE ESCAPE + // \n + + else if (unit.bk && (unit.ch == 'n')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\n',false); + } + + // RETURN ESCAPE + // \r + + else if (unit.bk && (unit.ch == 'r')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\r',false); + } + + // WHITESPACE OPERATOR + // \s if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-WHITESPACE OPERATOR + // \S + + else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // TAB ESCAPE + // \t + + else if (unit.bk && (unit.ch == 't')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\t',false); + } + + // ALPHANUMERIC OPERATOR + // \w + + else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-ALPHANUMERIC OPERATOR + // \W + + else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // END OF STRING OPERATOR + // \Z, \z + + // FIXME: \Z and \z are different in that if the input string + // ends with a line terminator, \Z matches the position before + // the final terminator. This special behavior of \Z is yet + // to be implemented. + + else if (unit.bk && (unit.ch == 'Z' || unit.ch == 'z') && + syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenEnd(subIndex,null); + } + + // HEX CHARACTER, UNICODE CHARACTER + // \x1B, \u1234 + + else if ((unit.bk && (unit.ch == 'x') && syntax.get(RESyntax.RE_HEX_CHAR)) || + (unit.bk && (unit.ch == 'u') && syntax.get(RESyntax.RE_UNICODE_CHAR))) { + CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException("invalid hex character", REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken(currentToken); + currentToken = new RETokenChar(subIndex,ce.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NAMED PROPERTY + // \p{prop}, \P{prop} + + else if ((unit.bk && (unit.ch == 'p') && syntax.get(RESyntax.RE_NAMED_PROPERTY)) || + (unit.bk && (unit.ch == 'P') && syntax.get(RESyntax.RE_NAMED_PROPERTY))) { + NamedProperty np = getNamedProperty(pattern, index - 2, pLength); + if (np == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + index = index - 2 + np.len; + addToken(currentToken); + currentToken = getRETokenNamedProperty(subIndex,np,insens,index); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // END OF PREVIOUS MATCH + // \G + + else if (unit.bk && (unit.ch == 'G') && + syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenEndOfPreviousMatch(subIndex); + } + + // NON-SPECIAL CHARACTER (or escape to make literal) + // c | \* for example + + else { // not a special character + addToken(currentToken); + currentToken = new RETokenChar(subIndex,unit.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + } // end while + + // Add final buffered token and an EndSub marker + addToken(currentToken); + + if (branches != null) { + branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength, maximumLength)); + branches.trimToSize(); // compact the Vector + minimumLength = 0; + maximumLength = 0; + firstToken = lastToken = null; + addToken(new RETokenOneOf(subIndex,branches,false)); + } + else addToken(new RETokenEndSub(subIndex)); + + } + + private static class ParseCharClassResult { + RETokenOneOf token; + int index; + boolean returnAtAndOperator = false; + } + + /** + * Parse [...] or [^...] and make an RETokenOneOf instance. + * @param subIndex subIndex to be given to the created RETokenOneOf instance. + * @param pattern Input array of characters to be parsed. + * @param index Index pointing to the character next to the beginning '['. + * @param pLength Limit of the input array. + * @param cflags Compilation flags used to parse the pattern. + * @param pflags Flags that affect the behavior of this method. + * @param syntax Syntax used to parse the pattern. + */ + private static ParseCharClassResult parseCharClass(int subIndex, + char[] pattern, int index, + int pLength, int cflags, RESyntax syntax, int pflags) + throws REException { + + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + Vector options = new Vector(); + Vector addition = new Vector(); + boolean additionAndAppeared = false; + final int RETURN_AT_AND = 0x01; + boolean returnAtAndOperator = ((pflags & RETURN_AT_AND) != 0); + boolean negative = false; + char ch; + + char lastChar = 0; + boolean lastCharIsSet = false; + if (index == pLength) throw new REException(getLocalizedMessage("unmatched.bracket"),REException.REG_EBRACK,index); + + // Check for initial caret, negation + if ((ch = pattern[index]) == '^') { + negative = true; + if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + ch = pattern[index]; + } + + // Check for leading right bracket literal + if (ch == ']') { + lastChar = ch; lastCharIsSet = true; + if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + } + + while ((ch = pattern[index++]) != ']') { + if ((ch == '-') && (lastCharIsSet)) { + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + if ((ch = pattern[index]) == ']') { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + lastChar = '-'; + } else { + if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { + CharExpression ce = getCharExpression(pattern, index, pLength, syntax); + if (ce == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + ch = ce.ch; + index = index + ce.len - 1; + } + RETokenRange t = new RETokenRange(subIndex,lastChar,ch,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + lastChar = 0; lastCharIsSet = false; + index++; + } + } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + int posixID = -1; + boolean negate = false; + char asciiEsc = 0; + boolean asciiEscIsSet = false; + NamedProperty np = null; + if (("dswDSW".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) { + switch (pattern[index]) { + case 'D': + negate = true; + case 'd': + posixID = RETokenPOSIX.DIGIT; + break; + case 'S': + negate = true; + case 's': + posixID = RETokenPOSIX.SPACE; + break; + case 'W': + negate = true; + case 'w': + posixID = RETokenPOSIX.ALNUM; + break; + } + } + if (("pP".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_NAMED_PROPERTY)) { + np = getNamedProperty(pattern, index - 1, pLength); + if (np == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + index = index - 1 + np.len - 1; + } + else { + CharExpression ce = getCharExpression(pattern, index - 1, pLength, syntax); + if (ce == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + asciiEsc = ce.ch; asciiEscIsSet = true; + index = index - 1 + ce.len - 1; + } + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + + if (posixID != -1) { + RETokenPOSIX t = new RETokenPOSIX(subIndex,posixID,insens,negate); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } else if (np != null) { + RETokenNamedProperty t = getRETokenNamedProperty(subIndex,np,insens,index); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } else if (asciiEscIsSet) { + lastChar = asciiEsc; lastCharIsSet = true; + } else { + lastChar = pattern[index]; lastCharIsSet = true; + } + ++index; + } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (index < pLength) && (pattern[index] == ':')) { + StringBuffer posixSet = new StringBuffer(); + index = getPosixSet(pattern,index+1,posixSet); + int posixId = RETokenPOSIX.intValue(posixSet.toString()); + if (posixId != -1) { + RETokenPOSIX t = new RETokenPOSIX(subIndex,posixId,insens,false); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + } else if ((ch == '[') && (syntax.get(RESyntax.RE_NESTED_CHARCLASS))) { + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index, pLength, cflags, syntax, 0); + addition.addElement(result.token); + addition.addElement("|"); + index = result.index; + } else if ((ch == '&') && + (syntax.get(RESyntax.RE_NESTED_CHARCLASS)) && + (index < pLength) && (pattern[index] == '&')) { + if (returnAtAndOperator) { + ParseCharClassResult result = new ParseCharClassResult(); + options.trimToSize(); + if (additionAndAppeared) addition.addElement("&"); + if (addition.size() == 0) addition = null; + result.token = new RETokenOneOf(subIndex, + options, addition, negative); + result.index = index - 1; + result.returnAtAndOperator = true; + return result; + } + // The precedence of the operator "&&" is the lowest. + // So we postpone adding "&" until other elements + // are added. And we insert Boolean.FALSE at the + // beginning of the list of tokens following "&&". + // So, "&&[a-b][k-m]" will be stored in the Vecter + // addition in this order: + // Boolean.FALSE, [a-b], "|", [k-m], "|", "&" + if (additionAndAppeared) addition.addElement("&"); + addition.addElement(Boolean.FALSE); + additionAndAppeared = true; + + // The part on which "&&" operates may be either + // (1) explicitly enclosed by [] + // or + // (2) not enclosed by [] and terminated by the + // next "&&" or the end of the character list. + // Let the preceding else if block do the case (1). + // We must do something in case of (2). + if ((index + 1 < pLength) && (pattern[index + 1] != '[')) { + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index+1, pLength, cflags, syntax, + RETURN_AT_AND); + addition.addElement(result.token); + addition.addElement("|"); + // If the method returned at the next "&&", it is OK. + // Otherwise we have eaten the mark of the end of this + // character list "]". In this case we must give back + // the end mark. + index = (result.returnAtAndOperator ? + result.index: result.index - 1); + } + } else { + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + lastChar = ch; lastCharIsSet = true; + } + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + } // while in list + // Out of list, index is one past ']' + + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + + ParseCharClassResult result = new ParseCharClassResult(); + // Create a new RETokenOneOf + options.trimToSize(); + if (additionAndAppeared) addition.addElement("&"); + if (addition.size() == 0) addition = null; + result.token = new RETokenOneOf(subIndex,options, addition, negative); + result.index = index; + return result; + } + + private static int getCharUnit(char[] input, int index, CharUnit unit, boolean quot) throws REException { + unit.ch = input[index++]; + unit.bk = (unit.ch == '\\' + && (!quot || index >= input.length || input[index] == 'E')); + if (unit.bk) + if (index < input.length) + unit.ch = input[index++]; + else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index); + return index; + } + + private static int parseInt(char[] input, int pos, int len, int radix) { + int ret = 0; + for (int i = pos; i < pos + len; i++) { + ret = ret * radix + Character.digit(input[i], radix); + } + return ret; + } + + /** + * This class represents various expressions for a character. + * "a" : 'a' itself. + * "\0123" : Octal char 0123 + * "\x1b" : Hex char 0x1b + * "\u1234" : Unicode char \u1234 + */ + private static class CharExpression { + /** character represented by this expression */ + char ch; + /** String expression */ + String expr; + /** length of this expression */ + int len; + public String toString() { return expr; } + } + + private static CharExpression getCharExpression(char[] input, int pos, int lim, + RESyntax syntax) { + CharExpression ce = new CharExpression(); + char c = input[pos]; + if (c == '\\') { + if (pos + 1 >= lim) return null; + c = input[pos + 1]; + switch(c) { + case 't': + ce.ch = '\t'; + ce.len = 2; + break; + case 'n': + ce.ch = '\n'; + ce.len = 2; + break; + case 'r': + ce.ch = '\r'; + ce.len = 2; + break; + case 'x': + case 'u': + if ((c == 'x' && syntax.get(RESyntax.RE_HEX_CHAR)) || + (c == 'u' && syntax.get(RESyntax.RE_UNICODE_CHAR))) { + int l = 0; + int expectedLength = (c == 'x' ? 2 : 4); + for (int i = pos + 2; i < pos + 2 + expectedLength; i++) { + if (i >= lim) break; + if (!((input[i] >= '0' && input[i] <= '9') || + (input[i] >= 'A' && input[i] <= 'F') || + (input[i] >= 'a' && input[i] <= 'f'))) + break; + l++; + } + if (l != expectedLength) return null; + ce.ch = (char)(parseInt(input, pos + 2, l, 16)); + ce.len = l + 2; + } + else { + ce.ch = c; + ce.len = 2; + } + break; + case '0': + if (syntax.get(RESyntax.RE_OCTAL_CHAR)) { + int l = 0; + for (int i = pos + 2; i < pos + 2 + 3; i++) { + if (i >= lim) break; + if (input[i] < '0' || input[i] > '7') break; + l++; + } + if (l == 3 && input[pos + 2] > '3') l--; + if (l <= 0) return null; + ce.ch = (char)(parseInt(input, pos + 2, l, 8)); + ce.len = l + 2; + } + else { + ce.ch = c; + ce.len = 2; + } + break; + default: + ce.ch = c; + ce.len = 2; + break; + } + } + else { + ce.ch = input[pos]; + ce.len = 1; + } + ce.expr = new String(input, pos, ce.len); + return ce; + } + + /** + * This class represents a substring in a pattern string expressing + * a named property. + * "\pA" : Property named "A" + * "\p{prop}" : Property named "prop" + * "\PA" : Property named "A" (Negated) + * "\P{prop}" : Property named "prop" (Negated) + */ + private static class NamedProperty { + /** Property name */ + String name; + /** Negated or not */ + boolean negate; + /** length of this expression */ + int len; + } + + private static NamedProperty getNamedProperty(char[] input, int pos, int lim) { + NamedProperty np = new NamedProperty(); + char c = input[pos]; + if (c == '\\') { + if (++pos >= lim) return null; + c = input[pos++]; + switch(c) { + case 'p': + np.negate = false; + break; + case 'P': + np.negate = true; + break; + default: + return null; + } + c = input[pos++]; + if (c == '{') { + int p = -1; + for (int i = pos; i < lim; i++) { + if (input[i] == '}') { + p = i; + break; + } + } + if (p < 0) return null; + int len = p - pos; + np.name = new String(input, pos, len); + np.len = len + 4; + } + else { + np.name = new String(input, pos - 1, 1); + np.len = 3; + } + return np; + } + else return null; + } + + private static RETokenNamedProperty getRETokenNamedProperty( + int subIndex, NamedProperty np, boolean insens, int index) + throws REException { + try { + return new RETokenNamedProperty(subIndex, np.name, insens, np.negate); + } + catch (REException e) { + REException ree; + ree = new REException(e.getMessage(), REException.REG_ESCAPE, index); + ree.initCause(e); + throw ree; + } + } + + /** + * Checks if the regular expression matches the input in its entirety. + * + * @param input The input text. + */ + public boolean isMatch(Object input) { + return isMatch(input,0,0); + } + + /** + * Checks if the input string, starting from index, is an exact match of + * this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + */ + public boolean isMatch(Object input,int index) { + return isMatch(input,index,0); + } + + + /** + * Checks if the input, starting from index and using the specified + * execution flags, is an exact match of this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + */ + public boolean isMatch(Object input,int index,int eflags) { + return isMatchImpl(makeCharIndexed(input,index),index,eflags); + } + + private boolean isMatchImpl(CharIndexed input, int index, int eflags) { + if (firstToken == null) // Trivial case + return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS); + REMatch m = new REMatch(numSubs, index, eflags); + if (firstToken.match(input, m)) { + if (m != null) { + if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) { + return true; + } + } + } + return false; + } + + /** + * Returns the maximum number of subexpressions in this regular expression. + * If the expression contains branches, the value returned will be the + * maximum subexpressions in any of the branches. + */ + public int getNumSubs() { + return numSubs; + } + + // Overrides REToken.setUncle + void setUncle(REToken uncle) { + if (lastToken != null) { + lastToken.setUncle(uncle); + } else super.setUncle(uncle); // to deal with empty subexpressions + } + + // Overrides REToken.chain + + boolean chain(REToken next) { + super.chain(next); + setUncle(next); + return true; + } + + /** + * Returns the minimum number of characters that could possibly + * constitute a match of this regular expression. + */ + public int getMinimumLength() { + return minimumLength; + } + + public int getMaximumLength() { + return maximumLength; + } + + /** + * Returns an array of all matches found in the input. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input) { + return getAllMatches(input,0,0); + } + + /** + * Returns an array of all matches found in the input, + * beginning at the specified index position. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input, int index) { + return getAllMatches(input,index,0); + } + + /** + * Returns an array of all matches found in the input string, + * beginning at the specified index position and using the specified + * execution flags. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input, int index, int eflags) { + return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags); + } + + // this has been changed since 1.03 to be non-overlapping matches + private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) { + Vector all = new Vector(); + REMatch m = null; + while ((m = getMatchImpl(input,index,eflags,null)) != null) { + all.addElement(m); + index = m.getEndIndex(); + if (m.end[0] == 0) { // handle pathological case of zero-length match + index++; + input.move(1); + } else { + input.move(m.end[0]); + } + if (!input.isValid()) break; + } + REMatch[] mset = new REMatch[all.size()]; + all.copyInto(mset); + return mset; + } + + /* Implements abstract method REToken.match() */ + boolean match(CharIndexed input, REMatch mymatch) { + if (firstToken == null) { + return next(input, mymatch); + } + + // Note the start of this subexpression + mymatch.start1[subIndex] = mymatch.index; + + return firstToken.match(input, mymatch); + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + boolean b = match(input, mymatch); + if (b) { + return mymatch; + } + return null; + } + + /** + * Returns the first match found in the input. If no match is found, + * null is returned. + * + * @param input The input text. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input) { + return getMatch(input,0,0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index. If no match is found, + * returns null. + * + * @param input The input text. + * @param index The offset within the text to begin looking for a match. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input, int index) { + return getMatch(input,index,0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index, and using the specified + * execution flags. If no match is found, returns null. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input, int index, int eflags) { + return getMatch(input,index,eflags,null); + } + + /** + * Returns the first match found in the input, beginning the search + * at the specified index, and using the specified execution flags. + * If no match is found, returns null. If a StringBuffer is + * provided and is non-null, the contents of the input text from the + * index to the beginning of the match (or to the end of the input, + * if there is no match) are appended to the StringBuffer. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @param buffer The StringBuffer to save pre-match text in. + * @return An REMatch instance referencing the match, or null if none. */ + public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) { + return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer); + } + + REMatch getMatchImpl(CharIndexed input, int anchor, int eflags, StringBuffer buffer) { + boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); + RE re = (tryEntireMatch ? (RE) this.clone() : this); + if (tryEntireMatch) { + re.chain(new RETokenEnd(0, null)); + } + // Create a new REMatch to hold results + REMatch mymatch = new REMatch(numSubs, anchor, eflags); + do { + // Optimization: check if anchor + minimumLength > length + if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) { + if (re.match(input, mymatch)) { + REMatch best = mymatch; + // We assume that the match that coms first is the best. + // And the following "The longer, the better" rule has + // been commented out. The longest is not neccesarily + // the best. For example, "a" out of "aaa" is the best + // match for /a+?/. + /* + // Find best match of them all to observe leftmost longest + while ((mymatch = mymatch.next) != null) { + if (mymatch.index > best.index) { + best = mymatch; + } + } + */ + best.end[0] = best.index; + best.finish(input); + input.setLastMatch(best); + return best; + } + } + mymatch.clear(++anchor); + // Append character to buffer if needed + if (buffer != null && input.charAt(0) != CharIndexed.OUT_OF_BOUNDS) { + buffer.append(input.charAt(0)); + } + } while (input.move(1)); + + // Special handling at end of input for e.g. "$" + if (minimumLength == 0) { + if (match(input, mymatch)) { + mymatch.finish(input); + return mymatch; + } + } + + return null; + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @return A non-null REMatchEnumeration instance. + */ + public REMatchEnumeration getMatchEnumeration(Object input) { + return getMatchEnumeration(input,0,0); + } + + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration(Object input, int index) { + return getMatchEnumeration(input,index,0); + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) { + return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags); + } + + + /** + * Substitutes the replacement text for the first match found in the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace) { + return substitute(input,replace,0,0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * beginning at the specified index position. Specifying an index + * effectively causes the regular expression engine to throw away the + * specified number of characters. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace,int index) { + return substitute(input,replace,index,0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * string, beginning at the specified index position and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace,int index,int eflags) { + return substituteImpl(makeCharIndexed(input,index),replace,index,eflags); + } + + private String substituteImpl(CharIndexed input,String replace,int index,int eflags) { + StringBuffer buffer = new StringBuffer(); + REMatch m = getMatchImpl(input,index,eflags,buffer); + if (m==null) return buffer.toString(); + buffer.append(getReplacement(replace, m, eflags)); + if (input.move(m.end[0])) { + do { + buffer.append(input.charAt(0)); + } while (input.move(1)); + } + return buffer.toString(); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace) { + return substituteAll(input,replace,0,0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace,int index) { + return substituteAll(input,replace,index,0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace,int index,int eflags) { + return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags); + } + + private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) { + StringBuffer buffer = new StringBuffer(); + REMatch m; + while ((m = getMatchImpl(input,index,eflags,buffer)) != null) { + buffer.append(getReplacement(replace, m, eflags)); + index = m.getEndIndex(); + if (m.end[0] == 0) { + char ch = input.charAt(0); + if (ch != CharIndexed.OUT_OF_BOUNDS) + buffer.append(ch); + input.move(1); + } else { + input.move(m.end[0]); + } + + if (!input.isValid()) break; + } + return buffer.toString(); + } + + public static String getReplacement(String replace, REMatch m, int eflags) { + if ((eflags & REG_NO_INTERPOLATE) > 0) + return replace; + else { + if ((eflags & REG_REPLACE_USE_BACKSLASHESCAPE) > 0) { + StringBuffer sb = new StringBuffer(); + int l = replace.length(); + for (int i = 0; i < l; i++) { + char c = replace.charAt(i); + switch(c) { + case '\\': + i++; + // Let StringIndexOutOfBoundsException be thrown. + sb.append(replace.charAt(i)); + break; + case '$': + int i1 = i + 1; + while (i1 < replace.length() && + Character.isDigit(replace.charAt(i1))) i1++; + sb.append(m.substituteInto(replace.substring(i, i1))); + i = i1 - 1; + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + else + return m.substituteInto(replace); + } + } + + /* Helper function for constructor */ + private void addToken(REToken next) { + if (next == null) return; + minimumLength += next.getMinimumLength(); + int nmax = next.getMaximumLength(); + if (nmax < Integer.MAX_VALUE && maximumLength < Integer.MAX_VALUE) + maximumLength += nmax; + else + maximumLength = Integer.MAX_VALUE; + + if (firstToken == null) { + lastToken = firstToken = next; + } else { + // if chain returns false, it "rejected" the token due to + // an optimization, and next was combined with lastToken + if (lastToken.chain(next)) { + lastToken = next; + } + } + } + + private static REToken setRepeated(REToken current, int min, int max, int index) throws REException { + if (current == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + return new RETokenRepeated(current.subIndex,current,min,max); + } + + private static int getPosixSet(char[] pattern,int index,StringBuffer buf) { + // Precondition: pattern[index-1] == ':' + // we will return pos of closing ']'. + int i; + for (i=index; i<(pattern.length-1); i++) { + if ((pattern[i] == ':') && (pattern[i+1] == ']')) + return i+2; + buf.append(pattern[i]); + } + return index; // didn't match up + } + + private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException { + // Precondition: input[index-1] == '{', minMax != null + + boolean mustMatch = !syntax.get(RESyntax.RE_NO_BK_BRACES); + int startIndex = index; + if (index == input.length) { + if (mustMatch) + throw new REException(getLocalizedMessage("unmatched.brace"),REException.REG_EBRACE,index); + else + return startIndex; + } + + int min,max=0; + CharUnit unit = new CharUnit(); + StringBuffer buf = new StringBuffer(); + + // Read string of digits + do { + index = getCharUnit(input,index,unit,false); + if (Character.isDigit(unit.ch)) + buf.append(unit.ch); + } while ((index != input.length) && Character.isDigit(unit.ch)); + + // Check for {} tomfoolery + if (buf.length() == 0) { + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + } + + min = Integer.parseInt(buf.toString()); + + if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) + max = min; + else if (index == input.length) + if (mustMatch) + throw new REException(getLocalizedMessage("interval.no.end"),REException.REG_EBRACE,index); + else + return startIndex; + else if ((unit.ch == ',') && !unit.bk) { + buf = new StringBuffer(); + // Read string of digits + while (((index = getCharUnit(input,index,unit,false)) != input.length) && Character.isDigit(unit.ch)) + buf.append(unit.ch); + + if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))) + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + + // This is the case of {x,} + if (buf.length() == 0) max = Integer.MAX_VALUE; + else max = Integer.parseInt(buf.toString()); + } else + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + + // We know min and max now, and they are valid. + + minMax.first = min; + minMax.second = max; + + // return the index following the '}' + return index; + } + + /** + * Return a human readable form of the compiled regular expression, + * useful for debugging. + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + dump(sb); + return sb.toString(); + } + + void dump(StringBuffer os) { + os.append("(?#startRE subIndex=" + subIndex + ")"); + if (subIndex == 0) + os.append("?:"); + if (firstToken != null) + firstToken.dumpAll(os); + if (subIndex == 0) + os.append(")"); + os.append("(?#endRE subIndex=" + subIndex + ")"); + } + + // Cast input appropriately or throw exception + // This method was originally a private method, but has been made + // public because java.util.regex.Matcher uses this. + public static CharIndexed makeCharIndexed(Object input, int index) { + // The case where input is already a CharIndexed is supposed + // be the most likely because this is the case with + // java.util.regex.Matcher. + // We could let a String or a CharSequence fall through + // to final input, but since it'a very likely input type, + // we check it first. + if (input instanceof CharIndexed) { + CharIndexed ci = (CharIndexed) input; + ci.setAnchor(index); + return ci; + } + else if (input instanceof CharSequence) + return new CharIndexedCharSequence((CharSequence) input,index); + else if (input instanceof String) + return new CharIndexedString((String) input,index); + else if (input instanceof char[]) + return new CharIndexedCharArray((char[]) input,index); + else if (input instanceof StringBuffer) + return new CharIndexedStringBuffer((StringBuffer) input,index); + else if (input instanceof InputStream) + return new CharIndexedInputStream((InputStream) input,index); + else + return new CharIndexedString(input.toString(), index); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/REException.java b/libjava/classpath/gnu/java/util/regex/REException.java new file mode 100644 index 00000000000..4104fbcd8a7 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REException.java @@ -0,0 +1,182 @@ +/* gnu/regexp/REException.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +import java.text.MessageFormat; + +/** + * This is the regular expression exception class. An exception of this type + * defines the three attributes: + * <OL> + * <LI> A descriptive message of the error. + * <LI> An integral type code equivalent to one of the statically + * defined symbols listed below. + * <LI> The approximate position in the input string where the error + * occurred. + * </OL> + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ + +public class REException extends Exception { + private int type; + private int pos; + + // Error conditions from GNU regcomp(3) manual + + /** + * Error flag. + * Invalid use of repetition operators such as using + * `*' as the first character. + */ + public static final int REG_BADRPT = 1; + + /** + * Error flag. + * Invalid use of back reference operator. + */ + public static final int REG_BADBR = 2; + + /** + * Error flag. + * Un-matched brace interval operators. + */ + public static final int REG_EBRACE = 3; + + /** + * Error flag. + * Un-matched bracket list operators. + */ + public static final int REG_EBRACK = 4; + + /** + * Error flag. + * Invalid use of the range operator, eg. the ending + * point of the range occurs prior to the starting + * point. + */ + public static final int REG_ERANGE = 5; + + /** + * Error flag. + * Unknown character class name. <B>Not implemented</B>. + */ + public static final int REG_ECTYPE = 6; + + /** + * Error flag. + * Un-matched parenthesis group operators. + */ + public static final int REG_EPAREN = 7; + + /** + * Error flag. + * Invalid back reference to a subexpression. + */ + public static final int REG_ESUBREG = 8; + + /** + * Error flag. + * Non specific error. <B>Not implemented</B>. + */ + public static final int REG_EEND = 9; + + /** + * Error flag. + * Invalid escape sequence. <B>Not implemented</B>. + */ + public static final int REG_ESCAPE = 10; + + /** + * Error flag. + * Invalid use of pattern operators such as group or list. + */ + public static final int REG_BADPAT = 11; + + /** + * Error flag. + * Compiled regular expression requires a pattern + * buffer larger than 64Kb. <B>Not implemented</B>. + */ + public static final int REG_ESIZE = 12; + + /** + * Error flag. + * The regex routines ran out of memory. <B>Not implemented</B>. + */ + public static final int REG_ESPACE = 13; + + REException(String msg, int type, int position) { + super(msg); + this.type = type; + this.pos = position; + } + + /** + * Returns the type of the exception, one of the constants listed above. + */ + + public int getType() { + return type; + } + + /** + * Returns the position, relative to the string or character array being + * compiled, where the error occurred. This position is generally the point + * where the error was detected, not necessarily the starting index of + * a bad subexpression. + */ + public int getPosition() { + return pos; + } + + /** + * Reports the descriptive message associated with this exception + * as well as its index position in the string or character array + * being compiled. + */ + public String getMessage() { + Object[] args = {new Integer(pos)}; + StringBuffer sb = new StringBuffer(); + String prefix = RE.getLocalizedMessage("error.prefix"); + sb.append(MessageFormat.format(prefix, args)); + sb.append('\n'); + sb.append(super.getMessage()); + return sb.toString(); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/REFilterInputStream.java b/libjava/classpath/gnu/java/util/regex/REFilterInputStream.java new file mode 100644 index 00000000000..abe86308bbc --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REFilterInputStream.java @@ -0,0 +1,140 @@ +/* gnu/regexp/REFilterInputStream.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * Replaces instances of a given RE found within an InputStream + * with replacement text. The replacements are interpolated into the + * stream when a match is found. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @deprecated This class cannot properly handle all character + * encodings. For proper handling, use the REFilterReader + * class instead. + */ + +public class REFilterInputStream extends FilterInputStream { + + private RE expr; + private String replace; + private String buffer; + private int bufpos; + private int offset; + private CharIndexedInputStream stream; + + /** + * Creates an REFilterInputStream. When reading from this stream, + * occurrences of patterns matching the supplied regular expression + * will be replaced with the supplied replacement text (the + * metacharacters $0 through $9 may be used to refer to the full + * match or subexpression matches). + * + * @param stream The InputStream to be filtered. + * @param expr The regular expression to search for. + * @param replace The text pattern to replace matches with. + */ + public REFilterInputStream(InputStream stream, RE expr, String replace) { + super(stream); + this.stream = new CharIndexedInputStream(stream,0); + this.expr = expr; + this.replace = replace; + } + + /** + * Reads the next byte from the stream per the general contract of + * InputStream.read(). Returns -1 on error or end of stream. + */ + public int read() { + // If we have buffered replace data, use it. + if ((buffer != null) && (bufpos < buffer.length())) { + return (int) buffer.charAt(bufpos++); + } + + // check if input is at a valid position + if (!stream.isValid()) return -1; + + REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0); + if (expr.match(stream, mymatch)) { + mymatch.end[0] = mymatch.index; + mymatch.finish(stream); + stream.move(mymatch.toString().length()); + offset += mymatch.toString().length(); + buffer = mymatch.substituteInto(replace); + bufpos = 1; + + // This is prone to infinite loops if replace string turns out empty. + if (buffer.length() > 0) { + return buffer.charAt(0); + } + } + char ch = stream.charAt(0); + if (ch == CharIndexed.OUT_OF_BOUNDS) return -1; + stream.move(1); + offset++; + return ch; + } + + /** + * Returns false. REFilterInputStream does not support mark() and + * reset() methods. + */ + public boolean markSupported() { + return false; + } + + /** Reads from the stream into the provided array. */ + public int read(byte[] b, int off, int len) { + int i; + int ok = 0; + while (len-- > 0) { + i = read(); + if (i == -1) return (ok == 0) ? -1 : ok; + b[off++] = (byte) i; + ok++; + } + return ok; + } + + /** Reads from the stream into the provided array. */ + public int read(byte[] b) { + return read(b,0,b.length); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/REMatch.java b/libjava/classpath/gnu/java/util/regex/REMatch.java new file mode 100644 index 00000000000..3ff5ad794b8 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REMatch.java @@ -0,0 +1,324 @@ +/* gnu/regexp/REMatch.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; +import java.io.Serializable; + +/** + * An instance of this class represents a match + * completed by a gnu.regexp matching function. It can be used + * to obtain relevant information about the location of a match + * or submatch. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public final class REMatch implements Serializable, Cloneable { + private String matchedText; + private CharIndexed matchedCharIndexed; + + // These variables are package scope for fast access within the engine + int eflags; // execution flags this match was made using + + // Offset in source text where match was tried. This is zero-based; + // the actual position in the source text is given by (offset + anchor). + int offset; + + // Anchor position refers to the index into the source input + // at which the matching operation began. + // This is also useful for the ANCHORINDEX option. + int anchor; + + // Package scope; used by RE. + int index; // used while matching to mark current match position in input + // start1[i] is set when the i-th subexp starts. And start1[i] is copied + // to start[i] when the i-th subexp ends. So start[i] keeps the previously + // assigned value while the i-th subexp is being processed. This makes + // backreference to the i-th subexp within the i-th subexp possible. + int[] start; // start positions (relative to offset) for each (sub)exp. + int[] start1; // start positions (relative to offset) for each (sub)exp. + int[] end; // end positions for the same + // start[i] == -1 or end[i] == -1 means that the start/end position is void. + // start[i] == p or end[i] == p where p < 0 and p != -1 means that + // the actual start/end position is (p+1). Start/end positions may + // become negative when the subexpression is in a RETokenLookBehind. + boolean empty; // empty string matched. This flag is used only within + // RETokenRepeated. + + BacktrackStack backtrackStack; + + public Object clone() { + try { + REMatch copy = (REMatch) super.clone(); + + copy.start = (int[]) start.clone(); + copy.start1 = (int[]) start1.clone(); + copy.end = (int[]) end.clone(); + + return copy; + } catch (CloneNotSupportedException e) { + throw new Error(); // doesn't happen + } + } + + void assignFrom(REMatch other) { + start = other.start; + start1 = other.start1; + end = other.end; + index = other.index; + backtrackStack = other.backtrackStack; + } + + REMatch(int subs, int anchor, int eflags) { + start = new int[subs+1]; + start1 = new int[subs+1]; + end = new int[subs+1]; + this.anchor = anchor; + this.eflags = eflags; + clear(anchor); + } + + void finish(CharIndexed text) { + start[0] = 0; + StringBuffer sb = new StringBuffer(); + int i; + for (i = 0; i < end[0]; i++) + sb.append(text.charAt(i)); + matchedText = sb.toString(); + matchedCharIndexed = text; + for (i = 0; i < start.length; i++) { + // If any subexpressions didn't terminate, they don't count + // TODO check if this code ever gets hit + if ((start[i] == -1) ^ (end[i] == -1)) { + start[i] = -1; + end[i] = -1; + } + } + backtrackStack = null; + } + + /** Clears the current match and moves the offset to the new index. */ + void clear(int index) { + offset = index; + this.index = 0; + for (int i = 0; i < start.length; i++) { + start[i] = start1[i] = end[i] = -1; + } + backtrackStack = null; + } + + /** + * Returns the string matching the pattern. This makes it convenient + * to write code like the following: + * <P> + * <code> + * REMatch myMatch = myExpression.getMatch(myString);<br> + * if (myMatch != null) System.out.println("Regexp found: "+myMatch); + * </code> + */ + public String toString() { + return matchedText; + } + + /** + * Returns the index within the input text where the match in its entirety + * began. + */ + public int getStartIndex() { + return offset + start[0]; + } + + /** + * Returns the index within the input string where the match in + * its entirety ends. The return value is the next position after + * the end of the string; therefore, a match created by the + * following call: + * + * <P> + * <code>REMatch myMatch = myExpression.getMatch(myString);</code> + * <P> + * can be viewed (given that myMatch is not null) by creating + * <P> + * <code>String theMatch = myString.substring(myMatch.getStartIndex(), + * myMatch.getEndIndex());</code> + * <P> + * But you can save yourself that work, since the <code>toString()</code> + * method (above) does exactly that for you. + */ + public int getEndIndex() { + return offset + end[0]; + } + + /** + * Returns the string matching the given subexpression. The subexpressions + * are indexed starting with one, not zero. That is, the subexpression + * identified by the first set of parentheses in a regular expression + * could be retrieved from an REMatch by calling match.toString(1). + * + * @param sub Index of the subexpression. + */ + public String toString(int sub) { + if ((sub >= start.length) || sub < 0) + throw new IndexOutOfBoundsException("No group " + sub); + if (start[sub] == -1) return null; + if (start[sub] >= 0 && end[sub] <= matchedText.length()) + return (matchedText.substring(start[sub],end[sub])); + else { + // This case occurs with RETokenLookAhead or RETokenLookBehind. + StringBuffer sb = new StringBuffer(); + int s = start[sub]; + int e = end[sub]; + if (s < 0) s += 1; + if (e < 0) e += 1; + for (int i = start[0] + s; i < start[0] + e; i++) + sb.append(matchedCharIndexed.charAt(i)); + return sb.toString(); + } + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> begins, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @deprecated Use getStartIndex(int) instead. + */ + public int getSubStartIndex(int sub) { + if (sub >= start.length) return -1; + int x = start[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> begins, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @since gnu.regexp 1.1.0 + */ + public int getStartIndex(int sub) { + if (sub >= start.length) return -1; + int x = start[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> ends, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @deprecated Use getEndIndex(int) instead + */ + public int getSubEndIndex(int sub) { + if (sub >= start.length) return -1; + int x = end[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> ends, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + */ + public int getEndIndex(int sub) { + if (sub >= start.length) return -1; + int x = end[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Substitute the results of this match to create a new string. + * This is patterned after PERL, so the tokens to watch out for are + * <code>$0</code> through <code>$9</code>. <code>$0</code> matches + * the full substring matched; <code>$<i>n</i></code> matches + * subexpression number <i>n</i>. + * <code>$10, $11, ...</code> may match the 10th, 11th, ... subexpressions + * if such subexpressions exist. + * + * @param input A string consisting of literals and <code>$<i>n</i></code> tokens. + */ + public String substituteInto(String input) { + // a la Perl, $0 is whole thing, $1 - $9 are subexpressions + StringBuffer output = new StringBuffer(); + int pos; + for (pos = 0; pos < input.length()-1; pos++) { + if ((input.charAt(pos) == '$') && (Character.isDigit(input.charAt(pos+1)))) { + int val = Character.digit(input.charAt(++pos),10); + int pos1 = pos + 1; + while (pos1 < input.length() && + Character.isDigit(input.charAt(pos1))) { + int val1 = val*10 + Character.digit(input.charAt(pos1),10); + if (val1 >= start.length) break; + pos1++; + val = val1; + } + pos = pos1 - 1; + + if (val < start.length) { + output.append(toString(val)); + } + } else output.append(input.charAt(pos)); + } + if (pos < input.length()) output.append(input.charAt(pos)); + return output.toString(); + } + +/* The following are used for debugging purpose + static String d(REMatch m) { + if (m == null) return "null"; + else return "[" + m.index + "]"; + } + + String substringUptoIndex(CharIndexed input) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < index; i++) { + sb.append(input.charAt(i)); + } + return sb.toString(); + } +*/ + +} diff --git a/libjava/classpath/gnu/java/util/regex/REMatchEnumeration.java b/libjava/classpath/gnu/java/util/regex/REMatchEnumeration.java new file mode 100644 index 00000000000..bc700beac5a --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REMatchEnumeration.java @@ -0,0 +1,135 @@ +/* gnu/regexp/REMatchEnumeration.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.Serializable; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * An REMatchEnumeration enumerates regular expression matches over a + * given input text. You obtain a reference to an enumeration using + * the <code>getMatchEnumeration()</code> methods on an instance of + * RE. + * + * <P> + * + * REMatchEnumeration does lazy computation; that is, it will not + * search for a match until it needs to. If you'd rather just get all + * the matches at once in a big array, use the + * <code>getAllMatches()</code> methods on RE. However, using an + * enumeration can help speed performance when the entire text does + * not need to be searched immediately. + * + * <P> + * + * The enumerated type is especially useful when searching on a Reader + * or InputStream, because the InputStream read position cannot be + * guaranteed after calling <code>getMatch()</code> (see the + * description of that method for an explanation of why). Enumeration + * also saves a lot of overhead required when calling + * <code>getMatch()</code> multiple times. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public class REMatchEnumeration implements Enumeration, Serializable { + private static final int YES = 1; + private static final int MAYBE = 0; + private static final int NO = -1; + + private int more; + private REMatch match; + private RE expr; + private CharIndexed input; + private int eflags; + private int index; + + // Package scope constructor is used by RE.getMatchEnumeration() + REMatchEnumeration(RE expr, CharIndexed input, int index, int eflags) { + more = MAYBE; + this.expr = expr; + this.input = input; + this.index = index; + this.eflags = eflags; + } + + /** Returns true if there are more matches in the input text. */ + public boolean hasMoreElements() { + return hasMoreMatches(null); + } + + /** Returns true if there are more matches in the input text. */ + public boolean hasMoreMatches() { + return hasMoreMatches(null); + } + + /** Returns true if there are more matches in the input text. + * Saves the text leading up to the match (or to the end of the input) + * in the specified buffer. + */ + public boolean hasMoreMatches(StringBuffer buffer) { + if (more == MAYBE) { + match = expr.getMatchImpl(input,index,eflags,buffer); + if (match != null) { + input.move((match.end[0] > 0) ? match.end[0] : 1); + + index = (match.end[0] > 0) ? match.end[0] + match.offset : index + 1; + more = YES; + } else more = NO; + } + return (more == YES); + } + + /** Returns the next match in the input text. */ + public Object nextElement() throws NoSuchElementException { + return nextMatch(); + } + + /** + * Returns the next match in the input text. This method is provided + * for convenience to avoid having to explicitly cast the return value + * to class REMatch. + */ + public REMatch nextMatch() throws NoSuchElementException { + if (hasMoreElements()) { + more = (input.isValid()) ? MAYBE : NO; + return match; + } + throw new NoSuchElementException(); + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RESyntax.java b/libjava/classpath/gnu/java/util/regex/RESyntax.java new file mode 100644 index 00000000000..b66b32f5878 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RESyntax.java @@ -0,0 +1,563 @@ +/* gnu/regexp/RESyntax.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; +import java.io.Serializable; +import java.util.BitSet; + +/** + * An RESyntax specifies the way a regular expression will be compiled. + * This class provides a number of predefined useful constants for + * emulating popular regular expression syntaxes. Additionally the + * user may construct his or her own syntax, using any combination of the + * syntax bit constants. The syntax is an optional argument to any of the + * matching methods on class RE. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ + +public final class RESyntax implements Serializable { + static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator"); + + private static final String SYNTAX_IS_FINAL = RE.getLocalizedMessage("syntax.final"); + + private BitSet bits; + + // true for the constant defined syntaxes + private boolean isFinal = false; + + private String lineSeparator = DEFAULT_LINE_SEPARATOR; + + // Values for constants are bit indexes + + /** + * Syntax bit. Backslash is an escape character in lists. + */ + public static final int RE_BACKSLASH_ESCAPE_IN_LISTS = 0; + + /** + * Syntax bit. Use \? instead of ? and \+ instead of +. + */ + public static final int RE_BK_PLUS_QM = 1; + + /** + * Syntax bit. POSIX character classes ([:...:]) in lists are allowed. + */ + public static final int RE_CHAR_CLASSES = 2; + + /** + * Syntax bit. ^ and $ are special everywhere. + * <B>Not implemented.</B> + */ + public static final int RE_CONTEXT_INDEP_ANCHORS = 3; + + /** + * Syntax bit. Repetition operators are only special in valid positions. + * <B>Not implemented.</B> + */ + public static final int RE_CONTEXT_INDEP_OPS = 4; + + /** + * Syntax bit. Repetition and alternation operators are invalid + * at start and end of pattern and other places. + * <B>Not implemented</B>. + */ + public static final int RE_CONTEXT_INVALID_OPS = 5; + + /** + * Syntax bit. Match-any-character operator (.) matches a newline. + */ + public static final int RE_DOT_NEWLINE = 6; + + /** + * Syntax bit. Match-any-character operator (.) does not match a null. + */ + public static final int RE_DOT_NOT_NULL = 7; + + /** + * Syntax bit. Intervals ({x}, {x,}, {x,y}) are allowed. + */ + public static final int RE_INTERVALS = 8; + + /** + * Syntax bit. No alternation (|), match one-or-more (+), or + * match zero-or-one (?) operators. + */ + public static final int RE_LIMITED_OPS = 9; + + /** + * Syntax bit. Newline is an alternation operator. + */ + public static final int RE_NEWLINE_ALT = 10; // impl. + + /** + * Syntax bit. Intervals use { } instead of \{ \} + */ + public static final int RE_NO_BK_BRACES = 11; + + /** + * Syntax bit. Grouping uses ( ) instead of \( \). + */ + public static final int RE_NO_BK_PARENS = 12; + + /** + * Syntax bit. Backreferences not allowed. + */ + public static final int RE_NO_BK_REFS = 13; + + /** + * Syntax bit. Alternation uses | instead of \| + */ + public static final int RE_NO_BK_VBAR = 14; + + /** + * Syntax bit. <B>Not implemented</B>. + */ + public static final int RE_NO_EMPTY_RANGES = 15; + + /** + * Syntax bit. An unmatched right parenthesis (')' or '\)', depending + * on RE_NO_BK_PARENS) will throw an exception when compiling. + */ + public static final int RE_UNMATCHED_RIGHT_PAREN_ORD = 16; + + /** + * Syntax bit. <B>Not implemented.</B> + */ + public static final int RE_HAT_LISTS_NOT_NEWLINE = 17; + + /** + * Syntax bit. Stingy matching is allowed (+?, *?, ??, {x,y}?). + */ + public static final int RE_STINGY_OPS = 18; + + /** + * Syntax bit. Allow character class escapes (\d, \D, \s, \S, \w, \W). + */ + public static final int RE_CHAR_CLASS_ESCAPES = 19; + + /** + * Syntax bit. Allow use of (?:xxx) grouping (subexpression is not saved). + */ + public static final int RE_PURE_GROUPING = 20; + + /** + * Syntax bit. Allow use of (?=xxx) and (?!xxx) apply the subexpression + * to the text following the current position without consuming that text. + */ + public static final int RE_LOOKAHEAD = 21; + + /** + * Syntax bit. Allow beginning- and end-of-string anchors (\A, \Z). + */ + public static final int RE_STRING_ANCHORS = 22; + + /** + * Syntax bit. Allow embedded comments, (?#comment), as in Perl5. + */ + public static final int RE_COMMENTS = 23; + + /** + * Syntax bit. Allow character class escapes within lists, as in Perl5. + */ + public static final int RE_CHAR_CLASS_ESC_IN_LISTS = 24; + + /** + * Syntax bit. Possessive matching is allowed (++, *+, ?+, {x,y}+). + */ + public static final int RE_POSSESSIVE_OPS = 25; + + /** + * Syntax bit. Allow embedded flags, (?is-x), as in Perl5. + */ + public static final int RE_EMBEDDED_FLAGS = 26; + + /** + * Syntax bit. Allow octal char (\0377), as in Perl5. + */ + public static final int RE_OCTAL_CHAR = 27; + + /** + * Syntax bit. Allow hex char (\x1b), as in Perl5. + */ + public static final int RE_HEX_CHAR = 28; + + /** + * Syntax bit. Allow Unicode char (\u1234), as in Java 1.4. + */ + public static final int RE_UNICODE_CHAR = 29; + + /** + * Syntax bit. Allow named property (\p{P}, \P{p}), as in Perl5. + */ + public static final int RE_NAMED_PROPERTY = 30; + + /** + * Syntax bit. Allow nested characterclass ([a-z&&[^p-r]]), as in Java 1.4. + */ + public static final int RE_NESTED_CHARCLASS = 31; + + private static final int BIT_TOTAL = 32; + + /** + * Predefined syntax. + * Emulates regular expression support in the awk utility. + */ + public static final RESyntax RE_SYNTAX_AWK; + + /** + * Predefined syntax. + * Emulates regular expression support in the ed utility. + */ + public static final RESyntax RE_SYNTAX_ED; + + /** + * Predefined syntax. + * Emulates regular expression support in the egrep utility. + */ + public static final RESyntax RE_SYNTAX_EGREP; + + /** + * Predefined syntax. + * Emulates regular expression support in the GNU Emacs editor. + */ + public static final RESyntax RE_SYNTAX_EMACS; + + /** + * Predefined syntax. + * Emulates regular expression support in the grep utility. + */ + public static final RESyntax RE_SYNTAX_GREP; + + /** + * Predefined syntax. + * Emulates regular expression support in the POSIX awk specification. + */ + public static final RESyntax RE_SYNTAX_POSIX_AWK; + + /** + * Predefined syntax. + * Emulates POSIX basic regular expression support. + */ + public static final RESyntax RE_SYNTAX_POSIX_BASIC; + + /** + * Predefined syntax. + * Emulates regular expression support in the POSIX egrep specification. + */ + public static final RESyntax RE_SYNTAX_POSIX_EGREP; + + /** + * Predefined syntax. + * Emulates POSIX extended regular expression support. + */ + public static final RESyntax RE_SYNTAX_POSIX_EXTENDED; + + /** + * Predefined syntax. + * Emulates POSIX basic minimal regular expressions. + */ + public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_BASIC; + + /** + * Predefined syntax. + * Emulates POSIX extended minimal regular expressions. + */ + public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_EXTENDED; + + /** + * Predefined syntax. + * Emulates regular expression support in the sed utility. + */ + public static final RESyntax RE_SYNTAX_SED; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 4, + */ + public static final RESyntax RE_SYNTAX_PERL4; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 4, + * using single line mode (/s modifier). + */ + public static final RESyntax RE_SYNTAX_PERL4_S; // single line mode (/s) + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 5. + */ + public static final RESyntax RE_SYNTAX_PERL5; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 5, + * using single line mode (/s modifier). + */ + public static final RESyntax RE_SYNTAX_PERL5_S; + + /** + * Predefined syntax. + * Emulates regular expression support in Java 1.4's java.util.regex + * package. + */ + public static final RESyntax RE_SYNTAX_JAVA_1_4; + + static { + // Define syntaxes + + RE_SYNTAX_EMACS = new RESyntax().makeFinal(); + + RESyntax RE_SYNTAX_POSIX_COMMON = new RESyntax() + .set(RE_CHAR_CLASSES) + .set(RE_DOT_NEWLINE) + .set(RE_DOT_NOT_NULL) + .set(RE_INTERVALS) + .set(RE_NO_EMPTY_RANGES) + .makeFinal(); + + RE_SYNTAX_POSIX_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_BK_PLUS_QM) + .makeFinal(); + + RE_SYNTAX_POSIX_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + RE_SYNTAX_AWK = new RESyntax() + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .set(RE_DOT_NOT_NULL) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_REFS) + .set(RE_NO_BK_VBAR) + .set(RE_NO_EMPTY_RANGES) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + RE_SYNTAX_POSIX_AWK = new RESyntax(RE_SYNTAX_POSIX_EXTENDED) + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .makeFinal(); + + RE_SYNTAX_GREP = new RESyntax() + .set(RE_BK_PLUS_QM) + .set(RE_CHAR_CLASSES) + .set(RE_HAT_LISTS_NOT_NEWLINE) + .set(RE_INTERVALS) + .set(RE_NEWLINE_ALT) + .makeFinal(); + + RE_SYNTAX_EGREP = new RESyntax() + .set(RE_CHAR_CLASSES) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) + .set(RE_HAT_LISTS_NOT_NEWLINE) + .set(RE_NEWLINE_ALT) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .makeFinal(); + + RE_SYNTAX_POSIX_EGREP = new RESyntax(RE_SYNTAX_EGREP) + .set(RE_INTERVALS) + .set(RE_NO_BK_BRACES) + .makeFinal(); + + /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ + + RE_SYNTAX_ED = new RESyntax(RE_SYNTAX_POSIX_BASIC) + .makeFinal(); + + RE_SYNTAX_SED = new RESyntax(RE_SYNTAX_POSIX_BASIC) + .makeFinal(); + + RE_SYNTAX_POSIX_MINIMAL_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_LIMITED_OPS) + .makeFinal(); + + /* Differs from RE_SYNTAX_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ + + RE_SYNTAX_POSIX_MINIMAL_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INVALID_OPS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_REFS) + .set(RE_NO_BK_VBAR) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + /* There is no official Perl spec, but here's a "best guess" */ + + RE_SYNTAX_PERL4 = new RESyntax() + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) // except for '{', apparently + .set(RE_INTERVALS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .set(RE_NO_EMPTY_RANGES) + .set(RE_CHAR_CLASS_ESCAPES) // \d,\D,\w,\W,\s,\S + .makeFinal(); + + RE_SYNTAX_PERL4_S = new RESyntax(RE_SYNTAX_PERL4) + .set(RE_DOT_NEWLINE) + .makeFinal(); + + RE_SYNTAX_PERL5 = new RESyntax(RE_SYNTAX_PERL4) + .set(RE_PURE_GROUPING) // (?:) + .set(RE_STINGY_OPS) // *?,??,+?,{}? + .set(RE_LOOKAHEAD) // (?=)(?!) + .set(RE_STRING_ANCHORS) // \A,\Z + .set(RE_CHAR_CLASS_ESC_IN_LISTS)// \d,\D,\w,\W,\s,\S within [] + .set(RE_COMMENTS) // (?#) + .set(RE_EMBEDDED_FLAGS) // (?imsx-imsx) + .set(RE_OCTAL_CHAR) // \0377 + .set(RE_HEX_CHAR) // \x1b + .set(RE_NAMED_PROPERTY) // \p{prop}, \P{prop} + .makeFinal(); + + RE_SYNTAX_PERL5_S = new RESyntax(RE_SYNTAX_PERL5) + .set(RE_DOT_NEWLINE) + .makeFinal(); + + RE_SYNTAX_JAVA_1_4 = new RESyntax(RE_SYNTAX_PERL5) + // XXX + .set(RE_POSSESSIVE_OPS) // *+,?+,++,{}+ + .set(RE_UNICODE_CHAR) // \u1234 + .set(RE_NESTED_CHARCLASS) // [a-z&&[^p-r]] + .makeFinal(); + } + + /** + * Construct a new syntax object with all bits turned off. + * This is equivalent to RE_SYNTAX_EMACS. + */ + public RESyntax() { + bits = new BitSet(BIT_TOTAL); + } + + /** + * Called internally when constructing predefined syntaxes + * so their interpretation cannot vary. Conceivably useful + * for your syntaxes as well. Causes IllegalAccessError to + * be thrown if any attempt to modify the syntax is made. + * + * @return this object for convenient chaining + */ + public RESyntax makeFinal() { + isFinal = true; + return this; + } + + /** + * Construct a new syntax object with all bits set the same + * as the other syntax. + */ + public RESyntax(RESyntax other) { + bits = (BitSet) other.bits.clone(); + } + + /** + * Check if a given bit is set in this syntax. + */ + public boolean get(int index) { + return bits.get(index); + } + + /** + * Set a given bit in this syntax. + * + * @param index the constant (RESyntax.RE_xxx) bit to set. + * @return a reference to this object for easy chaining. + */ + public RESyntax set(int index) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + bits.set(index); + return this; + } + + /** + * Clear a given bit in this syntax. + * + * @param index the constant (RESyntax.RE_xxx) bit to clear. + * @return a reference to this object for easy chaining. + */ + public RESyntax clear(int index) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + bits.clear(index); + return this; + } + + /** + * Changes the line separator string for regular expressions + * created using this RESyntax. The default separator is the + * value returned by the system property "line.separator", which + * should be correct when reading platform-specific files from a + * filesystem. However, many programs may collect input from + * sources where the line separator is differently specified (for + * example, in the applet environment, the text box widget + * interprets line breaks as single-character newlines, + * regardless of the host platform. + * + * Note that setting the line separator to a character or + * characters that have specific meaning within the current syntax + * can cause unexpected chronosynclastic infundibula. + * + * @return this object for convenient chaining + */ + public RESyntax setLineSeparator(String aSeparator) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + lineSeparator = aSeparator; + return this; + } + + /** + * Returns the currently active line separator string. The default + * is the platform-dependent system property "line.separator". + */ + public String getLineSeparator() { + return lineSeparator; + } +} diff --git a/libjava/classpath/gnu/java/util/regex/REToken.java b/libjava/classpath/gnu/java/util/regex/REToken.java new file mode 100644 index 00000000000..155c01878e8 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REToken.java @@ -0,0 +1,189 @@ +/* gnu/regexp/REToken.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.Serializable; + +abstract class REToken implements Serializable, Cloneable { + + protected REToken next = null; + protected REToken uncle = null; + protected int subIndex; + protected boolean unicodeAware = true; + + public Object clone() { + try { + REToken copy = (REToken) super.clone(); + return copy; + } catch (CloneNotSupportedException e) { + throw new Error(); // doesn't happen + } + } + + protected REToken(int subIndex) { + this.subIndex = subIndex; + } + + int getMinimumLength() { + return 0; + } + + int getMaximumLength() { + return Integer.MAX_VALUE; + } + + void setUncle(REToken anUncle) { + uncle = anUncle; + } + + /** Returns true if the match succeeded, false if it failed. */ + boolean match(CharIndexed input, REMatch mymatch) { + REMatch m = matchThis(input, mymatch); + if (m == null) return false; + if (next(input, m)) { + mymatch.assignFrom(m); + return true; + } + return false; + } + + /** Returns true if the match succeeded, false if it failed. + * The matching is done against this REToken only. Chained + * tokens are not checked. + * This method is used to define the default match method. + * Simple subclasses of REToken, for example, such that + * matches only one character, should implement this method. + * Then the default match method will work. But complicated + * subclasses of REToken, which needs a special match method, + * do not have to implement this method. + */ + REMatch matchThis(CharIndexed input, REMatch mymatch) { + throw new UnsupportedOperationException( + "This REToken does not have a matchThis method"); + } + + /** Returns true if the rest of the tokens match, false if they fail. */ + protected boolean next(CharIndexed input, REMatch mymatch) { + REToken nextToken = getNext(); + if (nextToken == null) return true; + return nextToken.match(input, mymatch); + } + + /** Returns the next REToken chained to this REToken. */ + REToken getNext() { + return (next != null ? next : uncle); + } + + /** Finds a match at the position specified by the given REMatch. + * If necessary, adds a BacktrackStack.Backtrack object to backtrackStack + * of the REmatch found this time so that another possible match + * may be found when backtrack is called. + * By default, nothing is added to the backtrackStack. + * @param CharIndexed input Input character sequence. + * @param mymatch Position at which a match should be found + * @return REMatch object if a match was found, null otherwise. + */ + REMatch findMatch(CharIndexed input, REMatch mymatch) { + boolean b = match(input, mymatch); + if (b) return mymatch; + return null; + } + + boolean returnsFixedLengthMatches() { + return false; + } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + throw new UnsupportedOperationException( + "This token does not support findFixedLengthMatches"); + } + + /** + * Backtrack to another possibility. + * Ordinary REToken cannot do anything if this method is called. + */ + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + throw new IllegalStateException("This token cannot be backtracked to"); + } + + boolean chain(REToken token) { + next = token; + return true; // Token was accepted + } + + abstract void dump(StringBuffer os); + + void dumpAll(StringBuffer os) { + dump(os); + if (next != null) next.dumpAll(os); + } + + public String toString() { + StringBuffer os = new StringBuffer(); + dump(os); + return os.toString(); + } + + /** + * Converts the character argument to lowercase. + * @param ch the character to be converted. + * @param unicodeAware If true, use java.lang.Character#toLowerCase; + * otherwise, only US-ASCII charactes can be converted. + * @return the lowercase equivalent of the character, if any; + * otherwise, the character itself. + */ + public static char toLowerCase(char ch, boolean unicodeAware) { + if (unicodeAware) return Character.toLowerCase(ch); + if (ch >= 'A' && ch <= 'Z') return (char)(ch + 'a' - 'A'); + return ch; + } + + /** + * Converts the character argument to uppercase. + * @param ch the character to be converted. + * @param unicodeAware If true, use java.lang.Character#toUpperCase; + * otherwise, only US-ASCII charactes can be converted. + * @return the uppercase equivalent of the character, if any; + * otherwise, the character itself. + */ + public static char toUpperCase(char ch, boolean unicodeAware) { + if (unicodeAware) return Character.toUpperCase(ch); + if (ch >= 'a' && ch <= 'z') return (char)(ch + 'A' - 'a'); + return ch; + } + +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenAny.java b/libjava/classpath/gnu/java/util/regex/RETokenAny.java new file mode 100644 index 00000000000..b99a54717c9 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenAny.java @@ -0,0 +1,99 @@ +/* gnu/regexp/RETokenAny.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenAny extends REToken { + /** True if '.' can match a newline (RE_DOT_NEWLINE) */ + private boolean newline; + + /** True if '.' can't match a null (RE_DOT_NOT_NULL) */ + private boolean matchNull; + + RETokenAny(int subIndex, boolean newline, boolean matchNull) { + super(subIndex); + this.newline = newline; + this.matchNull = matchNull; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char ch) { + if ((ch == CharIndexed.OUT_OF_BOUNDS) + || (!newline && (ch == '\n')) + || (matchNull && (ch == 0))) { + return false; + } + return true; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append('.'); + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenBackRef.java b/libjava/classpath/gnu/java/util/regex/RETokenBackRef.java new file mode 100644 index 00000000000..3a912f9bba7 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenBackRef.java @@ -0,0 +1,86 @@ +/* gnu/regexp/RETokenBackRef.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenBackRef extends REToken { + private int num; + private boolean insens; + + RETokenBackRef(int subIndex, int num, boolean insens) { + super(subIndex); + this.num = num; + this.insens = insens; + } + + // should implement getMinimumLength() -- any ideas? + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + if (num >= mymatch.start.length) return null; + if (num >= mymatch.end.length) return null; + int b,e; + b = mymatch.start[num]; + e = mymatch.end[num]; + if ((b==-1)||(e==-1)) return null; // this shouldn't happen, but... + if (b < 0) b += 1; + if (e < 0) e += 1; + for (int i=b; i<e; i++) { + char c1 = input.charAt(mymatch.index+i-b); + char c2 = input.charAt(i); + if (c1 != c2) { + if (insens) { + if (c1 != toLowerCase(c2, unicodeAware) && + c1 != toUpperCase(c2, unicodeAware)) { + return null; + } + } + else { + return null; + } + } + } + mymatch.index += e-b; + return mymatch; + } + + void dump(StringBuffer os) { + os.append('\\').append(num); + } +} + + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenChar.java b/libjava/classpath/gnu/java/util/regex/RETokenChar.java new file mode 100644 index 00000000000..92d3efcf85b --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenChar.java @@ -0,0 +1,128 @@ +/* gnu/regexp/RETokenChar.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenChar extends REToken { + private char[] ch; + private boolean insens; + + RETokenChar(int subIndex, char c, boolean ins) { + super(subIndex); + insens = ins; + ch = new char [1]; + ch[0] = c; + } + + int getMinimumLength() { + return ch.length; + } + + int getMaximumLength() { + return ch.length; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + int z = ch.length; + if (matchOneString(input, mymatch.index)) { + mymatch.index += z; + return mymatch; + } + return null; + } + + boolean matchOneString(CharIndexed input, int index) { + int z = ch.length; + char c; + for (int i=0; i<z; i++) { + c = input.charAt(index+i); + if (! charEquals(c, ch[i])) { + return false; + } + } + return true; + } + + private boolean charEquals(char c1, char c2) { + if (c1 == c2) return true; + if (! insens) return false; + if (toLowerCase(c1, unicodeAware) == c2) return true; + if (toUpperCase(c1, unicodeAware) == c2) return true; + return false; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + int z = ch.length; + while (true) { + if (numRepeats >= max) break; + if (matchOneString(input, index)) { + index += z; + numRepeats++; + } + else break; + } + return numRepeats; + } + + // Overrides REToken.chain() to optimize for strings + boolean chain(REToken next) { + if (next instanceof RETokenChar && ((RETokenChar)next).insens == insens) { + RETokenChar cnext = (RETokenChar) next; + // assume for now that next can only be one character + int newsize = ch.length + cnext.ch.length; + + char[] chTemp = new char [newsize]; + + System.arraycopy(ch,0,chTemp,0,ch.length); + System.arraycopy(cnext.ch,0,chTemp,ch.length,cnext.ch.length); + + ch = chTemp; + return false; + } else return super.chain(next); + } + + void dump(StringBuffer os) { + os.append(ch); + } +} + + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenEnd.java b/libjava/classpath/gnu/java/util/regex/RETokenEnd.java new file mode 100644 index 00000000000..00efdb6a712 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEnd.java @@ -0,0 +1,109 @@ +/* gnu/regexp/RETokenEnd.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenEnd extends REToken { + /** + * Indicates whether this token should match on a line break. + */ + private String newline; + private boolean check_java_line_terminators; + + RETokenEnd(int subIndex,String newline) { + super(subIndex); + this.newline = newline; + this.check_java_line_terminators = false; + } + + RETokenEnd(int subIndex, String newline, boolean b) { + super(subIndex); + this.newline = newline; + this.check_java_line_terminators = b; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + if (ch == CharIndexed.OUT_OF_BOUNDS) + return ((mymatch.eflags & RE.REG_NOTEOL)>0) ? + null : mymatch; + if (check_java_line_terminators) { + if (ch == '\n') { + char ch1 = input.charAt(mymatch.index - 1); + if (ch1 == '\r') return null; + return mymatch; + } + if (ch == '\r') return mymatch; + if (ch == '\u0085') return mymatch; // A next-line character + if (ch == '\u2028') return mymatch; // A line-separator character + if (ch == '\u2029') return mymatch; // A paragraph-separator character + return null; + } + if (newline != null) { + char z; + int i = 0; // position in newline + do { + z = newline.charAt(i); + if (ch != z) return null; + ++i; + ch = input.charAt(mymatch.index + i); + } while (i < newline.length()); + + return mymatch; + } + return null; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + REMatch m = (REMatch) mymatch.clone(); + REToken tk = (REToken) this.clone(); + tk.chain(null); + if (tk.match(input, m)) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append('$'); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenEndOfPreviousMatch.java b/libjava/classpath/gnu/java/util/regex/RETokenEndOfPreviousMatch.java new file mode 100644 index 00000000000..ea5580e1666 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEndOfPreviousMatch.java @@ -0,0 +1,72 @@ +/* gnu/regexp/RETokenEndOfPreviousMatch.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +class RETokenEndOfPreviousMatch extends RETokenStart { + + RETokenEndOfPreviousMatch(int subIndex) { + super(subIndex, null); + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + REMatch lastMatch = input.getLastMatch(); + if (lastMatch == null) return super.matchThis(input, mymatch); + if (input.getAnchor()+mymatch.index == + lastMatch.anchor+lastMatch.index) { + return mymatch; + } + else { + return null; + } + } + + boolean returnsFixedLengthmatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append("\\G"); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenEndSub.java b/libjava/classpath/gnu/java/util/regex/RETokenEndSub.java new file mode 100644 index 00000000000..57a146d03b9 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEndSub.java @@ -0,0 +1,66 @@ +/* gnu/regexp/RETokenEndSub.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +final class RETokenEndSub extends REToken { + RETokenEndSub(int subIndex) { + super(subIndex); + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + mymatch.start[subIndex] = mymatch.start1[subIndex]; + mymatch.end[subIndex] = mymatch.index; + return mymatch; + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + mymatch.start[subIndex] = mymatch.start1[subIndex]; + mymatch.end[subIndex] = mymatch.index; + return super.findMatch(input, mymatch); + } + + void dump(StringBuffer os) { + // handled by RE + // But add something for debugging. + os.append("(?#RETokenEndSub subIndex=" + subIndex + ")"); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenIndependent.java b/libjava/classpath/gnu/java/util/regex/RETokenIndependent.java new file mode 100644 index 00000000000..48f8656123d --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenIndependent.java @@ -0,0 +1,78 @@ +/* gnu/regexp/RETokenIndependent.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * @author Ito Kazumitsu + */ +final class RETokenIndependent extends REToken +{ + REToken re; + + RETokenIndependent(REToken re) throws REException { + super(0); + this.re = re; + } + + int getMinimumLength() { + return re.getMinimumLength(); + } + + int getMaximumLength() { + return re.getMaximumLength(); + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + boolean b = re.match(input, mymatch); + if (b) { + // Once we have found a match, we do not see other possible matches. + if (mymatch.backtrackStack != null) mymatch.backtrackStack.clear(); + return mymatch; + + } + return null; + } + + void dump(StringBuffer os) { + os.append("(?>"); + re.dumpAll(os); + os.append(')'); + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenLookAhead.java b/libjava/classpath/gnu/java/util/regex/RETokenLookAhead.java new file mode 100644 index 00000000000..134f17c609c --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenLookAhead.java @@ -0,0 +1,80 @@ +/* gnu/regexp/RETokenLookAhead.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * @since gnu.regexp 1.1.3 + * @author Shashank Bapat + */ +final class RETokenLookAhead extends REToken +{ + REToken re; + boolean negative; + + RETokenLookAhead(REToken re, boolean negative) throws REException { + super(0); + this.re = re; + this.negative = negative; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + REMatch trymatch = (REMatch)mymatch.clone(); + if (re.match(input, trymatch)) { + if (negative) return null; + trymatch.index = mymatch.index; + return trymatch; + } + else { + if (negative) return mymatch; + return null; + } + } + + void dump(StringBuffer os) { + os.append("(?"); + os.append(negative ? '!' : '='); + re.dumpAll(os); + os.append(')'); + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenLookBehind.java b/libjava/classpath/gnu/java/util/regex/RETokenLookBehind.java new file mode 100644 index 00000000000..a01a15bc90f --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenLookBehind.java @@ -0,0 +1,118 @@ +/* gnu/regexp/RETokenLookBehind.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * @author Ito Kazumitsu + */ +final class RETokenLookBehind extends REToken +{ + REToken re; + boolean negative; + + RETokenLookBehind(REToken re, boolean negative) throws REException { + super(0); + this.re = re; + this.negative = negative; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + int max = re.getMaximumLength(); + CharIndexed behind = input.lookBehind(mymatch.index, max); + REMatch trymatch = (REMatch)mymatch.clone(); + REMatch trymatch1 = (REMatch)mymatch.clone(); + REMatch newMatch = null; + int diff = behind.length() - input.length(); + int curIndex = trymatch.index + diff; + trymatch.index = 0; + trymatch.offset = 0; + RETokenMatchHereOnly stopper = new RETokenMatchHereOnly(curIndex); + REToken re1 = (REToken) re.clone(); + re1.chain(stopper); + if (re1.match(behind, trymatch)) { + if (negative) return null; + for (int i = 0; i < trymatch.start.length; i++) { + if (trymatch.start[i] != -1 && trymatch.end[i] != -1) { + trymatch.start[i] -= diff; + if (trymatch.start[i] < 0) trymatch.start[i] -= 1; + trymatch.end[i] -= diff; + if (trymatch.end[i] < 0) trymatch.end[i] -= 1; + } + } + trymatch.index = mymatch.index; + trymatch.offset = mymatch.offset; + return trymatch; + } + else { + if (negative) return mymatch; + return null; + } + } + + void dump(StringBuffer os) { + os.append("(?<"); + os.append(negative ? '!' : '='); + re.dumpAll(os); + os.append(')'); + } + + private static class RETokenMatchHereOnly extends REToken { + + int getMaximumLength() { return 0; } + + private int index; + + RETokenMatchHereOnly(int index) { + super(0); + this.index = index; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + return (index == mymatch.index ? mymatch : null); + } + + void dump(StringBuffer os) {} + + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenNamedProperty.java b/libjava/classpath/gnu/java/util/regex/RETokenNamedProperty.java new file mode 100644 index 00000000000..a286c5be8c6 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenNamedProperty.java @@ -0,0 +1,315 @@ +/* gnu/regexp/RETokenNamedProperty.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenNamedProperty extends REToken { + String name; + boolean insens; + boolean negate; + Handler handler; + + // Grouped properties + static final byte[] LETTER = new byte[] + { Character.LOWERCASE_LETTER, + Character.UPPERCASE_LETTER, + Character.TITLECASE_LETTER, + Character.MODIFIER_LETTER, + Character.OTHER_LETTER }; + + static final byte[] MARK = new byte[] + { Character.NON_SPACING_MARK, + Character.COMBINING_SPACING_MARK, + Character.ENCLOSING_MARK }; + + static final byte[] SEPARATOR = new byte[] + { Character.SPACE_SEPARATOR, + Character.LINE_SEPARATOR, + Character.PARAGRAPH_SEPARATOR }; + + static final byte[] SYMBOL = new byte[] + { Character.MATH_SYMBOL, + Character.CURRENCY_SYMBOL, + Character.MODIFIER_SYMBOL, + Character.OTHER_SYMBOL }; + + static final byte[] NUMBER = new byte[] + { Character.DECIMAL_DIGIT_NUMBER, + Character.LETTER_NUMBER, + Character.OTHER_NUMBER }; + + static final byte[] PUNCTUATION = new byte[] + { Character.DASH_PUNCTUATION, + Character.START_PUNCTUATION, + Character.END_PUNCTUATION, + Character.CONNECTOR_PUNCTUATION, + Character.OTHER_PUNCTUATION, + Character.INITIAL_QUOTE_PUNCTUATION, + Character.FINAL_QUOTE_PUNCTUATION}; + + static final byte[] OTHER = new byte[] + { Character.CONTROL, + Character.FORMAT, + Character.PRIVATE_USE, + Character.SURROGATE, + Character.UNASSIGNED }; + + RETokenNamedProperty(int subIndex, String name, boolean insens, boolean negate) throws REException { + super(subIndex); + this.name = name; + this.insens = insens; + this.negate = negate; + handler = getHandler(name); + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + private boolean matchOneChar(char ch) { + if (ch == CharIndexed.OUT_OF_BOUNDS) + return false; + + boolean retval = handler.includes(ch); + if (insens) { + retval = retval || + handler.includes(toUpperCase(ch, unicodeAware)) || + handler.includes(toLowerCase(ch, unicodeAware)); + } + + if (negate) retval = !retval; + return retval; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append("\\") + .append(negate ? "P" : "p") + .append("{" + name + "}"); + } + + private abstract static class Handler { + public abstract boolean includes(char c); + } + + private Handler getHandler(String name) throws REException { + if (name.equals("Lower") || + name.equals("Upper") || + // name.equals("ASCII") || + name.equals("Alpha") || + name.equals("Digit") || + name.equals("Alnum") || + name.equals("Punct") || + name.equals("Graph") || + name.equals("Print") || + name.equals("Blank") || + name.equals("Cntrl") || + name.equals("XDigit") || + name.equals("Space") ) { + return new POSIXHandler(name); + } + if (name.startsWith("In")) { + try { + name = name.substring(2); + Character.UnicodeBlock block = Character.UnicodeBlock.forName(name); + return new UnicodeBlockHandler(block); + } + catch (IllegalArgumentException e) { + throw new REException("Invalid Unicode block name: " + name, REException.REG_ESCAPE, 0); + } + } + if (name.startsWith("Is")) { + name = name.substring(2); + } + + // "grouped properties" + if (name.equals("L")) + return new UnicodeCategoriesHandler(LETTER); + if (name.equals("M")) + return new UnicodeCategoriesHandler(MARK); + if (name.equals("Z")) + return new UnicodeCategoriesHandler(SEPARATOR); + if (name.equals("S")) + return new UnicodeCategoriesHandler(SYMBOL); + if (name.equals("N")) + return new UnicodeCategoriesHandler(NUMBER); + if (name.equals("P")) + return new UnicodeCategoriesHandler(PUNCTUATION); + if (name.equals("C")) + return new UnicodeCategoriesHandler(OTHER); + + if (name.equals("Mc")) + return new UnicodeCategoryHandler(Character.COMBINING_SPACING_MARK); + if (name.equals("Pc")) + return new UnicodeCategoryHandler(Character.CONNECTOR_PUNCTUATION); + if (name.equals("Cc")) + return new UnicodeCategoryHandler(Character.CONTROL); + if (name.equals("Sc")) + return new UnicodeCategoryHandler(Character.CURRENCY_SYMBOL); + if (name.equals("Pd")) + return new UnicodeCategoryHandler(Character.DASH_PUNCTUATION); + if (name.equals("Nd")) + return new UnicodeCategoryHandler(Character.DECIMAL_DIGIT_NUMBER); + if (name.equals("Me")) + return new UnicodeCategoryHandler(Character.ENCLOSING_MARK); + if (name.equals("Pe")) + return new UnicodeCategoryHandler(Character.END_PUNCTUATION); + if (name.equals("Pf")) + return new UnicodeCategoryHandler(Character.FINAL_QUOTE_PUNCTUATION); + if (name.equals("Cf")) + return new UnicodeCategoryHandler(Character.FORMAT); + if (name.equals("Pi")) + return new UnicodeCategoryHandler(Character.INITIAL_QUOTE_PUNCTUATION); + if (name.equals("Nl")) + return new UnicodeCategoryHandler(Character.LETTER_NUMBER); + if (name.equals("Zl")) + return new UnicodeCategoryHandler(Character.LINE_SEPARATOR); + if (name.equals("Ll")) + return new UnicodeCategoryHandler(Character.LOWERCASE_LETTER); + if (name.equals("Sm")) + return new UnicodeCategoryHandler(Character.MATH_SYMBOL); + if (name.equals("Lm")) + return new UnicodeCategoryHandler(Character.MODIFIER_LETTER); + if (name.equals("Sk")) + return new UnicodeCategoryHandler(Character.MODIFIER_SYMBOL); + if (name.equals("Mn")) + return new UnicodeCategoryHandler(Character.NON_SPACING_MARK); + if (name.equals("Lo")) + return new UnicodeCategoryHandler(Character.OTHER_LETTER); + if (name.equals("No")) + return new UnicodeCategoryHandler(Character.OTHER_NUMBER); + if (name.equals("Po")) + return new UnicodeCategoryHandler(Character.OTHER_PUNCTUATION); + if (name.equals("So")) + return new UnicodeCategoryHandler(Character.OTHER_SYMBOL); + if (name.equals("Zp")) + return new UnicodeCategoryHandler(Character.PARAGRAPH_SEPARATOR); + if (name.equals("Co")) + return new UnicodeCategoryHandler(Character.PRIVATE_USE); + if (name.equals("Zs")) + return new UnicodeCategoryHandler(Character.SPACE_SEPARATOR); + if (name.equals("Ps")) + return new UnicodeCategoryHandler(Character.START_PUNCTUATION); + if (name.equals("Cs")) + return new UnicodeCategoryHandler(Character.SURROGATE); + if (name.equals("Lt")) + return new UnicodeCategoryHandler(Character.TITLECASE_LETTER); + if (name.equals("Cn")) + return new UnicodeCategoryHandler(Character.UNASSIGNED); + if (name.equals("Lu")) + return new UnicodeCategoryHandler(Character.UPPERCASE_LETTER); + throw new REException("unsupported name " + name, REException.REG_ESCAPE, 0); + } + + private static class POSIXHandler extends Handler { + private RETokenPOSIX retoken; + public POSIXHandler(String name) { + int posixId = RETokenPOSIX.intValue(name.toLowerCase()); + if (posixId != -1) + retoken = new RETokenPOSIX(0,posixId,false,false); + else + throw new RuntimeException("Unknown posix ID: " + name); + } + public boolean includes(char c) { + return retoken.matchOneChar(c); + } + } + + private static class UnicodeCategoryHandler extends Handler { + public UnicodeCategoryHandler(byte category) { + this.category = (int)category; + } + private int category; + public boolean includes(char c) { + return Character.getType(c) == category; + } + } + + private static class UnicodeCategoriesHandler extends Handler { + public UnicodeCategoriesHandler(byte[] categories) { + this.categories = categories; + } + private byte[] categories; + public boolean includes(char c) { + int category = Character.getType(c); + for (int i = 0; i < categories.length; i++) + if (category == categories[i]) + return true; + return false; + } + } + + private static class UnicodeBlockHandler extends Handler { + public UnicodeBlockHandler(Character.UnicodeBlock block) { + this.block = block; + } + private Character.UnicodeBlock block; + public boolean includes(char c) { + Character.UnicodeBlock cblock = Character.UnicodeBlock.of(c); + return (cblock != null && cblock.equals(block)); + } + } + +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenOneOf.java b/libjava/classpath/gnu/java/util/regex/RETokenOneOf.java new file mode 100644 index 00000000000..bccc783118a --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenOneOf.java @@ -0,0 +1,280 @@ +/* gnu/regexp/RETokenOneOf.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.util.Vector; +import java.util.Stack; + +final class RETokenOneOf extends REToken { + private Vector options; + private boolean negative; + // True if this RETokenOneOf is supposed to match only one character, + // which is typically the case of a character class expression. + private boolean matchesOneChar; + + private Vector addition; + // This Vector addition is used to store nested character classes. + // For example, if the original expression is + // [2-7a-c[f-k][m-z]&&[^p-v][st]] + // the basic part /2-7a-c/ is stored in the Vector options, and + // the additional part /[f-k][m-z]&&[^p-v][st]/ is stored in the + // Vector addition in the following order (Reverse Polish Notation): + // -- The matching result of the basic part is assumed here. + // [f-k] -- REToken + // "|" -- or + // [m-z] -- REToken + // "|" -- or + // false + // [^p-v] -- REToken + // "|" -- or + // [st] -- REToken + // "|" -- or + // "&" -- and + // + // As it is clear from the explanation above, the Vector addition is + // effective only when this REToken originates from a character class + // expression. + + // This constructor is used for convenience when we know the set beforehand, + // e.g. \d --> new RETokenOneOf("0123456789",false, ..) + // \D --> new RETokenOneOf("0123456789",true, ..) + + RETokenOneOf(int subIndex, String optionsStr, boolean negative, boolean insens) { + super(subIndex); + options = new Vector(); + this.negative = negative; + for (int i = 0; i < optionsStr.length(); i++) + options.addElement(new RETokenChar(subIndex,optionsStr.charAt(i),insens)); + matchesOneChar = true; + } + + RETokenOneOf(int subIndex, Vector options, boolean negative) { + super(subIndex); + this.options = options; + this.negative = negative; + matchesOneChar = negative; + } + + RETokenOneOf(int subIndex, Vector options, Vector addition, boolean negative) { + super(subIndex); + this.options = options; + this.addition = addition; + this.negative = negative; + matchesOneChar = (negative || addition != null); + } + + int getMinimumLength() { + if (matchesOneChar) return 1; + int min = Integer.MAX_VALUE; + int x; + for (int i=0; i < options.size(); i++) { + if ((x = ((REToken) options.elementAt(i)).getMinimumLength()) < min) + min = x; + } + return min; + } + + int getMaximumLength() { + if (matchesOneChar) return 1; + int max = 0; + int x; + for (int i=0; i < options.size(); i++) { + if ((x = ((REToken) options.elementAt(i)).getMaximumLength()) > max) + max = x; + } + return max; + } + + boolean match(CharIndexed input, REMatch mymatch) { + if (matchesOneChar) return matchOneChar(input, mymatch); + else return matchOneRE(input, mymatch); + } + + boolean matchOneChar(CharIndexed input, REMatch mymatch) { + REMatch tryMatch; + boolean tryOnly; + if (addition == null) { + tryMatch = mymatch; + tryOnly = false; + } + else { + tryMatch = (REMatch) mymatch.clone(); + tryOnly = true; + } + boolean b = negative ? + matchN(input, tryMatch, tryOnly) : + matchP(input, tryMatch, tryOnly); + if (addition == null) return b; + + Stack stack = new Stack(); + stack.push(new Boolean(b)); + for (int i=0; i < addition.size(); i++) { + Object obj = addition.elementAt(i); + if (obj instanceof REToken) { + b = ((REToken)obj).match(input, (REMatch)mymatch.clone()); + stack.push(new Boolean(b)); + } + else if (obj instanceof Boolean) { + stack.push(obj); + } + else if (obj.equals("|")) { + b = ((Boolean)stack.pop()).booleanValue(); + b = ((Boolean)stack.pop()).booleanValue() || b; + stack.push(new Boolean(b)); + } + else if (obj.equals("&")) { + b = ((Boolean)stack.pop()).booleanValue(); + b = ((Boolean)stack.pop()).booleanValue() && b; + stack.push(new Boolean(b)); + } + else { + throw new RuntimeException("Invalid object found"); + } + } + b = ((Boolean)stack.pop()).booleanValue(); + if (b) { + ++mymatch.index; + return next(input, mymatch); + } + return false; + } + + private boolean matchN(CharIndexed input, REMatch mymatch, boolean tryOnly) { + if (input.charAt(mymatch.index) == CharIndexed.OUT_OF_BOUNDS) + return false; + + REMatch newMatch = null; + REMatch last = null; + REToken tk; + for (int i=0; i < options.size(); i++) { + tk = (REToken) options.elementAt(i); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tk.match(input, tryMatch)) { // match was successful + return false; + } // is a match + } // try next option + + if (tryOnly) return true; + ++mymatch.index; + return next(input, mymatch); + } + + private boolean matchP(CharIndexed input, REMatch mymatch, boolean tryOnly) { + REToken tk; + for (int i=0; i < options.size(); i++) { + tk = (REToken) options.elementAt(i); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tk.match(input, tryMatch)) { // match was successful + if (tryOnly) return true; + if (next(input, tryMatch)) { + mymatch.assignFrom(tryMatch); + return true; + } + } + } + return false; + } + + private boolean matchOneRE(CharIndexed input, REMatch mymatch) { + REMatch newMatch = findMatch(input, mymatch); + if (newMatch != null) { + mymatch.assignFrom(newMatch); + return true; + } + return false; + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (matchesOneChar) return super.findMatch(input, mymatch); + return findMatch(input, mymatch, 0); + } + + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + return findMatch(input, mymatch, ((Integer)param).intValue()); + } + + private REMatch findMatch(CharIndexed input, REMatch mymatch, int optionIndex) { + for (int i = optionIndex; i < options.size(); i++) { + REToken tk = (REToken) options.elementAt(i); + tk = (REToken) tk.clone(); + tk.chain(getNext()); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tryMatch.backtrackStack == null) { + tryMatch.backtrackStack = new BacktrackStack(); + } + boolean stackPushed = false; + if (i + 1 < options.size()) { + tryMatch.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, new Integer(i + 1))); + stackPushed = true; + } + boolean b = tk.match(input, tryMatch); + if (b) { + return tryMatch; + } + if (stackPushed) tryMatch.backtrackStack.pop(); + } + return null; + } + + boolean returnsFixedLengthMatches() { return matchesOneChar; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (!matchesOneChar) + return super.findFixedLengthMatches(input, mymatch, max); + int numRepeats = 0; + REMatch m = (REMatch) mymatch.clone(); + REToken tk = (REToken) this.clone(); + tk.chain(null); + while (true) { + if (numRepeats >= max) break; + m = tk.findMatch(input, m); + if (m == null) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append(negative ? "[^" : "(?:"); + for (int i = 0; i < options.size(); i++) { + if (!negative && (i > 0)) os.append('|'); + ((REToken) options.elementAt(i)).dumpAll(os); + } + os.append(negative ? ']' : ')'); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenPOSIX.java b/libjava/classpath/gnu/java/util/regex/RETokenPOSIX.java new file mode 100644 index 00000000000..07298958365 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenPOSIX.java @@ -0,0 +1,167 @@ +/* gnu/regexp/RETokenPOSIX.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +final class RETokenPOSIX extends REToken { + int type; + boolean insens; + boolean negated; + + static final int ALNUM = 0; + static final int ALPHA = 1; + static final int BLANK = 2; + static final int CNTRL = 3; + static final int DIGIT = 4; + static final int GRAPH = 5; + static final int LOWER = 6; + static final int PRINT = 7; + static final int PUNCT = 8; + static final int SPACE = 9; + static final int UPPER = 10; + static final int XDIGIT = 11; + + // Array indices correspond to constants defined above. + static final String[] s_nameTable = { + "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", + "print", "punct", "space", "upper", "xdigit" + }; + + // The RE constructor uses this to look up the constant for a string + static int intValue(String key) { + for (int i = 0; i < s_nameTable.length; i++) { + if (s_nameTable[i].equals(key)) return i; + } + return -1; + } + + RETokenPOSIX(int subIndex, int type, boolean insens, boolean negated) { + super(subIndex); + this.type = type; + this.insens = insens; + this.negated = negated; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char ch) { + if (ch == CharIndexed.OUT_OF_BOUNDS) + return false; + + boolean retval = false; + switch (type) { + case ALNUM: + // Note that there is some debate over whether '_' should be included + retval = Character.isLetterOrDigit(ch) || (ch == '_'); + break; + case ALPHA: + retval = Character.isLetter(ch); + break; + case BLANK: + retval = ((ch == ' ') || (ch == '\t')); + break; + case CNTRL: + retval = Character.isISOControl(ch); + break; + case DIGIT: + retval = Character.isDigit(ch); + break; + case GRAPH: + retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))); + break; + case LOWER: + retval = ((insens && Character.isLetter(ch)) || Character.isLowerCase(ch)); + break; + case PRINT: + retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))) + || (ch == ' '); + break; + case PUNCT: + // This feels sloppy, especially for non-U.S. locales. + retval = ("`~!@#$%^&*()-_=+[]{}\\|;:'\"/?,.<>".indexOf(ch)!=-1); + break; + case SPACE: + retval = Character.isWhitespace(ch); + break; + case UPPER: + retval = ((insens && Character.isLetter(ch)) || Character.isUpperCase(ch)); + break; + case XDIGIT: + retval = (Character.isDigit(ch) || ("abcdefABCDEF".indexOf(ch)!=-1)); + break; + } + + if (negated) retval = !retval; + return retval; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + if (negated) os.append('^'); + os.append("[:" + s_nameTable[type] + ":]"); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenRange.java b/libjava/classpath/gnu/java/util/regex/RETokenRange.java new file mode 100644 index 00000000000..8a65f77f13f --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenRange.java @@ -0,0 +1,100 @@ +/* gnu/regexp/RETokenRange.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +final class RETokenRange extends REToken { + private char lo, hi; + private boolean insens; + + RETokenRange(int subIndex, char lo, char hi, boolean ins) { + super(subIndex); + insens = ins; + this.lo = lo; + this.hi = hi; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char c = input.charAt(mymatch.index); + if (matchOneChar(c)) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char c) { + if (c == CharIndexed.OUT_OF_BOUNDS) return false; + boolean matches = (c >= lo) && (c <= hi); + if (! matches && insens) { + char c1 = toLowerCase(c, unicodeAware); + matches = (c1 >= lo) && (c1 <= hi); + if (!matches) { + c1 = toUpperCase(c, unicodeAware); + matches = (c1 >= lo) && (c1 <= hi); + } + } + return matches; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append(lo).append('-').append(hi); + } +} + diff --git a/libjava/classpath/gnu/java/util/regex/RETokenRepeated.java b/libjava/classpath/gnu/java/util/regex/RETokenRepeated.java new file mode 100644 index 00000000000..531c4a31124 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenRepeated.java @@ -0,0 +1,427 @@ +/* gnu/regexp/RETokenRepeated.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +// import java.util.Vector; +// import java.util.Stack; + +final class RETokenRepeated extends REToken { + private REToken token; + private int min,max; + private boolean stingy; + private boolean possessive; + private int tokenFixedLength; + + RETokenRepeated(int subIndex, REToken token, int min, int max) { + super(subIndex); + this.token = token; + this.min = min; + this.max = max; + if (token.returnsFixedLengthMatches()) { + tokenFixedLength = token.getMaximumLength(); + } + else { + tokenFixedLength = -1; + } + } + + /** Sets the minimal matching mode to true. */ + void makeStingy() { + stingy = true; + } + + /** Queries if this token has minimal matching enabled. */ + boolean isStingy() { + return stingy; + } + + /** Sets possessive matching mode to true. */ + void makePossessive() { + possessive = true; + } + + /** Queries if this token has possessive matching enabled. */ + boolean isPossessive() { + return possessive; + } + + /** + * The minimum length of a repeated token is the minimum length + * of the token multiplied by the minimum number of times it must + * match. + */ + int getMinimumLength() { + return (min * token.getMinimumLength()); + } + + int getMaximumLength() { + if (max == Integer.MAX_VALUE) return Integer.MAX_VALUE; + int tmax = token.getMaximumLength(); + if (tmax == Integer.MAX_VALUE) return tmax; + return (max * tmax); + } + + // The comment "MUST make a clone" below means that some tests + // failed without doing clone(), + + private static class DoablesFinder { + private REToken tk; + private CharIndexed input; + private REMatch rematch; + private boolean findFirst; + + private DoablesFinder(REToken tk, CharIndexed input, REMatch mymatch) { + this.tk = tk; + this.input = input; + this.rematch = (REMatch) mymatch.clone(); // MUST make a clone + this.rematch.backtrackStack = new BacktrackStack(); + findFirst = true; + } + + private REMatch find() { + int origin = rematch.index; + REMatch rem; + if (findFirst) { + rem = tk.findMatch(input, rematch); + findFirst = false; + } + else { + while (true) { + if (rematch.backtrackStack.empty()) { + rem = null; + break; + } + BacktrackStack.Backtrack bt = rematch.backtrackStack.pop(); + rem = bt.token.backtrack(bt.input, bt.match, bt.param); + if (rem != null) break; + } + } + if (rem == null) return null; + if (rem.index == origin) rem.empty = true; + rematch = rem; + return (REMatch) rem.clone(); // MUST make a clone. + } + + boolean noMore() { + return rematch.backtrackStack.empty(); + } + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (tokenFixedLength >= 0) return findMatchFixedLength(input, mymatch); + BacktrackStack stack = new BacktrackStack(); + stack.push(new StackedInfo(input, 0, mymatch, null, null)); + return findMatch(stack); + } + + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + if (tokenFixedLength >= 0) return backtrackFixedLength(input, mymatch, param); + return findMatch((BacktrackStack)param); + } + + private static class StackedInfo extends BacktrackStack.Backtrack { + int numRepeats; + int[] visited; + DoablesFinder finder; + StackedInfo(CharIndexed input, int numRepeats, REMatch match, + int[] visited, DoablesFinder finder) { + super(null, input, match, null); + this.numRepeats = numRepeats; + this.visited = visited; + this.finder = finder; + } + } + + private REMatch findMatch(BacktrackStack stack) { + // Avoid using recursive calls. + MAIN_LOOP: + while (true) { + + if (stack.empty()) return null; + StackedInfo si = (StackedInfo)(stack.peek()); + CharIndexed input = si.input; + int numRepeats = si.numRepeats; + REMatch mymatch = si.match; + int[] visited = si.visited; + DoablesFinder finder = si.finder; + + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + + if (numRepeats >= max) { + stack.pop(); + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + if (stingy) { + continue MAIN_LOOP; + } + return null; + } + + if (finder == null) { + finder = new DoablesFinder(token, input, mymatch); + si.finder = finder; + } + + if (numRepeats < min) { + while (true) { + REMatch doable = finder.find(); + if (doable == null) { + if (stack.empty()) return null; + stack.pop(); + continue MAIN_LOOP; + } + if (finder.noMore()) stack.pop(); + int newNumRepeats = (doable.empty ? min : numRepeats + 1); + stack.push(new StackedInfo( + input, newNumRepeats, doable, visited, null)); + continue MAIN_LOOP; + } + } + + if (visited == null) visited = initVisited(); + + if (stingy) { + REMatch nextMatch = finder.find(); + if (nextMatch != null && !nextMatch.empty) { + stack.push(new StackedInfo( + input, numRepeats + 1, nextMatch, visited, null)); + } + else { + stack.pop(); + } + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (!stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + else { + continue MAIN_LOOP; + } + } + + visited = addVisited(mymatch.index, visited); + + DO_THIS: + do { + + boolean emptyMatchFound = false; + + DO_ONE_DOABLE: + while (true) { + + REMatch doable = finder.find(); + if (doable == null) { + break DO_THIS; + } + if (doable.empty) emptyMatchFound = true; + + if (!emptyMatchFound) { + int n = doable.index; + if (! visitedContains(n, visited)) { + visited = addVisited(n, visited); + } + else { + continue DO_ONE_DOABLE; + } + stack.push(new StackedInfo( + input, numRepeats + 1, doable, visited, null)); + REMatch m1 = findMatch(stack); + if (possessive) { + return m1; + } + if (m1 != null) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + return m1; + } + } + else { + REMatch m1 = matchRest(input, doable); + if (possessive) { + return m1; + } + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + } + + } // DO_ONE_DOABLE + + } while (false); // DO_THIS only once; + + if (!stack.empty()) { + stack.pop(); + } + if (possessive) { + stack.clear(); + } + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + + } // MAIN_LOOP + } + + boolean match(CharIndexed input, REMatch mymatch) { + REMatch m1 = findMatch(input, mymatch); + if (m1 != null) { + mymatch.assignFrom(m1); + return true; + } + return false; + } + + // Array visited is an array of character positions we have already + // visited. visited[0] is used to store the effective length of the + // array. + private static int[] initVisited() { + int[] visited = new int[32]; + visited[0] = 0; + return visited; + } + + private static boolean visitedContains(int n, int[] visited) { + // Experience tells that for a small array like this, + // simple linear search is faster than binary search. + for (int i = 1; i < visited[0]; i++) { + if (n == visited[i]) return true; + } + return false; + } + + private static int[] addVisited(int n, int[] visited) { + if (visitedContains(n, visited)) return visited; + if (visited[0] >= visited.length - 1) { + int[] newvisited = new int[visited.length + 32]; + System.arraycopy(visited, 0, newvisited, 0, visited.length); + visited = newvisited; + } + visited[0]++; + visited[visited[0]] = n; + return visited; + } + + private REMatch matchRest(CharIndexed input, final REMatch newMatch) { + if (next(input, newMatch)) { + return newMatch; + } + return null; + } + + private REMatch findMatchFixedLength(CharIndexed input, REMatch mymatch) { + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + int numRepeats = token.findFixedLengthMatches(input, (REMatch)mymatch.clone(), max); + if (numRepeats == Integer.MAX_VALUE) numRepeats = min; + int count = numRepeats - min + 1; + if (count <= 0) return null; + int index = 0; + if (!stingy) index = mymatch.index + (tokenFixedLength * numRepeats); + else index = mymatch.index + (tokenFixedLength * min); + return findMatchFixedLength(input, mymatch, index, count); + } + + private REMatch backtrackFixedLength(CharIndexed input, REMatch mymatch, + Object param) { + int[] params = (int[])param; + int index = params[0]; + int count = params[1]; + return findMatchFixedLength(input, mymatch, index, count); + } + + private REMatch findMatchFixedLength(CharIndexed input, REMatch mymatch, + int index, int count) { + REMatch tryMatch = (REMatch) mymatch.clone(); + while (true) { + tryMatch.index = index; + REMatch m = matchRest(input, tryMatch); + count--; + if (stingy) index += tokenFixedLength; + else index -= tokenFixedLength; + if (possessive) return m; + if (m != null) { + if (count > 0) { + m.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, + new int[] {index, count})); + } + return m; + } + if (count <= 0) return null; + } + } + + void dump(StringBuffer os) { + os.append("(?:"); + token.dumpAll(os); + os.append(')'); + if ((max == Integer.MAX_VALUE) && (min <= 1)) + os.append( (min == 0) ? '*' : '+' ); + else if ((min == 0) && (max == 1)) + os.append('?'); + else { + os.append('{').append(min); + if (max > min) { + os.append(','); + if (max != Integer.MAX_VALUE) os.append(max); + } + os.append('}'); + } + if (stingy) os.append('?'); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenStart.java b/libjava/classpath/gnu/java/util/regex/RETokenStart.java new file mode 100644 index 00000000000..aa5f0c91401 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenStart.java @@ -0,0 +1,121 @@ +/* gnu/regexp/RETokenStart.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +class RETokenStart extends REToken { + private String newline; // matches after a newline + private boolean check_java_line_terminators; + + RETokenStart(int subIndex, String newline) { + super(subIndex); + this.newline = newline; + this.check_java_line_terminators = false; + } + + RETokenStart(int subIndex, String newline, boolean b) { + super(subIndex); + this.newline = newline; + this.check_java_line_terminators = b; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + // charAt(index-n) may be unknown on a Reader/InputStream. FIXME + // Match after a newline if in multiline mode + + if (check_java_line_terminators) { + char ch = input.charAt(mymatch.index - 1); + if (ch != CharIndexed.OUT_OF_BOUNDS) { + if (ch == '\n') return mymatch; + if (ch == '\r') { + char ch1 = input.charAt(mymatch.index); + if (ch1 != '\n') return mymatch; + return null; + } + if (ch == '\u0085') return mymatch; // A next-line character + if (ch == '\u2028') return mymatch; // A line-separator character + if (ch == '\u2029') return mymatch; // A paragraph-separator character + } + } + + if (newline != null) { + int len = newline.length(); + if (mymatch.offset >= len) { + boolean found = true; + char z; + int i = 0; // position in REToken.newline + char ch = input.charAt(mymatch.index - len); + do { + z = newline.charAt(i); + if (ch != z) { + found = false; + break; + } + ++i; + ch = input.charAt(mymatch.index - len + i); + } while (i < len); + + if (found) return mymatch; + } + } + + // Don't match at all if REG_NOTBOL is set. + if ((mymatch.eflags & RE.REG_NOTBOL) > 0) return null; + + if ((mymatch.eflags & RE.REG_ANCHORINDEX) > 0) + return (mymatch.anchor == mymatch.offset) ? + mymatch : null; + else + return ((mymatch.index == 0) && (mymatch.offset == 0)) ? + mymatch : null; + } + + boolean returnsFixedLengthmatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append('^'); + } +} diff --git a/libjava/classpath/gnu/java/util/regex/RETokenWordBoundary.java b/libjava/classpath/gnu/java/util/regex/RETokenWordBoundary.java new file mode 100644 index 00000000000..538c6bef40c --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenWordBoundary.java @@ -0,0 +1,116 @@ +/* gnu/regexp/RETokenWordBoundary.java + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; + +/** + * Represents a combination lookahead/lookbehind for POSIX [:alnum:]. + */ +final class RETokenWordBoundary extends REToken { + private boolean negated; + private int where; + static final int BEGIN = 1; + static final int END = 2; + + RETokenWordBoundary(int subIndex, int where, boolean negated) { + super(subIndex); + this.where = where; + this.negated = negated; + } + + int getMaximumLength() { + return 0; + } + + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + // Word boundary means input[index-1] was a word character + // and input[index] is not, or input[index] is a word character + // and input[index-1] was not + // In the string "one two three", these positions match: + // |o|n|e| |t|w|o| |t|h|r|e|e| + // ^ ^ ^ ^ ^ ^ + boolean after = false; // is current character a letter or digit? + boolean before = false; // is previous character a letter or digit? + char ch; + + // TODO: Also check REG_ANCHORINDEX vs. anchor + if (((mymatch.eflags & RE.REG_ANCHORINDEX) != RE.REG_ANCHORINDEX) + || (mymatch.offset + mymatch.index > mymatch.anchor)) { + if ((ch = input.charAt(mymatch.index - 1)) != CharIndexed.OUT_OF_BOUNDS) { + before = Character.isLetterOrDigit(ch) || (ch == '_'); + } + } + + if ((ch = input.charAt(mymatch.index)) != CharIndexed.OUT_OF_BOUNDS) { + after = Character.isLetterOrDigit(ch) || (ch == '_'); + } + + // if (before) and (!after), we're at end (\>) + // if (after) and (!before), we're at beginning (\<) + boolean doNext = false; + + if ((where & BEGIN) == BEGIN) { + doNext = after && !before; + } + if ((where & END) == END) { + doNext ^= before && !after; + } + + if (negated) doNext = !doNext; + + return (doNext ? mymatch : null); + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if(matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + if (where == (BEGIN | END)) { + os.append( negated ? "\\B" : "\\b" ); + } else if (where == BEGIN) { + os.append("\\<"); + } else { + os.append("\\>"); + } + } +} diff --git a/libjava/classpath/gnu/java/util/regex/UncheckedRE.java b/libjava/classpath/gnu/java/util/regex/UncheckedRE.java new file mode 100644 index 00000000000..73a67c65736 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/UncheckedRE.java @@ -0,0 +1,109 @@ +/* gnu/regexp/UncheckedRE.java + Copyright (C) 2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * UncheckedRE is a subclass of RE that allows programmers an easier means + * of programmatically precompiling regular expressions. It is constructed + * and used in exactly the same manner as an instance of the RE class; the + * only difference is that its constructors do not throw REException. + * Instead, if a syntax error is encountered during construction, a + * RuntimeException will be thrown. + * <P> + * Note that this makes UncheckedRE dangerous if constructed with + * dynamic data. Do not use UncheckedRE unless you are completely sure + * that all input being passed to it contains valid, well-formed + * regular expressions for the syntax specified. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @see gnu.java.util.regex.RE + * @since gnu.regexp 1.1.4 + */ + +public final class UncheckedRE extends RE { + /** + * Constructs a regular expression pattern buffer without any compilation + * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer or char[]. Other input types will be converted to + * strings using the toString() method. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern) { + this(pattern,0,RESyntax.RE_SYNTAX_PERL5); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags in the RE class. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern, int cflags) { + this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and regular expression syntax. + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags in the RE class. + * @param syntax The type of regular expression syntax to use. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern, int cflags, RESyntax syntax) { + try { + initialize(pattern,cflags,syntax,0,0); + } catch (REException e) { + throw new RuntimeException(e.getMessage()); + } + } +} + + |