/*
 * 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.
 */
package org.apache.felix.resolver.util;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

public class CopyOnWriteSet<E> implements Set<E>, Cloneable {

    Object[] data;

    public CopyOnWriteSet() {
        data = new Object[0];
    }

    public CopyOnWriteSet(CopyOnWriteSet<? extends E> col) {
        data = col.data;
    }

    public CopyOnWriteSet(Collection<? extends E> col) {
        data = col.toArray(new Object[col.size()]);
    }

    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int idx = 0;
            public boolean hasNext() {
                return idx < data.length;
            }
            @SuppressWarnings("unchecked")
            public E next() {
                return (E) data[idx++];
            }
            public void remove() {
                CopyOnWriteSet.this.remove(--idx);
            }
        };
    }

    public int size() {
        return data.length;
    }

    public boolean add(E e) {
        Object[] d = data;
        if (d.length == 0) {
            data = new Object[] {e};
        } else {
            for (Object o : d) {
                if (o == null ? e == null : o.equals(e)) {
                    return false;
                }
            }
            Object[] a = new Object[d.length + 1];
            System.arraycopy(d, 0, a, 0, d.length);
            a[d.length] = e;
            data = a;
        }
        return true;
    }

    private void remove(int index) {
        Object[] d = data;
        int len = d.length;
        Object[] a = new Object[len - 1];
        int numMoved = len - index - 1;
        if (index > 0) {
            System.arraycopy(d, 0, a, 0, index);
        }
        if (numMoved > 0) {
            System.arraycopy(d, index + 1, a, index, numMoved);
        }
        data = a;
    }

    public Object[] toArray() {
        return data.clone();
    }

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        int size = data.length;
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) copyOf(data, size, a.getClass());
        System.arraycopy(data, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof CopyOnWriteSet)) {
            return false;
        }
        Object[] o1 = data;
        @SuppressWarnings("rawtypes")
        Object[] o2 = ((CopyOnWriteSet) o).data;
        if (o1 == o2) {
            return true;
        }
        int l = o1.length;
        if (l != o2.length) {
            return false;
        }
        loop:
        for (int i = l; i-- > 0;) {
            Object v1 = o1[i];
            for (int j = l; j-- > 0;) {
                Object v2 = o2[j];
                if (v1 == v2)
                    continue loop;
                if (v1 != null && v1.equals(v2))
                    continue loop;
            }
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(data);
    }

    
Clone this object
Returns:a cloned object.
/** * Clone this object * * @return a cloned object. */
@Override @SuppressWarnings("unchecked") public CopyOnWriteSet<E> clone() { try { return (CopyOnWriteSet<E>) super.clone(); } catch (CloneNotSupportedException exc) { InternalError e = new InternalError(); e.initCause(exc); throw e; //should never happen since we are cloneable } } public boolean isEmpty() { return size() == 0; } public boolean contains(Object o) { throw new UnsupportedOperationException(); } public boolean remove(Object o) { int index; if ((index = indexOf(o, data, data.length)) >= 0) { remove(index); return true; } return false; } private static int indexOf(Object o, Object[] d, int len) { if (o == null) { for (int i = len; i-- > 0;) { if (d[i] == null) return i; } } else { for (int i = len; i-- > 0;) { if (o.equals(d[i])) return i; } } return -1; } public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } public boolean addAll(Collection<? extends E> c) { Object[] cs = c.toArray(); if (cs.length == 0) return false; Object[] elements = data; int len = elements.length; int added = 0; // uniquify and compact elements in cs for (int i = 0; i < cs.length; ++i) { Object e = cs[i]; if (indexOf(e, elements, len) < 0 && indexOf(e, cs, added) < 0) cs[added++] = e; } if (added > 0) { Object[] newElements = copyOf(elements, len + added); System.arraycopy(cs, 0, newElements, len, added); data = newElements; return true; } return false; } public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } @SuppressWarnings("unchecked") public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy; if ((Object) newType == Object[].class) { copy = (T[]) new Object[newLength]; } else { copy = (T[]) Array.newInstance(newType.getComponentType(), newLength); } System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } }