package org.apache.commons.collections;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

public class ReferenceMap extends AbstractMap {
  private static final long serialVersionUID = -3370601314380922368L;
  
  public static final int HARD = 0;
  
  public static final int SOFT = 1;
  
  public static final int WEAK = 2;
  
  private int keyType;
  
  private int valueType;
  
  private float loadFactor;
  
  private transient ReferenceQueue queue = new ReferenceQueue();
  
  private transient Entry[] table;
  
  private transient int size;
  
  private transient int threshold;
  
  private volatile transient int modCount;
  
  private transient Set keySet;
  
  private transient Set entrySet;
  
  private transient Collection values;
  
  public ReferenceMap() {
    this(0, 1);
  }
  
  public ReferenceMap(int paramInt1, int paramInt2) {
    this(paramInt1, paramInt2, 16, 0.75F);
  }
  
  public ReferenceMap(int paramInt1, int paramInt2, int paramInt3, float paramFloat) {
    verify("keyType", paramInt1);
    verify("valueType", paramInt2);
    if (paramInt3 <= 0)
      throw new IllegalArgumentException("capacity must be positive"); 
    if (paramFloat <= 0.0F || paramFloat >= 1.0F)
      throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1."); 
    this.keyType = paramInt1;
    this.valueType = paramInt2;
    int i;
    for (i = 1; i < paramInt3; i *= 2);
    this.table = new Entry[i];
    this.loadFactor = paramFloat;
    this.threshold = (int)(i * paramFloat);
  }
  
  public void clear() {
    Arrays.fill((Object[])this.table, (Object)null);
    this.size = 0;
    do {
    
    } while (this.queue.poll() != null);
  }
  
  public boolean containsKey(Object paramObject) {
    purge();
    Entry entry = getEntry(paramObject);
    return (entry == null) ? false : (!(entry.getValue() == null));
  }
  
  public Set entrySet() {
    if (this.entrySet != null)
      return this.entrySet; 
    this.entrySet = new AbstractSet(this) {
        private final ReferenceMap this$0;
        
        public void clear() {
          this.this$0.clear();
        }
        
        public boolean contains(Object param1Object) {
          if (param1Object == null)
            return false; 
          if (!(param1Object instanceof Map.Entry))
            return false; 
          Map.Entry entry = (Map.Entry)param1Object;
          ReferenceMap.Entry entry1 = this.this$0.getEntry(entry.getKey());
          return !(entry1 == null || !entry.equals(entry1));
        }
        
        public Iterator iterator() {
          return new ReferenceMap.EntryIterator(this.this$0);
        }
        
        public boolean remove(Object param1Object) {
          boolean bool = contains(param1Object);
          if (bool) {
            Map.Entry entry = (Map.Entry)param1Object;
            this.this$0.remove(entry.getKey());
          } 
          return bool;
        }
        
        public int size() {
          return this.this$0.size();
        }
        
        public Object[] toArray() {
          return toArray(new Object[0]);
        }
        
        public Object[] toArray(Object[] param1ArrayOfObject) {
          ArrayList arrayList = new ArrayList();
          for (ReferenceMap.Entry entry : this)
            arrayList.add(new DefaultMapEntry(entry.getKey(), entry.getValue())); 
          return arrayList.toArray(param1ArrayOfObject);
        }
      };
    return this.entrySet;
  }
  
  public Object get(Object paramObject) {
    purge();
    Entry entry = getEntry(paramObject);
    return (entry == null) ? null : entry.getValue();
  }
  
  private Entry getEntry(Object paramObject) {
    if (paramObject == null)
      return null; 
    int i = paramObject.hashCode();
    int j = indexFor(i);
    for (Entry entry = this.table[j]; entry != null; entry = entry.next) {
      if (entry.hash == i && paramObject.equals(entry.getKey()))
        return entry; 
    } 
    return null;
  }
  
  private int indexFor(int paramInt) {
    paramInt += paramInt << 15 ^ 0xFFFFFFFF;
    paramInt ^= paramInt >>> 10;
    paramInt += paramInt << 3;
    paramInt ^= paramInt >>> 6;
    paramInt += paramInt << 11 ^ 0xFFFFFFFF;
    paramInt ^= paramInt >>> 16;
    return paramInt & this.table.length - 1;
  }
  
  public boolean isEmpty() {
    purge();
    return !(this.size != 0);
  }
  
  public Set keySet() {
    if (this.keySet != null)
      return this.keySet; 
    this.keySet = new AbstractSet(this) {
        private final ReferenceMap this$0;
        
        public void clear() {
          this.this$0.clear();
        }
        
        public boolean contains(Object param1Object) {
          return this.this$0.containsKey(param1Object);
        }
        
        public Iterator iterator() {
          return new ReferenceMap.KeyIterator(this.this$0);
        }
        
        public boolean remove(Object param1Object) {
          Object object = this.this$0.remove(param1Object);
          return !(object == null);
        }
        
        public int size() {
          return this.this$0.size;
        }
      };
    return this.keySet;
  }
  
  private void purge() {
    for (Reference reference = this.queue.poll(); reference != null; reference = this.queue.poll())
      purge(reference); 
  }
  
