/*
 * Copyright (c) 2016 Goldman Sachs.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v. 1.0 which accompany this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */

package org.eclipse.collections.impl.block.factory;

import java.util.Objects;

import org.eclipse.collections.api.block.HashingStrategy;
import org.eclipse.collections.api.block.function.Function;
import org.eclipse.collections.api.block.function.primitive.BooleanFunction;
import org.eclipse.collections.api.block.function.primitive.ByteFunction;
import org.eclipse.collections.api.block.function.primitive.CharFunction;
import org.eclipse.collections.api.block.function.primitive.DoubleFunction;
import org.eclipse.collections.api.block.function.primitive.FloatFunction;
import org.eclipse.collections.api.block.function.primitive.IntFunction;
import org.eclipse.collections.api.block.function.primitive.LongFunction;
import org.eclipse.collections.api.block.function.primitive.ShortFunction;

public final class HashingStrategies
{
    private static final HashingStrategy<Object> DEFAULT_HASHING_STRATEGY = new DefaultStrategy();
    private static final HashingStrategy<Object> IDENTITY_HASHING_STRATEGY = new IdentityHashingStrategy();

    private HashingStrategies()
    {
        throw new AssertionError("Suppress default constructor for noninstantiability");
    }

    public static <T> HashingStrategy<T> defaultStrategy()
    {
        return (HashingStrategy<T>) DEFAULT_HASHING_STRATEGY;
    }

    public static <T> HashingStrategy<T> nullSafeHashingStrategy(HashingStrategy<T> nonNullSafeStrategy)
    {
        return new NullSafeHashingStrategy<>(nonNullSafeStrategy);
    }

    public static <T, V> HashingStrategy<T> nullSafeFromFunction(Function<? super T, ? extends V> function)
    {
        return new NullSafeFunctionHashingStrategy<>(function);
    }

    public static <T, V> HashingStrategy<T> fromFunction(Function<? super T, ? extends V> function)
    {
        return new FunctionHashingStrategy<>(function);
    }

    public static HashingStrategy<Object> identityStrategy()
    {
        return IDENTITY_HASHING_STRATEGY;
    }

    public static <T> HashingStrategy<T> chain(HashingStrategy<T>... hashingStrategies)
    {
        if (hashingStrategies.length == 0)
        {
            throw new IllegalArgumentException("Nothing to chain");
        }

        return new ChainedHashingStrategy<>(hashingStrategies);
    }

    public static <T, V1, V2> HashingStrategy<T> fromFunctions(Function<? super T, ? extends V1> one, Function<? super T, ? extends V2> two)
    {
        return HashingStrategies.chain(
                HashingStrategies.fromFunction(one),
                HashingStrategies.fromFunction(two));
    }

    public static <T, V1, V2, V3> HashingStrategy<T> fromFunctions(Function<? super T, ? extends V1> one, Function<? super T, ? extends V2> two, Function<? super T, ? extends V3> three)
    {
        return HashingStrategies.chain(
                HashingStrategies.fromFunction(one),
                HashingStrategies.fromFunction(two),
                HashingStrategies.fromFunction(three));
    }

    public static <T> HashingStrategy<T> fromBooleanFunction(BooleanFunction<? super T> function)
    {
        return new BooleanFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromByteFunction(ByteFunction<? super T> function)
    {
        return new ByteFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromCharFunction(CharFunction<? super T> function)
    {
        return new CharFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromDoubleFunction(DoubleFunction<? super T> function)
    {
        return new DoubleFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromFloatFunction(FloatFunction<? super T> function)
    {
        return new FloatFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromIntFunction(IntFunction<? super T> function)
    {
        return new IntFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromLongFunction(LongFunction<? super T> function)
    {
        return new LongFunctionHashingStrategy<>(function);
    }

    public static <T> HashingStrategy<T> fromShortFunction(ShortFunction<? super T> function)
    {
        return new ShortFunctionHashingStrategy<>(function);
    }

    private static class DefaultStrategy implements HashingStrategy<Object>
    {
        private static final long serialVersionUID = 1L;

        @Override
        public int computeHashCode(Object object)
        {
            return object.hashCode();
        }

        @Override
        public boolean equals(Object object1, Object object2)
        {
            return object1.equals(object2);
        }
    }

    private static final class NullSafeHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final HashingStrategy<T> nonNullSafeStrategy;

        private NullSafeHashingStrategy(HashingStrategy<T> nonNullSafeStrategy)
        {
            this.nonNullSafeStrategy = nonNullSafeStrategy;
        }

        @Override
        public int computeHashCode(T object)
        {
            return object == null ? 0 : this.nonNullSafeStrategy.computeHashCode(object);
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return object1 == null || object2 == null
                    ? object1 == object2
                    : this.nonNullSafeStrategy.equals(object1, object2);
        }
    }

    private static final class NullSafeFunctionHashingStrategy<T, V> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final Function<? super T, ? extends V> function;

        private NullSafeFunctionHashingStrategy(Function<? super T, ? extends V> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return Objects.hashCode(this.function.valueOf(object));
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return Objects.equals(
                    this.function.valueOf(object1),
                    this.function.valueOf(object2));
        }
    }

    private static final class FunctionHashingStrategy<T, V> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final Function<? super T, ? extends V> function;

        private FunctionHashingStrategy(Function<? super T, ? extends V> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.valueOf(object).hashCode();
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.valueOf(object1).equals(this.function.valueOf(object2));
        }
    }

    private static final class BooleanFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final BooleanFunction<? super T> function;

        private BooleanFunctionHashingStrategy(BooleanFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.booleanValueOf(object) ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode();
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.booleanValueOf(object1) == this.function.booleanValueOf(object2);
        }
    }

    private static final class ByteFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final ByteFunction<? super T> function;

        private ByteFunctionHashingStrategy(ByteFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.byteValueOf(object);
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.byteValueOf(object1) == this.function.byteValueOf(object2);
        }
    }

    private static final class CharFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final CharFunction<? super T> function;

        private CharFunctionHashingStrategy(CharFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.charValueOf(object);
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.charValueOf(object1) == this.function.charValueOf(object2);
        }
    }

