package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.function.*;

import java.io.Serializable;
import java.util.*;

/**
 * This class represents an object that may or may not be present. It is intended as a more explicit, less error prone
 *  replacement for using null to represent the absence of a value.

 *
 * There are two flavors of AOption: AOption.some(...) and AOption.none().
 *  AOption.some(x) represents the presence of a value, namely 'x', while AOption.none()
 *  represents 'no value'.

 *
 * There are two key reasons for using this class instead of using null to encode the absence of a value.

 *
 * Firstly, AOption explicitly shows in the code that a value is optional, forcing code to explicitly deal
 *  with that fact. A method returning a value that may or may not be present (e.g. a call to get(...) on
 *  a map) can specify AOption as its return type to make that characteristic explicit.

 *
 * Secondly, code can operate on an AOption regardless of whether there is a value or not (e.g. by calling
 *  map()). This functional style of programming can make for code that is easier to read and less cluttered
 *  than having if statements checking for the presence of values all over the place.

 *
 * This class was inspired by the Option class from the Scala standard library. Thanks for the excellent
 *  code, Scala team!
 *
 * @author arno
 */
public abstract class AOption<T> implements ACollection<T>, Serializable {
    /**
     * @return a 'some' filled with the literal passed in as a parameter
     */
    public static <T> AOption<T> some(T el) {
        return new ASome<>(el);
    }

    /**
     * @return the 'none'
     */
    @SuppressWarnings("unchecked")
    public static <T> AOption<T> none() {
        return  (AOption<T>) ANone.INSTANCE;
    }

    /**
     * This is a convenience method that creates an AOption based on Java conventions.
     *
     * @return 'none()' if the parameter is null, 'some(nullable)' otherwise
     */
    public static <T> AOption<T> fromNullable(T nullable) {
        return nullable != null ? some(nullable) : AOption.<T>none();
    }

    /**
     * @return true if this AOption holds a value
     */
    public abstract boolean isDefined();

    /**
     * @return true if this option does not hold a value
     */
    public boolean isEmpty() {
        return !isDefined();
    }

    /**
     * @return the element held in this AOption if there is one; otherwise, a NoSuchElementException is thrown
     */
    public abstract T get();

    /**
     * @return the element held in this AOption if there is one, and the default element passed in as  parameter otherwise
     */
    public abstract T getOrElse (T defaultValue);

    /**
     * @return the element held in this AOption if there is one, and the result of evaluating the function passed in otherwise. The function is
     *  guaranteed to be evaluated only if this AOption is empty.
     */
    public abstract <E extends Throwable> T getOrElseEval (AFunction0<T,E> producer) throws E;

    public abstract <E extends Throwable> AOption<T> filter    (APredicate<? super T, E> pred) throws E;
    public abstract <E extends Throwable> AOption<T> filterNot (APredicate<? super T, E> pred) throws E;

    /**
     * This method returns the element contained in this AOption if it is defined. Otherwise, it evaluates the function passed in and throws the exception
     *  returned by that function. The function is guaranteed to be evaluated only if this AOption is empty.
     */
    public abstract <E extends Throwable> T getOrElseThrow (AFunction0NoThrow<E> producer) throws E;

    @Override public abstract <X, E extends Throwable> AOption<X> map(AFunction1<? super T, ? extends X, E> f) throws E;

    @Override public <X, E extends Throwable> ACollection<X> flatMap(AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
        throw new UnsupportedOperationException("AOption can not be flattened");
    }

    @Override public <X> ACollection<X> flatten() {
        throw new UnsupportedOperationException("AOption can not be flattened");
    }

    @Override public String mkString() {
        return ACollectionHelper.mkString (this);
    }

    @Override public String mkString(String separator) {
        return ACollectionHelper.mkString(this, separator);
    }

    @Override public String mkString(String prefix, String separator, String suffix) {
        return ACollectionHelper.mkString(this, prefix, separator, suffix);
    }

    public abstract boolean equals(Object o);
    public abstract int hashCode();

    @Override public Collection<T> asJavaUtilCollection () {
        return new AbstractCollection<T> () {
            @SuppressWarnings ("NullableProblems")
            @Override public Iterator<T> iterator () {
                return AOption.this.iterator ();
            }
            @Override public int size () {
                return iterator ().hasNext () ? 1 : 0;
            }
        };
    }

    static class ASome<T> extends AOption<T> {
        private final T el;

        ASome(T el) {
            this.el = el;
        }

        @Override public T get() {
            return el;
        }

        @Override public T getOrElse (T defaultValue) {
            return el;
        }

        @Override public <E extends Throwable> T getOrElseEval (AFunction0<T, E> producer) throws E {
            return el;
        }

        @Override public <E extends Throwable> T getOrElseThrow (AFunction0NoThrow<E> producer) throws E {
            return el;
        }

        @Override public boolean isDefined() {
            return true;
        }

        @Override public ACollection<T> clear () {
            return none ();
        }

        @Override public <E extends Throwable> void foreach(AStatement1<? super T, E> f) throws E {
            f.apply(el);
        }

        @Override public <E extends Throwable> AOption<T> find(APredicate<? super T, E> pred) throws E {
            return filter(pred);
        }

        @Override public <X,E extends Throwable> AOption<X> map(AFunction1<? super T, ? extends X, E> f) throws E {
            return some (f.apply (el));
        }

        @Override public <X, E extends Throwable> ACollection<X> collect (APartialFunction<? super T, ? extends X, E> pf) throws E {
            if (pf.isDefinedAt (el)) {
                return some (pf.apply (el));
            } else {
                return none();
            }
        }

        @Override public <E extends Throwable> AOption<T> filter(APredicate<? super T, E> pred) throws E {
            if(pred.apply(el))
                return this;
            else
                return none();
        }

        @Override public <E extends Throwable> AOption<T> filterNot (APredicate<? super T, E> pred) throws E {
            if (pred.apply (el))
                return none();
            else
                return this;
        }

        @Override public <R, E extends Throwable> R foldLeft (R startValue, AFunction2<R, ? super T, R, E> f) throws E {
            return f.apply(startValue, el);
        }

        @Override public int size() {
            return 1;
        }

        @Override public boolean nonEmpty() {
            return true;
        }

        @Override public <E extends Throwable> boolean forAll(APredicate<? super T, E> pred) throws E {
            return find(pred).isDefined();
        }

        @Override public <E extends Throwable> boolean exists(APredicate<? super T, E> pred) throws E {
            return find(pred).isDefined();
        }

        @Override public <X, E extends Throwable> AMap<X, AOption<T>> groupBy(AFunction1<? super T, ? extends X, E> f) throws E {
            return groupBy(f, AEquality.EQUALS);
        }

        @Override public <X, E extends Throwable> AMap<X, AOption<T>> groupBy(AFunction1<? super T, ? extends X, E> f, AEquality keyEquality) throws E { //TODO junit
            final AMap<X, AOption<T>> result = AHashMap.empty(keyEquality);
            return result.updated(f.apply(el), this);
        }

        @Override public AList<T> toList() {
            return AList.<T>nil().cons(el);
        }

        @Override public ASet<T> toSet() {
            return AHashSet.<T>empty ().with(el);
        }

        @Override public ASet<T> toSet(AEquality equality) {
            return AHashSet.<T>empty (equality).with(el);
        }

        @SuppressWarnings("NullableProblems")
        @Override public Iterator<T> iterator() {
            return Collections.singletonList(el).iterator();
        }

        @Override public String toString() {
            return "AOption.some(" + el + ")";
        }

        @SuppressWarnings("RedundantIfStatement")
        @Override public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ASome aSome = (ASome) o;

            if (el != null ? !el.equals(aSome.el) : aSome.el != null) return false;

            return true;
        }

        @Override public int hashCode() {
            return el != null ? el.hashCode() : 0;
        }