  private void purge(Reference paramReference) {
    int i = paramReference.hashCode();
    int j = indexFor(i);
    Entry entry1 = null;
    for (Entry entry2 = this.table[j]; entry2 != null; entry2 = entry2.next) {
      if (entry2.purge(paramReference)) {
        if (entry1 == null) {
          this.table[j] = entry2.next;
        } else {
          entry1.next = entry2.next;
        } 
        this.size--;
        return;
      } 
      entry1 = entry2;
    } 
  }
  
  public Object put(Object paramObject1, Object paramObject2) {
    if (paramObject1 == null)
      throw new NullPointerException("null keys not allowed"); 
    if (paramObject2 == null)
      throw new NullPointerException("null values not allowed"); 
    purge();
    if (this.size + 1 > this.threshold)
      resize(); 
    int i = paramObject1.hashCode();
    int j = indexFor(i);
    for (Entry entry = this.table[j]; entry != null; entry = entry.next) {
      if (i == entry.hash && paramObject1.equals(entry.getKey())) {
        Object object = entry.getValue();
        entry.setValue(paramObject2);
        return object;
      } 
    } 
    this.size++;
    this.modCount++;
    paramObject1 = toReference(this.keyType, paramObject1, i);
    paramObject2 = toReference(this.valueType, paramObject2, i);
    this.table[j] = new Entry(this, paramObject1, i, paramObject2, this.table[j]);
    return null;
  }
  
  private void readObject(ObjectInputStream paramObjectInputStream) throws IOException, ClassNotFoundException {
    paramObjectInputStream.defaultReadObject();
    this.table = new Entry[paramObjectInputStream.readInt()];
    this.threshold = (int)(this.table.length * this.loadFactor);
    this.queue = new ReferenceQueue();
    for (Object object = paramObjectInputStream.readObject(); object != null; object = paramObjectInputStream.readObject()) {
      Object object1 = paramObjectInputStream.readObject();
      put(object, object1);
    } 
  }
  
  public Object remove(Object paramObject) {
    if (paramObject == null)
      return null; 
    purge();
    int i = paramObject.hashCode();
    int j = indexFor(i);
    Entry entry1 = null;
    for (Entry entry2 = this.table[j]; entry2 != null; entry2 = entry2.next) {
      if (i == entry2.hash && paramObject.equals(entry2.getKey())) {
        if (entry1 == null) {
          this.table[j] = entry2.next;
        } else {
          entry1.next = entry2.next;
        } 
        this.size--;
        this.modCount++;
        return entry2.getValue();
      } 
      entry1 = entry2;
    } 
    return null;
  }
  
  private void resize() {
    Entry[] arrayOfEntry = this.table;
    this.table = new Entry[arrayOfEntry.length * 2];
    for (byte b = 0; b < arrayOfEntry.length; b++) {
      Entry entry = arrayOfEntry[b];
      while (entry != null) {
        Entry entry1 = entry;
        entry = entry.next;
        int i = indexFor(entry1.hash);
        entry1.next = this.table[i];
        this.table[i] = entry1;
      } 
      arrayOfEntry[b] = null;
    } 
    this.threshold = (int)(this.table.length * this.loadFactor);
  }
  
  public int size() {
    purge();
    return this.size;
  }
  
  private Object toReference(int paramInt1, Object paramObject, int paramInt2) {
    switch (paramInt1) {
      case 0:
        return paramObject;
      case 1:
        return new SoftRef(paramInt2, paramObject, this.queue);
      case 2:
        return new WeakRef(paramInt2, paramObject, this.queue);
    } 
    throw new Error();
  }
  
  public Collection values() {
    if (this.values != null)
      return this.values; 
    this.values = new AbstractCollection(this) {
        private final ReferenceMap this$0;
        
        public void clear() {
          this.this$0.clear();
        }
        
        public Iterator iterator() {
          return new ReferenceMap.ValueIterator(this.this$0);
        }
        
        public int size() {
          return this.this$0.size;
        }
      };
    return this.values;
  }
  
  private static void verify(String paramString, int paramInt) {
    if (paramInt < 0 || paramInt > 2)
      throw new IllegalArgumentException(String.valueOf(paramString) + " must be HARD, SOFT, WEAK."); 
  }
  
  private void writeObject(ObjectOutputStream paramObjectOutputStream) throws IOException {
    paramObjectOutputStream.defaultWriteObject();
    paramObjectOutputStream.writeInt(this.table.length);
    for (Map.Entry entry : entrySet()) {
      paramObjectOutputStream.writeObject(entry.getKey());
      paramObjectOutputStream.writeObject(entry.getValue());
    } 
    paramObjectOutputStream.writeObject(null);
  }
  
  private class Entry implements Map.Entry {
    private final ReferenceMap this$0;
    
    Object key;
    
    Object value;
    
    int hash;
    
    Entry next;
    
    public Entry(ReferenceMap this$0, Object param1Object1, int param1Int, Object param1Object2, Entry param1Entry) {
      this.this$0 = this$0;
      this.key = param1Object1;
      this.hash = param1Int;
      this.value = param1Object2;
      this.next = param1Entry;
    }
    