    private static final class DoubleFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final DoubleFunction<? super T> function;

        private DoubleFunctionHashingStrategy(DoubleFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return HashingStrategies.longHashCode(Double.doubleToLongBits(this.function.doubleValueOf(object)));
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return Double.compare(this.function.doubleValueOf(object1), this.function.doubleValueOf(object2)) == 0;
        }
    }

    private static final class FloatFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final FloatFunction<? super T> function;

        private FloatFunctionHashingStrategy(FloatFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return Float.floatToIntBits(this.function.floatValueOf(object));
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return Float.compare(this.function.floatValueOf(object1), this.function.floatValueOf(object2)) == 0;
        }
    }

    private static final class IntFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final IntFunction<? super T> function;

        private IntFunctionHashingStrategy(IntFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.intValueOf(object);
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.intValueOf(object1) == this.function.intValueOf(object2);
        }
    }

    private static final class LongFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final LongFunction<? super T> function;

        private LongFunctionHashingStrategy(LongFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return HashingStrategies.longHashCode(this.function.longValueOf(object));
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.longValueOf(object1) == this.function.longValueOf(object2);
        }
    }

    private static final class ShortFunctionHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;

        private final ShortFunction<? super T> function;

        private ShortFunctionHashingStrategy(ShortFunction<? super T> function)
        {
            this.function = function;
        }

        @Override
        public int computeHashCode(T object)
        {
            return this.function.shortValueOf(object);
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            return this.function.shortValueOf(object1) == this.function.shortValueOf(object2);
        }
    }

    private static final class IdentityHashingStrategy implements HashingStrategy<Object>
    {
        private static final long serialVersionUID = 1L;

        @Override
        public int computeHashCode(Object object)
        {
            return System.identityHashCode(object);
        }

        @Override
        public boolean equals(Object object1, Object object2)
        {
            return object1 == object2;
        }
    }

    private static final class ChainedHashingStrategy<T> implements HashingStrategy<T>
    {
        private static final long serialVersionUID = 1L;
        private final HashingStrategy<T>[] hashingStrategies;

        private ChainedHashingStrategy(HashingStrategy<T>... hashingStrategies)
        {
            this.hashingStrategies = hashingStrategies;
        }

        @Override
        public int computeHashCode(T object)
        {
            int hashCode = this.hashingStrategies[0].computeHashCode(object);
            for (int i = 1; i < this.hashingStrategies.length; i++)
            {
                hashCode = hashCode * 31 + this.hashingStrategies[i].computeHashCode(object);
            }
            return hashCode;
        }

        @Override
        public boolean equals(T object1, T object2)
        {
            for (HashingStrategy<T> hashingStrategy : this.hashingStrategies)
            {
                if (!hashingStrategy.equals(object1, object2))
                {
                    return false;
                }
            }
            return true;
        }
    }

    
This implementation is equivalent to the JDK Long hashcode because there is no public static hashCode(long value) method on Long. This method will be introduced in Java 1.8, at which point this can be replaced.
Params:
  • value – the long value to hash
Returns:hashcode for long, based on the Long.hashCode()
/** * This implementation is equivalent to the JDK Long hashcode because there is no public static hashCode(long value) method on Long. * This method will be introduced in Java 1.8, at which point this can be replaced. * * @param value the long value to hash * @return hashcode for long, based on the {@link Long#hashCode()} */
private static int longHashCode(long value) { return (int) (value ^ (value >>> 32)); } }