        @Override public boolean contains(Object o) {
            return AEquality.EQUALS.equals(el, o);
        }

//        @SuppressWarnings("NullableProblems")
//        @Override public Object[] toArray() {
//            return new Object[] {el};
//        }
//
//        @SuppressWarnings({"NullableProblems", "SuspiciousToArrayCall"})
//        @Override public <T1> T1[] toArray(T1[] a) {
//            return Arrays.asList(el).toArray(a);
//        }
//
//        @Override public boolean add(T t) {
//            throw new UnsupportedOperationException();
//        }
//
//        @Override public boolean remove(Object o) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings({"NullableProblems", "SimplifiableIfStatement"})
//        @Override public boolean containsAll(Collection<?> c) {
//            if(c.isEmpty()) {
//                return true;
//            }
//            return c.size() == 1 && c.contains(el);
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean addAll(Collection<? extends T> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean removeAll(Collection<?> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean retainAll(Collection<?> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @Override public void clear() {
//            throw new UnsupportedOperationException();
//        }
    }

    static class ANone extends AOption<Object> {
        public static final ANone INSTANCE = new ANone();

        private ANone() {}

        private Object readResolve() {
            return INSTANCE;
        }

        @Override public ACollection<Object> clear () {
            return this;
        }

        @Override public Object get() {
            throw new NoSuchElementException("no value for ANone");
        }

        @Override public Object getOrElse (Object defaultValue) {
            return defaultValue;

        }
        @Override public <E extends Throwable> Object getOrElseEval (AFunction0<Object, E> producer) throws E {
            return producer.apply ();
        }

        @Override public <E extends Throwable> Object getOrElseThrow (AFunction0NoThrow<E> producer) throws E {
            throw producer.apply ();
        }

        @Override public boolean isDefined() {
            return false;
        }

        @Override public <E extends Throwable> void foreach(AStatement1<? super Object, E> f) {
            // nothing to be done
        }

        @Override public <E extends Throwable> AOption<Object> find(APredicate<? super Object, E> pred) throws E {
            return none();
        }

        @Override public <X,E extends Throwable> AOption<X> map(AFunction1<? super Object, ? extends X, E> f) {
            return none();
        }

        @Override public <X, E extends Throwable> ACollection<X> collect (APartialFunction<? super Object, ? extends X, E> pf) throws E {
            return none();
        }

        @Override public <R, E extends Throwable> R foldLeft (R startValue, AFunction2<R, ? super Object, R, E> f) throws E {
            return startValue;
        }

        @Override public <E extends Throwable> AOption<Object> filter(APredicate<? super Object, E> pred) {
            return none();
        }

        @Override public <E extends Throwable> AOption<Object> filterNot (APredicate<? super Object, E> pred) throws E {
            return none();
        }

        @Override public boolean nonEmpty() {
            return false;
        }

        @Override public int size() {
            return 0;
        }

        @Override public <E extends Throwable> boolean forAll(APredicate<? super Object, E> pred) throws E {
            return true;
        }

        @Override public <E extends Throwable> boolean exists(APredicate<? super Object, E> pred) throws E {
            return false;
        }

        @Override public <X, E extends Throwable> AMap<X, AOption<Object>> groupBy(AFunction1<? super Object, ? extends X, E> f) throws E {
            return AHashMap.empty(); //TODO junit
        }

        @Override public <X, E extends Throwable> AMap<X, AOption<Object>> groupBy(AFunction1<? super Object, ? extends X, E> f, AEquality keyEquality) throws E {
            return AHashMap.empty(keyEquality); //TODO junit
        }

        @Override public AList<Object> toList() {
            return AList.nil();
        }

        @Override public ASet<Object> toSet() {
            return AHashSet.empty ();
        }

        @Override public ASet<Object> toSet(AEquality equality) {
            return AHashSet.empty (equality);
        }

        @SuppressWarnings("NullableProblems")
        @Override public Iterator<Object> iterator() {
            return Collections.emptyList().iterator();
        }

        @Override public String toString() {
            return "AOption.none()";
        }

        @Override public boolean equals(Object o) {
            return o == this;
        }

        @Override public int hashCode() {
            return 0;
        }

        @Override public boolean contains(Object o) {
            return false;
        }

//        @SuppressWarnings("NullableProblems")
//        @Override public Object[] toArray() {
//            return new Object[0];
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public <T> T[] toArray(T[] a) {
//            if(a.length > 0) {
//                a[0] = null;
//            }
//            return a;
//        }
//
//        @Override
//        public boolean add(Object o) {
//            throw new UnsupportedOperationException();
//        }
//
//        @Override public boolean remove(Object o) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean containsAll(Collection<?> c) {
//            return c.isEmpty();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean addAll(Collection<?> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean removeAll(Collection<?> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @SuppressWarnings("NullableProblems")
//        @Override public boolean retainAll(Collection<?> c) {
//            throw new UnsupportedOperationException();
//        }
//
//        @Override
//        public void clear() {
//            throw new UnsupportedOperationException();
//        }
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.function.AFunction1;

import java.io.Serializable;
import java.util.*;

/**
 * This is an {@link com.ajjpj.afoundation.collection.immutable.AHashMap} that is specialized for keys of type long, i.e. primitive numbers rather than objects. It duplicates its API
 *  to support both efficient primitive 'long' values and generified 'Long's,
 *
 * @author arno
 */
public class ALongHashMap<V> extends AbstractAMap<Long,V> {
    private static final int LEVEL_INCREMENT = 10;

    transient private Integer cachedHashcode = null; // intentionally not volatile: This class is immutable, so recalculating per thread works

    private static final ALongHashMap EMPTY = new ALongHashMap ();

    /**
     * Returns an empty ALongHashMap instance. Calling this factory method instead of
     *  the constructor allows internal reuse of empty map instances since they are immutable.
     */
    @SuppressWarnings("unchecked")
    public static <V> ALongHashMap<V> empty() {
        return EMPTY;
    }

    public static <V> ALongHashMap<V> fromJavaUtilMap(Map<? extends Number,V> map) {
        ALongHashMap<V> result = empty ();

        for(Map.Entry<? extends Number,V> entry: map.entrySet()) {
            result = result.updated(entry.getKey().longValue (), entry.getValue());
        }

        return result;
    }

    /**
     * Returns an ALongHashMap initialized from separate 'keys' and 'values' collections. Both collections
     *  are iterated exactly once and are expected to have the same size.
     */
    public static <V> ALongHashMap<V> fromKeysAndValues(Iterable<? extends Number> keys, Iterable<V> values) {
        final Iterator<? extends Number> ki = keys.iterator();
        final Iterator<V> vi = values.iterator();

        ALongHashMap<V> result = ALongHashMap.empty ();

        while(ki.hasNext()) {
            final Number key = ki.next();
            final V value = vi.next();

            result = result.updated(key.longValue (), value);
        }
        return result;
    }

    /**
     * Returns an ALongHashMap instance initialized from a collection of
     *  keys and a function. For each element of the keys collection, the function is called once to
     *  determine the corresponding value, and the pair is then stored in the map.
     */
    @SuppressWarnings("unused")
    public static <K extends Number, V, E extends Throwable> ALongHashMap<V> fromKeysAndFunction(Iterable<K> keys, AFunction1<? super K, ? extends V, E> f) throws E {
        final Iterator<K> ki = keys.iterator();

        ALongHashMap<V> result = empty ();

        while(ki.hasNext()) {
            final K key = ki.next();
            final V value = f.apply(key);

            result = result.updated(key.longValue (), value);
        }
        return result;
    }

    private ALongHashMap () {
    }

    @Override public AEquality keyEquality () {
        return AEquality.NATURAL_ORDER;
    }

    @Override public AMap<Long, V> clear () {
        return null;
    }

    @Override public int size() {
        return 0;
    }

    public boolean containsKey(long key) {
        return get(key).isDefined();
    }

    @Override public AOption<V> get (Long key) {
        return get (key.longValue ());
    }
    public AOption<V> get(long key) {
        return doGet(key, computeHash(key), 0);
    }

    @Override public V getRequired (Long key) {
        return getRequired (key.longValue());
    }
    public V getRequired (long key) {
        return get(key).get();
    }

    @Override public ALongHashMap<V> updated (Long key, V value) {
        return updated (key.longValue (), value);
    }
    public ALongHashMap<V> updated (long key, V value) {
        return doUpdated(key, computeHash(key), 0, value);
    }

    @Override public ALongHashMap<V> removed (Long key) {
        return removed (key.longValue ());
    }
    public ALongHashMap<V> removed (long key) {
        return doRemoved(key, computeHash(key), 0);
    }

    @Override
    public Iterator<AMapEntry<Long, V>> iterator() {
        return new Iterator<AMapEntry<Long, V>> () {
            final ALongMapIterator<V> inner = longIterator ();

            @Override public boolean hasNext () {
                return inner.hasNext ();
            }

            @Override public AMapEntry<Long, V> next () {
                inner.next ();
                return inner;
            }

            @Override public void remove () {
                throw new UnsupportedOperationException ();
            }
        };
    }

    public ALongMapIterator<V> longIterator() { //TODO test this - and all map iterator() implementations
        return new LongIteratorImpl<> (this);
    }

    @Override public ASet<Long> keys() {
        return new ALongHashSet (this);
    }

    /**
     * @param level number of least significant bits of the hash to discard for local hash lookup. This mechanism
     *              is used to create a 64-way hash trie - level increases by 10 at each level
     */
    AOption<V> doGet(long key, long hash, int level) {
        return AOption.none();
    }

    ALongHashMap<V> doUpdated(long key, long hash, int level, V value) {
        return new LongHashMap1<> (key, hash, value);
    }

    ALongHashMap<V> doRemoved(long key, long hash, int level) {
        return this;
    }

    private static long computeHash (long key) { //TODO better spread function?
        long h = key;
        h = h + ~(h << 18);
        h = h ^ (h >>> 28);
        h = h + (h << 8);
        return h ^ (h >>> 20);
    }

    @SuppressWarnings("unchecked")
    private static <V> ALongHashMap<V>[] createArray(int size) {
        return new ALongHashMap[size];
    }

    @Override
    public String toString() {
        final StringBuilder result = new StringBuilder("{");
        boolean first = true;

        for(AMapEntry<Long, V> e: this) {
            if(first) {
                first = false;
            }
            else {
                result.append(", ");
            }

            result.append(e.getKey ()).append("->").append(e.getValue());
        }

        result.append("}");
        return result.toString();
    }

    /**
     * very internal method. It assumes hash0 != hash1.
     */
    private static<V> LongHashTrieMap<V> mergeLeafMaps (long hash0, ALongHashMap<V> elem0, long hash1, ALongHashMap<V> elem1, int level, int size) {
        final int index0 = (int) ((hash0 >>> level) & 0x3f);
        final int index1 = (int) ((hash1 >>> level) & 0x3f);
        if(index0 != index1) {
            final long bitmap = (1L << index0) | (1L << index1);
            final ALongHashMap<V>[] elems = createArray(2);
            if(index0 < index1) {
                elems[0] = elem0;
                elems[1] = elem1;
            }
            else {
                elems[0] = elem1;
                elems[1] = elem0;
            }
            return new LongHashTrieMap<>(bitmap, elems, size);
        }
        else {
            final ALongHashMap<V>[] elems = createArray(1);
            final long bitmap = (1L << index0);
            // try again, based on the
            elems[0] = mergeLeafMaps(hash0, elem0, hash1, elem1, level + LEVEL_INCREMENT, size);
            return new LongHashTrieMap<>(bitmap, elems, size);
        }
    }

    static class LongIteratorImpl<V> implements ALongMapIterator<V> { //TODO test this - and all 'iterator()' methods for all maps
        private final Deque<ALongHashMap<V>> stack = new ArrayDeque<> ();
        private LongHashMap1<V> current;

        LongIteratorImpl (ALongHashMap<V> root) {
            if (root.nonEmpty ()) {
                stack.push (root);
            }
        }

        @Override public boolean hasNext () {
            return !stack.isEmpty ();
        }

        @Override public void next () {
            ALongHashMap<V> next;

            try {
                while (true) {
                    next = stack.pop ();
                    if (next.getClass () == LongHashMap1.class) {
                        current = (LongHashMap1<V>) next;
                        break;
                    }

                    for (ALongHashMap<V> child: ((LongHashTrieMap<V>) next).elems) {
                        stack.push (child);
                    }
                }
            }
            catch (Exception e) { // avoid the 'empty' check to put the penalty on the exceptional rather than the regular case
                current = null;
                throw new NoSuchElementException ();
            }
        }

        @Override public long getLongKey () {
            try {
                return current.key;
            }
            catch (Exception e) { // avoid the 'null' check to put the penalty on the exceptional rather than the regular case
                throw new NoSuchElementException ();
            }
        }

        @Override public Long getKey () {
            return getLongKey ();
        }

        @Override public V getValue () {
            try {
                return current.value;
            }
            catch (Exception e) { // avoid the 'null' check to put the penalty on the exceptional rather than the regular case
                throw new NoSuchElementException ();
            }
        }
    }

    static class LongHashMap1<V> extends ALongHashMap<V> {
        private final long key;
        private final long hash;
        private final V value;

        LongHashMap1(long key, long hash, V value) {
            super();

            this.key = key;
            this.hash = hash;
            this.value = value;
        }

        @Override public int size() {
            return 1;
        }

        @Override AOption<V> doGet(long key, long hash, int level) {
            if(this.key == key) {
                return AOption.some (value);
            }
            return AOption.none();
        }

        @Override ALongHashMap<V> doUpdated(long key, long hash, int level, V value) {
            if (key == this.key) {
                if(this.value == value) {
                    return this;
                }
                else {
                    return new LongHashMap1<>(key, hash, value);
                }
            }
            else {
                // find a level where they don't collide
                final ALongHashMap<V> that = new LongHashMap1<>(key, hash, value);
                return mergeLeafMaps(this.hash, this, hash, that, level, 2);
            }
        }

        @Override ALongHashMap<V> doRemoved(long key, long hash, int level) {
            if (key == this.key) {
                return empty();
            }
            else {
                return this;
            }
        }
    }

    static class LongHashTrieMap<V> extends ALongHashMap<V> {
        final long bitmap;
        final ALongHashMap<V>[] elems;
        final int size;

        LongHashTrieMap (long bitmap, ALongHashMap<V>[] elems, int size) {
            this.bitmap = bitmap;
            this.elems = elems;
            this.size = size;
        }

        @Override public int size() {
            return size;
        }

        @Override
        AOption<V> doGet(long key, long hash, int level) {
            final int index = (int) ((hash >>> level) & 0x3f);

            if (bitmap == - 1) {
                return elems[index & 0x3f].doGet(key, hash, level + LEVEL_INCREMENT);
            }

            final long mask = 1L << index;
            if ((bitmap & mask) != 0) {
                final int offset = Long.bitCount (bitmap & (mask - 1));
                return elems[offset].doGet(key, hash, level + LEVEL_INCREMENT);
            }

            return AOption.none();
        }

        @Override ALongHashMap<V> doUpdated(long key, long hash, int level, V value) {
            final int index = (int) ((hash >>> level) & 0x3f);
            final long mask = (1L << index);
            final int offset = Long.bitCount(bitmap & (mask - 1));

            if ((bitmap & mask) != 0) {
                final ALongHashMap<V> sub = elems[offset];

                final ALongHashMap<V> subNew = sub.doUpdated(key, hash, level + LEVEL_INCREMENT, value);
                if(subNew == sub) {
                    return this;
                }
                else {
                    final ALongHashMap<V>[] elemsNew = createArray(elems.length);
                    System.arraycopy(elems, 0, elemsNew, 0, elems.length);
                    elemsNew[offset] = subNew;
                    return new LongHashTrieMap<> (bitmap, elemsNew, size + (subNew.size() - sub.size()));
                }
            }
            else {
                final ALongHashMap<V>[] elemsNew = createArray(elems.length + 1);
                System.arraycopy(elems, 0, elemsNew, 0, offset);
                elemsNew[offset] = new LongHashMap1<>(key, hash, value);
                System.arraycopy(elems, offset, elemsNew, offset + 1, elems.length - offset);
                return new LongHashTrieMap<>(bitmap | mask, elemsNew, size + 1);
            }
        }

        @Override ALongHashMap<V> doRemoved (long key, long hash, int level) {
            final int index = (int) ((hash >>> level) & 0x3f);
            final long mask = 1L << index;
            final int  offset = Long.bitCount (bitmap & (mask - 1));

            if ((bitmap & mask) != 0) {
                final ALongHashMap<V> sub = elems[offset];
                final ALongHashMap<V> subNew = sub.doRemoved(key, hash, level + LEVEL_INCREMENT);

                if (subNew == sub) {
                    return this;
                }
                else if (subNew.isEmpty()) {
                    final long bitmapNew = bitmap ^ mask;
                    if (bitmapNew != 0) {
                        final ALongHashMap<V>[] elemsNew = createArray(elems.length - 1);
                        System.arraycopy(elems, 0, elemsNew, 0, offset);
                        System.arraycopy(elems, offset + 1, elemsNew, offset, elems.length - offset - 1);
                        final int sizeNew = size - sub.size();
                        if (elemsNew.length == 1 && ! (elemsNew[0] instanceof LongHashTrieMap)) {
                            return elemsNew[0];
                        }
                        else {
                            return new LongHashTrieMap<>(bitmapNew, elemsNew, sizeNew);
                        }
                    }
                    else {
                        return ALongHashMap.empty ();
                    }
                }
                else if(elems.length == 1 && ! (subNew instanceof LongHashTrieMap)) {
                    return subNew;
                }
                else {
                    final ALongHashMap<V>[] elemsNew = createArray(elems.length);
                    System.arraycopy(elems, 0, elemsNew, 0, elems.length);
                    elemsNew[offset] = subNew;
                    final int sizeNew = size + (subNew.size() - sub.size());
                    return new LongHashTrieMap<>(bitmap, elemsNew, sizeNew);
                }
            } else {
                return this;
            }
        }
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.collection.tuples.ATuple2;
import com.ajjpj.afoundation.function.AFunction1;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;

/**
 * This AMap implementation stores entries in a linked list, giving all lookup operations O(n) complexity. That makes
 *  AHashMap the better choice most of the time.<p>
 *
 * This class is however useful when keys are known to have the same hash codes (e.g. deep in the innards of AHashMap),
 *  or if control over iteration order is desirable.
 *
 * @author arno
 */
public class AListMap <K,V> extends AbstractAMap<K,V> implements AMapEntry<K,V> {
    private static final AEquality DEFAULT_EQUALITY = AEquality.EQUALS;

    private static final AListMap<Object, Object> emptyEquals = new AListMap<>(AEquality.EQUALS);
    private static final AListMap<Object, Object> emptyIdentity = new AListMap<>(AEquality.IDENTITY);

    /**
     * Returns an empty AListMap instance with default (i.e. equals-based) equalityForEquals. Calling this factory method instead
     *  of the constructor allows internal reuse of empty map instances since they are immutable.
     */
    public static <K,V> AListMap<K,V> empty() {
        return empty(DEFAULT_EQUALITY);
    }
    /**
     * Returns an empty AListMap instance with a given equalityForEquals. Calling this factory method instead
     *  of the constructor allows internal reuse of empty map instances since they are immutable.
     */
    @SuppressWarnings("unchecked")
    public static <K,V> AListMap<K,V> empty(AEquality equality) {
        if(equality == AEquality.EQUALS) return (AListMap<K, V>) emptyEquals;
        if(equality == AEquality.IDENTITY) return (AListMap<K, V>) emptyIdentity;

        return new AListMap<> (equality);
    }

    /**
     * Returns an AListMap instance with default (i.e. equals-based) equalityForEquals, initializing it from separate 'keys'
     *  and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size.
     */
    public static <K,V> AListMap<K,V> fromKeysAndValues(Iterable<ATuple2<K,V>> elements) {
        return fromKeysAndValues(DEFAULT_EQUALITY, elements);
    }
    /**
     * Returns an AHashMap instance with a given equalityForEquals, initializing it from separate 'keys'
     *  and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size.
     */
    public static <K,V> AListMap<K,V> fromKeysAndValues(AEquality equality, Iterable<ATuple2<K,V>> elements) {
        AListMap<K,V> result = empty(equality);

        for(ATuple2<K,V> el: elements) {
            result = result.updated(el._1, el._2);
        }
        return result;
    }

    public static <K,V> AListMap<K,V> fromKeysAndValues(Iterable<K> keys, Iterable<V> values) {
        return fromKeysAndValues(DEFAULT_EQUALITY, keys, values);
    }

    /**
     * Returns an AListMap instance with a given equalityForEquals, initializing it from separate 'keys'
     *  and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size.
     */
    public static <K,V> AListMap<K,V> fromKeysAndValues(AEquality equality, Iterable<K> keys, Iterable<V> values) {
        final Iterator<K> ki = keys.iterator();
        final Iterator<V> vi = values.iterator();

        AListMap<K,V> result = empty (equality);

        while(ki.hasNext()) {
            final K key = ki.next();
            final V value = vi.next();

            result = result.updated(key, value);
        }
        return result;
    }

    final AEquality equality;

    private Integer cachedHashcode = null; // intentionally not volatile - potentially recalculating for different threads is traded for better single threaded performance

    private AListMap(AEquality equality) {
        this.equality = equality;
    }

    @Override public AEquality keyEquality () {
        return equality;
    }

    @Override public AMap<K, V> clear () {
        return empty (equality);
    }

    @Override public int size() {
        return 0;
    }

    @Override public AOption<V> get(K key) {
        return AOption.none();
    }

    @Override public K getKey() {
        throw new NoSuchElementException("empty map");
    }

    @Override public V getValue() {
        throw new NoSuchElementException("empty map");
    }

    @Override public AListMap<K,V> updated(K key, V value) {
        return new Node<>(key, value, this);
    }

    @Override public AListMap<K,V> removed(K key) {
        return this;
    }

    public AListMap<K,V> tail() {
        return null;
    }

    @Override public Iterator<AMapEntry<K,V>> iterator() {
        return new ListMapIterator<>(this);
    }

    @Override public ASet<K> keys() {
        return new AListSet<> (this);
    }

    static class Node<K,V> extends AListMap<K,V> {
        private final K key;
        private final V value;
        private final AListMap<K,V> tail;

        Node(K key, V value, AListMap<K, V> tail) {
            super(tail.equality);

            this.key = key;
            this.value = value;
            this.tail = tail;
        }

        @Override public K getKey() {
            return key;
        }
        @Override public V getValue() {
            return value;
        }
        @Override public AListMap<K,V> tail() {
            return tail;
        }

        @Override public int size() {
            int result = 0;

            AListMap<K,V> m = this;
            while(m.tail() != null) {
                m = m.tail();
                result += 1;
            }
            return result;
        }

        @Override public AOption<V> get(K key) {
            AListMap<K,V> m = this;

            while(m.nonEmpty()) {
                if(equality.equals(m.getKey (), key)) {
                    return AOption.some(m.getValue ());
                }
                m = m.tail();
            }
            return AOption.none();
        }

        @Override public AListMap<K,V> updated(K key, V value) {
            final AListMap<K,V> m = removed(key);
            return new Node<>(key, value, m);
        }

        @Override public AListMap<K,V> removed(K key) {
            int idx = 0;

            AListMap<K,V> remaining = this;
            while(remaining.nonEmpty()) {
                if(equality.equals(remaining.getKey (), key)) {
                    remaining = remaining.tail();
                    break;
                }
                idx += 1;
                remaining = remaining.tail();
            }

            AListMap<K,V> result = remaining;

            AListMap<K,V> iter = this;
            for (int i=0; i<idx; i++) {
                result = new Node<> (iter.getKey (), iter.getValue (), result);
                iter = iter.tail ();
            }

            if (idx >= size ()) {
                return this;
            }

            return result;
        }
    }

    static class ListMapIterator<K,V> implements Iterator<AMapEntry<K,V>> {
        private AListMap<K,V> pos;

        ListMapIterator(AListMap<K, V> pos) {
            this.pos = pos;
        }

        @Override
        public boolean hasNext() {
            return pos.nonEmpty();
        }

        @Override
        public AMapEntry<K, V> next() {
            final AMapEntry<K,V> result = pos;
            pos = pos.tail();
            return result;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.AEquality;

import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * @author arno
 */
public class ARedBlackTreeMap<K,V> extends AbstractAMap<K,V> implements ASortedMap<K,V> {
    final Tree<K,V> root;
    private final Comparator<K> comparator;

    public static <K,V> ARedBlackTreeMap<K,V> empty (Comparator<K> comparator) {
        return new ARedBlackTreeMap<> (null, comparator);
    }

    private ARedBlackTreeMap (Tree<K, V> root, Comparator<K> comparator) {
        this.root = root;
        this.comparator = comparator;
    }

    @Override public AEquality keyEquality () {
        return new AEquality.ComparatorBased (comparator);
    }
    @Override public AMap<K, V> clear () {
        return empty (comparator);
    }

    @Override public int size () {
        return root == null ? 0 : root.count;
    }

    @Override public boolean containsKey (K key) {
        return lookup (root, key, comparator) != null;
    }

    @Override public AOption<V> get (K key) {
        final Tree<K,V> raw = lookup (root, key, comparator);
        if (raw == null) {
            return AOption.none ();
        }
        return AOption.some (raw.value);
    }

    @Override public ASet<K> keys () {
        return ARedBlackTreeSet.create (this);
    }

    @Override public ARedBlackTreeMap<K, V> updated (K key, V value) {
        return new ARedBlackTreeMap<> (blacken (upd (root, key, value, comparator)), comparator);
    }

    @Override public ARedBlackTreeMap<K, V> removed (K key) {
        return new ARedBlackTreeMap<> (blacken (del (root, key, comparator)), comparator);
    }

    @Override public Iterator<AMapEntry<K, V>> iterator () {
        return new TreeIterator<AMapEntry<K, V>> () {
            @Override AMapEntry<K, V> nextResult (Tree<K, V> tree) {
                return tree;
            }
        };
    }

    @Override public AOption<AMapEntry<K, V>> first () {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        while (cur.left != null) cur = cur.left;

        return AOption.<AMapEntry<K,V>>some (cur);
    }

    @Override public AOption<AMapEntry<K, V>> last () {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        while (cur.right != null) cur = cur.right;

        return AOption.<AMapEntry<K,V>>some (cur);
    }

    @Override public AOption<AMapEntry<K, V>> firstGreaterThan (K key) {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        Tree<K,V> candidate = null;
        while (true) {
            final int cmp = comparator.compare (cur.key, key);
            if (cmp <= 0) {
                // this node is smaller than the key --> go right
                if (cur.right == null) return AOption.<AMapEntry<K,V>>fromNullable (candidate);
                cur = cur.right;
            }
            else {
                // this node is greater than the key --> go left
                if (cur.left == null) return AOption.<AMapEntry<K,V>>some (cur);
                candidate = cur;
                cur = cur.left;
            }
        }
    }

    @Override public AOption<AMapEntry<K, V>> firstGreaterOrEquals (K key) {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        Tree<K,V> candidate = null;
        while (true) {
            final int cmp = comparator.compare (cur.key, key);
            if (cmp == 0) return AOption.<AMapEntry<K,V>>some (cur);
            if (cmp < 0) {
                // this node is smaller than the key --> go right
                if (cur.right == null) return AOption.<AMapEntry<K,V>>fromNullable (candidate);
                cur = cur.right;
            }
            else {
                // this node is greater than the key --> go left
                if (cur.left == null) return AOption.<AMapEntry<K,V>>some (cur);
                candidate = cur;
                cur = cur.left;
            }
        }
    }

    @Override public AOption<AMapEntry<K, V>> lastSmallerThan (K key) {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        Tree<K,V> candidate = null;
        while (true) {
            final int cmp = comparator.compare (cur.key, key);
            if (cmp >= 0) {
                // this node is greater than the key --> go left
                if (cur.left == null) return AOption.<AMapEntry<K,V>>fromNullable (candidate);
                cur = cur.left;
            }
            else {
                // this node is smaller than the key --> go right
                if (cur.right == null) return AOption.<AMapEntry<K,V>>some (cur);
                candidate = cur;
                cur = cur.right;
            }
        }
    }

    @Override public AOption<AMapEntry<K, V>> lastSmallerOrEquals (K key) {
        if (root == null) return AOption.none ();

        Tree<K,V> cur = root;
        Tree<K,V> candidate = null;
        while (true) {
            final int cmp = comparator.compare (cur.key, key);
            if (cmp == 0) return AOption.<AMapEntry<K,V>>some (cur);
            if (cmp > 0) {
                // this node is greater than the key --> go left
                if (cur.left == null) return AOption.<AMapEntry<K,V>>fromNullable (candidate);
                cur = cur.left;
            }
            else {
                // this node is smaller than the key --> go right
                if (cur.right == null) return AOption.<AMapEntry<K,V>>some (cur);
                candidate = cur;
                cur = cur.right;
            }
        }
    }

    private Iterator<AMapEntry<K,V>> iterator (final K from, final K to, boolean fromInclusive, boolean toInclusive) {
        if (root == null) {
            return new TreeIterator<AMapEntry<K, V>> (null, null) {
                @Override AMapEntry<K, V> nextResult (Tree<K, V> tree) {
                    return null;
                }
            };
        }

        // this stack contains all nodes for which the left side was visited, while they themselves were not, and neither was their right side
        final ArrayDeque<Tree<K,V>> pathStack = new ArrayDeque<> ();

        Tree<K, V> cur = root;
        while (true) {
            final int cmp = (from == null) ? 1 : comparator.compare (cur.key, from);
            if (cmp == 0 && fromInclusive) {
                pathStack.push (cur);
                break;
            }
            if (cmp <= 0) { // cmp == 0 can only happen if !fromInclusive
                // this node is smaller than the key --> go right
                if (cur.right == null) {
                    break;
                }
                // this node is not in range --> do not push
                cur = cur.right;
            }
            else {
                pathStack.push (cur);

                // this node is greater than the key --> go left
                if (cur.left == null) {
                    break;
                }

                cur = cur.left;
            }
        }

        if (to == null) {
            return new TreeIterator<AMapEntry<K, V>> (pathStack, pathStack.isEmpty () ? null : pathStack.pop ()) {
                @Override AMapEntry<K, V> nextResult (Tree<K, V> tree) {
                    return tree;
                }
            };
        }

        Tree<K,V> first = null;
        if (! pathStack.isEmpty ()) {
            first = pathStack.pop ();

            final int cmp = comparator.compare (first.key, to);
            if (cmp > 0 || (cmp == 0 && !toInclusive)) {
                first = null;
            }
        }

        if (toInclusive) {
            return new TreeIterator<AMapEntry<K, V>> (pathStack, first) {
                @Override AMapEntry<K, V> nextResult (Tree<K, V> tree) {
                    return tree;
                }
                @Override protected boolean isAfterIntendedEnd (Tree<K, V> tree) {
                    return comparator.compare (tree.key, to) > 0;
                }
            };
        }
        else {
            return new TreeIterator<AMapEntry<K, V>> (pathStack, first) {
                @Override AMapEntry<K, V> nextResult (Tree<K, V> tree) {
                    return tree;
                }
                @Override protected boolean isAfterIntendedEnd (Tree<K, V> tree) {
                    return comparator.compare (tree.key, to) >= 0;
                }
            };
        }
    }

    @Override public Iterable<AMapEntry<K, V>> rangeII (final K fromKey, final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, toKey, true, true);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> rangeIE (final K fromKey, final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, toKey, true, false);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> rangeEI (final K fromKey, final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, toKey, false, true);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> rangeEE (final K fromKey, final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, toKey, false, false);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> fromI (final K fromKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, null, true, false);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> fromE (final K fromKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (fromKey, null, false, false);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> toI (final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (null, toKey, false, true);
            }
        };
    }

    @Override public Iterable<AMapEntry<K, V>> toE (final K toKey) {
        return new Iterable<AMapEntry<K, V>> () {
            @Override public Iterator<AMapEntry<K, V>> iterator () {
                return ARedBlackTreeMap.this.iterator (null, toKey, false, false);
            }
        };
    }

    public void dump() {
        dump (root, 0);
    }

    static void indent (int level) {
        System.out.print ("                                                                                ".substring (0, 4*level));
    }

    static void dump (Tree tree, int indent) {
        if (tree == null) {
            indent (indent);
            System.out.println ("<>");
        }
        else {
            dump (tree.left, indent+1);

            indent (indent);
            System.out.print (tree instanceof BlackTree ? "+ " : "* ");
            System.out.println (tree.key);

            dump (tree.right, indent+1);
        }
    }

    static void validate(Tree tree) {
        if (tree == null) {
            return;
        }

        validate (tree.left);
        validate (tree.right);

        // rule 4: every redden node has two blacken children
        if (isRedTree (tree)) {
            if (tree.left != null && isRedTree (tree.left)) {
                throw new IllegalStateException ("tree " + tree.key + " is redden and has a left child that is redden");
            }
            if (tree.right != null && isRedTree (tree.right)) {
                throw new IllegalStateException ("tree " + tree.key + " is redden and has a right child that is redden");
            }
        }

        checkBlackDepth (tree);
    }

    private static int checkBlackDepth (Tree tree) {
        if (tree == null) {
            return 1;
        }

        final int own = isBlackTree (tree) ? 1 : 0;
        final int left  = checkBlackDepth (tree.left);
        final int right = checkBlackDepth (tree.right);

        // rule 5: every path to 'leaf' nodes must have the same number of blacken nodes
        if (left != right) {
            throw new IllegalStateException ("left and right side have paths to leaf nodes with different numbers of blacken nodes: " + tree.key);
        }
        return own + left;
    }

    private abstract class TreeIterator<R> implements Iterator<R> {
        @SuppressWarnings ("unchecked")
        private final ArrayDeque<Tree<K,V>> pathStack;
        private Tree<K,V> next;

        abstract R nextResult (Tree<K,V> tree); //TODO rename this

        @SuppressWarnings ("unchecked")
        private TreeIterator() {
            // initialize 'next' with the leftmost element
            if (root == null) {
                pathStack = null;
                next = null;
            }
            else {
                pathStack = new ArrayDeque<> ();
                next = root;
                while (next.left != null) {
                    pathStack.push (next);
                    next = next.left;
                }
            }
        }

        private TreeIterator (ArrayDeque<Tree<K,V>> pathStack, Tree<K,V> first) {
            this.pathStack = pathStack;
            this.next = first;
        }

        @Override public boolean hasNext() {
            return next != null;
        }

        @Override public R next() {
            if (next == null) {
                throw new NoSuchElementException ();
            }

            final Tree<K,V> cur = next;
            next = filteredFindNext (next.right);
            return nextResult (cur);
        }

        @Override public void remove () {
            throw new UnsupportedOperationException ();
        }

        private Tree<K,V> filteredFindNext (Tree<K,V> tree) {
            final Tree<K,V> result = findNext (tree);
            if (result == null || isAfterIntendedEnd (result)) return null;
            return result;
        }

        private Tree<K,V> findNext (Tree<K,V> tree) {
            while (true) {
                if (tree == null) {
                    return popPath ();
                }
                if (tree.left == null) {
                    return tree;
                }
                pathStack.push (tree);
                tree = tree.left;
            }
        }

        /**
         * override if an iterator should finish before the entire map is traversed
         */
        protected boolean isAfterIntendedEnd (Tree<K,V> tree) {
            return false;
        }

        private Tree<K,V> popPath() {
            if (pathStack.isEmpty ()) {
                // convenience for handling the end of iteration
                return null;
            }
            return pathStack.pop ();
        }
    }

    static <K,V> Tree<K,V> lookup(Tree<K,V> tree, K key, Comparator<K> comparator) {
        while (tree != null) {
            final int cmp = comparator.compare (key, tree.key);
            if (cmp == 0) return tree;

            tree = (cmp < 0) ? tree.left : tree.right;
        }
        return null;
    }

    static boolean isRedTree (Tree tree) {
        return tree != null && tree.isRed ();
    }
    static boolean isBlackTree (Tree tree) {
        return tree != null && tree.isBlack ();
    }

    static <K,V> Tree<K,V> blacken (Tree<K,V> tree) {
        if (tree == null) {
            return null;
        }
        return tree.blacken ();
    }

    static <K,V> Tree<K,V> balanceLeft (TreeFactory<K,V> treeFactory, K key, V value, Tree<K,V> l, Tree<K,V> d) {
        if (isRedTree (l) && isRedTree (l.left)) {
            return new RedTree<> (l.key, l.value,
                    new BlackTree<> (l.left.key, l.left.value, l.left.left, l.left.right),
                    new BlackTree<> (key, value, l.right, d));
        }
        if (isRedTree (l) && isRedTree (l.right)) {
            return new RedTree<> (l.right.key, l.right.value,
                    new BlackTree<> (l.key, l.value, l.left, l.right.left),
                    new BlackTree<> (key, value, l.right.right, d));
        }
        return treeFactory.create (l, d);
    }

    static <K,V> Tree<K,V> balanceRight (TreeFactory<K,V> treeFactory, K key, V value, Tree<K,V> a, Tree<K,V> r) {
        if (isRedTree (r) && isRedTree (r.left)) {
            return new RedTree<> (r.left.key, r.left.value,
                    new BlackTree<> (key, value, a, r.left.left),
                    new BlackTree<> (r.key, r.value, r.left.right, r.right));
        }
        if (isRedTree (r) && isRedTree (r.right)) {
            return new RedTree<> (r.key, r.value,
                    new BlackTree<> (key, value, a, r.left),
                    new BlackTree<> (r.right.key, r.right.value, r.right.left, r.right.right));
        }
        return treeFactory.create (a, r);
    }

    static <K,V> Tree<K,V> upd (Tree<K,V> tree, K key, V value, Comparator<K> comparator) {
        if (tree == null) {
            return new RedTree<> (key, value, null, null);
        }
        final int cmp = comparator.compare (key, tree.key);
        if (cmp < 0) {
            return balanceLeft (tree, tree.key, tree.value, upd (tree.left, key, value, comparator), tree.right);
        }
        if (cmp > 0) {
            return balanceRight (tree, tree.key, tree.value, tree.left, upd (tree.right, key, value, comparator));
        }
        return tree.withNewValue (key, value);
    }

    static <K,V> Tree<K,V> del (Tree<K,V> tree, K key, Comparator<K> comparator) {
        if (tree == null) {
            return null;
        }

        final int cmp = comparator.compare(key, tree.key);
        if (cmp < 0) {
            // the node that must be deleted is to the left
            return isBlackTree (tree.left) ?
                    balanceLeft (tree.key, tree.value, del (tree.left, key, comparator), tree.right) :

                // tree.left is 'redden', so its children are guaranteed to be blacken.
                new RedTree<> (tree.key, tree.value, del (tree.left, key, comparator), tree.right);
        }
        else if (cmp > 0) {
            // the node that must be deleted is to the right
            return isBlackTree (tree.right) ?
                    balanceRight (tree.key, tree.value, tree.left, del (tree.right, key, comparator)) :
                new RedTree<> (tree.key, tree.value, tree.left, del (tree.right, key, comparator));
        }

        // delete this node and we are finished
        return append (tree.left, tree.right);

    }

    static <K,V> Tree<K,V> balance (K key, V value, Tree<K, V> tl, Tree<K, V> tr) {
        if (isRedTree (tl) && isRedTree (tr)) return new RedTree<> (key, value, tl.blacken (), tr.blacken ());

        if (isRedTree (tl)) {
            // left is redden, right is blacken
            if (isRedTree (tl.left)) return new RedTree<> (tl.key, tl.value, tl.left.blacken (), new BlackTree<> (key, value, tl.right, tr));
            if (isRedTree (tl.right)) {
                return new RedTree<> (tl.right.key, tl.right.value,
                        new BlackTree<> (tl.key, tl.value, tl.left, tl.right.left),
                        new BlackTree<> (key, value, tl.right.right, tr));
            }
            return new BlackTree<> (key, value, tl, tr);
        }

        if (isRedTree (tr)) {
            // left is blacken, right is redden
            if (isRedTree (tr.right)) return new RedTree<> (tr.key, tr.value, new BlackTree<> (key, value, tl, tr.left), tr.right.blacken ());
            if (isRedTree (tr.left))  return new RedTree<> (tr.left.key, tr.left.value, new BlackTree<> (key, value, tl, tr.left.left), new BlackTree<> (tr.key, tr.value, tr.left.right, tr.right));
            return new BlackTree<> (key, value, tl, tr);
        }

        // tl and tr are both blacken
        return new BlackTree<> (key, value, tl, tr);
    }

    private static <K,V> Tree<K,V> balanceLeft (K key, V value, Tree<K, V> tl, Tree<K, V> tr) { //TODO merge with other 'balanceLeft' method?
        if (isRedTree (tl)) {
            return new RedTree<> (key, value, tl.blacken (), tr);
        }
        if (isBlackTree (tr)) {
            return balance (key, value, tl, tr.redden ());
        }
        if (isRedTree (tr) && isBlackTree (tr.left)) {
            return new RedTree<> (tr.left.key, tr.left.value, new BlackTree<> (key, value, tl, tr.left.left), balance (tr.key, tr.value, tr.left.right, tr.right.blackToRed ()));
        }
        throw new IllegalStateException ("invariant violation");
    }

    static <K,V> Tree<K,V> balanceRight (K key, V value, Tree<K, V> tl, Tree<K, V> tr) {
        if (isRedTree (tr)) {
            return new RedTree<> (key, value, tl, tr.blacken ());
        }
        if (isBlackTree (tl)) {
            return balance (key, value, tl.redden (), tr);
        }
        if (isRedTree (tl) && isBlackTree (tl.right)) {
            return new RedTree<> (tl.right.key, tl.right.value, balance (tl.key, tl.value, tl.left.blackToRed (), tl.right.left), new BlackTree <> (key, value, tl.right.right, tr));
        }
        throw new IllegalStateException ("invariant violation");
    }

    /**
     * This method combines two separate sub-trees into a single (balanced) tree. It assumes that both subtrees are
     *  balanced and that all elements in 'tl' are smaller than all elements in 'tr'. This situation occurs when a
     *  node is deleted and its child nodes must be combined into a resulting tree.
     */
    private static <K,V> Tree<K,V> append (Tree<K,V> tl, Tree<K,V> tr) {
        if (tl == null) return tr;
        if (tr == null) return tl;

        if (isRedTree (tl) && isRedTree (tr)) {
            final Tree<K,V> bc = append (tl.right, tr.left);
            return isRedTree (bc) ?
                    new RedTree<> (bc.key, bc.value, new RedTree<> (tl.key, tl.value, tl.left, bc.left), new RedTree<> (tr.key, tr.value, bc.right, tr.right)) :
                    new RedTree<> (tl.key, tl.value, tl.left, new RedTree<> (tr.key, tr.value, bc, tr.right));
        }
        if (isBlackTree (tl) && isBlackTree (tr)) {
            final Tree<K,V> bc = append (tl.right, tr.left);
            return isRedTree (bc) ?
                    new RedTree<> (bc.key, bc.value, new BlackTree<> (tl.key, tl.value, tl.left, bc.left), new BlackTree<> (tr.key, tr.value, bc.right, tr.right)) :
                    balanceLeft (tl.key, tl.value, tl.left, new BlackTree<> (tr.key, tr.value, bc, tr.right));
        }
        if (isRedTree (tr)) {
            return new RedTree<> (tr.key, tr.value, append (tl, tr.left), tr.right);
        }
        if (isRedTree (tl)) {
            return new RedTree<> (tl.key, tl.value, tl.left, append (tl.right, tr));
        }
        throw new IllegalStateException ("invariant violation: unmatched tree on append: " + tl + ", " + tr);
    }

    /**
     * encapsulates tree creation for a given colour
     */
    interface TreeFactory<K,V> {
        Tree<K,V> create (Tree<K,V> left, Tree<K,V> right);
    }

    static abstract class Tree<K,V> implements TreeFactory<K,V>, AMapEntry<K,V> {
        final K key;
        final V value;
        final int count;

        final Tree<K,V> left;
        final Tree<K,V> right;

        public Tree (K key, V value, Tree<K, V> left, Tree<K, V> right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;

            this.count = 1 +
                    (left == null ? 0 : left.count) +
                    (right == null ? 0 : right.count);
        }

        @Override public K getKey () {
            return key;
        }
        @Override public V getValue () {
            return value;
        }

        abstract Tree<K,V> withNewValue (K key, V value);

        abstract Tree<K,V> blackToRed();

        abstract boolean isRed();
        abstract boolean isBlack();

        abstract Tree<K,V> redden ();
        abstract Tree<K,V> blacken ();
    }

    static class BlackTree<K,V> extends Tree<K,V> {
        public BlackTree (K key, V value, Tree<K, V> left, Tree<K, V> right) {
            super(key, value, left, right);
        }

        @Override Tree<K, V> withNewValue (K key, V value) {
            return new BlackTree<> (key, value, left, right);
        }
        @Override public Tree<K, V> create (Tree<K, V> left, Tree<K, V> right) {
            return new BlackTree<> (key, value, left, right);
        }

        @Override Tree<K, V> blackToRed () {
            return redden ();
        }

        @Override boolean isRed () {
            return false;
        }
        @Override boolean isBlack () {
            return true;
        }

        @Override Tree<K, V> redden () {
            return new RedTree<> (key, value, left, right);
        }
        @Override Tree<K, V> blacken () {
            return this;
        }
    }

    static class RedTree<K,V> extends Tree<K,V> {
        public RedTree (K key, V value, Tree<K, V> left, Tree<K, V> right) {
            super (key, value, left, right);
        }

        @Override Tree<K, V> withNewValue (K key, V value) {
            return new RedTree<> (key, value, left, right);
        }
        @Override public Tree<K, V> create (Tree<K, V> left, Tree<K, V> right) {
            return new RedTree<> (key, value, left, right);
        }

        @Override Tree<K, V> blackToRed () {
            throw new IllegalStateException ();
        }

        @Override boolean isRed () {
            return true;
        }
        @Override boolean isBlack () {
            return false;
        }

        @Override Tree<K, V> redden () {
            return this;
        }
        @Override Tree<K, V> blacken () {
            return new BlackTree<> (key, value, left, right);
        }
    }
}


package com.ajjpj.afoundation.concurrent;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

class ExecutorServiceWrapper extends AbstractExecutorService {
    private final AThreadPool pool;
    private final AThreadPoolWithAdmin poolWithAdmin;
    private final AtomicReference<AFuture<?>> shutdown = new AtomicReference<> ();

    ExecutorServiceWrapper (AThreadPool pool) {
        this.pool = pool;
        if (pool instanceof AThreadPoolWithAdmin) {
            this.poolWithAdmin = (AThreadPoolWithAdmin) pool;
        }
        else {
            this.poolWithAdmin = null;
        }
    }

    @Override public void execute (Runnable command) {
        pool.submit (command);
    }

    //------------------- shutdown handling below this point ---------------------

    @Override public void shutdown () {
        if (poolWithAdmin == null) throw new UnsupportedOperationException ("shutdown only supported for AThreadPoolWithAdmin");
        shutdown.set (AFuture.lift (AThreadPool.SYNC_THREADPOOL, poolWithAdmin.shutdown (AThreadPoolWithAdmin.ShutdownMode.ExecuteSubmitted)));
    }

    @Override public List<Runnable> shutdownNow () {
        if (poolWithAdmin == null) throw new UnsupportedOperationException ("shutdown only supported for AThreadPoolWithAdmin");
        shutdown.set (AFuture.lift (AThreadPool.SYNC_THREADPOOL, poolWithAdmin.shutdown (AThreadPoolWithAdmin.ShutdownMode.SkipUnstarted)));
        System.err.println ("Work may have been lost - keeping track of unfinished work on pool shutdown is not supported by AThreadPool");
        return Collections.emptyList ();
    }

    @Override public boolean isShutdown () {
        return shutdown.get() != null;
    }

    @Override public boolean isTerminated () {
        return isShutdown() && shutdown.get().isComplete ();
    }

    @Override public boolean awaitTermination (long timeout, TimeUnit unit) throws InterruptedException {
        if (!isShutdown ()) return false;

        try {
            shutdown.get().await (timeout, unit);
            return true;
        }
        catch (TimeoutException e) {
            return false;
        }
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.function.*;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

/**
 * @author arno
 */
abstract class MapAsSetWrapper<K, C extends MapAsSetWrapper<K, C>> implements ASet<K>, Serializable {
    private final AMap<K, Boolean> inner;

    @SuppressWarnings ("unchecked")
    protected MapAsSetWrapper (AMap<K, ?> inner) {
        this.inner = (AMap<K, Boolean>) inner;
    }

    @Override public C clear () {
        return wrapAsSet (inner.clear ());
    }

    @Override public AEquality equalityForEquals () {
        return inner.keyEquality();
    }

    @Override public int size () {
        return inner.size ();
    }

    @Override public boolean isEmpty () {
        return inner.isEmpty ();
    }

    @Override public boolean nonEmpty () {
        return inner.nonEmpty ();
    }

    @Override public C added (K el) {
        return with (el);
    }

    @SuppressWarnings ("unchecked")
    @Override public C with (K el) {
        return wrapAsSet (inner.updated (el, Boolean.TRUE));
    }

    @Override public C removed (K el) {
        return without (el);
    }

    @SuppressWarnings ("unchecked")
    @Override public C without (K el) {
        return wrapAsSet (inner.removed (el));
    }

    @Override public boolean contains (K el) {
        return inner.containsKey (el);
    }

    @Override public Iterator<K> iterator () {
        return new Iterator<K> () {
            final Iterator<AMapEntry<K, Boolean>> iter = inner.iterator ();

            @Override public boolean hasNext () {
                return iter.hasNext ();
            }
            @Override public K next () {
                return iter.next ().getKey ();
            }
            @Override public void remove () {
                throw new UnsupportedOperationException ();
            }
        };
    }

    @Override public String mkString () {
        return ACollectionHelper.mkString (this);
    }

    @Override public String mkString (String separator) {
        return ACollectionHelper.mkString (this, separator);
    }

    @Override public String mkString (String prefix, String separator, String suffix) {
        return ACollectionHelper.mkString (this, prefix, separator, suffix);
    }

    @Override public AList<K> toList () {
        return AList.create (this);
    }

    @Override public ASet<K> toSet () {
        return this;
    }

    @SuppressWarnings ("unchecked")
    @Override public ASet<K> toSet (AEquality equality) {
        if (equality.equals (equalityForEquals ())) {
            return this;
        }

        return AHashSet.create (equality, this);
    }

    @Override public Collection<K> asJavaUtilCollection () {
        return asJavaUtilSet ();
    }

    @Override public Set<K> asJavaUtilSet () {
        return inner.asJavaUtilMap ().keySet ();
    }

    @Override public String toString () {
        return mkString ("[", ", ", "]");
    }

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

    @SuppressWarnings("SimplifiableIfStatement")
    @Override
    public boolean equals(Object o) {
        if(o == this) {
            return true;
        }
        if(! (o instanceof MapAsSetWrapper)) {
            return false;
        }
        return inner.equals(((MapAsSetWrapper) o).inner);
    }

    //-------------------------------------- collection transformations

    @SuppressWarnings ("unchecked")
    protected <X> ASet<X> createInternal (Iterable<X> elements) {
        AMap result = inner.clear ();

        for (X el: elements) {
            result = result.updated (el, Boolean.TRUE);
        }
        return (ASet) wrapAsSet (result);
    }

    protected abstract C wrapAsSet (AMap<K,?> inner);

    @Override public <E extends Throwable> ASet<K> filter (APredicate<? super K, E> pred) throws E {
        return createInternal (ACollectionHelper.<K,E>filter (this, pred));
    }

    @Override public <E extends Throwable> ASet<K> filterNot (APredicate<? super K, E> pred) throws E {
        return createInternal (ACollectionHelper.<K,E>filterNot (this, pred));
    }

    @SuppressWarnings ("unchecked")
    @Override public <X> ASet<X> flatten () {
        return (ASet<X>) createInternal (ACollectionHelper.flatten ((Iterable<? extends Iterable<Object>>) this));
    }

    @SuppressWarnings ("unchecked")
    @Override public <X, E extends Throwable> AMap<X, ? extends ACollection<K>> groupBy (AFunction1<? super K, ? extends X, E> f) throws E {
        return groupBy (f, inner.keyEquality ());
    }

    @SuppressWarnings ("unchecked")
    @Override public <X, E extends Throwable> AMap<X, ? extends ACollection<K>> groupBy (AFunction1<? super K, ? extends X, E> f, AEquality keyEquality) throws E {
        AMap<X, ASet<K>> result = (AMap<X, ASet<K>>) (keyEquality.equals (inner.keyEquality ()) ? inner.clear () : AHashMap.empty (keyEquality));

        final ASet<K> emptySet = createInternal (Collections.<K>emptyList ());

        for(K o: this) {
            final X key = f.apply(o);
            final ASet<K> perKey = result
                    .get(key)
                    .getOrElse (emptySet)
                    .with (o);
            result = result.updated (key, perKey);
        }
        return result;
    }

    @Override public <E extends Throwable> void foreach(AStatement1<? super K, E> f) throws E {
        for (K el: this) {
            f.apply (el);
        }
    }

    @Override public <X, E extends Throwable> ASet<X> map (AFunction1<? super K, ? extends X, E> f) throws E {
        return ACollectionHelper.asASetView (ACollectionHelper.<K,X,E>map (this, f));
    }

    @Override public <X, E extends Throwable> ASet<X> flatMap (AFunction1<? super K, ? extends Iterable<X>, E> f) throws E {
        return ACollectionHelper.asASetView (ACollectionHelper.<K,X,E>flatMap (this, f));
    }

    @Override public <X, E extends Throwable> ASet<X> collect (APartialFunction<? super K, ? extends X, E> pf) throws E {
        return ACollectionHelper.asASetView (ACollectionHelper.<K,X,E>collect (this, pf));
    }

    @Override public <R, E extends Throwable> R foldLeft (R startValue, AFunction2<R, ? super K, R, E> f) throws E {
        return ACollectionHelper.foldLeft (this, startValue, f);
    }

    @Override public <E extends Throwable> AOption<K> find (APredicate<? super K, E> pred) throws E {
        return ACollectionHelper.find (this, pred);
    }

    @Override public <E extends Throwable> boolean forAll (APredicate<? super K, E> pred) throws E {
        return ACollectionHelper.forAll (this, pred);
    }

    @Override public <E extends Throwable> boolean exists (APredicate<? super K, E> pred) throws E {
        return ACollectionHelper.exists (this, pred);
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.tuples.ATuple2;

import java.util.*;

/**
 * @author arno
 */
class JavaUtilMapWrapper<K,V> implements java.util.Map<K,V> {
    private final AMap<K,V> inner;

    JavaUtilMapWrapper(AMap<K, V> inner) {
        this.inner = inner;
    }

    @Override
    public int size() {
        return inner.size();
    }

    @Override
    public boolean isEmpty() {
        return inner.isEmpty();
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean containsKey(Object key) {
        return inner.containsKey((K) key);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean containsValue(Object value) {
        return inner.containsValue((V) value);
    }

    @SuppressWarnings("unchecked")
    @Override
    public V get(Object key) {
        return inner.get((K) key).getOrElse(null);
    }

    /**
     * There is usually a performance gain to be had by overriding this default implementation
     */
    @Override
    public Set<K> keySet() {
        return new AbstractSet<K>() {
            @Override
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    final Iterator<AMapEntry<K,V>> it = inner.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @Override
                    public K next() {
                        return it.next().getKey ();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public int size() {
                return inner.size();
            }
        };
    }

    @Override
    public Collection<V> values() {
        return new AbstractCollection<V>() {
            @Override
            public Iterator<V> iterator() {
                return new Iterator<V>() {
                    final Iterator<AMapEntry<K,V>> it = inner.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @Override
                    public V next() {
                        return it.next().getValue ();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public int size() {
                return inner.size();
            }
        };
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return new AbstractSet<Entry<K,V>>() {
            @Override
            public Iterator<Entry<K, V>> iterator() {
                return new Iterator<Entry<K, V>>() {
                    final Iterator<AMapEntry<K,V>> it = inner.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @Override
                    public Entry<K, V> next() {
                        final AMapEntry<K,V> kv = it.next();

                        return new Entry<K, V>() {
                            @Override
                            public K getKey() {
                                return kv.getKey ();
                            }

                            @Override
                            public V getValue() {
                                return kv.getValue ();
                            }

                            @Override
                            public V setValue(V value) {
                                throw new UnsupportedOperationException();
                            }
                        };
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }

            @Override
            public int size() {
                return inner.size();
            }
        };
    }

    //------------- mutators

    @Override
    public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }
    @Override
    public V remove(Object key) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void clear() {
        throw new UnsupportedOperationException();
    }
}


/*
 * Copyright © 2015 Stefan Niederhauser (nidin@gmx.ch)
 *
 * Licensed 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 guru.nidi.loader.use.raml;

import guru.nidi.loader.Loader;
import guru.nidi.loader.LoadingException;
import org.raml.v2.api.RamlModelBuilder;
import org.raml.v2.api.RamlModelResult;

import java.io.*;

public class RamlCache {
    private static final File CACHE_DIR = new File(System.getProperty("java.io.tmpdir"), "bamls");

    static {
        CACHE_DIR.mkdirs();
    }

    private final Loader loader;

    public RamlCache(Loader loader) {
        this.loader = loader;
    }

    //TODO save all included ramls of a raml. when any of them changes, reparse!
    public RamlModelResult loadRaml(String name) {
        final File file = new File(CACHE_DIR, escaped(loader.config()) + "-" + escaped(simpleName(name)) + ".braml");
        if (!file.exists()) {
            return parseAndSave(loader, name, file);
        }
        final InputStream in = loader.fetchResource(name, file.lastModified());
        if (in != null) {
            final byte[] data = loadedData(name, in);
            return parseAndSave(new PreloadedLoader(loader, name, data), name, file);
        }
        try {
            return load(file);
        } catch (IOException | ClassNotFoundException e) {
            return parseAndSave(loader, name, file);
        }
    }

    private String simpleName(String name) {
        final int user = name.indexOf('@');
        final int suffix = name.lastIndexOf('.');
        return name.substring(user + 1, suffix < 0 ? name.length() : suffix);
    }

    private RamlModelResult parseAndSave(Loader loader, String name, File file) {
        final RamlModelResult raml = new RamlModelBuilder(new LoaderRamlResourceLoader(loader)).buildApi(name);
        save(raml, file);
        return raml;
    }

    private void save(RamlModelResult raml, File file) {
        try (final ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
            oos.writeObject(raml);
        } catch (IOException e) {
            //ignore
        }
    }

    private RamlModelResult load(File file) throws IOException, ClassNotFoundException {
        try (final ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            return (RamlModelResult) ois.readObject();
        }
    }

    private String escaped(String name) {
        final StringBuilder s = new StringBuilder(name);
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
                s.setCharAt(i, '-');
            }
        }
        return s.toString();
    }

    private byte[] loadedData(String name, InputStream ramlInputStream) {
        try (final InputStream in = ramlInputStream;
             final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            final byte[] buf = new byte[1000];
            int read;
            while ((read = in.read(buf)) > 0) {
                baos.write(buf, 0, read);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            throw new LoadingException("Could not read resource '" + name + "'", e);
        }
    }

    private static class PreloadedLoader implements Loader {
        private final Loader loader;
        private final String name;
        private final byte[] data;

        public PreloadedLoader(Loader loader, String name, byte[] data) {
            this.loader = loader;
            this.name = name;
            this.data = data;
        }

        @Override
        public InputStream fetchResource(String name, long ifModifiedSince) {
            if (name.equals(this.name)) {
                return new ByteArrayInputStream(data);
            }
            return loader.fetchResource(name, ifModifiedSince);
        }

        @Override
        public String config() {
            return loader.config();
        }
    }
}


/*
 * Copyright © 2015 Stefan Niederhauser (nidin@gmx.ch)
 *
 * Licensed 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 guru.nidi.loader.basic;

import guru.nidi.loader.Loader;
import guru.nidi.loader.LoaderFactory;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Handles resources with absolute URIs. Handling of relative URIs are delegated to another RamlLoader.
 * Loaders are registered in META-INF/services/guru.nidi.ramltester.loader.RamlLoaderFactory
 */
public class UriLoader implements Loader {
    private static final Pattern ABSOLUTE_URI_PATTERN = Pattern.compile("(([^:]+):?([^@]*)@)?([^:]+)://(.+)");
    private static final int
            GROUP_USER = 2,
            GROUP_PASSWORD = 3,
            GROUP_PROTOCOL = 4,
            GROUP_PATH = 5;

    private static final Map<String, LoaderFactory> FACTORIES = new HashMap<>();

    static {
        final ServiceLoader<LoaderFactory> loader = ServiceLoader.load(LoaderFactory.class);
        for (final LoaderFactory factory : loader) {
            FACTORIES.put(factory.supportedProtocol(), factory);
        }
    }

    private final Loader relativeLoader;

    public UriLoader() {
        this(null);
    }

    public UriLoader(Loader relativeLoader) {
        this.relativeLoader = relativeLoader;
    }

    @Override
    public InputStream fetchResource(String name, long ifModifiedSince) {
        final String normalized = normalizeResourceName(name);
        final Matcher matcher = ABSOLUTE_URI_PATTERN.matcher(normalized);
        if (matcher.matches()) {
            String path = matcher.group(GROUP_PATH);
            String res = "";
            final int lastSlash = path.lastIndexOf('/');
            if (lastSlash >= 0 && lastSlash < path.length() - 1) {
                res = path.substring(lastSlash + 1);
                path = path.substring(0, lastSlash);
            }
            final int query = res.indexOf('?');
            if (query >= 0) {
                path += res.substring(query);
                res = res.substring(0, query);
            }
            return absoluteLoader(matcher.group(GROUP_PROTOCOL), path, matcher.group(GROUP_USER), matcher.group(GROUP_PASSWORD))
                    .fetchResource(res, ifModifiedSince);
        }
        if (relativeLoader == null) {
            throw new IllegalArgumentException("Expected absolute uri '[username:password@]protocol://base[/file]', but got '" + name + "'");
        }
        return relativeLoader.fetchResource(normalized, ifModifiedSince);
    }

    @Override
    public String config() {
        return "uri-" + (relativeLoader == null ? "" : relativeLoader.config());
    }

    //raml parser does its own absolute/relative handling (org.raml.parser.tagresolver.ContextPath#resolveAbsolutePath)
    // -> hack to undo this
    private String normalizeResourceName(String name) {
        if (name.startsWith("//")) {
            return "classpath:" + name;
        }
        final int firstProtocol = name.indexOf("://");
        final int secondProtocol = name.indexOf("://", firstProtocol + 1);
        final int protocol = secondProtocol < 0 ? firstProtocol : secondProtocol;
        final int endOfFirst = name.lastIndexOf("/", protocol);
        if (endOfFirst >= 0) {
            return name.substring(endOfFirst + 1);
        }
        return name;
    }

    private Loader absoluteLoader(String protocol, String base, String username, String password) {
        final LoaderFactory factory = FACTORIES.get(protocol);
        if (factory == null) {
            throw new IllegalArgumentException("Unsupported protocol " + protocol);
        }
        return factory.getLoader(base, username, password);
    }
}


/*
 * Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.
 *
 * Licensed 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.eiichiro.bootleg.xml;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.converters.AbstractConverter;
import org.eiichiro.bootleg.Types;

/**
 * {@code ValueTypeXmlAdapter} is a custom XML marshaling adapter to convert
 * user-defined value type object to XML value string representation.
 * If you want to marshal user-defined value type object to a single XML value
 * string representation, annotate the type or field with
 * {@code @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(ValueTypeXmlAdapter.class)}.
 *
 * @author Eiichiro Uchiumi
 */
public class ValueTypeXmlAdapter<BoundType> extends XmlAdapter<String, BoundType> {

	/**
	 * Marshals the specified user-defined value type object to single XML value
	 * string representation.
	 *
	 * @throws If the specified object is not a user-defined value type.
	 */
	@Override
	public String marshal(BoundType v) throws Exception {
		Class<? extends Object> type = v.getClass();

		if (!Types.isUserDefinedValueType(type)) {
			throw new IllegalArgumentException("Type [" + type
					+ "] must be an user-defined value type; "
					+ "@XmlJavaTypeAdapter(ValueTypeXmlAdapter.class) "
					+ "can be annotated to user-defined value type and field only");
		}

		Converter converter = ConvertUtils.lookup(type);

		if ((converter != null && converter instanceof AbstractConverter)) {
			String string = (String) ConvertUtils.convert(v, String.class);

			if (string != null) {
				return string;
			}
		}

		return v.toString();
	}

	/** Unsupported. Unmarshaling is provided out of JAXB in Bootleg. */
	@Override
	public BoundType unmarshal(String v) throws Exception {
		throw new UnsupportedOperationException("Unmarshaling is provided out of JAXB");
	}

}


/*
 * Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.
 *
 * Licensed 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.eiichiro.bootleg.xml;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eiichiro.bootleg.AbstractRequest;
import org.eiichiro.bootleg.Types;
import org.eiichiro.bootleg.WebContext;
import org.eiichiro.reverb.lang.UncheckedException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;

import com.google.common.base.Function;

/**
 * {@code XMLRequest} is a XML-based implementation of {@code Request}.
 * If the requested MIME media type is 'application/xml', default configuration of
 * Bootleg uses this implementation.
 *
 * @author Eiichiro Uchiumi
 */
public class XMLRequest extends AbstractRequest {

	private Node node;

	/**
	 * Constructs a new {@code XMLRequest} from the current {@code WebContext}.
	 *
	 * @param context The current {@code WebContext}.
	 */
	public void from(WebContext context) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);

		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(new InputSource(context.request().getReader()));
			node = document.getDocumentElement();
		} catch (Exception e) {
			logger.warn("Cannot parse XML document into DOM object", e);
			throw new UncheckedException(e);
		}

		super.from(context);
	}

	/**
	 * Returns the Web endpoint method parameter value from the current HTTP
	 * request body sent as XML format.
	 * This method supports the following parameter declaration independently.
	 * Other than them are the same as {@link AbstractRequest}.
	 *

	 *
Named collection of user-defined object type

	 *
Named user-defined object type

	 *

	 *
	 * @param type Web endpoint method parameter type.
	 * @param name Web endpoint method parameter name.
	 * @return Web endpoint method parameter value.
	 */
	@Override
	protected Object body(Type type, String name) {
		return body(type, name, node);
	}

	@SuppressWarnings("unchecked")
	private Object body(Type type, String name, final Node node) {
		Function<String, Object> value = new Function<String, Object>() {

			public Object apply(String name) {
				NodeList nodes = node.getChildNodes();

				for (int i = 0; i < nodes.getLength(); i++) {
					Node item = nodes.item(i);

					if (item.getNodeType() == Node.ELEMENT_NODE
							&& item.getNodeName().equals(name)) {
						return item;
					}
				}

				return null;
			}

		};
		Function<String, Collection<Object>> values = new Function<String, Collection<Object>>() {

			public Collection<Object> apply(String name) {
				Collection<Object> collection = new ArrayList<Object>();
				NodeList nodes = node.getChildNodes();

				for (int i = 0; i < nodes.getLength(); i++) {
					Node item = nodes.item(i);

					if (item.getNodeType() == Node.ELEMENT_NODE
							&& item.getNodeName().equals(name)) {
						collection.add(item);
					}
				}

				return (collection.isEmpty()) ? null : collection;
			}

		};

		if (name != null && !name.isEmpty()) {
			if (Types.isCollection(type)) {
				if (Types.isSupportedCollection(type)) {
					Class<?> elementType = Types.getElementType(type);

					if (!Types.isCoreValueType(elementType)
							&& !Types.isUserDefinedValueType(elementType)) {
						// Named collection of user-defined object type.
						Collection<Object> nodes = values.apply(name);

						if (nodes == null) {
							logger.debug("Collection named [" + name + "] not found");
							return nodes;
						}

						try {
							Class<?> implementationType = Types.getDefaultImplementationType(type);
							Collection<Object> collection = (Collection<Object>) implementationType.newInstance();

							try {
								for (Object n : nodes) {
									Object instance = elementType.newInstance();

									for (Field field : elementType.getDeclaredFields()) {
										Object object = body(field.getGenericType(), field.getName(), (Node) n);

										if (object != null) {
											field.setAccessible(true);
											field.set(instance, object);
										}
									}

									collection.add(instance);
								}

								return (collection.isEmpty()) ? null : collection;
							} catch (Exception e) {
								logger.debug("Cannot instantiate ["
										+ Types.getDefaultImplementationType(type)
										+ "] (Default implementation type of ["
										+ type + "])", e);
							}

						} catch (Exception e) {
							logger.warn("Cannot instantiate [" + elementType
									+ "] (Collection element type of [" + type
									+ "])", e);
						}

						return null;
					}
				}

			} else if (!Types.isCoreValueType(type)
					&& !Types.isUserDefinedValueType(type)) {
				// Named user-defined object type.
				Class<?> rawType = Types.getRawType(type);
				Object n = value.apply(name);

				try {
					Object instance = rawType.newInstance();

					for (Field field : rawType.getDeclaredFields()) {
						Object object = body(field.getGenericType(), field.getName(), (Node) n);

						if (object != null) {
							field.setAccessible(true);
							field.set(instance, object);
						}
					}

					return instance;
				} catch (Exception e) {
					logger.warn("Cannot instantiate [" + type + "]", e);
				}

				return null;
			}
		}

		return parameter(type, name, value, values);
	}

	/**
	 * Converts the specified object to the specified type.
	 * The object to be converted must be a {@code Node} that has no child, or
	 * this method return null.
	 *
	 * @param object The object to be converted
	 * @param type The type to which the specified object be converted.
	 * @return The converted object.
	 */
	@Override
	protected Object convert(Object object, Class<?> type) {
		if (object instanceof Node) {
			Node node = (Node) object;
			NodeList nodes = node.getChildNodes();
			Text text = null;

			for (int i = 0; i < nodes.getLength(); i++) {
				Node child = nodes.item(i);

				if (child.getNodeType() == Node.TEXT_NODE) {
					text = (Text) child;
				}
			}

			if (text == null) {
				logger.warn("Parameter [" + object + "] cannot be converted to ["
						+ type + "]; Converted 'object' must have one child ["
						+ Text.class + "] node");
				return null;
			}

			return super.convert(text.getNodeValue(), type);
		} else {
			logger.warn("Parameter [" + object + "] cannot be converted to ["
					+ type + "]; Converted 'object' must be a ["
					+ Node.class + "]");
			return null;
		}
	}

	/**
	 * Converts the specified object to the specified user-defined value type.
	 * The object to be converted must be a {@code Node} that has no child, or
	 * this method return null.
	 *
	 * @param object The object to be converted
	 * @param type The user-defined value type to which the specified object be
	 * converted.
	 * @return The converted object.
	 */
	@Override
	protected Object convertUserDefinedValueType(Object object, Class<?> type) {
		if (object instanceof Node) {
			Node node = (Node) object;
			NodeList nodes = node.getChildNodes();
			Text text = null;

			for (int i = 0; i < nodes.getLength(); i++) {
				Node child = nodes.item(i);

				if (child.getNodeType() == Node.TEXT_NODE) {
					text = (Text) child;
				}
			}

			if (text == null) {
				logger.warn("Parameter [" + object + "] cannot be converted to ["
						+ type + "]; Converted 'object' must have one child ["
						+ Text.class + "] node");
				return null;
			}

			return super.convertUserDefinedValueType(text.getNodeValue(), type);
		} else {
			logger.warn("Parameter [" + object + "] cannot be converted to ["
					+ type + "]; Converted 'object' must be a ["
					+ Node.class + "]");
			return null;
		}
	}

}