    public boolean equals(Object param1Object) {
      if (param1Object == null)
        return false; 
      if (param1Object == this)
        return true; 
      if (!(param1Object instanceof Map.Entry))
        return false; 
      Map.Entry entry = (Map.Entry)param1Object;
      Object object1 = entry.getKey();
      Object object2 = entry.getValue();
      return (object1 == null || object2 == null) ? false : (!(!object1.equals(getKey()) || !object2.equals(getValue())));
    }
    
    public Object getKey() {
      return (this.this$0.keyType > 0) ? ((Reference)this.key).get() : this.key;
    }
    
    public Object getValue() {
      return (this.this$0.valueType > 0) ? ((Reference)this.value).get() : this.value;
    }
    
    public int hashCode() {
      Object object = getValue();
      return this.hash ^ ((object == null) ? 0 : object.hashCode());
    }
    
    boolean purge(Reference param1Reference) {
      boolean bool = (this.this$0.keyType <= 0 || this.key != param1Reference) ? false : true;
      bool = (!bool && (this.this$0.valueType <= 0 || this.value != param1Reference)) ? false : true;
      if (bool) {
        if (this.this$0.keyType > 0)
          ((Reference)this.key).clear(); 
        if (this.this$0.valueType > 0)
          ((Reference)this.value).clear(); 
      } 
      return bool;
    }
    
    public Object setValue(Object param1Object) {
      Object object = getValue();
      if (this.this$0.valueType > 0)
        ((Reference)this.value).clear(); 
      this.value = this.this$0.toReference(this.this$0.valueType, param1Object, this.hash);
      return object;
    }
    
    public String toString() {
      return String.valueOf(String.valueOf(getKey())) + "=" + getValue();
    }
  }
  
  private class EntryIterator implements Iterator {
    private final ReferenceMap this$0;
    
    int index;
    
    ReferenceMap.Entry entry;
    
    ReferenceMap.Entry previous;
    
    Object nextKey;
    
    Object nextValue;
    
    Object currentKey;
    
    Object currentValue;
    
    int expectedModCount;
    
    public EntryIterator(ReferenceMap this$0) {
      this.this$0 = this$0;
      this.index = (this$0.size() != 0) ? this$0.table.length : 0;
      this.expectedModCount = this$0.modCount;
    }
    
    private void checkMod() {
      if (this.this$0.modCount != this.expectedModCount)
        throw new ConcurrentModificationException(); 
    }
    
    public boolean hasNext() {
      checkMod();
      while (nextNull()) {
        ReferenceMap.Entry entry = this.entry;
        int i = this.index;
        while (entry == null && i > 0)
          entry = this.this$0.table[--i]; 
        this.entry = entry;
        this.index = i;
        if (entry == null) {
          this.currentKey = null;
          this.currentValue = null;
          return false;
        } 
        this.nextKey = entry.getKey();
        this.nextValue = entry.getValue();
        if (nextNull())
          this.entry = this.entry.next; 
      } 
      return true;
    }
    
    public Object next() {
      return nextEntry();
    }
    
    protected ReferenceMap.Entry nextEntry() {
      checkMod();
      if (nextNull() && !hasNext())
        throw new NoSuchElementException(); 
      this.previous = this.entry;
      this.entry = this.entry.next;
      this.currentKey = this.nextKey;
      this.currentValue = this.nextValue;
      this.nextKey = null;
      this.nextValue = null;
      return this.previous;
    }
    
    private boolean nextNull() {
      return !(this.nextKey != null && this.nextValue != null);
    }
    
    public void remove() {
      checkMod();
      if (this.previous == null)
        throw new IllegalStateException(); 
      this.this$0.remove(this.currentKey);
      this.previous = null;
      this.currentKey = null;
      this.currentValue = null;
      this.expectedModCount = this.this$0.modCount;
    }
  }
  
  private class ValueIterator extends EntryIterator {
    private final ReferenceMap this$0;
    
    ValueIterator(ReferenceMap this$0) {
      super(this$0);
      this.this$0 = this$0;
    }
    
    public Object next() {
      return nextEntry().getValue();
    }
  }
  
  private class KeyIterator extends EntryIterator {
    private final ReferenceMap this$0;
    
    KeyIterator(ReferenceMap this$0) {
      super(this$0);
      this.this$0 = this$0;
    }
    
    public Object next() {
      return nextEntry().getKey();
    }
  }
  
  private static class SoftRef extends SoftReference {
    private int hash;
    
    public SoftRef(int param1Int, Object param1Object, ReferenceQueue param1ReferenceQueue) {
      super((T)param1Object, param1ReferenceQueue);
      this.hash = param1Int;
    }
    
    public int hashCode() {
      return this.hash;
    }
  }
  
  private static class WeakRef extends WeakReference {
    private int hash;
    
    public WeakRef(int param1Int, Object param1Object, ReferenceQueue param1ReferenceQueue) {
      super((T)param1Object, param1ReferenceQueue);
      this.hash = param1Int;
    }
    
    public int hashCode() {
      return this.hash;
    }
  }
}
