diff options
Diffstat (limited to 'libjava/classpath/gnu/java/util')
49 files changed, 13269 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/java/util/Base64.java b/libjava/classpath/gnu/java/util/Base64.java new file mode 100644 index 000000000..80b1fcc02 --- /dev/null +++ b/libjava/classpath/gnu/java/util/Base64.java @@ -0,0 +1,342 @@ +/* Base64.java -- Base64 encoding and decoding. + Copyright (C) 2006, 2007 Free Software Foundation, Inc. + +This file is a 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 of the License, 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; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, 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. + +-- +Base64 encoding derived from ISC's DHCP. Copyright notices from DHCP +follow. See http://www.isc.org/products/DHCP/. + +Copyright (c) 1996 by Internet Software Consortium. + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM +DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING +FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-- +Portions Copyright (c) 1995 by International Business Machines, Inc. + +International Business Machines, Inc. (hereinafter called IBM) grants +permission under its copyrights to use, copy, modify, and distribute +this Software with or without fee, provided that the above copyright +notice and all paragraphs of this notice appear in all copies, and +that the name of IBM not be used in connection with the marketing of +any product incorporating the Software or modifications thereof, +without specific, written prior permission. + +To the extent it has a right to do so, IBM grants an immunity from +suit under its patents, if any, for the use, sale or manufacture of +products to the extent that such products are used for performing +Domain Name System dynamic updates in TCP/IP networks by means of the +Software. No immunity is granted for any product per se or for any +other function of any product. + +THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, +DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE, EVEN IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH +DAMAGES. */ + + +package gnu.java.util; + +import gnu.java.lang.CPStringBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class Base64 +{ + + // No constructor. + private Base64() { } + + // Class methods. + // ------------------------------------------------------------------------- + + /** Base-64 characters. */ + private static final String BASE_64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** Base-64 padding character. */ + private static final char BASE_64_PAD = '='; + + /** + * Base64 encode a byte array, with no line wrapping. + * + * @param buf The byte array to encode. + * @return <tt>buf</tt> encoded in Base64. + */ + public static String encode(byte[] buf) + { + return encode(buf, 0); + } + + /** + * Base64 encode a byte array, returning the returning string. + * + * @param buf The byte array to encode. + * @param tw The total length of any line, 0 for unlimited. + * @return <tt>buf</tt> encoded in Base64. + */ + public static String encode(byte[] buf, int tw) + { + return encode(buf, 0, buf.length, tw); + } + + /** + * Base64 encode a byte array, returning the returning string. + * + * @param buf The byte array to encode. + * @param offset The offset in the byte array to start. + * @param length The number of bytes to encode. + * @param tw The total length of any line, 0 for unlimited. + * @return <tt>buf</tt> encoded in Base64. + */ + public static String encode(byte[] buf, int offset, int length, int tw) + { + if (offset < 0 || length < 0 || offset + length > buf.length) + throw new ArrayIndexOutOfBoundsException(buf.length + " " + + offset + " " + + length); + int srcLength = buf.length - offset; + byte[] input = new byte[3]; + int[] output = new int[4]; + CPStringBuilder out = new CPStringBuilder(); + int i = offset; + int chars = 0; + + while (srcLength > 2) + { + input[0] = buf[i++]; + input[1] = buf[i++]; + input[2] = buf[i++]; + srcLength -= 3; + + output[0] = (input[0] & 0xff) >>> 2; + output[1] = ((input[0] & 0x03) << 4) + ((input[1] & 0xff) >>> 4); + output[2] = ((input[1] & 0x0f) << 2) + ((input[2] & 0xff) >>> 6); + output[3] = input[2] & 0x3f; + + out.append(BASE_64.charAt(output[0])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + out.append(BASE_64.charAt(output[1])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + out.append(BASE_64.charAt(output[2])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + out.append(BASE_64.charAt(output[3])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + } + + if (srcLength != 0) + { + input[0] = input[1] = input[2] = 0; + for (int j = 0; j < srcLength; j++) + { + input[j] = buf[i+j]; + } + output[0] = (input[0] & 0xff) >>> 2; + output[1] = ((input[0] & 0x03) << 4) + ((input[1] & 0xff) >>> 4); + output[2] = ((input[1] & 0x0f) << 2) + ((input[2] & 0xff) >>> 6); + + out.append(BASE_64.charAt(output[0])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + out.append(BASE_64.charAt(output[1])); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + if (srcLength == 1) + { + out.append(BASE_64_PAD); + } + else + { + out.append(BASE_64.charAt(output[2])); + } + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + out.append(BASE_64_PAD); + if (tw > 0 && ++chars % tw == 0) + { + out.append("\n"); + } + } + if (tw > 0) + { + out.append("\n"); + } + + return out.toString(); + } + + /** + * Decode a Base-64 string into a byte array. + * + * @param b64 The Base-64 encoded string. + * @return The decoded bytes. + * @throws java.io.IOException If the argument is not a valid Base-64 + * encoding. + */ + public static byte[] decode(String b64) throws IOException + { + ByteArrayOutputStream result = new ByteArrayOutputStream(b64.length() / 3); + int state = 0, i; + byte temp = 0; + + for (i = 0; i < b64.length(); i++) + { + if (Character.isWhitespace(b64.charAt(i))) + { + continue; + } + if (b64.charAt(i) == BASE_64_PAD) + { + break; + } + + int pos = BASE_64.indexOf(b64.charAt(i)); + if (pos < 0) + { + throw new IOException("non-Base64 character " + b64.charAt(i)); + } + switch (state) + { + case 0: + temp = (byte) (pos - BASE_64.indexOf('A') << 2); + state = 1; + break; + + case 1: + temp |= (byte) (pos - BASE_64.indexOf('A') >>> 4); + result.write(temp); + temp = (byte) ((pos - BASE_64.indexOf('A') & 0x0f) << 4); + state = 2; + break; + + case 2: + temp |= (byte) ((pos - BASE_64.indexOf('A') & 0x7f) >>> 2); + result.write(temp); + temp = (byte) ((pos - BASE_64.indexOf('A') & 0x03) << 6); + state = 3; + break; + + case 3: + temp |= (byte) (pos - BASE_64.indexOf('A') & 0xff); + result.write(temp); + state = 0; + break; + + default: + throw new Error("this statement should be unreachable"); + } + } + + if (i < b64.length() && b64.charAt(i) == BASE_64_PAD) + { + switch (state) + { + case 0: + case 1: + throw new IOException("malformed Base64 sequence"); + + case 2: + i++; + for ( ; i < b64.length(); i++) + { + if (!Character.isWhitespace(b64.charAt(i))) + { + break; + } + } + // We must see a second pad character here. + if (b64.charAt(i) != BASE_64_PAD) + { + throw new IOException("malformed Base64 sequence"); + } + i++; + // Fall-through. + + case 3: + i++; + for ( ; i < b64.length(); i++) + { + // We should only see whitespace after this. + if (!Character.isWhitespace(b64.charAt(i))) + { + throw new IOException("malformed Base64 sequence"); + } + } + } + } + else + { + if (state != 0) + { + throw new IOException("malformed Base64 sequence"); + } + } + + return result.toByteArray(); + } +} diff --git a/libjava/classpath/gnu/java/util/DoubleEnumeration.java b/libjava/classpath/gnu/java/util/DoubleEnumeration.java new file mode 100644 index 000000000..852644bc6 --- /dev/null +++ b/libjava/classpath/gnu/java/util/DoubleEnumeration.java @@ -0,0 +1,138 @@ +/* gnu.java.util.DoubleEnumeration + Copyright (C) 1998, 1999, 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; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + + +/** + * This is a helper class that combines two Enumerations. + * It returns the elements of the first Enumeration until it has + * no more elements and then returns the elements of the second + * Enumeration.<br> + * + * In the default case: + * <pre> + * doubleEnum = new DoubleEnumeration(enum1, enum2); + * while (doubleEnum.hasMoreElements()) { + * Object o = doubleEnum.nextElement(); + * do_something(o); + * } + * </pre> + * it calls hasMoreElements of the Enumerations as few times as + * possible. + * The references to the Enumerations are cleared as soon as they have no + * more elements to help garbage collecting. + * + * @author Jochen Hoenicke + * @author Mark Wielaard (mark@klomp.org) + */ +public class DoubleEnumeration<T> implements Enumeration<T> +{ + /** + * This is true as long as one of the enumerations has more + * elements. + * Only valid when hasChecked is true. + * Set in <code>hasMoreElements()</code> + */ + private boolean hasMore; + /** + * This is true, if it is sure that hasMore indicates wether there are + * more elements. + * Set to true in <code>hasMoreElements()</code>. + * Set to false in <code>getNextElement()</code>. + */ + private boolean hasChecked; + /** + * The first enumeration. + */ + private Enumeration<T> e1; + /** + * The second enumeration. + */ + private Enumeration<T> e2; + + /** + * Creates a new Enumeration combining the given two enumerations. + * The enumerations mustn't be accessed by other classes. + */ + public DoubleEnumeration(Enumeration<T> e1, Enumeration<T> e2) + { + this.e1 = e1; + this.e2 = e2; + hasChecked = false; + } + + /** + * Returns true, if at least one of the two enumerations has more + * elements. + */ + public boolean hasMoreElements() + { + if (hasChecked) + return hasMore; + + hasMore = (e1 != null && e1.hasMoreElements()); + + if (!hasMore) { + e1 = e2; + e2 = null; + hasMore = (e1 != null && e1.hasMoreElements()); + } + + hasChecked = true; + return hasMore; + } + + /** + * Returns the next element. This returns the next element of the + * first enumeration, if it has more elements, otherwise the next + * element of the second enumeration. If both enumeration don't have + * any elements it throws a <code>NoSuchElementException</code>. + */ + public T nextElement() + { + if (!hasMoreElements()) + throw new NoSuchElementException(); + else { + hasChecked = false; + return e1.nextElement(); + } + } +} diff --git a/libjava/classpath/gnu/java/util/EmptyEnumeration.java b/libjava/classpath/gnu/java/util/EmptyEnumeration.java new file mode 100644 index 000000000..30c50d81d --- /dev/null +++ b/libjava/classpath/gnu/java/util/EmptyEnumeration.java @@ -0,0 +1,90 @@ +/* EmptyEnumeration.java -- a constant empty enumeration + Copyright (C) 2001, 2002 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; + +import java.io.Serializable; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * This is a helper class that produces an empty Enumerations. There is only + * one instance of this class that can be used whenever one needs a + * non-null but empty enumeration. Using this class prevents multiple + * small objects and inner classes. <code>getInstance()</code> returns + * the only instance of this class. It can be shared by multiple objects and + * threads. + * + * @author Mark Wielaard (mark@klomp.org) + */ +public final class EmptyEnumeration<T> implements Enumeration<T>, Serializable +{ + /** The only instance of this class */ + private static final EmptyEnumeration<Object> instance = + new EmptyEnumeration<Object>(); + + /** + * Returns an instance of this class for Object. + * It can be shared by multiple objects and threads. + * + * @return the common empty enumeration + */ + public static EmptyEnumeration<Object> getInstance() + { + return instance; + } + + /** + * Returns false, since there are no elements. + * + * @return false + */ + public boolean hasMoreElements() + { + return false; + } + + /** + * Always throws <code>NoSuchElementException</code>, since it is empty. + * + * @throws NoSuchElementException this is empty + */ + public T nextElement() + { + throw new NoSuchElementException(); + } +} diff --git a/libjava/classpath/gnu/java/util/LRUCache.java b/libjava/classpath/gnu/java/util/LRUCache.java new file mode 100644 index 000000000..784a442ab --- /dev/null +++ b/libjava/classpath/gnu/java/util/LRUCache.java @@ -0,0 +1,77 @@ +/* LRUCache.java -- A LRU Cache implementation + Copyright (C) 2007 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; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A least recently used cache, based on LinkedHashMap. + */ +public class LRUCache<K,V> + extends LinkedHashMap<K,V> +{ + + /** + * The capacity of the cache. + */ + private int capacity; + + /** + * Creates a new LRUCache instance with the specified capacity. + * + * @param cap the capacity of the new cache + */ + public LRUCache(int cap) + { + super(); + capacity = cap; + } + + /** + * Returns <code>true</code> when the oldest entry should be removed. + * + * @param eldest the entry about to be removed + * + * @return <code>true</code> when the oldest entry should be removed + */ + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) + { + return size() > capacity; + } +} diff --git a/libjava/classpath/gnu/java/util/WeakIdentityHashMap.java b/libjava/classpath/gnu/java/util/WeakIdentityHashMap.java new file mode 100644 index 000000000..4d9014969 --- /dev/null +++ b/libjava/classpath/gnu/java/util/WeakIdentityHashMap.java @@ -0,0 +1,862 @@ +/* WeakIdentityHashMap -- an identity hashtable that keeps only weak references + to its keys, allowing the virtual machine to reclaim them + Copyright (C) 1999, 2000, 2001, 2002, 2003, 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; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A weak hash map has only weak references to the key. This means that it + * allows the key to be garbage collected if it is not used otherwise. If + * this happens, the entry will eventually disappear from the map, + * asynchronously. + * + * <p>Other strange behaviors to be aware of: The size of this map may + * spontaneously shrink (even if you use a synchronized map and synchronize + * it); it behaves as if another thread removes entries from this table + * without synchronization. The entry set returned by <code>entrySet</code> + * has similar phenomenons: The size may spontaneously shrink, or an + * entry, that was in the set before, suddenly disappears. + * + * <p>A weak hash map is not meant for caches; use a normal map, with + * soft references as values instead, or try {@link LinkedHashMap}. + * + * <p>The weak hash map supports null values and null keys. The null key + * is never deleted from the map (except explictly of course). The + * performance of the methods are similar to that of a hash map. + * + * <p>The value objects are strongly referenced by this table. So if a + * value object maintains a strong reference to the key (either direct + * or indirect) the key will never be removed from this map. According + * to Sun, this problem may be fixed in a future release. It is not + * possible to do it with the jdk 1.2 reference model, though. + * + * @author Jochen Hoenicke + * @author Eric Blake (ebb9@email.byu.edu) + * @author Jeroen Frijters + * + * @see HashMap + * @see WeakReference + * @see WeakHashMap + * @see IdentityHashMap + * @see LinkedHashMap + */ +public class WeakIdentityHashMap extends AbstractMap implements Map +{ + /** + * The default capacity for an instance of HashMap. + * Sun's documentation mildly suggests that this (11) is the correct + * value. + */ + private static final int DEFAULT_CAPACITY = 11; + + /** + * The default load factor of a HashMap. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75F; + + /** + * This is used instead of the key value <i>null</i>. It is needed + * to distinguish between an null key and a removed key. + */ + // Package visible for use by nested classes. + static final Object NULL_KEY = new Object(); + + /** + * The reference queue where our buckets (which are WeakReferences) are + * registered to. + */ + private final ReferenceQueue queue; + + /** + * The number of entries in this hash map. + */ + // Package visible for use by nested classes. + int size; + + /** + * The load factor of this WeakIdentityHashMap. This is the maximum ratio of + * size versus number of buckets. If size grows the number of buckets + * must grow, too. + */ + private float loadFactor; + + /** + * The rounded product of the capacity (i.e. number of buckets) and + * the load factor. When the number of elements exceeds the + * threshold, the HashMap calls <code>rehash()</code>. + */ + private int threshold; + + /** + * The number of structural modifications. This is used by + * iterators, to see if they should fail. This doesn't count + * the silent key removals, when a weak reference is cleared + * by the garbage collection. Instead the iterators must make + * sure to have strong references to the entries they rely on. + */ + // Package visible for use by nested classes. + int modCount; + + /** + * The entry set. There is only one instance per hashmap, namely + * theEntrySet. Note that the entry set may silently shrink, just + * like the WeakIdentityHashMap. + */ + private final class WeakEntrySet extends AbstractSet + { + /** + * Non-private constructor to reduce bytecode emitted. + */ + WeakEntrySet() + { + } + + /** + * Returns the size of this set. + * + * @return the set size + */ + public int size() + { + return size; + } + + /** + * Returns an iterator for all entries. + * + * @return an Entry iterator + */ + public Iterator iterator() + { + return new Iterator() + { + /** + * The entry that was returned by the last + * <code>next()</code> call. This is also the entry whose + * bucket should be removed by the <code>remove</code> call. <br> + * + * It is null, if the <code>next</code> method wasn't + * called yet, or if the entry was already removed. <br> + * + * Remembering this entry here will also prevent it from + * being removed under us, since the entry strongly refers + * to the key. + */ + WeakBucket.WeakEntry lastEntry; + + /** + * The entry that will be returned by the next + * <code>next()</code> call. It is <code>null</code> if there + * is no further entry. <br> + * + * Remembering this entry here will also prevent it from + * being removed under us, since the entry strongly refers + * to the key. + */ + WeakBucket.WeakEntry nextEntry = findNext(null); + + /** + * The known number of modification to the list, if it differs + * from the real number, we throw an exception. + */ + int knownMod = modCount; + + /** + * Check the known number of modification to the number of + * modifications of the table. If it differs from the real + * number, we throw an exception. + * @throws ConcurrentModificationException if the number + * of modifications doesn't match. + */ + private void checkMod() + { + // This method will get inlined. + cleanQueue(); + if (knownMod != modCount) + throw new ConcurrentModificationException(knownMod + " != " + + modCount); + } + + /** + * Get a strong reference to the next entry after + * lastBucket. + * @param lastEntry the previous bucket, or null if we should + * get the first entry. + * @return the next entry. + */ + private WeakBucket.WeakEntry findNext(WeakBucket.WeakEntry lastEntry) + { + int slot; + WeakBucket nextBucket; + if (lastEntry != null) + { + nextBucket = lastEntry.getBucket().next; + slot = lastEntry.getBucket().slot; + } + else + { + nextBucket = buckets[0]; + slot = 0; + } + + while (true) + { + while (nextBucket != null) + { + WeakBucket.WeakEntry entry = nextBucket.getEntry(); + if (entry != null) + // This is the next entry. + return entry; + + // Entry was cleared, try next. + nextBucket = nextBucket.next; + } + + slot++; + if (slot == buckets.length) + // No more buckets, we are through. + return null; + + nextBucket = buckets[slot]; + } + } + + /** + * Checks if there are more entries. + * @return true, iff there are more elements. + * @throws ConcurrentModificationException if the hash map was + * modified. + */ + public boolean hasNext() + { + checkMod(); + return nextEntry != null; + } + + /** + * Returns the next entry. + * @return the next entry. + * @throws ConcurrentModificationException if the hash map was + * modified. + * @throws NoSuchElementException if there is no entry. + */ + public Object next() + { + checkMod(); + if (nextEntry == null) + throw new NoSuchElementException(); + lastEntry = nextEntry; + nextEntry = findNext(lastEntry); + return lastEntry; + } + + /** + * Removes the last returned entry from this set. This will + * also remove the bucket of the underlying weak hash map. + * @throws ConcurrentModificationException if the hash map was + * modified. + * @throws IllegalStateException if <code>next()</code> was + * never called or the element was already removed. + */ + public void remove() + { + checkMod(); + if (lastEntry == null) + throw new IllegalStateException(); + modCount++; + internalRemove(lastEntry.getBucket()); + lastEntry = null; + knownMod++; + } + }; + } + } + + /** + * A bucket is a weak reference to the key, that contains a strong + * reference to the value, a pointer to the next bucket and its slot + * number. <br> + * + * It would be cleaner to have a WeakReference as field, instead of + * extending it, but if a weak reference gets cleared, we only get + * the weak reference (by queue.poll) and wouldn't know where to + * look for this reference in the hashtable, to remove that entry. + * + * @author Jochen Hoenicke + */ + private static class WeakBucket extends WeakReference + { + /** + * The value of this entry. The key is stored in the weak + * reference that we extend. + */ + Object value; + + /** + * The next bucket describing another entry that uses the same + * slot. + */ + WeakBucket next; + + /** + * The slot of this entry. This should be + * <code>Math.abs(key.hashCode() % buckets.length)</code>. + * + * But since the key may be silently removed we have to remember + * the slot number. + * + * If this bucket was removed the slot is -1. This marker will + * prevent the bucket from being removed twice. + */ + int slot; + + /** + * Creates a new bucket for the given key/value pair and the specified + * slot. + * @param key the key + * @param queue the queue the weak reference belongs to + * @param value the value + * @param slot the slot. This must match the slot where this bucket + * will be enqueued. + */ + public WeakBucket(Object key, ReferenceQueue queue, Object value, + int slot) + { + super(key, queue); + this.value = value; + this.slot = slot; + } + + /** + * This class gives the <code>Entry</code> representation of the + * current bucket. It also keeps a strong reference to the + * key; bad things may happen otherwise. + */ + class WeakEntry implements Map.Entry + { + /** + * The strong ref to the key. + */ + Object key; + + /** + * Creates a new entry for the key. + * @param key the key + */ + public WeakEntry(Object key) + { + this.key = key; + } + + /** + * Returns the underlying bucket. + * @return the owning bucket + */ + public WeakBucket getBucket() + { + return WeakBucket.this; + } + + /** + * Returns the key. + * @return the key + */ + public Object getKey() + { + return key == NULL_KEY ? null : key; + } + + /** + * Returns the value. + * @return the value + */ + public Object getValue() + { + return value; + } + + /** + * This changes the value. This change takes place in + * the underlying hash map. + * @param newVal the new value + * @return the old value + */ + public Object setValue(Object newVal) + { + Object oldVal = value; + value = newVal; + return oldVal; + } + + /** + * The hashCode as specified in the Entry interface. + * @return the hash code + */ + public int hashCode() + { + return System.identityHashCode(key) + ^ (value == null ? 0 : value.hashCode()); + } + + /** + * The equals method as specified in the Entry interface. + * @param o the object to compare to + * @return true iff o represents the same key/value pair + */ + public boolean equals(Object o) + { + if (o instanceof Map.Entry) + { + Map.Entry e = (Map.Entry) o; + return getKey() == e.getKey() + && (value == null ? e.getValue() == null + : value.equals(e.getValue())); + } + return false; + } + + public String toString() + { + return getKey() + "=" + value; + } + } + + /** + * This returns the entry stored in this bucket, or null, if the + * bucket got cleared in the mean time. + * @return the Entry for this bucket, if it exists + */ + WeakEntry getEntry() + { + final Object key = this.get(); + if (key == null) + return null; + return new WeakEntry(key); + } + } + + /** + * The entry set returned by <code>entrySet()</code>. + */ + private final WeakEntrySet theEntrySet; + + /** + * The hash buckets. These are linked lists. Package visible for use in + * nested classes. + */ + WeakBucket[] buckets; + + /** + * Creates a new weak hash map with default load factor and default + * capacity. + */ + public WeakIdentityHashMap() + { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + /** + * Creates a new weak hash map with default load factor and the given + * capacity. + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if initialCapacity is negative + */ + public WeakIdentityHashMap(int initialCapacity) + { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Creates a new weak hash map with the given initial capacity and + * load factor. + * @param initialCapacity the initial capacity. + * @param loadFactor the load factor (see class description of HashMap). + * @throws IllegalArgumentException if initialCapacity is negative, or + * loadFactor is non-positive + */ + public WeakIdentityHashMap(int initialCapacity, float loadFactor) + { + // Check loadFactor for NaN as well. + if (initialCapacity < 0 || ! (loadFactor > 0)) + throw new IllegalArgumentException(); + if (initialCapacity == 0) + initialCapacity = 1; + this.loadFactor = loadFactor; + threshold = (int) (initialCapacity * loadFactor); + theEntrySet = new WeakEntrySet(); + queue = new ReferenceQueue(); + buckets = new WeakBucket[initialCapacity]; + } + + /** + * Construct a new WeakIdentityHashMap with the same mappings as the given map. + * The WeakIdentityHashMap has a default load factor of 0.75. + * + * @param m the map to copy + * @throws NullPointerException if m is null + * @since 1.3 + */ + public WeakIdentityHashMap(Map m) + { + this(m.size(), DEFAULT_LOAD_FACTOR); + putAll(m); + } + + /** + * Simply hashes a non-null Object to its array index. + * @param key the key to hash + * @return its slot number + */ + private int hash(Object key) + { + return Math.abs(System.identityHashCode(key) % buckets.length); + } + + /** + * Cleans the reference queue. This will poll all references (which + * are WeakBuckets) from the queue and remove them from this map. + * This will not change modCount, even if it modifies the map. The + * iterators have to make sure that nothing bad happens. <br> + * + * Currently the iterator maintains a strong reference to the key, so + * that is no problem. + */ + // Package visible for use by nested classes. + void cleanQueue() + { + Object bucket = queue.poll(); + while (bucket != null) + { + internalRemove((WeakBucket) bucket); + bucket = queue.poll(); + } + } + + /** + * Rehashes this hashtable. This will be called by the + * <code>add()</code> method if the size grows beyond the threshold. + * It will grow the bucket size at least by factor two and allocates + * new buckets. + */ + private void rehash() + { + WeakBucket[] oldBuckets = buckets; + int newsize = buckets.length * 2 + 1; // XXX should be prime. + threshold = (int) (newsize * loadFactor); + buckets = new WeakBucket[newsize]; + + // Now we have to insert the buckets again. + for (int i = 0; i < oldBuckets.length; i++) + { + WeakBucket bucket = oldBuckets[i]; + WeakBucket nextBucket; + while (bucket != null) + { + nextBucket = bucket.next; + + Object key = bucket.get(); + if (key == null) + { + // This bucket should be removed; it is probably + // already on the reference queue. We don't insert it + // at all, and mark it as cleared. + bucket.slot = -1; + size--; + } + else + { + // Add this bucket to its new slot. + int slot = hash(key); + bucket.slot = slot; + bucket.next = buckets[slot]; + buckets[slot] = bucket; + } + bucket = nextBucket; + } + } + } + + /** + * Finds the entry corresponding to key. Since it returns an Entry + * it will also prevent the key from being removed under us. + * @param key the key, may be null + * @return The WeakBucket.WeakEntry or null, if the key wasn't found. + */ + private WeakBucket.WeakEntry internalGet(Object key) + { + if (key == null) + key = NULL_KEY; + int slot = hash(key); + WeakBucket bucket = buckets[slot]; + while (bucket != null) + { + WeakBucket.WeakEntry entry = bucket.getEntry(); + if (entry != null && key == entry.key) + return entry; + + bucket = bucket.next; + } + return null; + } + + /** + * Adds a new key/value pair to the hash map. + * @param key the key. This mustn't exists in the map. It may be null. + * @param value the value. + */ + private void internalAdd(Object key, Object value) + { + if (key == null) + key = NULL_KEY; + int slot = hash(key); + WeakBucket bucket = new WeakBucket(key, queue, value, slot); + bucket.next = buckets[slot]; + buckets[slot] = bucket; + size++; + } + + /** + * Removes a bucket from this hash map, if it wasn't removed before + * (e.g. one time through rehashing and one time through reference queue). + * Package visible for use in nested classes. + * + * @param bucket the bucket to remove. + */ + void internalRemove(WeakBucket bucket) + { + int slot = bucket.slot; + if (slot == -1) + // This bucket was already removed. + return; + + // Mark the bucket as removed. This is necessary, since the + // bucket may be enqueued later by the garbage collection, and + // internalRemove will be called a second time. + bucket.slot = -1; + + WeakBucket prev = null; + WeakBucket next = buckets[slot]; + while (next != bucket) + { + if (next == null) + throw new InternalError("WeakIdentityHashMap in inconsistent state"); + prev = next; + next = prev.next; + } + if (prev == null) + buckets[slot] = bucket.next; + else + prev.next = bucket.next; + + size--; + } + + /** + * Returns the size of this hash map. Note that the size() may shrink + * spontaneously, if the some of the keys were only weakly reachable. + * @return the number of entries in this hash map. + */ + public int size() + { + cleanQueue(); + return size; + } + + /** + * Tells if the map is empty. Note that the result may change + * spontanously, if all of the keys were only weakly reachable. + * @return true, iff the map is empty. + */ + public boolean isEmpty() + { + cleanQueue(); + return size == 0; + } + + /** + * Tells if the map contains the given key. Note that the result + * may change spontanously, if the key was only weakly + * reachable. + * @param key the key to look for + * @return true, iff the map contains an entry for the given key. + */ + public boolean containsKey(Object key) + { + cleanQueue(); + return internalGet(key) != null; + } + + /** + * Gets the value the key is mapped to. + * @return the value the key was mapped to. It returns null if + * the key wasn't in this map, or if the mapped value was + * explicitly set to null. + */ + public Object get(Object key) + { + cleanQueue(); + WeakBucket.WeakEntry entry = internalGet(key); + return entry == null ? null : entry.getValue(); + } + + /** + * Adds a new key/value mapping to this map. + * @param key the key, may be null + * @param value the value, may be null + * @return the value the key was mapped to previously. It returns + * null if the key wasn't in this map, or if the mapped value + * was explicitly set to null. + */ + public Object put(Object key, Object value) + { + cleanQueue(); + WeakBucket.WeakEntry entry = internalGet(key); + if (entry != null) + return entry.setValue(value); + + modCount++; + if (size >= threshold) + rehash(); + + internalAdd(key, value); + return null; + } + + /** + * Removes the key and the corresponding value from this map. + * @param key the key. This may be null. + * @return the value the key was mapped to previously. It returns + * null if the key wasn't in this map, or if the mapped value was + * explicitly set to null. + */ + public Object remove(Object key) + { + cleanQueue(); + WeakBucket.WeakEntry entry = internalGet(key); + if (entry == null) + return null; + + modCount++; + internalRemove(entry.getBucket()); + return entry.getValue(); + } + + /** + * Returns a set representation of the entries in this map. This + * set will not have strong references to the keys, so they can be + * silently removed. The returned set has therefore the same + * strange behaviour (shrinking size(), disappearing entries) as + * this weak hash map. + * @return a set representation of the entries. + */ + public Set entrySet() + { + cleanQueue(); + return theEntrySet; + } + + /** + * Clears all entries from this map. + */ + public void clear() + { + super.clear(); + } + + /** + * Returns true if the map contains at least one key which points to + * the specified object as a value. Note that the result + * may change spontanously, if its key was only weakly reachable. + * @param value the value to search for + * @return true if it is found in the set. + */ + public boolean containsValue(Object value) + { + cleanQueue(); + return super.containsValue(value); + } + + /** + * Returns a set representation of the keys in this map. This + * set will not have strong references to the keys, so they can be + * silently removed. The returned set has therefore the same + * strange behaviour (shrinking size(), disappearing entries) as + * this weak hash map. + * @return a set representation of the keys. + */ + public Set keySet() + { + cleanQueue(); + return super.keySet(); + } + + /** + * Puts all of the mappings from the given map into this one. If the + * key already exists in this map, its value is replaced. + * @param m the map to copy in + */ + public void putAll(Map m) + { + super.putAll(m); + } + + /** + * Returns a collection representation of the values in this map. This + * collection will not have strong references to the keys, so mappings + * can be silently removed. The returned collection has therefore the same + * strange behaviour (shrinking size(), disappearing entries) as + * this weak hash map. + * @return a collection representation of the values. + */ + public Collection values() + { + cleanQueue(); + return super.values(); + } +} // class WeakIdentityHashMap diff --git a/libjava/classpath/gnu/java/util/ZoneInfo.java b/libjava/classpath/gnu/java/util/ZoneInfo.java new file mode 100644 index 000000000..117ef3cf2 --- /dev/null +++ b/libjava/classpath/gnu/java/util/ZoneInfo.java @@ -0,0 +1,1160 @@ +/* gnu.java.util.ZoneInfo + Copyright (C) 2007 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; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +/** + * This class represents more advanced variant of java.util.SimpleTimeZone. + * It can handle zic(8) compiled transition dates plus uses a SimpleTimeZone + * for years beyond last precomputed transition. Before first precomputed + * transition it assumes no daylight saving was in effect. + * Timezones that never used daylight saving time should use just + * SimpleTimeZone instead of this class. + * + * This object is tightly bound to the Gregorian calendar. It assumes + * a regular seven days week, and the month lengths are that of the + * Gregorian Calendar. + * + * @see Calendar + * @see GregorianCalendar + * @see SimpleTimeZone + * @author Jakub Jelinek + */ +public class ZoneInfo extends TimeZone +{ + private static final int SECS_SHIFT = 22; + private static final long OFFSET_MASK = (1 << 21) - 1; + private static final int OFFSET_SHIFT = 64 - 21; + private static final long IS_DST = 1 << 21; + + /** + * The raw time zone offset in milliseconds to GMT, ignoring + * daylight savings. + * @serial + */ + private int rawOffset; + + /** + * Cached DST savings for the last transition rule. + */ + private int dstSavings; + + /** + * Cached flag whether last transition rule uses DST saving. + */ + private boolean useDaylight; + + /** + * Array of encoded transitions. + * Transition time in UTC seconds since epoch is in the most + * significant 64 - SECS_SHIFT bits, then one bit flag + * whether DST is active and the least significant bits + * containing offset relative to rawOffset. Both the DST + * flag and relative offset apply to time before the transition + * and after or equal to previous transition if any. + * The array must be sorted. + */ + private long[] transitions; + + /** + * SimpleTimeZone rule which applies on or after the latest + * transition. If the DST changes are not expresible as a + * SimpleTimeZone rule, then the rule should just contain + * the standard time and no DST time. + */ + private SimpleTimeZone lastRule; + + /** + * Cached GMT SimpleTimeZone object for internal use in + * getOffset method. + */ + private static SimpleTimeZone gmtZone = null; + + static final long serialVersionUID = -3740626706860383657L; + + /** + * Create a <code>ZoneInfo</code> with the given time offset + * from GMT and with daylight savings. + * + * @param rawOffset The time offset from GMT in milliseconds. + * @param id The identifier of this time zone. + * @param transitions Array of transition times in UTC seconds since + * Epoch in topmost 42 bits, below that 1 boolean bit whether the time + * before that transition used daylight saving and in bottommost 21 + * bits relative daylight saving offset against rawOffset in seconds + * that applies before this transition. + * @param endRule SimpleTimeZone class describing the daylight saving + * rules after the last transition. + */ + public ZoneInfo(int rawOffset, String id, long[] transitions, + SimpleTimeZone lastRule) + { + if (transitions == null || transitions.length < 1) + throw new IllegalArgumentException("transitions must not be null"); + if (lastRule == null) + throw new IllegalArgumentException("lastRule must not be null"); + this.rawOffset = rawOffset; + this.transitions = transitions; + this.lastRule = lastRule; + setID(id); + computeDSTSavings(); + } + + /** + * Gets the time zone offset, for current date, modified in case of + * daylight savings. This is the offset to add to UTC to get the local + * time. + * + * The day must be a positive number and dayOfWeek must be a positive value + * from Calendar. dayOfWeek is redundant, but must match the other values + * or an inaccurate result may be returned. + * + * @param era the era of the given date + * @param year the year of the given date + * @param month the month of the given date, 0 for January. + * @param day the day of month + * @param dayOfWeek the day of week; this must match the other fields. + * @param millis the millis in the day (in local standard time) + * @return the time zone offset in milliseconds. + * @throws IllegalArgumentException if arguments are incorrect. + */ + public int getOffset(int era, int year, int month, int day, int dayOfWeek, + int millis) + { + if (gmtZone == null) + gmtZone = new SimpleTimeZone(0, "GMT"); + + if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) + throw new IllegalArgumentException("dayOfWeek out of range"); + if (month < Calendar.JANUARY || month > Calendar.DECEMBER) + throw new IllegalArgumentException("month out of range:" + month); + + if (era != GregorianCalendar.AD) + return (int) (((transitions[0] << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000); + + GregorianCalendar cal = new GregorianCalendar((TimeZone) gmtZone); + cal.set(year, month, day, 0, 0, 0); + if (cal.get(Calendar.DAY_OF_MONTH) != day) + throw new IllegalArgumentException("day out of range"); + + return getOffset(cal.getTimeInMillis() - rawOffset + millis); + } + + private long findTransition(long secs) + { + if (secs < (transitions[0] >> SECS_SHIFT)) + return transitions[0]; + + if (secs >= (transitions[transitions.length-1] >> SECS_SHIFT)) + return Long.MAX_VALUE; + + long val = (secs + 1) << SECS_SHIFT; + int lo = 1; + int hi = transitions.length; + int mid = 1; + while (lo < hi) + { + mid = (lo + hi) / 2; + // secs < (transitions[mid-1] >> SECS_SHIFT) + if (val <= transitions[mid-1]) + hi = mid; + // secs >= (transitions[mid] >> SECS_SHIFT) + else if (val > transitions[mid]) + lo = mid + 1; + else + break; + } + return transitions[mid]; + } + + /** + * Get the time zone offset for the specified date, modified in case of + * daylight savings. This is the offset to add to UTC to get the local + * time. + * @param date the date represented in millisecends + * since January 1, 1970 00:00:00 GMT. + */ + public int getOffset(long date) + { + long d = (date >= 0 ? date / 1000 : (date + 1) / 1000 - 1); + long transition = findTransition(d); + + // For times on or after last transition use lastRule. + if (transition == Long.MAX_VALUE) + return lastRule.getOffset(date); + + return (int) (((transition << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000); + } + + /** + * Returns the time zone offset to GMT in milliseconds, ignoring + * day light savings. + * @return the time zone offset. + */ + public int getRawOffset() + { + return rawOffset; + } + + /** + * Sets the standard time zone offset to GMT. + * @param rawOffset The time offset from GMT in milliseconds. + */ + public void setRawOffset(int rawOffset) + { + this.rawOffset = rawOffset; + lastRule.setRawOffset(rawOffset); + } + + private void computeDSTSavings() + { + if (lastRule.useDaylightTime()) + { + dstSavings = lastRule.getDSTSavings(); + useDaylight = true; + } + else + { + dstSavings = 0; + useDaylight = false; + // lastRule might say no DST is in effect simply because + // the DST rules are too complex for SimpleTimeZone, say + // for Asia/Jerusalem. + // Look at the last DST offset if it is newer than current time. + long currentSecs = System.currentTimeMillis() / 1000; + int i; + for (i = transitions.length - 1; + i >= 0 && currentSecs < (transitions[i] >> SECS_SHIFT); + i--) + if ((transitions[i] & IS_DST) != 0) + { + dstSavings = (int) (((transitions[i] << OFFSET_SHIFT) + >> OFFSET_SHIFT) * 1000) + - rawOffset; + useDaylight = true; + break; + } + } + } + + /** + * Gets the daylight savings offset. This is a positive offset in + * milliseconds with respect to standard time. Typically this + * is one hour, but for some time zones this may be half an our. + * @return the daylight savings offset in milliseconds. + */ + public int getDSTSavings() + { + return dstSavings; + } + + /** + * Returns if this time zone uses daylight savings time. + * @return true, if we use daylight savings time, false otherwise. + */ + public boolean useDaylightTime() + { + return useDaylight; + } + + /** + * Determines if the given date is in daylight savings time. + * @return true, if it is in daylight savings time, false otherwise. + */ + public boolean inDaylightTime(Date date) + { + long d = date.getTime(); + d = (d >= 0 ? d / 1000 : (d + 1) / 1000 - 1); + long transition = findTransition(d); + + // For times on or after last transition use lastRule. + if (transition == Long.MAX_VALUE) + return lastRule.inDaylightTime(date); + + return (transition & IS_DST) != 0; + } + + /** + * Generates the hashCode for the SimpleDateFormat object. It is + * the rawOffset, possibly, if useDaylightSavings is true, xored + * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime. + */ + public synchronized int hashCode() + { + int hash = lastRule.hashCode(); + // FIXME - hash transitions? + return hash; + } + + public synchronized boolean equals(Object o) + { + if (! hasSameRules((TimeZone) o)) + return false; + + ZoneInfo zone = (ZoneInfo) o; + return getID().equals(zone.getID()); + } + + /** + * Test if the other time zone uses the same rule and only + * possibly differs in ID. This implementation for this particular + * class will return true if the other object is a ZoneInfo, + * the raw offsets and useDaylight are identical and if useDaylight + * is true, also the start and end datas are identical. + * @return true if this zone uses the same rule. + */ + public boolean hasSameRules(TimeZone o) + { + if (this == o) + return true; + if (! (o instanceof ZoneInfo)) + return false; + ZoneInfo zone = (ZoneInfo) o; + if (zone.hashCode() != hashCode() || rawOffset != zone.rawOffset) + return false; + if (! lastRule.equals(zone.lastRule)) + return false; + // FIXME - compare transitions? + return true; + } + + /** + * Returns a string representation of this ZoneInfo object. + * @return a string representation of this ZoneInfo object. + */ + public String toString() + { + return getClass().getName() + "[" + "id=" + getID() + ",offset=" + + rawOffset + ",transitions=" + transitions.length + + ",useDaylight=" + useDaylight + + (useDaylight ? (",dstSavings=" + dstSavings) : "") + + ",lastRule=" + lastRule.toString() + "]"; + } + + /** + * Reads zic(8) compiled timezone data file from file + * and returns a TimeZone class describing it, either + * SimpleTimeZone or ZoneInfo depending on whether + * it can be described by SimpleTimeZone rule or not. + */ + public static TimeZone readTZFile(String id, String file) + { + DataInputStream dis = null; + try + { + FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis); + dis = new DataInputStream(bis); + + // Make sure we are reading a tzfile. + byte[] tzif = new byte[5]; + dis.readFully(tzif); + int tzif2 = 4; + if (tzif[0] == 'T' && tzif[1] == 'Z' + && tzif[2] == 'i' && tzif[3] == 'f') + { + if (tzif[4] >= '2') + tzif2 = 8; + // Reserved bytes + skipFully(dis, 16 - 1); + } + else + // Darwin has tzdata files that don't start with the TZif marker + skipFully(dis, 16 - 5); + + int ttisgmtcnt = dis.readInt(); + int ttisstdcnt = dis.readInt(); + int leapcnt = dis.readInt(); + int timecnt = dis.readInt(); + int typecnt = dis.readInt(); + int charcnt = dis.readInt(); + if (tzif2 == 8) + { + skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt + + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt); + + dis.readFully(tzif); + if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i' + || tzif[3] != 'f' || tzif[4] < '2') + return null; + + // Reserved bytes + skipFully(dis, 16 - 1); + ttisgmtcnt = dis.readInt(); + ttisstdcnt = dis.readInt(); + leapcnt = dis.readInt(); + timecnt = dis.readInt(); + typecnt = dis.readInt(); + charcnt = dis.readInt(); + } + + // Sanity checks + if (typecnt <= 0 || timecnt < 0 || charcnt < 0 + || leapcnt < 0 || ttisgmtcnt < 0 || ttisstdcnt < 0 + || ttisgmtcnt > typecnt || ttisstdcnt > typecnt) + return null; + + // Transition times + long[] times = new long[timecnt]; + for (int i = 0; i < timecnt; i++) + if (tzif2 == 8) + times[i] = dis.readLong(); + else + times[i] = (long) dis.readInt(); + + // Transition types + int[] types = new int[timecnt]; + for (int i = 0; i < timecnt; i++) + { + types[i] = dis.readByte(); + if (types[i] < 0) + types[i] += 256; + if (types[i] >= typecnt) + return null; + } + + // Types + int[] offsets = new int[typecnt]; + int[] typeflags = new int[typecnt]; + for (int i = 0; i < typecnt; i++) + { + offsets[i] = dis.readInt(); + if (offsets[i] >= IS_DST / 2 || offsets[i] <= -IS_DST / 2) + return null; + int dst = dis.readByte(); + int abbrind = dis.readByte(); + if (abbrind < 0) + abbrind += 256; + if (abbrind >= charcnt) + return null; + typeflags[i] = (dst != 0 ? (1 << 8) : 0) + abbrind; + } + + // Abbrev names + byte[] names = new byte[charcnt]; + dis.readFully(names); + + // Leap transitions, for now ignore + skipFully(dis, leapcnt * (tzif2 + 4) + ttisstdcnt + ttisgmtcnt); + + // tzIf2 format has optional POSIX TZ env string + String tzstr = null; + if (tzif2 == 8 && dis.readByte() == '\n') + { + tzstr = dis.readLine(); + if (tzstr.length() <= 0) + tzstr = null; + } + + // Get std/dst_offset and dst/non-dst time zone names. + int std_ind = -1; + int dst_ind = -1; + if (timecnt == 0) + std_ind = 0; + else + for (int i = timecnt - 1; i >= 0; i--) + { + if (std_ind == -1 && (typeflags[types[i]] & (1 << 8)) == 0) + std_ind = types[i]; + else if (dst_ind == -1 && (typeflags[types[i]] & (1 << 8)) != 0) + dst_ind = types[i]; + if (dst_ind != -1 && std_ind != -1) + break; + } + + if (std_ind == -1) + return null; + + int j = typeflags[std_ind] & 255; + while (j < charcnt && names[j] != 0) + j++; + String std_zonename = new String(names, typeflags[std_ind] & 255, + j - (typeflags[std_ind] & 255), + "ASCII"); + + String dst_zonename = ""; + if (dst_ind != -1) + { + j = typeflags[dst_ind] & 255; + while (j < charcnt && names[j] != 0) + j++; + dst_zonename = new String(names, typeflags[dst_ind] & 255, + j - (typeflags[dst_ind] & 255), "ASCII"); + } + + // Only use gmt offset when necessary. + // Also special case GMT+/- timezones. + String std_offset_string = ""; + String dst_offset_string = ""; + if (tzstr == null + && (dst_ind != -1 + || (offsets[std_ind] != 0 + && !std_zonename.startsWith("GMT+") + && !std_zonename.startsWith("GMT-")))) + { + std_offset_string = Integer.toString(-offsets[std_ind] / 3600); + int seconds = -offsets[std_ind] % 3600; + if (seconds != 0) + { + if (seconds < 0) + seconds *= -1; + if (seconds < 600) + std_offset_string += ":0" + Integer.toString(seconds / 60); + else + std_offset_string += ":" + Integer.toString(seconds / 60); + seconds = seconds % 60; + if (seconds >= 10) + std_offset_string += ":" + Integer.toString(seconds); + else if (seconds > 0) + std_offset_string += ":0" + Integer.toString(seconds); + } + + if (dst_ind != -1 && offsets[dst_ind] != offsets[std_ind] + 3600) + { + dst_offset_string = Integer.toString(-offsets[dst_ind] / 3600); + seconds = -offsets[dst_ind] % 3600; + if (seconds != 0) + { + if (seconds < 0) + seconds *= -1; + if (seconds < 600) + dst_offset_string + += ":0" + Integer.toString(seconds / 60); + else + dst_offset_string + += ":" + Integer.toString(seconds / 60); + seconds = seconds % 60; + if (seconds >= 10) + dst_offset_string += ":" + Integer.toString(seconds); + else if (seconds > 0) + dst_offset_string += ":0" + Integer.toString(seconds); + } + } + } + + /* + * If no tzIf2 POSIX TZ string is available and the timezone + * uses DST, try to guess the last rule by trying to make + * sense from transitions at 5 years in the future and onwards. + * tzdata actually uses only 3 forms of rules: + * fixed date within a month, e.g. change on April, 5th + * 1st weekday on or after Nth: change on Sun>=15 in April + * last weekday in a month: change on lastSun in April + */ + String[] change_spec = { null, null }; + if (tzstr == null && dst_ind != -1 && timecnt > 10) + { + long nowPlus5y = System.currentTimeMillis() / 1000 + + 5 * 365 * 86400; + int first; + + for (first = timecnt - 1; first >= 0; first--) + if (times[first] < nowPlus5y + || (types[first] != std_ind && types[first] != dst_ind) + || types[first] != types[timecnt - 2 + ((first ^ timecnt) & 1)]) + break; + first++; + + if (timecnt - first >= 10 && types[timecnt - 1] != types[timecnt - 2]) + { + GregorianCalendar cal + = new GregorianCalendar(new SimpleTimeZone(0, "GMT")); + + int[] values = new int[2 * 11]; + int i; + for (i = timecnt - 1; i >= first; i--) + { + int base = (i % 2) * 11; + int offset = offsets[types[i > first ? i - 1 : i + 1]]; + cal.setTimeInMillis((times[i] + offset) * 1000); + if (i >= timecnt - 2) + { + values[base + 0] = cal.get(Calendar.YEAR); + values[base + 1] = cal.get(Calendar.MONTH); + values[base + 2] = cal.get(Calendar.DAY_OF_MONTH); + values[base + 3] + = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + values[base + 4] = cal.get(Calendar.DAY_OF_WEEK); + values[base + 5] = cal.get(Calendar.HOUR_OF_DAY); + values[base + 6] = cal.get(Calendar.MINUTE); + values[base + 7] = cal.get(Calendar.SECOND); + values[base + 8] = values[base + 2]; // Range start + values[base + 9] = values[base + 2]; // Range end + values[base + 10] = 0; // Determined type + } + else + { + int year = cal.get(Calendar.YEAR); + int month = cal.get(Calendar.MONTH); + int day_of_month = cal.get(Calendar.DAY_OF_MONTH); + int month_days + = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + int day_of_week = cal.get(Calendar.DAY_OF_WEEK); + int hour = cal.get(Calendar.HOUR_OF_DAY); + int minute = cal.get(Calendar.MINUTE); + int second = cal.get(Calendar.SECOND); + if (year != values[base + 0] - 1 + || month != values[base + 1] + || hour != values[base + 5] + || minute != values[base + 6] + || second != values[base + 7]) + break; + if (day_of_week == values[base + 4]) + { + // Either a Sun>=8 or lastSun rule. + if (day_of_month < values[base + 8]) + values[base + 8] = day_of_month; + if (day_of_month > values[base + 9]) + values[base + 9] = day_of_month; + if (values[base + 10] < 0) + break; + if (values[base + 10] == 0) + { + values[base + 10] = 1; + // If day of month > 28, this is + // certainly lastSun rule. + if (values[base + 2] > 28) + values[base + 2] = 3; + // If day of month isn't in the last + // week, it can't be lastSun rule. + else if (values[base + 2] + <= values[base + 3] - 7) + values[base + 3] = 2; + } + if (values[base + 10] == 1) + { + // If day of month is > 28, this is + // certainly lastSun rule. + if (day_of_month > 28) + values[base + 10] = 3; + // If day of month isn't in the last + // week, it can't be lastSun rule. + else if (day_of_month <= month_days - 7) + values[base + 10] = 2; + } + else if ((values[base + 10] == 2 + && day_of_month > 28) + || (values[base + 10] == 3 + && day_of_month <= month_days - 7)) + break; + } + else + { + // Must be fixed day in month rule. + if (day_of_month != values[base + 2] + || values[base + 10] > 0) + break; + values[base + 4] = day_of_week; + values[base + 10] = -1; + } + values[base + 0] -= 1; + } + } + + if (i < first) + { + for (i = 0; i < 2; i++) + { + int base = 11 * i; + if (values[base + 10] == 0) + continue; + if (values[base + 10] == -1) + { + int[] dayCount + = { 0, 31, 59, 90, 120, 151, + 181, 212, 243, 273, 304, 334 }; + int d = dayCount[values[base + 1] + - Calendar.JANUARY]; + d += values[base + 2]; + change_spec[i] = ",J" + Integer.toString(d); + } + else if (values[base + 10] == 2) + { + // If we haven't seen all days of the week, + // we can't be sure what the rule really is. + if (values[base + 8] + 6 != values[base + 9]) + continue; + + int d; + d = values[base + 1] - Calendar.JANUARY + 1; + // E.g. Sun >= 5 is not representable in POSIX + // TZ env string, use ",Am.n.d" extension + // where m is month 1 .. 12, n is the date on + // or after which it happens and d is day + // of the week, 0 .. 6. So Sun >= 5 in April + // is ",A4.5.0". + if ((values[base + 8] % 7) == 1) + { + change_spec[i] = ",M" + Integer.toString(d); + d = (values[base + 8] + 6) / 7; + } + else + { + change_spec[i] = ",A" + Integer.toString(d); + d = values[base + 8]; + } + change_spec[i] += "." + Integer.toString(d); + d = values[base + 4] - Calendar.SUNDAY; + change_spec[i] += "." + Integer.toString(d); + } + else + { + // If we don't know whether this is lastSun or + // Sun >= 22 rule. That can be either because + // there was insufficient number of + // transitions, or February, where it is quite + // probable we haven't seen any 29th dates. + // For February, assume lastSun rule, otherwise + // punt. + if (values[base + 10] == 1 + && values[base + 1] != Calendar.FEBRUARY) + continue; + + int d; + d = values[base + 1] - Calendar.JANUARY + 1; + change_spec[i] = ",M" + Integer.toString(d); + d = values[base + 4] - Calendar.SUNDAY; + change_spec[i] += ".5." + Integer.toString(d); + } + + // Don't add time specification if time is + // 02:00:00. + if (values[base + 5] != 2 + || values[base + 6] != 0 + || values[base + 7] != 0) + { + int d = values[base + 5]; + change_spec[i] += "/" + Integer.toString(d); + if (values[base + 6] != 0 || values[base + 7] != 0) + { + d = values[base + 6]; + if (d < 10) + change_spec[i] + += ":0" + Integer.toString(d); + else + change_spec[i] += ":" + Integer.toString(d); + d = values[base + 7]; + if (d >= 10) + change_spec[i] + += ":" + Integer.toString(d); + else if (d > 0) + change_spec[i] + += ":0" + Integer.toString(d); + } + } + } + if (types[(timecnt - 1) & -2] == std_ind) + { + String tmp = change_spec[0]; + change_spec[0] = change_spec[1]; + change_spec[1] = tmp; + } + } + } + } + + if (tzstr == null) + { + tzstr = std_zonename + std_offset_string; + if (change_spec[0] != null && change_spec[1] != null) + tzstr += dst_zonename + dst_offset_string + + change_spec[0] + change_spec[1]; + } + + if (timecnt == 0) + return new SimpleTimeZone(offsets[std_ind] * 1000, + id != null ? id : tzstr); + + SimpleTimeZone endRule = createLastRule(tzstr); + if (endRule == null) + return null; + + /* Finally adjust the times array into the form the constructor + * expects. times[0] is special, the offset and DST flag there + * are for all times before that transition. Use the first non-DST + * type. For all other transitions, the data file has the type + * (<offset, isdst, zonename>) for the time interval starting + */ + for (int i = 0; i < typecnt; i++) + if ((typeflags[i] & (1 << 8)) == 0) + { + times[0] = (times[0] << SECS_SHIFT) | (offsets[i] & OFFSET_MASK); + break; + } + + for (int i = 1; i < timecnt; i++) + times[i] = (times[i] << SECS_SHIFT) + | (offsets[types[i - 1]] & OFFSET_MASK) + | ((typeflags[types[i - 1]] & (1 << 8)) != 0 ? IS_DST : 0); + + return new ZoneInfo(offsets[std_ind] * 1000, id != null ? id : tzstr, + times, endRule); + } + catch (IOException ioe) + { + // Parse error, not a proper tzfile. + return null; + } + finally + { + try + { + if (dis != null) + dis.close(); + } + catch(IOException ioe) + { + // Error while close, nothing we can do. + } + } + } + + /** + * Skips the requested number of bytes in the given InputStream. + * Throws EOFException if not enough bytes could be skipped. + * Negative numbers of bytes to skip are ignored. + */ + private static void skipFully(InputStream is, long l) throws IOException + { + while (l > 0) + { + long k = is.skip(l); + if (k <= 0) + throw new EOFException(); + l -= k; + } + } + + /** + * Create a SimpleTimeZone from a POSIX TZ environment string, + * see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html + * for details. + * It supports also an extension, where Am.n.d rule (m 1 .. 12, n 1 .. 25, d + * 0 .. 6) describes day of week d on or after nth day of month m. + * Say A4.5.0 is Sun>=5 in April. + */ + private static SimpleTimeZone createLastRule(String tzstr) + { + String stdName = null; + int stdOffs; + int dstOffs; + try + { + int idLength = tzstr.length(); + + int index = 0; + int prevIndex; + char c; + + // get std + do + c = tzstr.charAt(index); + while (c != '+' && c != '-' && c != ',' && c != ':' + && ! Character.isDigit(c) && c != '\0' && ++index < idLength); + + if (index >= idLength) + return new SimpleTimeZone(0, tzstr); + + stdName = tzstr.substring(0, index); + prevIndex = index; + + // get the std offset + do + c = tzstr.charAt(index++); + while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c)) + && index < idLength); + if (index < idLength) + index--; + + { // convert the dst string to a millis number + String offset = tzstr.substring(prevIndex, index); + prevIndex = index; + + if (offset.charAt(0) == '+' || offset.charAt(0) == '-') + stdOffs = parseTime(offset.substring(1)); + else + stdOffs = parseTime(offset); + + if (offset.charAt(0) == '-') + stdOffs = -stdOffs; + + // TZ timezone offsets are positive when WEST of the meridian. + stdOffs = -stdOffs; + } + + // Done yet? (Format: std offset) + if (index >= idLength) + return new SimpleTimeZone(stdOffs, stdName); + + // get dst + do + c = tzstr.charAt(index); + while (c != '+' && c != '-' && c != ',' && c != ':' + && ! Character.isDigit(c) && c != '\0' && ++index < idLength); + + // Done yet? (Format: std offset dst) + if (index >= idLength) + return new SimpleTimeZone(stdOffs, stdName); + + // get the dst offset + prevIndex = index; + do + c = tzstr.charAt(index++); + while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c)) + && index < idLength); + if (index < idLength) + index--; + + if (index == prevIndex && (c == ',' || c == ';')) + { + // Missing dst offset defaults to one hour ahead of standard + // time. + dstOffs = stdOffs + 60 * 60 * 1000; + } + else + { // convert the dst string to a millis number + String offset = tzstr.substring(prevIndex, index); + prevIndex = index; + + if (offset.charAt(0) == '+' || offset.charAt(0) == '-') + dstOffs = parseTime(offset.substring(1)); + else + dstOffs = parseTime(offset); + + if (offset.charAt(0) == '-') + dstOffs = -dstOffs; + + // TZ timezone offsets are positive when WEST of the meridian. + dstOffs = -dstOffs; + } + + // Done yet? (Format: std offset dst offset) + if (index >= idLength) + return new SimpleTimeZone(stdOffs, stdName); + + // get the DST rule + if (tzstr.charAt(index) == ',' + || tzstr.charAt(index) == ';') + { + index++; + int offs = index; + while (tzstr.charAt(index) != ',' + && tzstr.charAt(index) != ';') + index++; + String startTime = tzstr.substring(offs, index); + index++; + String endTime = tzstr.substring(index); + + index = startTime.indexOf('/'); + int startMillis; + int endMillis; + String startDate; + String endDate; + if (index != -1) + { + startDate = startTime.substring(0, index); + startMillis = parseTime(startTime.substring(index + 1)); + } + else + { + startDate = startTime; + // if time isn't given, default to 2:00:00 AM. + startMillis = 2 * 60 * 60 * 1000; + } + index = endTime.indexOf('/'); + if (index != -1) + { + endDate = endTime.substring(0, index); + endMillis = parseTime(endTime.substring(index + 1)); + } + else + { + endDate = endTime; + // if time isn't given, default to 2:00:00 AM. + endMillis = 2 * 60 * 60 * 1000; + } + + int[] start = getDateParams(startDate); + int[] end = getDateParams(endDate); + return new SimpleTimeZone(stdOffs, tzstr, start[0], start[1], + start[2], startMillis, end[0], end[1], + end[2], endMillis, (dstOffs - stdOffs)); + } + } + + catch (IndexOutOfBoundsException _) + { + } + catch (NumberFormatException _) + { + } + + return null; + } + + /** + * Parses and returns the params for a POSIX TZ date field, + * in the format int[]{ month, day, dayOfWeek }, following the + * SimpleTimeZone constructor rules. + */ + private static int[] getDateParams(String date) + { + int[] dayCount = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + int month; + int type = 0; + + if (date.charAt(0) == 'M' || date.charAt(0) == 'm') + type = 1; + else if (date.charAt(0) == 'A' || date.charAt(0) == 'a') + type = 2; + + if (type > 0) + { + int day; + + // Month, week of month, day of week + // "Mm.w.d". d is between 0 (Sunday) and 6. Week w is + // between 1 and 5; Week 1 is the first week in which day d + // occurs and Week 5 specifies the last d day in the month. + // Month m is between 1 and 12. + + // Month, day of month, day of week + // ZoneInfo extension, not in POSIX + // "Am.n.d". d is between 0 (Sunday) and 6. Day of month n is + // between 1 and 25. Month m is between 1 and 12. + + month = Integer.parseInt(date.substring(1, date.indexOf('.'))); + int week = Integer.parseInt(date.substring(date.indexOf('.') + 1, + date.lastIndexOf('.'))); + int dayOfWeek = Integer.parseInt(date.substring(date.lastIndexOf('.') + + 1)); + dayOfWeek++; // Java day of week is one-based, Sunday is first day. + + if (type == 2) + { + day = week; + dayOfWeek = -dayOfWeek; + } + else if (week == 5) + day = -1; // last day of month is -1 in java, 5 in TZ + else + { + // First day of week starting on or after. For example, + // to specify the second Sunday of April, set month to + // APRIL, day-of-month to 8, and day-of-week to -SUNDAY. + day = (week - 1) * 7 + 1; + dayOfWeek = -dayOfWeek; + } + + month--; // Java month is zero-based. + return new int[] { month, day, dayOfWeek }; + } + + // julian day, either zero-based 0<=n<=365 (incl feb 29) + // or one-based 1<=n<=365 (no feb 29) + int julianDay; // Julian day + + if (date.charAt(0) != 'J' || date.charAt(0) != 'j') + { + julianDay = Integer.parseInt(date.substring(1)); + julianDay++; // make 1-based + // Adjust day count to include feb 29. + dayCount = new int[] + { + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 + }; + } + else + // 1-based julian day + julianDay = Integer.parseInt(date); + + int i = 11; + while (i > 0) + if (dayCount[i] < julianDay) + break; + else + i--; + julianDay -= dayCount[i]; + month = i; + return new int[] { month, julianDay, 0 }; + } + + /** + * Parses a time field hh[:mm[:ss]], returning the result + * in milliseconds. No leading sign. + */ + private static int parseTime(String time) + { + int millis = 0; + int i = 0; + + while (i < time.length()) + if (time.charAt(i) == ':') + break; + else + i++; + millis = 60 * 60 * 1000 * Integer.parseInt(time.substring(0, i)); + if (i >= time.length()) + return millis; + + int iprev = ++i; + while (i < time.length()) + if (time.charAt(i) == ':') + break; + else + i++; + millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i)); + if (i >= time.length()) + return millis; + + millis += 1000 * Integer.parseInt(time.substring(++i)); + return millis; + } +} diff --git a/libjava/classpath/gnu/java/util/jar/JarUtils.java b/libjava/classpath/gnu/java/util/jar/JarUtils.java new file mode 100644 index 000000000..aa2bc2ea8 --- /dev/null +++ b/libjava/classpath/gnu/java/util/jar/JarUtils.java @@ -0,0 +1,451 @@ +/* JarUtils.java -- Utility methods for reading/writing Manifest[-like] files + Copyright (C) 2006, 2007 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.jar; + +import gnu.classpath.SystemProperties; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarException; +import java.util.jar.Attributes.Name; +import java.util.logging.Logger; + +/** + * Utility methods for reading and writing JAR <i>Manifest</i> and + * <i>Manifest-like</i> files. + * <p> + * JAR-related files that resemble <i>Manifest</i> files are Signature files + * (with an <code>.SF</code> extension) found in signed JARs. + */ +public abstract class JarUtils +{ + // We used to log here, but this causes problems during bootstrap, + // and it didn't seem worthwhile to preserve this. Still, this + // might be useful for debugging. + // private static final Logger log = Logger.getLogger(JarUtils.class.getName()); + public static final String META_INF = "META-INF/"; + public static final String DSA_SUFFIX = ".DSA"; + public static final String SF_SUFFIX = ".SF"; + public static final String NAME = "Name"; + + /** + * The original string representation of the manifest version attribute name. + */ + public static final String MANIFEST_VERSION = "Manifest-Version"; + + /** + * The original string representation of the signature version attribute + * name. + */ + public static final String SIGNATURE_VERSION = "Signature-Version"; + + /** Platform-independent line-ending. */ + public static final byte[] CRLF = new byte[] { 0x0D, 0x0A }; + private static final String DEFAULT_MF_VERSION = "1.0"; + private static final String DEFAULT_SF_VERSION = "1.0"; + private static final Name CREATED_BY = new Name("Created-By"); + private static final String CREATOR = SystemProperties.getProperty("java.version") + + " (" + + SystemProperties.getProperty("java.vendor") + + ")"; + + // default 0-arguments constructor + + // Methods for reading Manifest files from InputStream ---------------------- + + public static void + readMFManifest(Attributes attr, Map entries, InputStream in) + throws IOException + { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + readMainSection(attr, br); + readIndividualSections(entries, br); + } + + public static void + readSFManifest(Attributes attr, Map entries, InputStream in) + throws IOException + { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String version_header = Name.SIGNATURE_VERSION.toString(); + try + { + String version = expectHeader(version_header, br); + attr.putValue(SIGNATURE_VERSION, version); + // This may cause problems during VM bootstrap. + // if (! DEFAULT_SF_VERSION.equals(version)) + // log.warning("Unexpected version number: " + version + // + ". Continue (but may fail later)"); + } + catch (IOException ioe) + { + throw new JarException("Signature file MUST start with a " + + version_header + ": " + ioe.getMessage()); + } + read_attributes(attr, br); + + // read individual sections + String s = br.readLine(); + while (s != null && s.length() > 0) + { + Attributes eAttr = readSectionName(s, br, entries); + read_attributes(eAttr, br); + s = br.readLine(); + } + } + + private static void readMainSection(Attributes attr, BufferedReader br) + throws IOException + { + // According to the spec we should actually call read_version_info() here. + read_attributes(attr, br); + // Explicitly set Manifest-Version attribute if not set in Main + // attributes of Manifest. + // XXX (rsn): why 0.0 and not 1.0? + if (attr.getValue(Name.MANIFEST_VERSION) == null) + attr.putValue(MANIFEST_VERSION, "0.0"); + } + + private static void readIndividualSections(Map entries, BufferedReader br) + throws IOException + { + String s = br.readLine(); + while (s != null && (! s.equals(""))) + { + Attributes attr = readSectionName(s, br, entries); + read_attributes(attr, br); + s = br.readLine(); + } + } + + /** + * Pedantic method that requires the next attribute in the Manifest to be the + * "Manifest-Version". This follows the Manifest spec closely but reject some + * jar Manifest files out in the wild. + */ + private static void readVersionInfo(Attributes attr, BufferedReader br) + throws IOException + { + String version_header = Name.MANIFEST_VERSION.toString(); + try + { + String value = expectHeader(version_header, br); + attr.putValue(MANIFEST_VERSION, value); + } + catch (IOException ioe) + { + throw new JarException("Manifest should start with a " + version_header + + ": " + ioe.getMessage()); + } + } + + private static String expectHeader(String header, BufferedReader br) + throws IOException + { + String s = br.readLine(); + if (s == null) + throw new JarException("unexpected end of file"); + + return expectHeader(header, br, s); + } + + private static void read_attributes(Attributes attr, BufferedReader br) + throws IOException + { + String s = br.readLine(); + while (s != null && (! s.equals(""))) + { + readAttribute(attr, s, br); + s = br.readLine(); + } + } + + private static void + readAttribute(Attributes attr, String s, BufferedReader br) throws IOException + { + try + { + int colon = s.indexOf(": "); + String name = s.substring(0, colon); + String value_start = s.substring(colon + 2); + String value = readHeaderValue(value_start, br); + attr.putValue(name, value); + } + catch (IndexOutOfBoundsException iobe) + { + throw new JarException("Manifest contains a bad header: " + s); + } + } + + private static String readHeaderValue(String s, BufferedReader br) + throws IOException + { + boolean try_next = true; + while (try_next) + { + // Lets see if there is something on the next line + br.mark(1); + if (br.read() == ' ') + s += br.readLine(); + else + { + br.reset(); + try_next = false; + } + } + return s; + } + + private static Attributes + readSectionName(String s, BufferedReader br, Map entries) throws JarException + { + try + { + String name = expectHeader(NAME, br, s); + Attributes attr = new Attributes(); + entries.put(name, attr); + return attr; + } + catch (IOException ioe) + { + throw new JarException("Section should start with a Name header: " + + ioe.getMessage()); + } + } + + private static String expectHeader(String header, BufferedReader br, String s) + throws IOException + { + try + { + String name = s.substring(0, header.length() + 1); + if (name.equalsIgnoreCase(header + ":")) + { + String value_start = s.substring(header.length() + 2); + return readHeaderValue(value_start, br); + } + } + catch (IndexOutOfBoundsException ignored) + { + } + // If we arrive here, something went wrong + throw new JarException("unexpected '" + s + "'"); + } + + // Methods for writing Manifest files to an OutputStream -------------------- + + public static void + writeMFManifest(Attributes attr, Map entries, OutputStream stream) + throws IOException + { + BufferedOutputStream out = stream instanceof BufferedOutputStream + ? (BufferedOutputStream) stream + : new BufferedOutputStream(stream, 4096); + writeVersionInfo(attr, out); + Iterator i; + Map.Entry e; + for (i = attr.entrySet().iterator(); i.hasNext();) + { + e = (Map.Entry) i.next(); + // Don't print the manifest version again + if (! Name.MANIFEST_VERSION.equals(e.getKey())) + writeAttributeEntry(e, out); + } + out.write(CRLF); + + Iterator j; + for (i = entries.entrySet().iterator(); i.hasNext();) + { + e = (Map.Entry) i.next(); + writeHeader(NAME, e.getKey().toString(), out); + Attributes eAttr = (Attributes) e.getValue(); + for (j = eAttr.entrySet().iterator(); j.hasNext();) + { + Map.Entry e2 = (Map.Entry) j.next(); + writeAttributeEntry(e2, out); + } + out.write(CRLF); + } + + out.flush(); + } + + public static void + writeSFManifest(Attributes attr, Map entries, OutputStream stream) + throws IOException + { + BufferedOutputStream out = stream instanceof BufferedOutputStream + ? (BufferedOutputStream) stream + : new BufferedOutputStream(stream, 4096); + writeHeader(Name.SIGNATURE_VERSION.toString(), DEFAULT_SF_VERSION, out); + writeHeader(CREATED_BY.toString(), CREATOR, out); + Iterator i; + Map.Entry e; + for (i = attr.entrySet().iterator(); i.hasNext();) + { + e = (Map.Entry) i.next(); + Name name = (Name) e.getKey(); + if (Name.SIGNATURE_VERSION.equals(name) || CREATED_BY.equals(name)) + continue; + + writeHeader(name.toString(), (String) e.getValue(), out); + } + out.write(CRLF); + + Iterator j; + for (i = entries.entrySet().iterator(); i.hasNext();) + { + e = (Map.Entry) i.next(); + writeHeader(NAME, e.getKey().toString(), out); + Attributes eAttr = (Attributes) e.getValue(); + for (j = eAttr.entrySet().iterator(); j.hasNext();) + { + Map.Entry e2 = (Map.Entry) j.next(); + writeHeader(e2.getKey().toString(), (String) e2.getValue(), out); + } + out.write(CRLF); + } + + out.flush(); + } + + private static void writeVersionInfo(Attributes attr, OutputStream out) + throws IOException + { + // First check if there is already a version attribute set + String version = attr.getValue(Name.MANIFEST_VERSION); + if (version == null) + version = DEFAULT_MF_VERSION; + + writeHeader(Name.MANIFEST_VERSION.toString(), version, out); + } + + private static void writeAttributeEntry(Map.Entry entry, OutputStream out) + throws IOException + { + String name = entry.getKey().toString(); + String value = entry.getValue().toString(); + if (name.equalsIgnoreCase(NAME)) + throw new JarException("Attributes cannot be called 'Name'"); + + if (name.startsWith("From")) + throw new JarException("Header cannot start with the four letters 'From'" + + name); + + writeHeader(name, value, out); + } + + /** + * The basic method for writing <code>Mainfest</code> attributes. This + * implementation respects the rule stated in the Jar Specification concerning + * the maximum allowed line length; i.e. + * + * <pre> + * No line may be longer than 72 bytes (not characters), in its UTF8-encoded + * form. If a value would make the initial line longer than this, it should + * be continued on extra lines (each starting with a single SPACE). + * </pre> + * + * and + * + * <pre> + * Because header names cannot be continued, the maximum length of a header + * name is 70 bytes (there must be a colon and a SPACE after the name). + * </pre> + * + * @param name the name of the attribute. + * @param value the value of the attribute. + * @param out the output stream to write the attribute's name/value pair to. + * @throws IOException if an I/O related exception occurs during the process. + */ + private static void writeHeader(String name, String value, OutputStream out) + throws IOException + { + String target = name + ": "; + byte[] b = target.getBytes("UTF-8"); + if (b.length > 72) + throw new IOException("Attribute's name already longer than 70 bytes"); + + if (b.length == 72) + { + out.write(b); + out.write(CRLF); + target = " " + value; + } + else + target = target + value; + + int n; + while (true) + { + b = target.getBytes("UTF-8"); + if (b.length < 73) + { + out.write(b); + break; + } + + // find an appropriate character position to break on + n = 72; + while (true) + { + b = target.substring(0, n).getBytes("UTF-8"); + if (b.length < 73) + break; + + n--; + if (n < 1) + throw new IOException("Header is unbreakable and longer than 72 bytes"); + } + + out.write(b); + out.write(CRLF); + target = " " + target.substring(n); + } + + out.write(CRLF); + } +} diff --git a/libjava/classpath/gnu/java/util/package.html b/libjava/classpath/gnu/java/util/package.html new file mode 100644 index 000000000..d90fa59d4 --- /dev/null +++ b/libjava/classpath/gnu/java/util/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in gnu.java.util package. + Copyright (C) 2005 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. --> + +<html> +<head><title>GNU Classpath - gnu.java.util</title></head> + +<body> +<p></p> + +</body> +</html> diff --git a/libjava/classpath/gnu/java/util/prefs/FileBasedFactory.java b/libjava/classpath/gnu/java/util/prefs/FileBasedFactory.java new file mode 100644 index 000000000..91ea861c4 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/FileBasedFactory.java @@ -0,0 +1,65 @@ +/* FileBasedFactory - Default Classpath implementation of a PreferencesFactory + Copyright (C) 2001 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.*; + +/** + * Default Classpath implementation of a PreferencesFactory. + * Returns system and user root Preferences nodes that are read from files. + * + * @author Mark Wielaard (mark@klomp.org) + */ +public class FileBasedFactory implements PreferencesFactory { + + // We don't save or read any system preferences for the + // time being. + private static final Preferences systemPreferences + = new MemoryBasedPreferences(null, "", false); + + private static final Preferences userPreferences + = new FileBasedPreferences(); + + public Preferences systemRoot() { + return systemPreferences; + } + + public Preferences userRoot() { + return userPreferences; + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/FileBasedPreferences.java b/libjava/classpath/gnu/java/util/prefs/FileBasedPreferences.java new file mode 100644 index 000000000..f89ed6be2 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/FileBasedPreferences.java @@ -0,0 +1,273 @@ +/* FileBasedPreferences.java -- File-based preference 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.classpath.SystemProperties; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.Properties; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; + +/** + * This is a simple file-based preference implementation which writes + * the preferences as properties files. A node is represented as a directory + * beneath the user's home directory. The preferences for the node are + * stored in a single properties file in that directory. Sub-nodes are + * stored in subdirectories. This implementation uses file locking to + * mediate access to the properties files. + */ +public class FileBasedPreferences + extends AbstractPreferences +{ + /** + * Name of the property file storing the data in a given directory. + */ + private static final String DATA_FILE = "data.properties"; + + /** + * The directory corresponding to this preference node. + */ + private File directory; + + /** + * The file holding the data for this node. + */ + private File dataFile; + + /** + * The data in this node. + */ + private Properties properties; + + /** + * Create the root node for the file-based preferences. + */ + FileBasedPreferences() + { + super(null, ""); + String home = SystemProperties.getProperty("user.home"); + this.directory = new File(new File(home, ".classpath"), "userPrefs"); + this.dataFile = new File(this.directory, DATA_FILE); + load(); + } + + /** + * Create a new file-based preference object with the given parent + * and the given name. + * @param parent the parent + * @param name the name of this node + */ + FileBasedPreferences(FileBasedPreferences parent, String name) + { + super(parent, name); + this.directory = new File(parent.directory, name); + this.dataFile = new File(this.directory, DATA_FILE); + load(); + } + + private void load() + { + this.properties = new Properties(); + FileInputStream fis = null; + FileLock lock = null; + try + { + fis = new FileInputStream(this.dataFile); + FileChannel channel = fis.getChannel(); + lock = channel.lock(0, Long.MAX_VALUE, true); + this.properties.load(fis); + // We release the lock and close the stream in the 'finally' + // clause. + } + catch (IOException _) + { + // We don't mind; this means we're making a new node. + newNode = true; + } + finally + { + try + { + // Release the lock and close the stream. + if (lock != null) + lock.release(); + } + catch (IOException ignore) + { + // Ignore. + } + try + { + // Close the stream. + if (fis != null) + fis.close(); + } + catch (IOException ignore) + { + // Ignore. + } + } + } + + public boolean isUserNode() + { + // For now file preferences are always user nodes. + return true; + } + + protected String[] childrenNamesSpi() throws BackingStoreException + { + // FIXME: security manager. + String[] result = directory.list(new FilenameFilter() + { + public boolean accept(File dir, String name) + { + return new File(dir, name).isDirectory(); + } + }); + if (result == null) + result = new String[0]; + return result; + } + + protected AbstractPreferences childSpi(String name) + { + return new FileBasedPreferences(this, name); + } + + protected String[] keysSpi() throws BackingStoreException + { + return (String[]) properties.keySet().toArray(new String[0]); + } + + protected String getSpi(String key) + { + return properties.getProperty(key); + } + + protected void putSpi(String key, String value) + { + properties.put(key, value); + } + + protected void removeSpi(String key) + { + properties.remove(key); + } + + protected void flushSpi() throws BackingStoreException + { + // FIXME: security manager. + try + { + if (isRemoved()) + { + // Delete the underlying file. + // FIXME: ideally we would also delete the directory + // if it had no subdirectories. This doesn't matter + // much though. + // FIXME: there's a strange race here if a different VM is + // simultaneously updating this node. + dataFile.delete(); + } + else + { + // Write the underlying file. + directory.mkdirs(); + + FileOutputStream fos = null; + FileLock lock = null; + try + { + // Note that we let IOExceptions from the try clause + // propagate to the outer 'try'. + fos = new FileOutputStream(dataFile); + FileChannel channel = fos.getChannel(); + lock = channel.lock(); + properties.store(fos, "created by GNU Classpath FileBasedPreferences"); + // Lock is released and file closed in the finally clause. + } + finally + { + try + { + if (lock != null) + lock.release(); + } + catch (IOException _) + { + // Ignore. + } + try + { + if (fos != null) + fos.close(); + } + catch (IOException _) + { + // Ignore. + } + } + } + } + catch (IOException ioe) + { + throw new BackingStoreException(ioe); + } + } + + protected void syncSpi() throws BackingStoreException + { + // FIXME: we ought to synchronize but instead we merely flush. + flushSpi(); + } + + protected void removeNodeSpi() throws BackingStoreException + { + // We can simply delegate. + flushSpi(); + } +} 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 000000000..f5a189471 --- /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 000000000..e3374eee9 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/GConfBasedPreferences.java @@ -0,0 +1,419 @@ +/* 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.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> + */ +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); + } + + // strip invalid characters + // please, note that all names are unescaped into the native peer + int index = absolutePath.lastIndexOf('/'); + if (index > -1) + { + absolutePath = absolutePath.substring(0, index + 1); + absolutePath = absolutePath + GConfNativePeer.escapeString(name); + } + + this.node = this.getRealRoot(isUser) + absolutePath; + + boolean nodeExist = backend.nodeExist(this.node); + + this.newNode = !nodeExist; + } + + /** + * 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. + + GConfBasedPreferences preferenceNode + = new GConfBasedPreferences(this, name, this.isUser); + + return preferenceNode; + } + + /** + * 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<String> 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<String> 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<String> dirs = backend.getChildrenNodes(directory); + + if (dirs.size() != 0) + { + for (String currentDir : dirs) + { + // recursive search inside this directory + postorderRemove(currentDir); + } + } + + // remove all the keys associated to this directory + List<String> entries = backend.getKeys(directory); + + if (entries.size() != 0) + { + for (String key : entries) + { + 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 = ""; + + // strip key + // please, note that all names are unescaped into the native peer + key = GConfNativePeer.escapeString(key); + + 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/MemoryBasedFactory.java b/libjava/classpath/gnu/java/util/prefs/MemoryBasedFactory.java new file mode 100644 index 000000000..32ed12fc7 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/MemoryBasedFactory.java @@ -0,0 +1,64 @@ +/* MemoryBasedFactory - Memory based PreferencesFactory usefull for testing + Copyright (C) 2001 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.*; + +/** + * Memory based PreferencesFactory useful for testing. + * Returns completely empty Preferences for system and user roots. + * All changes are only backed by the current instances in memory. + * + * @author Mark Wielaard (mark@klomp.org) + */ +public class MemoryBasedFactory implements PreferencesFactory { + + // Static fields containing the preferences root nodes + private static final Preferences systemPreferences + = new MemoryBasedPreferences(null, "", false); + private static final Preferences userPreferences + = new MemoryBasedPreferences(null, "", true); + + public Preferences systemRoot() { + return systemPreferences; + } + + public Preferences userRoot() { + return userPreferences; + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/MemoryBasedPreferences.java b/libjava/classpath/gnu/java/util/prefs/MemoryBasedPreferences.java new file mode 100644 index 000000000..ee184d182 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/MemoryBasedPreferences.java @@ -0,0 +1,144 @@ +/* MemoryBasedPreferences - A Preference node which holds all entries in memory + Copyright (C) 2001, 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.HashMap; + +import java.util.prefs.*; + +/** + * A Preference node which holds all entries in memory + * + * @author Mark Wielaard (mark@klomp.org) + */ +public class MemoryBasedPreferences extends AbstractPreferences { + + /** True if this is a preference node in the user tree, false otherwise. */ + private final boolean isUser; + + /** Contains all the preference entries of this node. */ + private HashMap<String, String> entries = new HashMap<String, String>(); + + /** + * Creates a new preferences node with the given name and parent. + * When isUser is true it will be user node otherwise it will be a system + * node. It will always set the <code>newNode</code> field to true + * since there is no real backing store, so all nodes are new. + */ + public MemoryBasedPreferences(MemoryBasedPreferences parent, + String name, + boolean isUser) { + super(parent, name); + this.isUser = isUser; + + // Since we do not have a real backing store all nodes are new + newNode = true; + } + + /** + * Returns true if this node was created as a user node. + */ + public boolean isUserNode() { + return isUser; + } + + /** + * Returns an empty array since all children names are always already + * cached. + */ + protected String[] childrenNamesSpi() throws BackingStoreException { + return new String[0]; + } + + /** + * Returns a new node with the given name with as parent this node and + * with the <code>isUser</code> flag set to the same value as this node. + */ + protected AbstractPreferences childSpi(String childName) { + return new MemoryBasedPreferences(this, childName, isUser); + } + + /** + * Returns a (possibly empty) array of keys of the preferences entries of + * this node. + */ + protected String[] keysSpi() throws BackingStoreException { + return entries.keySet().toArray(new String[entries.size()]); + } + + /** + * Returns the associated value from this nodes preferences entries or + * null when the key has not been set. + */ + protected String getSpi(String key) { + return entries.get(key); + } + + /** + * Sets the value for the given key. + */ + protected void putSpi(String key, String value) { + entries.put(key, value); + } + + /** + * Removes the entry with the given key. + */ + protected void removeSpi(String key) { + entries.remove(key); + } + + /** + * Does nothing since we do not have any backing store. + */ + protected void flushSpi() { + } + + /** + * Does nothing since we do not have any backing store. + */ + protected void syncSpi() { + } + + /** + * Just removes the entries map of this node. + */ + protected void removeNodeSpi() { + entries = null; + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/NodeReader.java b/libjava/classpath/gnu/java/util/prefs/NodeReader.java new file mode 100644 index 000000000..0a49fc777 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/NodeReader.java @@ -0,0 +1,221 @@ +/* NodeReader - Reads and imports preferences nodes from files + Copyright (C) 2001 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.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.prefs.InvalidPreferencesFormatException; +import java.util.prefs.Preferences; +import java.util.prefs.PreferencesFactory; + +/** + * Reads and imports preferences nodes from files. + * + * @author Mark Wielaard (mark@klomp.org) + */ +public class NodeReader { + + private final BufferedReader br; + private String line = ""; + + private final PreferencesFactory factory; + + public NodeReader(Reader r, PreferencesFactory factory) { + if(r instanceof BufferedReader) { + br = (BufferedReader) r; + } else { + br = new BufferedReader(r); + } + this.factory = factory; + } + + public NodeReader(InputStream is, PreferencesFactory factory) { + this(new InputStreamReader(is), factory); + } + + public void importPreferences() + throws InvalidPreferencesFormatException, IOException + { + readPreferences(); + } + + private void readPreferences() + throws InvalidPreferencesFormatException, IOException + { + // Begin starting tag + skipTill("<preferences"); + + readRoot(); + + // Ending tag + skipTill("</preferences>"); + } + + private void readRoot() + throws InvalidPreferencesFormatException, IOException + { + // Begin starting tag + skipTill("<root"); + + // type attribute + skipTill("type=\""); + String type = readTill("\""); + Preferences root; + if ("user".equals(type)) { + root = factory.userRoot(); + } else if ("system".equals(type)) { + root = factory.systemRoot(); + } else { + throw new InvalidPreferencesFormatException("Unknown type: " + + type); + } + + // Read root map and subnodes + readMap(root); + readNodes(root); + + // Ending tag + skipTill("</root>"); + } + + private void readNodes(Preferences node) + throws InvalidPreferencesFormatException, IOException + { + while ("node".equals(nextTag())) { + skipTill("<node"); + skipTill("name=\""); + String name = readTill("\""); + Preferences subnode = node.node(name); + readMap(subnode); + readNodes(subnode); + skipTill("</node>"); + } + + } + + private void readMap(Preferences node) + throws InvalidPreferencesFormatException, IOException + { + // Begin map tag + skipTill("<map"); + + // Empty map? + if (line.startsWith("/>")) { + line = line.substring(2); + return; + } + + // Map entries + readEntries(node); + + // Ending tag + skipTill("</map>"); + } + + private void readEntries(Preferences node) + throws InvalidPreferencesFormatException, IOException + { + while ("entry".equals(nextTag())) { + skipTill("<entry"); + skipTill("key=\""); + String key = readTill("\""); + skipTill("value=\""); + String value = readTill("\""); + node.put(key, value); + } + } + + private void skipTill(String s) + throws InvalidPreferencesFormatException, IOException + { + while(true) { + if (line == null) + throw new InvalidPreferencesFormatException(s + " not found"); + + int index = line.indexOf(s); + if (index == -1) { + line = br.readLine(); + } else { + line = line.substring(index+s.length()); + return; + } + } + } + + private String readTill(String s) + throws InvalidPreferencesFormatException + { + int index = line.indexOf(s); + if (index == -1) + throw new InvalidPreferencesFormatException(s + " not found"); + + String read = line.substring(0, index); + line = line.substring(index+s.length()); + + return read; + } + + private String nextTag() + throws InvalidPreferencesFormatException, IOException + { + while(true) { + if (line == null) + throw new InvalidPreferencesFormatException("unexpected EOF"); + + int start = line.indexOf("<"); + if (start == -1) { + line = br.readLine(); + } else { + // Find end of tag + int end = start+1; + while (end != line.length() + && " \t\r\n".indexOf(line.charAt(end)) == -1) { + end++; + } + // Line now starts at the found tag + String tag = line.substring(start+1,end); + line = line.substring(start); + return tag; + } + } + } + +} diff --git a/libjava/classpath/gnu/java/util/prefs/NodeWriter.java b/libjava/classpath/gnu/java/util/prefs/NodeWriter.java new file mode 100644 index 000000000..3e4f972ed --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/NodeWriter.java @@ -0,0 +1,319 @@ +/* NodeWriter - Writes and exports preferences nodes to files + Copyright (C) 2001, 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.lang.CPStringBuilder; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +import java.util.StringTokenizer; + +import java.util.prefs.*; + +/** + * Writes and exports preferences nodes to files + * + * @author Mark Wielaard (mark@klomp.org) + */ +public class NodeWriter { + + /** The Preferences node to write. */ + private final Preferences prefs; + + /** The bufferedWriter to write the node to. */ + private final BufferedWriter bw; + + /** + * True if the complete sub tree should be written, + * false if only the node should be written. + */ + private boolean subtree; + + /** + * Creates a new NodeWriter for the given preferences node and + * outputstream. Creates a new OutputStreamWriter. + */ + public NodeWriter(Preferences prefs, OutputStream os) { + this.prefs = prefs; + Writer w; + try + { + w = new OutputStreamWriter(os, "UTF-8"); + } + catch (UnsupportedEncodingException uee) + { + // Shouldn't happen, since we always have UTF-8 available. + InternalError ie = new InternalError("UTF-8 encoding missing"); + ie.initCause(uee); + throw ie; + } + this.bw = new BufferedWriter(w); + } + + /** + * Writes the preference node plus the complete subtree. + */ + public void writePrefsTree() throws BackingStoreException, IOException { + subtree = true; + writeHeader(); + writePreferences(); + bw.flush(); + } + + /** + * Writes only the preference node. + */ + public void writePrefs() throws BackingStoreException, IOException { + subtree = false; + writeHeader(); + writePreferences(); + bw.flush(); + } + + /** + * Writes the standard header. + */ + private void writeHeader() throws BackingStoreException, IOException { + bw.write("<?xml version=\"1.0\"?>"); + bw.newLine(); + bw.write("<!DOCTYPE preferences SYSTEM " + + "\"http://java.sun.com/dtd/preferences.dtd\">"); + bw.newLine(); + bw.newLine(); + bw.write("<!-- GNU Classpath java.util.prefs Preferences "); + + if (prefs.isUserNode()) { + bw.write("user"); + } else { + bw.write("system"); + } + + // root node? + if (prefs.parent() == null) { + bw.write(" root"); + } + + if (subtree) { + bw.write(" tree"); + } else { + bw.write(" node"); + } + + // no root? + if (prefs.parent() != null) { + bw.newLine(); + bw.write(" '"); + bw.write(prefs.absolutePath()); + bw.write('\''); + bw.newLine(); + } + bw.write(" -->"); + bw.newLine(); + bw.newLine(); + } + + /** + * Write the preferences tag and the root. + */ + private void writePreferences() throws BackingStoreException, IOException { + bw.write("<preferences>"); + bw.newLine(); + writeRoot(); + bw.write("</preferences>"); + bw.newLine(); + } + + private void writeRoot() throws BackingStoreException, IOException { + bw.write(" <root type=\""); + if (prefs.isUserNode()) { + bw.write("user"); + } else { + bw.write("system"); + } + bw.write("\">"); + + writeRootMap(); + writeNode(); + + bw.write(" </root>"); + bw.newLine(); + } + + private void writeRootMap() throws BackingStoreException, IOException { + // Is it a root node? + if(prefs.parent() == null && prefs.keys().length > 0) { + bw.newLine(); + writeMap(prefs, 2); + } else { + bw.write("<map/>"); + bw.newLine(); + } + } + + /** + * Writes all the parents of the preferences node without any entries. + * Returns the number of parents written, which has to be used as + * argument to <code>writeCloseParents()</code> after writing the node + * itself. + */ + private int writeParents() throws IOException { + int parents; + String path = prefs.absolutePath(); + int lastslash = path.lastIndexOf("/"); + if (lastslash > 0) { + path = path.substring(1, lastslash); + StringTokenizer st = new StringTokenizer(path); + parents = st.countTokens(); + + for (int i=0; i<parents; i++) { + String name = st.nextToken(); + indent(i+2); + bw.write("<node name=\"" + name + "\">"); + bw.write("<map/>"); + bw.write("</node>"); + bw.newLine(); + } + } else { + parents = 0; + } + + return parents; + } + + private void writeCloseParents(int parents) throws IOException { + while(parents > 0) { + indent(parents+1); + bw.write("</node>"); + bw.newLine(); + parents--; + } + } + + private void writeNode() throws BackingStoreException, IOException { + int parents = writeParents(); + // root? + int indent; + if (prefs.parent() == null) { + indent = parents+1; + } else { + indent = parents+2; + } + writeNode(prefs, indent); + writeCloseParents(parents); + } + + private void writeNode(Preferences node, int indent) + throws BackingStoreException, IOException + { + // not root? + if (node.parent() != null) { + indent(indent); + bw.write("<node name=\"" + node.name() + "\">"); + if (node.keys().length > 0) { + bw.newLine(); + } + writeMap(node, indent+1); + } + + if (subtree) { + String[] children = node.childrenNames(); + for (int i=0; i<children.length; i++) { + Preferences child = node.node(children[i]); + writeNode(child, indent+1); + } + } + + // not root? + if (node.parent() != null) { + indent(indent); + bw.write("</node>"); + bw.newLine(); + } + } + + private void writeMap(Preferences node, int indent) + throws BackingStoreException, IOException + { + // construct String used for indentation + CPStringBuilder indentBuffer = new CPStringBuilder(2*indent); + for (int i=0; i < indent; i++) + indentBuffer.append(" "); + String indentString = indentBuffer.toString(); + + if (node.keys().length > 0) { + bw.write(indentString); + bw.write("<map>"); + bw.newLine(); + writeEntries(node, indentString + " "); + bw.write(indentString); + bw.write("</map>"); + } else { + bw.write("<map/>"); + } + bw.newLine(); + } + + private void writeEntries(Preferences node, String indent) + throws BackingStoreException, IOException + { + String[] keys = node.keys(); + for(int i = 0; i < keys.length; i++) { + String value = node.get(keys[i], null); + if (value == null) { + throw new BackingStoreException("null value for key '" + + keys[i] + "'"); + } + + bw.write(indent); + bw.write("<entry key=\"" + keys[i] + "\"" + + " value=\"" + value + "\"/>"); + bw.newLine(); + } + } + + private void indent(int x) throws IOException { + for (int i=0; i<x; i++) { + bw.write(" "); + } + } +} 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 000000000..64fc0498a --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/gconf/GConfNativePeer.java @@ -0,0 +1,286 @@ +/* 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> + */ +public final class GConfNativePeer +{ + /** + * Creates a new instance of GConfNativePeer + */ + public GConfNativePeer() + { + 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_dir_exists(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_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_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_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<String> getKeys(String node) throws BackingStoreException + { + return gconf_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<String> getChildrenNodes(String node) throws BackingStoreException + { + return gconf_all_nodes(node); + } + + /** + * Escape the given string so the it is a valid GConf name. + */ + public static String escapeString(String plain) + { + return gconf_escape_key(plain); + } + + /** + * Unescape a string escaped with {@link #escapeString}. + */ + public static String unescapeString(String escaped) + { + return gconf_unescape_key(escaped); + } + + /** + * Suggest to the backend GConf daemon to synch with the database. + */ + public void suggestSync() throws BackingStoreException + { + gconf_suggest_sync(); + } + + protected void finalize() throws Throwable + { + try + { + 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 synchronized static final private void init_id_cache(); + + /** + * Initialize the GConf native peer. This is meant to be used by the + * class constructor. + */ + native synchronized static final private void init_class(); + + /** + * Class finalizer. + */ + native synchronized 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 synchronized + static final protected boolean gconf_dir_exists(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 synchronized + static final protected boolean gconf_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 synchronized + static final protected String gconf_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 synchronized static final protected boolean gconf_unset(String key); + + /** + * Suggest to the GConf native peer a sync with the database. + * + */ + native synchronized static final protected void gconf_suggest_sync() + throws BackingStoreException; + + /** + * 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 synchronized final protected List<String> gconf_all_nodes(String node) + throws BackingStoreException; + + /** + * 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 synchronized + static final protected List<String> gconf_all_keys(String node) + throws BackingStoreException; + + /** + * Escape the input String so that it's a valid element for GConf. + * + * @param plain the String to escape. + * @return An escaped String for use with GConf. + */ + native synchronized + static final protected String gconf_escape_key(String plain); + + /** + * Converts a string escaped with gconf_escape_key back into its + * original form. + * + * @param escaped key as returned by gconf_escape_key + * @return An unescaped key. + */ + native synchronized + static final protected String gconf_unescape_key(String escaped); + + static + { + System.loadLibrary("gconfpeer"); + init_id_cache(); + } +} diff --git a/libjava/classpath/gnu/java/util/prefs/package.html b/libjava/classpath/gnu/java/util/prefs/package.html new file mode 100644 index 000000000..ee5d67f72 --- /dev/null +++ b/libjava/classpath/gnu/java/util/prefs/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in gnu.java.util.prefs package. + Copyright (C) 2005 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. --> + +<html> +<head><title>GNU Classpath - gnu.java.util.prefs</title></head> + +<body> +<p></p> + +</body> +</html> 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 000000000..b03fb8707 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/BacktrackStack.java @@ -0,0 +1,124 @@ +/* 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 000000000..de4b1667f --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexed.java @@ -0,0 +1,134 @@ +/* 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); + + /** + * Shifts the input buffer by a given number of positions. Returns + * true if the new cursor position is valid or cursor position is at + * the end of input. + */ + boolean move1 (int index); // I cannot think of a better name for this. + + /** + * 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 (); + + /** + * Sets the information used for hitEnd(). + */ + void setHitEnd (REMatch match); + + /** + * Returns whether the matcher has hit the end of input. + */ + boolean hitEnd (); + + /** + * 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 000000000..565773837 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedCharArray.java @@ -0,0 +1,48 @@ +/* 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 000000000..bc3cbbd1d --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedCharSequence.java @@ -0,0 +1,119 @@ +/* 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 boolean move1 (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; + } + + private int rightmostTriedPosition = 0; + public void setHitEnd (REMatch match) + { + int pos = anchor + match.index; + if (pos > rightmostTriedPosition) + rightmostTriedPosition = pos; + } + public boolean hitEnd () + { + return rightmostTriedPosition >= len; + } + + 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 000000000..d6340298a --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedInputStream.java @@ -0,0 +1,253 @@ +/* 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 void setHitEnd (REMatch match) + { + throw new + UnsupportedOperationException + ("difficult to support setHitEnd for an input stream"); + } + + public boolean hitEnd () + { + throw new + UnsupportedOperationException + ("difficult to support hitEnd 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"); + } + + public boolean move1 (int index) + { + throw new + UnsupportedOperationException + ("difficult to support move1 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 000000000..7a56f487e --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedString.java @@ -0,0 +1,46 @@ +/* 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 000000000..d6484f89b --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/CharIndexedStringBuffer.java @@ -0,0 +1,47 @@ +/* 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 000000000..5e9974a49 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RE.java @@ -0,0 +1,2675 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +import java.io.InputStream; +import java.io.Serializable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +/** + * 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 + // Used by getLocalizedMessage(). + private static ResourceBundle messages; + + // Name of the bundle that contains the localized messages. + private static final String bundle = "gnu/java/util/regex/MessagesBundle"; + + // 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; + + /** + * Execution flag. + * Do not move the position at which the search begins. If not set, + * the starting position will be moved until a match is found. + */ + public static final int REG_FIX_STARTING_POSITION = 0x1000; + + /** 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) + { + if (messages == null) + messages = + PropertyResourceBundle.getBundle (bundle, Locale.getDefault ()); + 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 if (patternObj instanceof StringBuilder) + { + pattern = new char[((StringBuilder) patternObj).length ()]; + ((StringBuilder) patternObj).getChars (0, pattern.length, pattern, 0); + } + else if (patternObj instanceof CPStringBuilder) + { + pattern = new char[((CPStringBuilder) patternObj).length ()]; + ((CPStringBuilder) patternObj).getChars (0, pattern.length, pattern, + 0); + } + else + { + pattern = patternObj.toString ().toCharArray (); + } + + int pLength = pattern.length; + + numSubs = 0; // Number of subexpressions in this token. + ArrayList < REToken > 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; + 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 ArrayList < REToken > (); + } + branches.add (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. + add (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); + final ArrayList < REToken > options = new ArrayList < REToken > (); + ArrayList < Object > addition = new ArrayList < Object > (); + 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.add (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.add (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.add (t); + } + + if (posixID != -1) + { + RETokenPOSIX t = + new RETokenPOSIX (subIndex, posixID, insens, negate); + if (insensUSASCII) + t.unicodeAware = false; + options.add (t); + } + else if (np != null) + { + RETokenNamedProperty t = + getRETokenNamedProperty (subIndex, np, insens, index); + if (insensUSASCII) + t.unicodeAware = false; + options.add (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] == ':')) + { + CPStringBuilder posixSet = new CPStringBuilder (); + 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.add (t); + } + } + else if ((ch == '[') && (syntax.get (RESyntax.RE_NESTED_CHARCLASS))) + { + ParseCharClassResult result = + parseCharClass (subIndex, pattern, index, pLength, cflags, + syntax, 0); + addition.add (result.token); + addition.add ("|"); + 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.add ("&"); + 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.add ("&"); + addition.add (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.add (result.token); + addition.add ("|"); + // 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.add (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.add (t); + } + + ParseCharClassResult result = new ParseCharClassResult (); + // Create a new RETokenOneOf + options.trimToSize (); + if (additionAndAppeared) + addition.add ("&"); + 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) + { + List < REMatch > all = new ArrayList < REMatch > (); + REMatch m = null; + while ((m = getMatchImpl (input, index, eflags, null)) != null) + { + all.add (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; + } + return all.toArray (new REMatch[all.size ()]); + } + + /* Implements abstract method REToken.match() */ + boolean match (CharIndexed input, REMatch mymatch) + { + input.setHitEnd (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, + CPStringBuilder buffer) + { + return getMatchImpl (makeCharIndexed (input, index), index, eflags, + buffer); + } + + REMatch getMatchImpl (CharIndexed input, int anchor, int eflags, + CPStringBuilder buffer) + { + boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); + boolean doMove = ((eflags & REG_FIX_STARTING_POSITION) == 0); + RE re = (tryEntireMatch ? (RE) this.clone () : this); + if (tryEntireMatch) + { + RETokenEnd reEnd = new RETokenEnd (0, null); + reEnd.setFake (true); + re.chain (reEnd); + } + // Create a new REMatch to hold results + REMatch mymatch = new REMatch (numSubs, anchor, eflags); + do + { + /* The following potimization is commented out because + the matching should be tried even if the length of + input is obviously too short in order that + java.util.regex.Matcher#hitEnd() may work correctly. + // 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; + } + /* End of the optimization commented out + } + */ + mymatch.clear (++anchor); + // Append character to buffer if needed + if (buffer != null && input.charAt (0) != CharIndexed.OUT_OF_BOUNDS) + { + buffer.append (input.charAt (0)); + } + // java.util.regex.Matcher#hitEnd() requires that the search should + // be tried at the end of input, so we use move1(1) instead of move(1) + } + while (doMove && input.move1 (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) + { + CPStringBuilder buffer = new CPStringBuilder (); + 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) + { + CPStringBuilder buffer = new CPStringBuilder (); + 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) + { + CPStringBuilder sb = new CPStringBuilder (); + 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, + CPStringBuilder 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 (); + CPStringBuilder buf = new CPStringBuilder (); + + // 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 CPStringBuilder (); + // 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 () + { + CPStringBuilder sb = new CPStringBuilder (); + dump (sb); + return sb.toString (); + } + + void dump (CPStringBuilder 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 000000000..5681ceeea --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REException.java @@ -0,0 +1,198 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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; + } + + REException (String msg, Throwable cause, int type, int position) + { + super (msg, cause); + 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)}; + CPStringBuilder sb = new CPStringBuilder (); + 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 000000000..c9fb346ba --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REFilterInputStream.java @@ -0,0 +1,153 @@ +/* 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 000000000..5940094ba --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REMatch.java @@ -0,0 +1,362 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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; + CPStringBuilder sb = new CPStringBuilder (); + 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. + CPStringBuilder sb = new CPStringBuilder (); + 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 + CPStringBuilder output = new CPStringBuilder (); + 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 + public static String d(REMatch m) { + if (m == null) return "null"; + else return "[" + m.index + "]"; + } + + public 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 000000000..04432d07c --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REMatchEnumeration.java @@ -0,0 +1,141 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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 < REMatch >, 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 final RE expr; + private final CharIndexed input; + private final 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 (CPStringBuilder 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 REMatch nextElement () 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 000000000..2080cb763 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RESyntax.java @@ -0,0 +1,537 @@ +/* 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 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 (RE.getLocalizedMessage ("syntax.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 (RE.getLocalizedMessage ("syntax.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 (RE.getLocalizedMessage ("syntax.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 000000000..ed9b31747 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/REToken.java @@ -0,0 +1,244 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +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) + { + return match (input, mymatch, false); + } + boolean matchFake (CharIndexed input, REMatch mymatch) + { + return match (input, mymatch, true); + } + + private boolean match (CharIndexed input, REMatch mymatch, boolean fake) + { + if (!fake) + { + setHitEnd (input, mymatch); + } + REMatch m = matchThis (input, mymatch); + if (m == null) + return false; + if (next (input, m)) + { + mymatch.assignFrom (m); + return true; + } + return false; + } + + /** Sets whether the matching occurs at the end of input */ + void setHitEnd (CharIndexed input, REMatch mymatch) + { + input.setHitEnd (mymatch); + } + + /** 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 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 (CPStringBuilder os); + + void dumpAll (CPStringBuilder os) + { + dump (os); + if (next != null) + next.dumpAll (os); + } + + public String toString () + { + CPStringBuilder os = new CPStringBuilder (); + 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 000000000..f5039682d --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenAny.java @@ -0,0 +1,115 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 (CPStringBuilder 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 000000000..52061ca2e --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenBackRef.java @@ -0,0 +1,100 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 (CPStringBuilder 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 000000000..3469ecfa8 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenChar.java @@ -0,0 +1,162 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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) + { + if (matchOneString (input, mymatch.index)) + { + mymatch.index += matchedLength; + return mymatch; + } + // java.util.regex.Matcher#hitEnd() requires that the length of + // partial match be counted. + mymatch.index += matchedLength; + input.setHitEnd (mymatch); + return null; + } + + private int matchedLength; + private boolean matchOneString (CharIndexed input, int index) + { + matchedLength = 0; + 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; + } + ++matchedLength; + } + 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; + 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; + if (cnext.next == null) + return false; + return chain (cnext.next); + } + else + return super.chain (next); + } + + void dump (CPStringBuilder 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 000000000..28d78231a --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEnd.java @@ -0,0 +1,151 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +final class RETokenEnd extends REToken +{ + /** + * Indicates whether this token should match on a line break. + */ + private String newline; + private boolean check_java_line_terminators; + + /** + * Indicates whether this token is a real one generated at compile time, + * or a fake one temporarily added by RE#getMatchImpl. + */ + private boolean fake = false; + + 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; + } + + void setFake (boolean fake) + { + this.fake = fake; + } + + int getMaximumLength () + { + return 0; + } + + boolean match (CharIndexed input, REMatch mymatch) + { + if (!fake) + return super.match (input, mymatch); + return super.matchFake (input, mymatch); + } + + 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 (CPStringBuilder 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 000000000..67a1b852e --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEndOfPreviousMatch.java @@ -0,0 +1,88 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 (CPStringBuilder 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 000000000..0848207f4 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenEndSub.java @@ -0,0 +1,79 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 setHitEnd (CharIndexed input, REMatch mymatch) + { + // Do nothing + } + + void dump (CPStringBuilder 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 000000000..089aa18af --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenIndependent.java @@ -0,0 +1,85 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +/** + * @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 (CPStringBuilder 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 000000000..34625aac6 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenLookAhead.java @@ -0,0 +1,88 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +/** + * @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 (CPStringBuilder 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 000000000..c85e37f69 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenLookBehind.java @@ -0,0 +1,136 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +/** + * @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 (); + 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 (CPStringBuilder 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 (CPStringBuilder 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 000000000..340da03fb --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenNamedProperty.java @@ -0,0 +1,410 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +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 (CPStringBuilder 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); + if (name.equals ("all")) + return new Handler () + { + public boolean includes (char c) + { + return true; + } + }; + if (name.startsWith ("java")) + { + try + { + Method m = Character.class.getMethod ("is" + name.substring (4), + Character.TYPE); + return new JavaCategoryHandler (m); + } + catch (NoSuchMethodException e) + { + throw new REException ("Unsupported Java handler: " + name, e, + REException.REG_ESCAPE, 0); + } + } + 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)); + } + } + + /** + * Handle the Java-specific extensions \p{javaX} where X + * is a method from Character of the form isX + * + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + */ + private static class JavaCategoryHandler extends Handler + { + private Method method; + + public JavaCategoryHandler (Method m) + { + this.method = m; + } + + public boolean includes (char c) + { + try + { + return (Boolean) method.invoke (null, c); + } + catch (IllegalAccessException e) + { + throw new InternalError ("Unable to access method " + method); + } + catch (InvocationTargetException e) + { + throw new InternalError ("Error invoking " + method); + } + } + } + +} 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 000000000..3bea889ec --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenOneOf.java @@ -0,0 +1,332 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +final class RETokenOneOf extends REToken +{ + private final List < REToken > 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 final List < Object > addition; + // This ArrayList 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 ArrayList options, and + // the additional part /[f-k][m-z]&&[^p-v][st]/ is stored in the + // ArrayList 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 ArrayList 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 ArrayList < REToken > (); + this.negative = negative; + for (int i = 0; i < optionsStr.length (); i++) + options.add (new RETokenChar (subIndex, optionsStr.charAt (i), insens)); + matchesOneChar = true; + addition = null; + } + + RETokenOneOf (int subIndex, List < REToken > options, boolean negative) + { + this (subIndex, options, null, negative); + } + + RETokenOneOf (int subIndex, List < REToken > options, + List < Object > 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 (REToken t:options) + { + if ((x = t.getMinimumLength ()) < min) + min = x; + } + return min; + } + + int getMaximumLength () + { + if (matchesOneChar) + return 1; + int max = 0; + int x; + for (REToken t:options) + { + if ((x = t.getMaximumLength ()) > max) + max = x; + } + return max; + } + + boolean match (CharIndexed input, REMatch mymatch) + { + setHitEnd (input, 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; + + final Deque < Boolean > stack = new ArrayDeque < Boolean > (); + stack.push (new Boolean (b)); + for (Object obj:addition) + { + if (obj instanceof REToken) + { + b = ((REToken) obj).match (input, (REMatch) mymatch.clone ()); + stack.push (new Boolean (b)); + } + else if (obj instanceof Boolean) + { + stack.push ((Boolean) obj); + } + else if (obj.equals ("|")) + { + b = stack.pop (); + b = stack.pop () || b; + stack.push (new Boolean (b)); + } + else if (obj.equals ("&")) + { + b = stack.pop (); + b = stack.pop () && b; + stack.push (new Boolean (b)); + } + else + { + throw new RuntimeException ("Invalid object found"); + } + } + if (stack.pop ()) + { + ++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; + + for (REToken tk:options) + { + 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) + { + for (REToken tk:options) + { + 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 = options.get (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, + i + 1)); + stackPushed = true; + } + if (tk.match (input, tryMatch)) + { + 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 (CPStringBuilder os) + { + os.append (negative ? "[^" : "(?:"); + for (int i = 0; i < options.size (); i++) + { + if (!negative && (i > 0)) + os.append ('|'); + options.get (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 000000000..f0fd04bd3 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenPOSIX.java @@ -0,0 +1,195 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 (CPStringBuilder 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 000000000..8898ef502 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenRange.java @@ -0,0 +1,119 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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 (CPStringBuilder 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 000000000..cd8635396 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenRepeated.java @@ -0,0 +1,639 @@ +/* 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 gnu.java.lang.CPStringBuilder; + +import java.util.ArrayDeque; +import java.util.Deque; + +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 static class FindMatchControl + { + DoablesFinder finder; + FindMatchControl (DoablesFinder finder) + { + this.finder = finder; + } + } + + private REMatch findMatch (BacktrackStack stack) + { + return findMatch (stack, new ArrayDeque < FindMatchControl > ()); + } + + private REMatch findMatch (BacktrackStack stack, + Deque < FindMatchControl > controlStack) + { + REMatch result = null; + StackedInfo si = null; + CharIndexed input = null; + int numRepeats = 0; + REMatch mymatch = null; + int[] visited = null; + DoablesFinder finder = null; + + // Avoid using recursive calls because a match can be very long. + + // This is the first entry point of this method. + // If you want to call this method recursively and you need the + // result returned, save necessary information in a FindMatchControl + // object and push it to controlStack, then continue from this point. + // You can check the result after exiting MAIN_LOOP. + MAIN_LOOP0: + while (true) + { + + // This is the second entry point of this method. + // If you want to call this method recursively but you do not need the + // result returned, just continue from this point. + MAIN_LOOP: + while (true) + { + + if (stack.empty ()) + break MAIN_LOOP; + si = (StackedInfo) (stack.peek ()); + input = si.input; + numRepeats = si.numRepeats; + mymatch = si.match; + visited = si.visited; + 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)); + } + result = m1; + break MAIN_LOOP; + } + if (stingy) + { + continue MAIN_LOOP; + } + break MAIN_LOOP; + } + + 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)); + } + result = m1; + break MAIN_LOOP; + } + else + { + continue MAIN_LOOP; + } + } + + visited = addVisited (mymatch.index, visited); + + TryAnotherResult taresult = + tryAnother (stack, input, mymatch, numRepeats, finder, visited); + visited = taresult.visited; + switch (taresult.status) + { + case TryAnotherResult.TRY_FURTHER: + controlStack.push (new FindMatchControl (finder)); + continue MAIN_LOOP0; + case TryAnotherResult.RESULT_FOUND: + result = taresult.result; + break MAIN_LOOP; + } + + 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)); + } + result = m1; + break MAIN_LOOP; + } + + } // MAIN_LOOP + + if (controlStack.isEmpty ()) + return result; + FindMatchControl control = controlStack.pop (); + if (possessive) + { + return result; + } + if (result != null) + { + result.backtrackStack.push (new BacktrackStack. + Backtrack (this, input, mymatch, + stack)); + return result; + } + + finder = control.finder; + + TryAnotherResult taresult = + tryAnother (stack, input, mymatch, numRepeats, finder, visited); + visited = taresult.visited; + switch (taresult.status) + { + case TryAnotherResult.TRY_FURTHER: + controlStack.push (new FindMatchControl (finder)); + continue MAIN_LOOP0; + case TryAnotherResult.RESULT_FOUND: + return taresult.result; + } + continue MAIN_LOOP0; + + } // MAIN_LOOP0 + } + + private static class TryAnotherResult + { + REMatch result; + int status; + static final int RESULT_FOUND = 1; + static final int TRY_FURTHER = 2; + static final int NOTHING_FOUND = 3; + int[] visited; + } + + private TryAnotherResult tryAnother (BacktrackStack stack, + CharIndexed input, REMatch mymatch, + int numRepeats, DoablesFinder finder, + int[]visited) + { + + TryAnotherResult taresult = new TryAnotherResult (); + taresult.visited = visited; + + DO_THIS: + { + + 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)) + { + continue DO_ONE_DOABLE; + } + visited = addVisited (n, visited); + stack. + push (new + StackedInfo (input, numRepeats + 1, doable, visited, + null)); + taresult.visited = visited; + taresult.status = TryAnotherResult.TRY_FURTHER; + return taresult; + } + else + { + REMatch m1 = matchRest (input, doable); + if (possessive) + { + taresult.result = m1; + taresult.status = TryAnotherResult.RESULT_FOUND; + return taresult; + } + if (m1 != null) + { + if (!stack.empty ()) + { + m1.backtrackStack.push (new BacktrackStack. + Backtrack (this, input, mymatch, + stack)); + } + taresult.result = m1; + taresult.status = TryAnotherResult.RESULT_FOUND; + return taresult; + } + } + + } // DO_ONE_DOABLE + + } // DO_THIS + + taresult.status = TryAnotherResult.NOTHING_FOUND; + return taresult; + + } + + boolean match (CharIndexed input, REMatch mymatch) + { + setHitEnd (input, 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 (CPStringBuilder 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 000000000..c57ba9c75 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenStart.java @@ -0,0 +1,153 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +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; + } + + @Override + int getMaximumLength () + { + return 0; + } + + @Override + 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; + } + + @Override + boolean returnsFixedLengthMatches () + { + return true; + } + + @Override + int findFixedLengthMatches (CharIndexed input, REMatch mymatch, int max) + { + if (matchThis (input, mymatch) != null) + return max; + else + return 0; + } + + @Override + void dump (CPStringBuilder 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 000000000..04fd8391f --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/RETokenWordBoundary.java @@ -0,0 +1,141 @@ +/* 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; + +import gnu.java.lang.CPStringBuilder; + +/** + * 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 (CPStringBuilder 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 000000000..7d215a6c1 --- /dev/null +++ b/libjava/classpath/gnu/java/util/regex/UncheckedRE.java @@ -0,0 +1,114 @@ +/* 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 ()); + } + } +} |