pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / analysis / CharArrayMap.java
diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/analysis/CharArrayMap.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/analysis/CharArrayMap.java
new file mode 100644 (file)
index 0000000..e9d929b
--- /dev/null
@@ -0,0 +1,700 @@
+package org.apache.lucene.analysis;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Arrays;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.lucene.util.CharacterUtils;
+import org.apache.lucene.util.Version;
+
+/**
+ * A simple class that stores key Strings as char[]'s in a
+ * hash table. Note that this is not a general purpose
+ * class.  For example, it cannot remove items from the
+ * map, nor does it resize its hash table to be smaller,
+ * etc.  It is designed to be quick to retrieve items
+ * by char[] keys without the necessity of converting
+ * to a String first.
+ * <p>You must specify the required {@link Version}
+ * compatibility when creating {@link CharArrayMap}:
+ * <ul>
+ *   <li> As of 3.1, supplementary characters are
+ *       properly lowercased.</li>
+ * </ul>
+ * Before 3.1 supplementary characters could not be
+ * lowercased correctly due to the lack of Unicode 4
+ * support in JDK 1.4. To use instances of
+ * {@link CharArrayMap} with the behavior before Lucene
+ * 3.1 pass a {@link Version} &lt; 3.1 to the constructors.
+ */
+public class CharArrayMap<V> extends AbstractMap<Object,V> {
+  // private only because missing generics
+  private static final CharArrayMap<?> EMPTY_MAP = new EmptyCharArrayMap<Object>();
+
+  private final static int INIT_SIZE = 8;
+  private final CharacterUtils charUtils;
+  private boolean ignoreCase;  
+  private int count;
+  final Version matchVersion; // package private because used in CharArraySet
+  char[][] keys; // package private because used in CharArraySet's non Set-conform CharArraySetIterator
+  V[] values; // package private because used in CharArraySet's non Set-conform CharArraySetIterator
+
+  /**
+   * Create map with enough capacity to hold startSize terms
+   * 
+   * @param matchVersion
+   *          compatibility match version see <a href="#version">Version
+   *          note</a> above for details.
+   * @param startSize
+   *          the initial capacity
+   * @param ignoreCase
+   *          <code>false</code> if and only if the set should be case sensitive
+   *          otherwise <code>true</code>.
+   */
+  @SuppressWarnings("unchecked")
+  public CharArrayMap(Version matchVersion, int startSize, boolean ignoreCase) {
+    this.ignoreCase = ignoreCase;
+    int size = INIT_SIZE;
+    while(startSize + (startSize>>2) > size)
+      size <<= 1;
+    keys = new char[size][];
+    values = (V[]) new Object[size];
+    this.charUtils = CharacterUtils.getInstance(matchVersion);
+    this.matchVersion = matchVersion;
+  }
+
+  /**
+   * Creates a map from the mappings in another map. 
+   * 
+   * @param matchVersion
+   *          compatibility match version see <a href="#version">Version
+   *          note</a> above for details.
+   * @param c
+   *          a map whose mappings to be copied
+   * @param ignoreCase
+   *          <code>false</code> if and only if the set should be case sensitive
+   *          otherwise <code>true</code>.
+   */
+  public CharArrayMap(Version matchVersion, Map<?,? extends V> c, boolean ignoreCase) {
+    this(matchVersion, c.size(), ignoreCase);
+    putAll(c);
+  }
+  
+  /** Create set from the supplied map (used internally for readonly maps...) */
+  private CharArrayMap(CharArrayMap<V> toCopy){
+    this.keys = toCopy.keys;
+    this.values = toCopy.values;
+    this.ignoreCase = toCopy.ignoreCase;
+    this.count = toCopy.count;
+    this.charUtils = toCopy.charUtils;
+    this.matchVersion = toCopy.matchVersion;
+  }
+  
+  /** Clears all entries in this map. This method is supported for reusing, but not {@link Map#remove}. */
+  @Override
+  public void clear() {
+    count = 0;
+    Arrays.fill(keys, null);
+    Arrays.fill(values, null);
+  }
+
+  /** true if the <code>len</code> chars of <code>text</code> starting at <code>off</code>
+   * are in the {@link #keySet} */
+  public boolean containsKey(char[] text, int off, int len) {
+    return keys[getSlot(text, off, len)] != null;
+  }
+
+  /** true if the <code>CharSequence</code> is in the {@link #keySet} */
+  public boolean containsKey(CharSequence cs) {
+    return keys[getSlot(cs)] != null;
+  }
+
+  @Override
+  public boolean containsKey(Object o) {
+    if (o instanceof char[]) {
+      final char[] text = (char[])o;
+      return containsKey(text, 0, text.length);
+    } 
+    return containsKey(o.toString());
+  }
+
+  /** returns the value of the mapping of <code>len</code> chars of <code>text</code>
+   * starting at <code>off</code> */
+  public V get(char[] text, int off, int len) {
+    return values[getSlot(text, off, len)];
+  }
+
+  /** returns the value of the mapping of the chars inside this {@code CharSequence} */
+  public V get(CharSequence cs) {
+    return values[getSlot(cs)];
+  }
+
+  @Override
+  public V get(Object o) {
+    if (o instanceof char[]) {
+      final char[] text = (char[])o;
+      return get(text, 0, text.length);
+    } 
+    return get(o.toString());
+  }
+
+  private int getSlot(char[] text, int off, int len) {
+    int code = getHashCode(text, off, len);
+    int pos = code & (keys.length-1);
+    char[] text2 = keys[pos];
+    if (text2 != null && !equals(text, off, len, text2)) {
+      final int inc = ((code>>8)+code)|1;
+      do {
+        code += inc;
+        pos = code & (keys.length-1);
+        text2 = keys[pos];
+      } while (text2 != null && !equals(text, off, len, text2));
+    }
+    return pos;
+  }
+
+  /** Returns true if the String is in the set */  
+  private int getSlot(CharSequence text) {
+    int code = getHashCode(text);
+    int pos = code & (keys.length-1);
+    char[] text2 = keys[pos];
+    if (text2 != null && !equals(text, text2)) {
+      final int inc = ((code>>8)+code)|1;
+      do {
+        code += inc;
+        pos = code & (keys.length-1);
+        text2 = keys[pos];
+      } while (text2 != null && !equals(text, text2));
+    }
+    return pos;
+  }
+
+  /** Add the given mapping. */
+  public V put(CharSequence text, V value) {
+    return put(text.toString(), value); // could be more efficient
+  }
+
+  @Override
+  public V put(Object o, V value) {
+    if (o instanceof char[]) {
+      return put((char[])o, value);
+    }
+    return put(o.toString(), value);
+  }
+  
+  /** Add the given mapping. */
+  public V put(String text, V value) {
+    return put(text.toCharArray(), value);
+  }
+
+  /** Add the given mapping.
+   * If ignoreCase is true for this Set, the text array will be directly modified.
+   * The user should never modify this text array after calling this method.
+   */
+  public V put(char[] text, V value) {
+    if (ignoreCase)
+      for(int i=0;i<text.length;){
+        i += Character.toChars(
+              Character.toLowerCase(
+                  charUtils.codePointAt(text, i)), text, i);
+      }
+    int slot = getSlot(text, 0, text.length);
+    if (keys[slot] != null) {
+      final V oldValue = values[slot];
+      values[slot] = value;
+      return oldValue;
+    }
+    keys[slot] = text;
+    values[slot] = value;
+    count++;
+
+    if (count + (count>>2) > keys.length) {
+      rehash();
+    }
+
+    return null;
+  }
+
+  @SuppressWarnings("unchecked")
+  private void rehash() {
+    assert keys.length == values.length;
+    final int newSize = 2*keys.length;
+    final char[][] oldkeys = keys;
+    final V[] oldvalues = values;
+    keys = new char[newSize][];
+    values = (V[]) new Object[newSize];
+
+    for(int i=0; i<oldkeys.length; i++) {
+      char[] text = oldkeys[i];
+      if (text != null) {
+        // todo: could be faster... no need to compare strings on collision
+        final int slot = getSlot(text,0,text.length);
+        keys[slot] = text;
+        values[slot] = oldvalues[i];
+      }
+    }
+  }
+  
+  private boolean equals(char[] text1, int off, int len, char[] text2) {
+    if (len != text2.length)
+      return false;
+    final int limit = off+len;
+    if (ignoreCase) {
+      for(int i=0;i<len;) {
+        final int codePointAt = charUtils.codePointAt(text1, off+i, limit);
+        if (Character.toLowerCase(codePointAt) != charUtils.codePointAt(text2, i))
+          return false;
+        i += Character.charCount(codePointAt); 
+      }
+    } else {
+      for(int i=0;i<len;i++) {
+        if (text1[off+i] != text2[i])
+          return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean equals(CharSequence text1, char[] text2) {
+    int len = text1.length();
+    if (len != text2.length)
+      return false;
+    if (ignoreCase) {
+      for(int i=0;i<len;) {
+        final int codePointAt = charUtils.codePointAt(text1, i);
+        if (Character.toLowerCase(codePointAt) != charUtils.codePointAt(text2, i))
+          return false;
+        i += Character.charCount(codePointAt);
+      }
+    } else {
+      for(int i=0;i<len;i++) {
+        if (text1.charAt(i) != text2[i])
+          return false;
+      }
+    }
+    return true;
+  }
+  
+  private int getHashCode(char[] text, int offset, int len) {
+    if (text == null)
+      throw new NullPointerException();
+    int code = 0;
+    final int stop = offset + len;
+    if (ignoreCase) {
+      for (int i=offset; i<stop;) {
+        final int codePointAt = charUtils.codePointAt(text, i, stop);
+        code = code*31 + Character.toLowerCase(codePointAt);
+        i += Character.charCount(codePointAt);
+      }
+    } else {
+      for (int i=offset; i<stop; i++) {
+        code = code*31 + text[i];
+      }
+    }
+    return code;
+  }
+
+  private int getHashCode(CharSequence text) {
+    if (text == null)
+      throw new NullPointerException();
+    int code = 0;
+    int len = text.length();
+    if (ignoreCase) {
+      for (int i=0; i<len;) {
+        int codePointAt = charUtils.codePointAt(text, i);
+        code = code*31 + Character.toLowerCase(codePointAt);
+        i += Character.charCount(codePointAt);
+      }
+    } else {
+      for (int i=0; i<len; i++) {
+        code = code*31 + text.charAt(i);
+      }
+    }
+    return code;
+  }
+
+  @Override
+  public V remove(Object key) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int size() {
+    return count;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("{");
+    for (Map.Entry<Object,V> entry : entrySet()) {
+      if (sb.length()>1) sb.append(", ");
+      sb.append(entry);
+    }
+    return sb.append('}').toString();
+  }
+
+  private EntrySet entrySet = null;
+  private CharArraySet keySet = null;
+  
+  EntrySet createEntrySet() {
+    return new EntrySet(true);
+  }
+  
+  @Override
+  public final EntrySet entrySet() {
+    if (entrySet == null) {
+      entrySet = createEntrySet();
+    }
+    return entrySet;
+  }
+  
+  // helper for CharArraySet to not produce endless recursion
+  final Set<Object> originalKeySet() {
+    return super.keySet();
+  }
+
+  /** Returns an {@link CharArraySet} view on the map's keys.
+   * The set will use the same {@code matchVersion} as this map. */
+  @Override @SuppressWarnings("unchecked")
+  public final CharArraySet keySet() {
+    if (keySet == null) {
+      // prevent adding of entries
+      keySet = new CharArraySet((CharArrayMap) this) {
+        @Override
+        public boolean add(Object o) {
+          throw new UnsupportedOperationException();
+        }
+        @Override
+        public boolean add(CharSequence text) {
+          throw new UnsupportedOperationException();
+        }
+        @Override
+        public boolean add(String text) {
+          throw new UnsupportedOperationException();
+        }
+        @Override
+        public boolean add(char[] text) {
+          throw new UnsupportedOperationException();
+        }
+      };
+    }
+    return keySet;
+  }
+
+  /** public iterator class so efficient methods are exposed to users */
+  public class EntryIterator implements Iterator<Map.Entry<Object,V>> {
+    private int pos=-1;
+    private int lastPos;
+    private final boolean allowModify;
+    
+    private EntryIterator(boolean allowModify) {
+      this.allowModify = allowModify;
+      goNext();
+    }
+
+    private void goNext() {
+      lastPos = pos;
+      pos++;
+      while (pos < keys.length && keys[pos] == null) pos++;
+    }
+
+    public boolean hasNext() {
+      return pos < keys.length;
+    }
+
+    /** gets the next key... do not modify the returned char[] */
+    public char[] nextKey() {
+      goNext();
+      return keys[lastPos];
+    }
+
+    /** gets the next key as a newly created String object */
+    public String nextKeyString() {
+      return new String(nextKey());
+    }
+
+    /** returns the value associated with the last key returned */
+    public V currentValue() {
+      return values[lastPos];
+    }
+
+    /** sets the value associated with the last key returned */    
+    public V setValue(V value) {
+      if (!allowModify)
+        throw new UnsupportedOperationException();
+      V old = values[lastPos];
+      values[lastPos] = value;
+      return old;      
+    }
+
+    /** use nextCharArray() + currentValue() for better efficiency. */
+    public Map.Entry<Object,V> next() {
+      goNext();
+      return new MapEntry(lastPos, allowModify);
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private final class MapEntry implements Map.Entry<Object,V> {
+    private final int pos;
+    private final boolean allowModify;
+
+    private MapEntry(int pos, boolean allowModify) {
+      this.pos = pos;
+      this.allowModify = allowModify;
+    }
+
+    public Object getKey() {
+      // we must clone here, as putAll to another CharArrayMap
+      // with other case sensitivity flag would corrupt the keys
+      return keys[pos].clone();
+    }
+
+    public V getValue() {
+      return values[pos];
+    }
+
+    public V setValue(V value) {
+      if (!allowModify)
+        throw new UnsupportedOperationException();
+      final V old = values[pos];
+      values[pos] = value;
+      return old;
+    }
+
+    @Override
+    public String toString() {
+      return new StringBuilder().append(keys[pos]).append('=')
+        .append((values[pos] == CharArrayMap.this) ? "(this Map)" : values[pos])
+        .toString();
+    }
+  }
+
+  /** public EntrySet class so efficient methods are exposed to users */
+  public final class EntrySet extends AbstractSet<Map.Entry<Object,V>> {
+    private final boolean allowModify;
+    
+    private EntrySet(boolean allowModify) {
+      this.allowModify = allowModify;
+    }
+  
+    @Override
+    public EntryIterator iterator() {
+      return new EntryIterator(allowModify);
+    }
+    
+    @Override
+    public boolean contains(Object o) {
+      if (!(o instanceof Map.Entry))
+        return false;
+      final Map.Entry e = (Map.Entry)o;
+      final Object key = e.getKey();
+      final Object val = e.getValue();
+      final Object v = get(key);
+      return v == null ? val == null : v.equals(val);
+    }
+    
+    @Override
+    public boolean remove(Object o) {
+      throw new UnsupportedOperationException();
+    }
+    
+    @Override
+    public int size() {
+      return count;
+    }
+    
+    @Override
+    public void clear() {
+      if (!allowModify)
+        throw new UnsupportedOperationException();
+      CharArrayMap.this.clear();
+    }
+  }
+  
+  /**
+   * Returns an unmodifiable {@link CharArrayMap}. This allows to provide
+   * unmodifiable views of internal map for "read-only" use.
+   * 
+   * @param map
+   *          a map for which the unmodifiable map is returned.
+   * @return an new unmodifiable {@link CharArrayMap}.
+   * @throws NullPointerException
+   *           if the given map is <code>null</code>.
+   */
+  public static <V> CharArrayMap<V> unmodifiableMap(CharArrayMap<V> map) {
+    if (map == null)
+      throw new NullPointerException("Given map is null");
+    if (map == emptyMap() || map.isEmpty())
+      return emptyMap();
+    if (map instanceof UnmodifiableCharArrayMap)
+      return map;
+    return new UnmodifiableCharArrayMap<V>(map);
+  }
+
+  /**
+   * Returns a copy of the given map as a {@link CharArrayMap}. If the given map
+   * is a {@link CharArrayMap} the ignoreCase property will be preserved.
+   * <p>
+   * <b>Note:</b> If you intend to create a copy of another {@link CharArrayMap} where
+   * the {@link Version} of the source map differs from its copy
+   * {@link #CharArrayMap(Version, Map, boolean)} should be used instead.
+   * The {@link #copy(Version, Map)} will preserve the {@link Version} of the
+   * source map it is an instance of {@link CharArrayMap}.
+   * </p>
+   * 
+   * @param matchVersion
+   *          compatibility match version see <a href="#version">Version
+   *          note</a> above for details. This argument will be ignored if the
+   *          given map is a {@link CharArrayMap}.
+   * @param map
+   *          a map to copy
+   * @return a copy of the given map as a {@link CharArrayMap}. If the given map
+   *         is a {@link CharArrayMap} the ignoreCase property as well as the
+   *         matchVersion will be of the given map will be preserved.
+   */
+  @SuppressWarnings("unchecked")
+  public static <V> CharArrayMap<V> copy(final Version matchVersion, final Map<?,? extends V> map) {
+    if(map == EMPTY_MAP)
+      return emptyMap();
+    if(map instanceof CharArrayMap) {
+      CharArrayMap<V> m = (CharArrayMap<V>) map;
+      // use fast path instead of iterating all values
+      // this is even on very small sets ~10 times faster than iterating
+      final char[][] keys = new char[m.keys.length][];
+      System.arraycopy(m.keys, 0, keys, 0, keys.length);
+      final V[] values = (V[]) new Object[m.values.length];
+      System.arraycopy(m.values, 0, values, 0, values.length);
+      m = new CharArrayMap<V>(m);
+      m.keys = keys;
+      m.values = values;
+      return m;
+    }
+    return new CharArrayMap<V>(matchVersion, map, false);
+  }
+  
+  /** Returns an empty, unmodifiable map. */
+  @SuppressWarnings("unchecked")
+  public static <V> CharArrayMap<V> emptyMap() {
+    return (CharArrayMap<V>) EMPTY_MAP;
+  }
+  
+  // package private CharArraySet instanceof check in CharArraySet
+  static class UnmodifiableCharArrayMap<V> extends CharArrayMap<V> {
+
+    UnmodifiableCharArrayMap(CharArrayMap<V> map) {
+      super(map);
+    }
+
+    @Override
+    public void clear() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V put(Object o, V val){
+      throw new UnsupportedOperationException();
+    }
+    
+    @Override
+    public V put(char[] text, V val) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V put(CharSequence text, V val) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V put(String text, V val) {
+      throw new UnsupportedOperationException();
+    }
+    
+    @Override
+    public V remove(Object key) {
+      throw new UnsupportedOperationException();
+    }
+  
+    @Override
+    EntrySet createEntrySet() {
+      return new EntrySet(false);
+    }
+  }
+  
+  /**
+   * Empty {@link UnmodifiableCharArrayMap} optimized for speed.
+   * Contains checks will always return <code>false</code> or throw
+   * NPE if necessary.
+   */
+  private static final class EmptyCharArrayMap<V> extends UnmodifiableCharArrayMap<V> {
+    EmptyCharArrayMap() {
+      super(new CharArrayMap<V>(Version.LUCENE_CURRENT, 0, false));
+    }
+    
+    @Override
+    public boolean containsKey(char[] text, int off, int len) {
+      if(text == null)
+        throw new NullPointerException();
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(CharSequence cs) {
+      if(cs == null)
+        throw new NullPointerException();
+      return false;
+    }
+
+    @Override
+    public boolean containsKey(Object o) {
+      if(o == null)
+        throw new NullPointerException();
+      return false;
+    }
+    
+    @Override
+    public V get(char[] text, int off, int len) {
+      if(text == null)
+        throw new NullPointerException();
+      return null;
+    }
+
+    @Override
+    public V get(CharSequence cs) {
+      if(cs == null)
+        throw new NullPointerException();
+      return null;
+    }
+
+    @Override
+    public V get(Object o) {
+      if(o == null)
+        throw new NullPointerException();
+      return null;
+    }
+  }
+}