/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.collection;

import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Immutable {@link Set}. This implementation provides a predictable iteration
 * order by remembering the order in which elements were inserted into the
 * {@code Set} (insertion-order). Note that insertion order is not affected if
 * an element is re-inserted into the {@code Set}. Accepts {@code null} values.
 *
 * @param <E> the type of the {@code Set}'s elements.
 *
 * @author Osman KOCAK
 */
public final class ImmutableSet<E> extends AbstractSet<E> implements Serializable
{
	private static final long serialVersionUID = 6744093868687684286L;

	/**
	 * {@code ImmutableSet} builder. Not thread-safe.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 */
	public static final class Builder<E>
	{
		private final Set<E> inner = new LinkedHashSet<E>();

		/**
		 * Adds the given values to the {@code Set} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(E... values)
		{
			inner.addAll(Arrays.asList(values));
			return this;
		}

		/**
		 * Adds all the values contained in the given {@code Iterable}
		 * to the {@code Set} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(Iterable<? extends E> values)
		{
			for (E value : values) {
				inner.add(value);
			}
			return this;
		}

		/**
		 * Adds all the values contained in the given {@code Iterator}
		 * to the {@code Set} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(Iterator<? extends E> values)
		{
			while (values.hasNext()) {
				inner.add(values.next());
			}
			return this;
		}

		/**
		 * Finalizes the creation of the {@code ImmutableSet}.
		 *
		 * @return an instance of {@code ImmutableSet}.
		 */
		public Set<E> build()
		{
			return new ImmutableSet<E>(inner);
		}
	}

	/**
	 * Creates a new {@code ImmutableSet} from the given {@code Collection}.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 * @param c the source {@code Collection}.
	 *
	 * @return the created {@code ImmutableSet}.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static <E> Set<E> copyOf(Collection<? extends E> c)
	{
		return new ImmutableSet<E>(new LinkedHashSet<E>(c));
	}

	/**
	 * Creates a new {@code ImmutableSet} from the given {@code Iterable}.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 * @param i the source {@code Iterable}.
	 *
	 * @return the created {@code ImmutableSet}.
	 *
	 * @throws NullPointerException if {@code i} is {@code null}.
	 */
	public static <E> Set<E> copyOf(Iterable<? extends E> i)
	{
		return new ImmutableSet<E>(Iterables.toSet(i));
	}

	/**
	 * Creates a new {@code ImmutableSet} from the given {@code Iterator}.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 * @param i the source {@code Iterator}.
	 *
	 * @return the created {@code ImmutableSet}.
	 *
	 * @throws NullPointerException if {@code i} is {@code null}.
	 */
	public static <E> Set<E> copyOf(Iterator<? extends E> i)
	{
		return new ImmutableSet<E>(Iterators.toSet(i));
	}

	/**
	 * Creates a new {@code ImmutableSet} containing the given elements.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 * @param values the {@code Set}'s elements.
	 *
	 * @return the created {@code ImmutableSet}.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 */
	public static <E> Set<E> copyOf(E[] values)
	{
		return new ImmutableSet<E>(new LinkedHashSet<E>(Arrays.asList(values)));
	}

	/**
	 * Creates a new {@code ImmutableSet} containing the given elements.
	 *
	 * @param <E> the type of the {@code Set}'s elements.
	 * @param values the {@code Set}'s elements.
	 *
	 * @return the created {@code ImmutableSet}.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 */
	public static <E> Set<E> of(E... values)
	{
		return new ImmutableSet<E>(new LinkedHashSet<E>(Arrays.asList(values)));
	}

	private final Set<E> inner;

	private ImmutableSet(Set<E> inner)
	{
		this.inner = inner;
	}

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

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

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

	@Override
	public Iterator<E> iterator()
	{
		return new UnmodifiableIterator<E>(inner.iterator());
	}

	@Override
	public Object[] toArray()
	{
		return inner.toArray();
	}

	@Override
	public <T> T[] toArray(T[] a)
	{
		return inner.toArray(a);
	}

	@Override
	public boolean add(E e)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean remove(Object o)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsAll(Collection<?> c)
	{
		return inner.containsAll(c);
	}

	@Override
	public boolean addAll(Collection<? extends E> c)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean retainAll(Collection<?> c)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean removeAll(Collection<?> c)
	{
		throw new UnsupportedOperationException();
	}

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


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.time;

import static org.kocakosm.pitaya.math.Numbers.*;
import static java.util.concurrent.TimeUnit.*;

import org.kocakosm.pitaya.util.Parameters;

import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * A duration. Instances of this class are immutable. Implementation note: the
 * duration is stored as milliseconds in a {@code long} value, thus some methods
 * on this class are subject to overflow.
 *
 * @author Osman KOCAK
 */
public final class Duration implements Comparable<Duration>, Serializable
{
	private static final long serialVersionUID = 2629881563004939761L;

	/** Zero millisecond. */
	public static final Duration ZERO = new Duration(0L);

	/** One millisecond. */
	public static final Duration ONE_MILLISECOND = new Duration(1L);

	/** One second. */
	public static final Duration ONE_SECOND = new Duration(1000L);

	/** One minute. */
	public static final Duration ONE_MINUTE = new Duration(60000L);

	/** One hour. */
	public static final Duration ONE_HOUR = new Duration(3600000L);

	/** One day. */
	public static final Duration ONE_DAY = new Duration(86400000L);

	/** One week. */
	public static final Duration ONE_WEEK = new Duration(604800000L);

	/**
	 * Parses the given duration's {@code String} representation into a
	 * {@code Duration} instance. The given {@code String} must be a list
	 * of value/unit pairs separated by ' ', '+', '-', '\n', '\t', '\r',
	 * {@code '&'} or ',' characters or by the "and" {@code String}. Values
	 * must be integer (either positive or negative) values. Valid units are:
	 * "milliseconds", "millisecond", "millis" and "ms" for milliseconds,
	 * "seconds", "second", "sec", "secs" and "s" for seconds, "minutes",
	 * "minute", "min", "mins" and "m" for minutes, "hours", "hour" and "h"
	 * for hours, "days", "day" and "d" for days. Values must be separated
	 * from their unit by a whitespace character. Parsing is not case
	 * sensitive.
	 *
	 * @param duration the {@code String} to parse into a {@code Duration}.
	 *
	 * @return the parsed {@code Duration}.
	 *
	 * @throws NullPointerException if {@code duration} is {@code null}.
	 * @throws IllegalArgumentException if {@code duration} can't be parsed.
	 * @throws ArithmeticException if the total duration value overflows the
	 *	storage capacity of this class.
	 */
	public static Duration parse(String duration)
	{
		String[] tokens = duration.toLowerCase().replace("and", "")
			.replaceAll("[,&\\+]", " ").replaceAll("-\\s+", "-")
			.replace("--", "").replaceAll("[\n\t\r]", " ").trim()
			.replaceAll("\\s+", " ").split("\\s");
		Parameters.checkCondition(tokens.length % 2 == 0);
		long ms = 0L;
		for (int i = 0; i < tokens.length; i += 2) {
			long v = Long.parseLong(tokens[i]);
			TimeUnit u = parseTimeUnit(tokens[i + 1]);
			ms = safeAdd(ms, MILLISECONDS.convert(v, u));
		}
		return new Duration(ms);
	}

	private static TimeUnit parseTimeUnit(String unit)
	{
		for (Unit u : Unit.values()) {
			for (String name : u.names) {
				if (unit.equals(name)) {
					return u.unit;
				}
			}
		}
		throw new IllegalArgumentException("Unknown unit: " + unit);
	}

	private enum Unit
	{
		MS(MILLISECONDS, "milliseconds", "millisecond", "millis", "ms"),
		S(SECONDS, "seconds", "second", "sec", "secs", "s"),
		M(MINUTES, "minutes", "minute", "min", "mins", "m"),
		H(HOURS, "hours", "hour", "h"),
		D(DAYS, "days", "day", "d");

		private final TimeUnit unit;
		private final String[] names;

		private Unit(TimeUnit unit, String... names)
		{
			this.unit = unit;
			this.names = names;
		}
	}

	/**
	 * Creates a new {@code Duration} representing the given amount in the
	 * given unit.
	 *
	 * @param amount the amount.
	 * @param unit the unit in which the amout is given.
	 *
	 * @return the created {@code Duration} instance.
	 *
	 * @throws NullPointerException if {@code unit} is {@code null}.
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public static Duration of(long amount, TimeUnit unit)
	{
		long duration = MILLISECONDS.convert(amount, unit);
		if (unit.convert(duration, MILLISECONDS) != amount) {
			throw new ArithmeticException(
				"Too large duration: " + amount + " " + unit);
		}
		return new Duration(duration);
	}

	/**
	 * Creates a new {@code Duration} instance by summing all the given
	 * {@code Duration}s' values.
	 *
	 * @param durations the {@code Duration}s to sum.
	 *
	 * @return the created {@code Duration}.
	 *
	 * @throws NullPointerException if {@code durations} is {@code null} or
	 *	if it contains {@code null} reference.
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public static Duration of(Duration... durations)
	{
		if (durations.length == 0) {
			return new Duration(0L);
		}
		long ms = durations[0].milliseconds;
		for (int i = 1; i < durations.length; i++) {
			ms = safeAdd(ms, durations[i].milliseconds);
		}
		return new Duration(ms);
	}

	/**
	 * Creates a new {@code Duration} instance representing the amount of
	 * time elapsed between the given dates.
	 *
	 * @param start the start date.
	 * @param end the end date.
	 *
	 * @return the duration between {@code start} and {@code end}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static Duration between(Date start, Date end)
	{
		return new Duration(end.getTime() - start.getTime());
	}

	/**
	 * Creates a new {@code Duration} instance representing the amount of
	 * time elapsed since the given date.
	 *
	 * @param d the start date.
	 *
	 * @return the duration since {@code d}.
	 *
	 * @throws NullPointerException if {@code d} is {@code null}.
	 */
	public static Duration since(Date d)
	{
		return new Duration(now() - d.getTime());
	}

	/**
	 * Creates a new {@code Duration} instance representing the amount of
	 * time that will elapse until the given date.
	 *
	 * @param d the end date.
	 *
	 * @return the duration until {@code d}.
	 *
	 * @throws NullPointerException if {@code d} is {@code null}.
	 */
	public static Duration until(Date d)
	{
		return new Duration(d.getTime() - now());
	}

	private static long now()
	{
		return System.currentTimeMillis();
	}

	private final long milliseconds;

	private Duration(long milliseconds)
	{
		this.milliseconds = milliseconds;
	}

	/**
	 * Returns the result of adding the given duration to this duration.
	 *
	 * @param d the duration to add.
	 *
	 * @return the sum of this duration and the given one.
	 *
	 * @throws NullPointerException if {@code d} is {@code null}.
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration plus(Duration d)
	{
		return new Duration(safeAdd(milliseconds, d.milliseconds));
	}

	/**
	 * Returns the result of subtracting the given duration from this one.
	 *
	 * @param d the duration to subtract.
	 *
	 * @return the difference between this duration and the given one.
	 *
	 * @throws NullPointerException if {@code d} is {@code null}.
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration minus(Duration d)
	{
		return new Duration(safeSubtract(milliseconds, d.milliseconds));
	}

	/**
	 * Returns the result of multiplying this duration by the given factor.
	 *
	 * @param factor the multiplication factor.
	 *
	 * @return the product of this duration by the given factor.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration multipliedBy(int factor)
	{
		return new Duration(safeMultiply(milliseconds, factor));
	}

	/**
	 * Returns the result of dividing this duration by the given divisor.
	 *
	 * @param divisor the divisor.
	 *
	 * @return the resulting duration.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration dividedBy(int divisor)
	{
		return new Duration(safeDivide(milliseconds, divisor));
	}

	/**
	 * Returns the sign of this duration, namely it returns {@code 1} if
	 * this duration is positive, {@code 0} if it is equal to {@code 0}, and
	 * {@code -1} if it is negative.
	 *
	 * @return the sign of this duration.
	 */
	public int sign()
	{
		return milliseconds > 0L ? 1 : milliseconds < 0L ? -1 : 0;
	}

	/**
	 * Returns the result of negating this duration.
	 *
	 * @return the negated duration.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration negated()
	{
		return new Duration(safeNegate(milliseconds));
	}

	/**
	 * Returns a copy of this duration with a positive length.
	 *
	 * @return a copy of this duration with a positive length.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Duration absoluteValue()
	{
		return new Duration(safeAbs(milliseconds));
	}

	/**
	 * Returns the date obtained by adding this duration to the given date.
	 *
	 * @param d the origin date.
	 *
	 * @return the date obtained by adding this duration to the given date.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Date after(Date d)
	{
		return new Date(safeAdd(d.getTime(), milliseconds));
	}

	/**
	 * Returns the date obtained by subtracting this duration from the given
	 * date.
	 *
	 * @param d the origin date.
	 *
	 * @return the date obtained by subtracting this duration from the given
	 * date.
	 *
	 * @throws ArithmeticException if the resulting duration is too large.
	 */
	public Date before(Date d)
	{
		return new Date(safeSubtract(d.getTime(), milliseconds));
	}

	/**
	 * Converts this duration into the given unit. Beware of possible
	 * precision loss.
	 *
	 * @param unit the target unit.
	 *
	 * @return the value of the duration in the given unit.
	 *
	 * @throws NullPointerException if {@code unit} is {@code null}.
	 */
	public long to(TimeUnit unit)
	{
		return unit.convert(milliseconds, MILLISECONDS);
	}

	/**
	 * Returns the number of milliseconds in this duration.
	 *
	 * @return the number of milliseconds in this duration.
	 */
	public long toMilliseconds()
	{
		return milliseconds;
	}

	@Override
	public int compareTo(Duration d)
	{
		try {
			long c = safeSubtract(milliseconds, d.milliseconds);
			return c > 0L ? 1 : c < 0L ? -1 : 0;
		} catch (ArithmeticException e) {
			return sign();
		}
	}

	@Override
	public boolean equals(Object o)
	{
		if (o == this) {
			return true;
		}
		if (!(o instanceof Duration)) {
			return false;
		}
		final Duration d = (Duration) o;
		return milliseconds == d.milliseconds;
	}

	@Override
	public int hashCode()
	{
		return (int) (milliseconds ^ (milliseconds >>> 32));
	}

	@Override
	public String toString()
	{
		if (milliseconds == 0L) {
			return "0 millisecond";
		}
		StringBuilder sb = new StringBuilder();
		long time = milliseconds;
		long days = MILLISECONDS.toDays(time);
		time -= DAYS.toMillis(days);
		append(sb, days, "day");
		long hours = MILLISECONDS.toHours(time);
		time -= HOURS.toMillis(hours);
		append(sb, hours, "hour");
		long minutes = MILLISECONDS.toMinutes(time);
		time -= MINUTES.toMillis(minutes);
		append(sb, minutes, "minute");
		long seconds = MILLISECONDS.toSeconds(time);
		time -= SECONDS.toMillis(seconds);
		append(sb, seconds, "second");
		append(sb, time, "millisecond");
		return sb.toString();
	}

	private void append(StringBuilder sb, long value, String unit)
	{
		if (value != 0L) {
			if (sb.length() > 0) {
				sb.append(", ");
			}
			sb.append(value).append(' ').append(unit);
			if (value != 1L && value != -1L) {
				sb.append('s');
			}
		}
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.math;

import org.kocakosm.pitaya.util.Parameters;

import java.math.BigDecimal;

/**
 * Extra math utilities.
 *
 * @author Osman KOCAK
 */
public final class Numbers
{
	/**
	 * Returns the maximum of the given values.
	 *
	 * @param values a non-empty array of {@code long} values.
	 *
	 * @return the maximum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static long max(long... values)
	{
		Parameters.checkCondition(values.length > 0);
		long max = values[0];
		for (int i = 1; i < values.length; i++) {
			max = Math.max(max, values[i]);
		}
		return max;
	}

	/**
	 * Returns the maximum of the given values.
	 *
	 * @param values a non-empty array of {@code int} values.
	 *
	 * @return the maximum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static int max(int... values)
	{
		Parameters.checkCondition(values.length > 0);
		int max = values[0];
		for (int i = 1; i < values.length; i++) {
			max = Math.max(max, values[i]);
		}
		return max;
	}

	/**
	 * Returns the maximum of the given values, using the same comparison
	 * rules as {@link Math#max(float, float)}.
	 *
	 * @param values a non-empty array of {@code float} values.
	 *
	 * @return the maximum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static float max(float... values)
	{
		Parameters.checkCondition(values.length > 0);
		float max = values[0];
		for (int i = 1; i < values.length; i++) {
			max = Math.max(max, values[i]);
		}
		return max;
	}

	/**
	 * Returns the maximum of the given values, using the same comparison
	 * rules as {@link Math#max(double, double)}.
	 *
	 * @param values a non-empty array of {@code double} values.
	 *
	 * @return the maximum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static double max(double... values)
	{
		Parameters.checkCondition(values.length > 0);
		double max = values[0];
		for (int i = 1; i < values.length; i++) {
			max = Math.max(max, values[i]);
		}
		return max;
	}

	/**
	 * Returns the minimum of the given values.
	 *
	 * @param values a non-empty array of {@code long} values.
	 *
	 * @return the minimum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static long min(long... values)
	{
		Parameters.checkCondition(values.length > 0);
		long min = values[0];
		for (int i = 1; i < values.length; i++) {
			min = Math.min(min, values[i]);
		}
		return min;
	}

	/**
	 * Returns the minimum of the given values.
	 *
	 * @param values a non-empty array of {@code int} values.
	 *
	 * @return the minimum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static int min(int... values)
	{
		Parameters.checkCondition(values.length > 0);
		int min = values[0];
		for (int i = 1; i < values.length; i++) {
			min = Math.min(min, values[i]);
		}
		return min;
	}

	/**
	 * Returns the minimum of the given values, using the same comparison
	 * rules as {@link Math#min(float, float)}.
	 *
	 * @param values a non-empty array of {@code float} values.
	 *
	 * @return the minimum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static float min(float... values)
	{
		Parameters.checkCondition(values.length > 0);
		float min = values[0];
		for (int i = 1; i < values.length; i++) {
			min = Math.min(min, values[i]);
		}
		return min;
	}

	/**
	 * Returns the minimum of the given values, using the same comparison
	 * rules as {@link Math#min(double, double)}.
	 *
	 * @param values a non-empty array of {@code double} values.
	 *
	 * @return the minimum of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static double min(double... values)
	{
		Parameters.checkCondition(values.length > 0);
		double min = values[0];
		for (int i = 1; i < values.length; i++) {
			min = Math.min(min, values[i]);
		}
		return min;
	}

	/**
	 * Returns the arithmetic mean of the given values. This method is not
	 * subject to overflow.
	 *
	 * @param values the values.
	 *
	 * @return the arithmetic mean of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static double mean(int... values)
	{
		Parameters.checkCondition(values.length > 0);
		long sum = 0L;
		for (int value : values) {
			sum += value;
		}
		return (double) sum / values.length;
	}

	/**
	 * Returns the arithmetic mean of the given values. This method is not
	 * subject to overflow.
	 *
	 * @param values the values.
	 *
	 * @return the arithmetic mean of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 */
	public static double mean(long... values)
	{
		Parameters.checkCondition(values.length > 0);
		BigDecimal sum = BigDecimal.ZERO;
		for (long value : values) {
			sum = sum.add(BigDecimal.valueOf(value));
		}
		return sum.divide(BigDecimal.valueOf(values.length)).doubleValue();
	}

	/**
	 * Returns the arithmetic mean of the given values. This method is not
	 * subject to overflow.
	 *
	 * @param values the values.
	 *
	 * @return the arithmetic mean of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 * @throws NumberFormatException if {@code values} contains a NaN or an
	 *	infinite value.
	 */
	public static double mean(double... values)
	{
		Parameters.checkCondition(values.length > 0);
		BigDecimal sum = BigDecimal.ZERO;
		for (double value : values) {
			sum = sum.add(BigDecimal.valueOf(value));
		}
		return sum.divide(BigDecimal.valueOf(values.length)).doubleValue();
	}

	/**
	 * Returns the arithmetic mean of the given values. This method is not
	 * subject to overflow.
	 *
	 * @param values the values.
	 *
	 * @return the arithmetic mean of the given values.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 * @throws IllegalArgumentException if {@code values} is empty.
	 * @throws NumberFormatException if {@code values} contains a NaN or an
	 *	infinite value.
	 */
	public static double mean(float... values)
	{
		Parameters.checkCondition(values.length > 0);
		BigDecimal sum = BigDecimal.ZERO;
		for (float value : values) {
			sum = sum.add(BigDecimal.valueOf(value));
		}
		return sum.divide(BigDecimal.valueOf(values.length)).doubleValue();
	}

	/**
	 * Returns whether the given value is a "real" value. Namely, it returns
	 * {@code false} if the given value is infinite or NaN, and {@code true}
	 * in all other cases.
	 *
	 * @param f the value to test.
	 *
	 * @return whether the given value is a "real" value.
	 */
	public static boolean isFinite(float f)
	{
		return !Float.isInfinite(f) && !Float.isNaN(f);
	}

	/**
	 * Returns whether the given value is a "real" value. Namely, it returns
	 * {@code false} if the given value is infinite or NaN, and {@code true}
	 * in all other cases.
	 *
	 * @param d the value to test.
	 *
	 * @return whether the given value is a "real" value.
	 */
	public static boolean isFinite(double d)
	{
		return !Double.isInfinite(d) && !Double.isNaN(d);
	}

	/**
	 * Returns the absolute value of the given value, provided the result
	 * fits into a {@code long}.
	 *
	 * @param a the value.
	 *
	 * @return the absolute value of {@code a}.
	 *
	 * @throws ArithmeticException if the absolute value of {@code a} can't
	 *	be represented by a {@code long}.
	 */
	public static long safeAbs(long a)
	{
		if (a == Long.MIN_VALUE) {
			throw new ArithmeticException(
				"Long overflow: abs(" + a + ")");
		}
		return Math.abs(a);
	}

	/**
	 * Returns the absolute value of the given value, provided the result
	 * fits into an {@code int}.
	 *
	 * @param a the value.
	 *
	 * @return the absolute value of {@code a}.
	 *
	 * @throws ArithmeticException if the absolute value of {@code a} can't
	 *	be represented by an {@code int}.
	 */
	public static int safeAbs(int a)
	{
		if (a == Integer.MIN_VALUE) {
			throw new ArithmeticException(
				"Int overflow: abs(" + a + ")");
		}
		return Math.abs(a);
	}

	/**
	 * Returns the opposite of the given value, provided the result fits
	 * into a {@code long}.
	 *
	 * @param a the value to negate.
	 *
	 * @return {@code -a}.
	 *
	 * @throws ArithmeticException if {@code -a} can't be represented by a
	 *	{@code long}.
	 */
	public static long safeNegate(long a)
	{
		if (a == Long.MIN_VALUE) {
			throw new ArithmeticException("Long overflow: -" + a);
		}
		return -a;
	}

	/**
	 * Returns the opposite of the given value, provided the result fits
	 * into an {@code int}.
	 *
	 * @param a the value to negate.
	 *
	 * @return {@code -a}.
	 *
	 * @throws ArithmeticException if {@code -a} can't be represented by an
	 *	{@code int}.
	 */
	public static int safeNegate(int a)
	{
		if (a == Integer.MIN_VALUE) {
			throw new ArithmeticException("Int overflow: -" + a);
		}
		return -a;
	}

	/**
	 * Returns the sum of {@code a} and {@code b}, provided the result fits
	 * into a {@code long}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a + b}.
	 *
	 * @throws ArithmeticException if {@code a + b} can't be represented by
	 *	a {@code long}.
	 */
	public static long safeAdd(long a, long b)
	{
		long sum = a + b;
		if (a < 0L != sum < 0L && a < 0L == b < 0L) {
			throw new ArithmeticException(
				"Long overflow: " + a + " + " + b);
		}
		return sum;
	}

	/**
	 * Returns the sum of {@code a} and {@code b}, provided the result fits
	 * into an {@code int}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a + b}.
	 *
	 * @throws ArithmeticException if {@code a + b} can't be represented by
	 *	an {@code int}.
	 */
	public static int safeAdd(int a, int b)
	{
		int sum = a + b;
		if (a < 0 != sum < 0 && a < 0 == b < 0) {
			throw new ArithmeticException(
				"Int overflow: " + a + " + " + b);
		}
		return sum;
	}

	/**
	 * Returns the difference of {@code a} and {@code b}, provided the
	 * result fits into a {@code long}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a - b}.
	 *
	 * @throws ArithmeticException if {@code a - b} can't be represented by
	 *	a {@code long}.
	 */
	public static long safeSubtract(long a, long b)
	{
		long diff = a - b;
		if (a < 0L != diff < 0L && a < 0L != b < 0L) {
			throw new ArithmeticException(
				"Long overflow: " + a + " - " + b);
		}
		return diff;
	}

	/**
	 * Returns the difference of {@code a} and {@code b}, provided the
	 * result fits into an {@code int}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a - b}.
	 *
	 * @throws ArithmeticException if {@code a - b} can't be represented by
	 *	an {@code int}.
	 */
	public static int safeSubtract(int a, int b)
	{
		int diff = a - b;
		if (a < 0 != diff < 0 && a < 0 != b < 0) {
			throw new ArithmeticException(
				"Int overflow: " + a + " - " + b);
		}
		return diff;
	}

	/**
	 * Returns the product of {@code a} and {@code b}, provided the result
	 * fits into a {@code long}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a * b}.
	 *
	 * @throws ArithmeticException if {@code a * b} can't be represented by
	 *	a {@code long}.
	 */
	public static long safeMultiply(long a, long b)
	{
		if (a == 0L || b == 0L) {
			return 0L;
		}
		long max = a < 0L == b < 0L ? Long.MAX_VALUE : Long.MIN_VALUE;
		if ((b > 0L && b > max / a) || (b < 0L && b < max / a)) {
			throw new ArithmeticException(
				"Long overflow: " + a + " * " + b);
		}
		return a * b;
	}

	/**
	 * Returns the product of {@code a} and {@code b}, provided the result
	 * fits into an {@code int}.
	 *
	 * @param a the first operand.
	 * @param b the second operand.
	 *
	 * @return {@code a * b}.
	 *
	 * @throws ArithmeticException if {@code a * b} can't be represented by
	 *	an {@code int}.
	 */
	public static int safeMultiply(int a, int b)
	{
		if (a == 0 || b == 0) {
			return 0;
		}
		long max = a < 0 == b < 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
		if ((b > 0 && b > max / a) || (b < 0 && b < max / a)) {
			throw new ArithmeticException(
				"Int overflow: " + a + " * " + b);
		}
		return a * b;
	}

	/**
	 * Returns the result of the integer division of the first number by the
	 * second, provided the result fits into a {@code long}.
	 *
	 * @param a the dividend.
	 * @param b the divisor.
	 *
	 * @return {@code a / b}.
	 *
	 * @throws ArithmeticException if {@code b} is equal to {@code 0} or if
	 *	{@code a / b} can't be represented by a {@code long}.
	 */
	public static long safeDivide(long a, long b)
	{
		if (a == Long.MIN_VALUE && b == -1L) {
			throw new ArithmeticException(
				"Long overflow: " + a + " / " + b);
		}
		return a / b;
	}

	/**
	 * Returns the result of the integer division of the first number by the
	 * second, provided the result fits into an {@code int}.
	 *
	 * @param a the dividend.
	 * @param b the divisor.
	 *
	 * @return {@code a / b}.
	 *
	 * @throws ArithmeticException if {@code b} is equal to {@code 0} or if
	 *	{@code a / b} can't be represented by an {@code int}.
	 */
	public static int safeDivide(int a, int b)
	{
		if (a == Integer.MIN_VALUE && b == -1L) {
			throw new ArithmeticException(
				"Int overflow: " + a + " / " + b);
		}
		return a / b;
	}

	/**
	 * Returns the value of the first argument raised to the power of the
	 * second, provided the result fits into a {@code long}. This method
	 * implements the exponentiation by squaring method. Please refer to
	 *
	 * Wikipedia for further information.
	 *
	 * @param a the base.
	 * @param b the exponent.
	 *
	 * @return {@code a ^ b}.
	 *
	 * @throws IllegalArgumentException if {@code b < 0}.
	 * @throws ArithmeticException if {@code a ^ b} can't be represented by
	 *	a {@code long}.
	 */
	public static long safePow(long a, int b)
	{
		Parameters.checkCondition(b >= 0);
		if (b == 0) {
			return 1L;
		}
		long base = a;
		int exponent = b;
		long result = 1L;
		try {
			while (exponent > 1) {
				if ((exponent & 1) != 0) {
					result = safeMultiply(result, base);
					exponent -= 1;
				}
				base = safeMultiply(base, base);
				exponent >>= 1;
			}
			return safeMultiply(result, base);
		} catch (ArithmeticException e) {
			throw new ArithmeticException(
				"Long overflow: " + a + " ^ " + b);
		}
	}

	/**
	 * Returns the value of the first argument raised to the power of the
	 * second, provided the result fits into an {@code int}. This method
	 * implements the exponentiation by squaring method. Please refer to
	 *
	 * Wikipedia for further information.
	 *
	 * @param a the base.
	 * @param b the exponent.
	 *
	 * @return {@code a ^ b}.
	 *
	 * @throws IllegalArgumentException if {@code b < 0}.
	 * @throws ArithmeticException if {@code a ^ b} can't be represented by
	 *	an {@code int}.
	 */
	public static int safePow(int a, int b)
	{
		Parameters.checkCondition(b >= 0);
		if (b == 0) {
			return 1;
		}
		int base = a;
		int exponent = b;
		int result = 1;
		try {
			while (exponent > 1) {
				if ((exponent & 1) != 0) {
					result = safeMultiply(result, base);
					exponent -= 1;
				}
				base = safeMultiply(base, base);
				exponent >>= 1;
			}
			return safeMultiply(result, base);
		} catch (ArithmeticException e) {
			throw new ArithmeticException(
				"Int overflow: " + a + " ^ " + b);
		}
	}

	/**
	 * Returns the greatest common divisor of the absolute value of the
	 * given two numbers. This method first computes the absolute values of
	 * the given numbers, thus it doesn't accept {@code Long.MIN_VALUE}.
	 * Also, note that if one of the given values is {@code 0}, this method
	 * will return the absolute value of the second argument.
	 * This method implements the binary GCD algorithm (also known as
	 * Stein's algorithm). For further information on this algorithm, please
	 * refer to
	 * Wikipedia.
	 *
	 * @param a the first number.
	 * @param b the second number.
	 *
	 * @return the greatest common divisor of {@code a} and {@code b}.
	 *
	 * @throws ArithmeticException if {@code a} or {@code b} is equal to
	 *	{@code Long.MIN_VALUE}.
	 */
	public static long gcd(long a, long b)
	{
		if (a < 0L || b < 0L) {
			try {
				return gcd(safeAbs(a), safeAbs(b));
			} catch (ArithmeticException e) {
				throw new ArithmeticException(
					"Long overflow: gcd(" + a + ", " + b + ")");
			}
		}
		if (a == 0L) {
			return b;
		}
		if (b == 0L) {
			return a;
		}
		int shift = 0;
		while (((a | b) & 1L) == 0) {
			a >>= 1;
			b >>= 1;
			shift++;
		}
		while ((a & 1L) == 0L) {
			a >>= 1;
		}
		do {
			while ((b & 1L) == 0L) {
				b >>= 1;
			}
			if (a > b) {
				long tmp = b;
				b = a;
				a = tmp;
			}
			b -= a;
		} while (b != 0L);
		return a << shift;
	}

	/**
	 * Returns the greatest common divisor of the absolute value of the
	 * given two numbers. This method first computes the absolute values of
	 * the given numbers, thus it doesn't accept {@code Integer.MIN_VALUE}.
	 * Also, note that if one of the given values is {@code 0}, this method
	 * will return the absolute value of the second argument.
	 * This method implements the binary GCD algorithm (also known as
	 * Stein's algorithm). For further information on this algorithm, please
	 * refer to
	 * Wikipedia.
	 *
	 * @param a the first number.
	 * @param b the second number.
	 *
	 * @return the greatest common divisor of {@code a} and {@code b}.
	 *
	 * @throws ArithmeticException if {@code a} or {@code b} is equal to
	 *	{@code Integer.MIN_VALUE}.
	 */
	public static int gcd(int a, int b)
	{
		try {
			if (a < 0 || b < 0) {
				return gcd(safeAbs(a), safeAbs(b));
			}
			return (int) gcd((long) a, (long) b);
		} catch (ArithmeticException e) {
			throw new ArithmeticException(
				"Int overflow: gcd(" + a + ", " + b + ")");
		}
	}

	/**
	 * Returns the least common multiple of the absolute value of the given
	 * two numbers. This method first computes the absolute values of the
	 * given numbers, thus it doesn't accept {@code Long.MIN_VALUE}. Also,
	 * note that if one of the given values is {@code 0}, this method will
	 * return the absolute value of the second argument. This method uses
	 * the formula {@code lcm(a,b) = (a / gcd(a,b)) * b}. Please refer to
	 *
	 * Wikipedia for further information.
	 *
	 * @param a the first number.
	 * @param b the second number.
	 *
	 * @return the least common multiple of {@code a} and {@code b}.
	 *
	 * @throws ArithmeticException if {@code a} or {@code b} is equal to
	 *	{@code Long.MIN_VALUE}.
	 */
	public static long lcm(long a, long b)
	{
		try {
			if (a < 0L || b < 0L) {
				return lcm(safeAbs(a), safeAbs(b));
			}
			if (a == 0L) {
				return b;
			}
			if (b == 0L) {
				return a;
			}
			return safeMultiply(a / gcd(a, b), b);
		} catch (ArithmeticException e) {
			throw new ArithmeticException(
				"Long overflow: lcm(" + a + ", " + b + ")");
		}
	}

	/**
	 * Returns the least common multiple of the absolute value of the given
	 * two numbers. This method first computes the absolute values of the
	 * given numbers, thus it doesn't accept {@code Integer.MIN_VALUE}.
	 * Also, note that if one of the given values is {@code 0}, this method
	 * will return the absolute value of the second argument. This method
	 * uses the formula {@code lcm(a,b) = (a / gcd(a,b)) * b}. Please refer
	 * to
	 * Wikipedia for further information.
	 *
	 * @param a the first number.
	 * @param b the second number.
	 *
	 * @return the least common multiple of {@code a} and {@code b}.
	 *
	 * @throws ArithmeticException if {@code a} or {@code b} is equal to
	 *	{@code Integer.MIN_VALUE}.
	 */
	public static int lcm(int a, int b)
	{
		try {
			if (a < 0 || b < 0) {
				return lcm(safeAbs(a), safeAbs(b));
			}
			if (a == 0) {
				return b;
			}
			if (b == 0) {
				return a;
			}
			return safeMultiply(a / gcd(a, b), b);
		} catch (ArithmeticException e) {
			throw new ArithmeticException(
				"Int overflow: lcm(" + a + ", " + b + ")");
		}
	}

	/**
	 * Casts the given {@code long} value to {@code int}, if possible.
	 *
	 * @param a the value to cast to {@code int}.
	 *
	 * @return the casted value.
	 *
	 * @throws ArithmeticException if {@code a} cannot be cast into an
	 *	{@code int}.
	 */
	public static int safeToInt(long a)
	{
		if (a >= Integer.MIN_VALUE && a <= Integer.MAX_VALUE) {
			return (int) a;
		}
		throw new ArithmeticException(a + " can't be cast to int");
	}

	private Numbers()
	{
		/* ... */
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.util;

/**
 * Utility class to test parameters validity.
 *
 * @author Osman KOCAK
 */
public final class Parameters
{
	/**
	 * Checks that the given reference is not {@code null} and returns it
	 * in case of success.
	 *
	 * @param <T> the type of the given reference.
	 * @param ref the reference to test.
	 *
	 * @return the validated (non-{@code null}) reference.
	 *
	 * @throws NullPointerException if the given reference is {@code null}.
	 */
	public static <T> T checkNotNull(T ref)
	{
		if (ref == null) {
			throw new NullPointerException();
		}
		return ref;
	}

	/**
	 * Checks that the given reference is not {@code null} and returns it
	 * in case of success.
	 *
	 * @param <T> the type of the given reference.
	 * @param ref the reference to test.
	 * @param msg the error message format string.
	 * @param args the error message arguments.
	 *
	 * @return the validated (non-{@code null}) reference.
	 *
	 * @throws NullPointerException if the given reference is {@code null}.
	 */
	public static <T> T checkNotNull(T ref, String msg, Object... args)
	{
		if (ref == null) {
			throw new NullPointerException(format(msg, args));
		}
		return ref;
	}

	/**
	 * Checks the truth of the given condition checking parameters validity.
	 *
	 * @param condition the boolean condition to test.
	 *
	 * @throws IllegalArgumentException if the condition is {@code false}.
	 */
	public static void checkCondition(boolean condition)
	{
		if (!condition) {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * Checks the truth of the given condition checking parameters validity.
	 *
	 * @param condition the boolean condition to test.
	 * @param msg the error message format string.
	 * @param args the error message arguments.
	 *
	 * @throws IllegalArgumentException if the condition is {@code false}.
	 */
	public static void checkCondition(boolean condition, String msg,
		Object... args)
	{
		if (!condition) {
			throw new IllegalArgumentException(format(msg, args));
		}
	}

	/**
	 * Checks the type of the given reference and, in case of success, casts
	 * and returns it.
	 *
	 * @param <T> the expected type of the given reference.
	 * @param ref the reference to test.
	 * @param type the expected type of the given reference.
	 *
	 * @return the given reference in the expected type.
	 *
	 * @throws NullPointerException if {@code type} is {@code null}.
	 * @throws ClassCastException if the given reference doesn't represent
	 *	an instance of the expected class.
	 */
	public static <T> T checkType(Object ref, Class<T> type)
	{
		if (type.isInstance(ref)) {
			return type.cast(ref);
		}
		throw new ClassCastException();
	}

	/**
	 * Checks the type of the given reference and, in case of success, casts
	 * and returns it.
	 *
	 * @param <T> the expected type of the given reference.
	 * @param ref the reference to test.
	 * @param type the expected type of the given reference.
	 * @param msg the error message format string.
	 * @param args the error message arguments.
	 *
	 * @return the given reference in the expected type.
	 *
	 * @throws NullPointerException if {@code type} is {@code null}.
	 * @throws ClassCastException if the given reference doesn't represent
	 *	an instance of the given class.
	 */
	public static <T> T checkType(Object ref, Class<T> type, String msg,
		Object... args)
	{
		if (type.isInstance(ref)) {
			return type.cast(ref);
		}
		throw new ClassCastException(format(msg, args));
	}

	private static String format(String format, Object... args)
	{
		if (format == null) {
			return null;
		}
		return args != null ? String.format(format, args) : format;
	}

	private Parameters()
	{
		/* ... */
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.util;

/**
 * Static utility methods that operate on or return boolean values.
 *
 * @author Osman KOCAK
 */
public final class Booleans
{
	/**
	 * The {@code Boolean} object corresponding to the primitive value
	 * {@code true}.
	 */
	public static final Boolean TRUE = Boolean.TRUE;

	/**
	 * The {@code Boolean} object corresponding to the primitive value
	 * {@code false}.
	 */
	public static final Boolean FALSE = Boolean.FALSE;

	/**
	 * Performs a logical "and" on the given values, that is, this method
	 * returns {@code true} if, and only if, all the given {@code Boolean}
	 * values are {@code true}, and {@code false} in all other cases.
	 *
	 * @param bools the {@code Boolean} values.
	 *
	 * @return the result of the logical "and" on the given values.
	 *
	 * @throws NullPointerException if {@code bools} is {@code null} or if
	 *	it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code bools} is empty.
	 */
	public static Boolean and(Boolean... bools)
	{
		Parameters.checkCondition(bools.length > 0);
		for (Boolean bool : bools) {
			if (!bool) {
				return FALSE;
			}
		}
		return TRUE;
	}

	/**
	 * Performs a logical "or" on the given values, that is, this method
	 * returns {@code true} if, and only if, at least one of the given
	 * {@code Boolean} values is {@code true}, and {@code false} in all
	 * other cases.
	 *
	 * @param bools the {@code Boolean} values.
	 *
	 * @return the result of the logical "or" on the given values.
	 *
	 * @throws NullPointerException if {@code bools} is {@code null} or if
	 *	it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code bools} is empty.
	 */
	public static Boolean or(Boolean... bools)
	{
		Parameters.checkCondition(bools.length > 0);
		for (Boolean bool : bools) {
			if (bool) {
				return TRUE;
			}
		}
		return FALSE;
	}

	/**
	 * Returns the negation of the given {@code Boolean}.
	 *
	 * @param bool the {@code Boolean} to negate.
	 *
	 * @return the negation of the given {@code Boolean}.
	 *
	 * @throws NullPointerException if {@code bool} is {@code null}.
	 */
	public static Boolean not(Boolean bool)
	{
		return bool ? FALSE : TRUE;
	}

	/**
	 * Performs an "exclusive or" on the given values, that is, this method
	 * returns {@code true} if, and only if, only one of the given
	 * {@code Boolean} values is {@code true}, and {@code false} in all
	 * other cases.
	 *
	 * @param bools the {@code Boolean} values.
	 *
	 * @return the result of the "exclusive or" on the given values.
	 *
	 * @throws NullPointerException if {@code bools} is {@code null} or if
	 *	it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code bools} is empty.
	 */
	public static Boolean xor(Boolean... bools)
	{
		Parameters.checkCondition(bools.length > 0);
		boolean xor = false;
		for (Boolean bool : bools) {
			if (bool) {
				if (xor) {
					return FALSE;
				} else {
					xor = true;
				}
			}
		}
		return xor ? TRUE : FALSE;
	}

	/**
	 * Returns {@code true} if and only if the given {@code Boolean} object
	 * is {@code false}.
	 *
	 * @param bool the {@code Boolean} to test, may be {@code null}.
	 *
	 * @return whether the given {@code Boolean} is {@code false}.
	 */
	public static boolean isFalse(Boolean bool)
	{
		return bool == null ? false : !bool;
	}

	/**
	 * Returns {@code true} if and only if the given {@code Boolean} object
	 * is not {@code false}.
	 *
	 * @param bool the {@code Boolean} to test, may be {@code null}.
	 *
	 * @return whether the given {@code Boolean} is not {@code false}.
	 */
	public static boolean isNotFalse(Boolean bool)
	{
		return bool == null ? true : bool;
	}

	/**
	 * Returns {@code true} if and only if the given {@code Boolean} object
	 * is {@code true}.
	 *
	 * @param bool the {@code Boolean} to test, may be {@code null}.
	 *
	 * @return whether the given {@code Boolean} is {@code true}.
	 */
	public static boolean isTrue(Boolean bool)
	{
		return bool == null ? false : bool;
	}

	/**
	 * Returns {@code true} if and only if the given {@code Boolean} object
	 * is not {@code true}.
	 *
	 * @param bool the {@code Boolean} to test, may be {@code null}.
	 *
	 * @return whether the given {@code Boolean} is not {@code true}.
	 */
	public static boolean isNotTrue(Boolean bool)
	{
		return bool == null ? true : !bool;
	}

	/**
	 * Parses the given {@code String} into a {@code Boolean}. This method
	 * returns {@code true} if the given {@code String} contains "true",
	 * "yes", "1" or "on".
	 *
	 * @param bool the {@code String} to parse.
	 *
	 * @return the parsed {@code Boolean}.
	 *
	 * @throws NullPointerException if {@code bool} is {@code null}.
	 */
	public static Boolean valueOf(String bool)
	{
		String s = bool.replaceAll("\\s", "");
		boolean b = "true".equalsIgnoreCase(s)
			|| "yes".equalsIgnoreCase(s)
			|| "on".equalsIgnoreCase(s)
			|| "1".equalsIgnoreCase(s);
		return b ? TRUE : FALSE;
	}

	/**
	 * Converts the given {@code int} into a {@code Boolean} using the
	 * convention that zero represents {@code false} and all other values
	 * represent {@code true}.
	 *
	 * @param i the {@code int} to convert.
	 *
	 * @return {@code false} if {@code i == 0}, {@code true} otherswise.
	 */
	public static Boolean valueOf(int i)
	{
		return i == 0 ? FALSE : TRUE;
	}

	/**
	 * Converts the given {@code Integer} into a {@code Boolean} using the
	 * convention that zero represents {@code false} and all other values
	 * represent {@code true}.
	 *
	 * @param i the {@code int} to convert.
	 *
	 * @return {@code false} if {@code i == 0}, {@code true} otherswise.
	 *
	 * @throws NullPointerException if {@code i} is {@code null}.
	 */
	public static Boolean valueOf(Integer i)
	{
		return valueOf(i.intValue());
	}

	/**
	 * Returns the {@code Boolean} instance representing the given primitive
	 * {@code boolean} value.
	 *
	 * @param bool the {@code boolean} value to convert.
	 *
	 * @return the {@code Boolean} instance representing the given value.
	 */
	public static Boolean valueOf(boolean bool)
	{
		return bool ? TRUE : FALSE;
	}

	/**
	 * Returns 1 if {@code bool} is {@code true}, 0 otherwise.
	 *
	 * @param bool the {@code boolean} value to convert.
	 *
	 * @return 1 if {@code bool} is {@code true}, 0 otherwise.
	 */
	public static Integer toInteger(boolean bool)
	{
		return bool ? 1 : 0;
	}

	/**
	 * Returns 1 if {@code bool} is {@code true}, 0 if {@code false}.
	 *
	 * @param bool the {@code Boolean} instance to convert.
	 *
	 * @return 1 if {@code bool} is {@code true}, 0 if {@code false}.
	 *
	 * @throws NullPointerException if {@code bool} is {@code null}.
	 */
	public static Integer toInteger(Boolean bool)
	{
		return toInteger(bool.booleanValue());
	}

	/**
	 * Returns the given {@code boolean}'s {@code String} representation.
	 *
	 * @param bool the {@code boolean} value to convert.
	 *
	 * @return {@code bool}'s {@code String} representation.
	 */
	public static String toString(boolean bool)
	{
		return bool ? "true" : "false";
	}

	private Booleans()
	{
		/* ... */
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Splits {@code String}s using a predefined separator. Instances of this class
 * are immutable.
 *
 * @see Joiner
 *
 * @author Osman KOCAK
 */
public final class Splitter
{
	/**
	 * Returns a new {@code Splitter} that will use the specified separator
	 * to split {@code String}s.
	 *
	 * @param separator the separator.
	 *
	 * @return the created {@code Splitter}.
	 *
	 * @throws NullPointerException if {@code separator} is {@code null}.
	 */
	public static Splitter on(String separator)
	{
		return on(Pattern.compile(Pattern.quote(separator)));
	}

	/**
	 * Returns a new {@code Splitter} that will use the specified pattern
	 * to split {@code String}s.
	 *
	 * @param pattern the separator pattern.
	 *
	 * @return the created {@code Splitter}.
	 *
	 * @throws NullPointerException if {@code pattern} is {@code null}.
	 */
	public static Splitter on(Pattern pattern)
	{
		return new Splitter(-1, false, pattern, null, null, "", false);
	}

	private final int limit;
	private final boolean trim;
	private final Pattern pattern;
	private final String prefix;
	private final String suffix;
	private final String forEmpty;
	private final boolean ignoreEmptyStrings;

	private Splitter(int limit, boolean trim, Pattern pattern, String prefix,
		String suffix, String forEmpty, boolean ignoreEmptyStrings)
	{
		Parameters.checkNotNull(pattern);
		this.limit = limit;
		this.trim = trim;
		this.pattern = pattern;
		this.suffix = suffix;
		this.prefix = prefix;
		this.forEmpty = forEmpty;
		this.ignoreEmptyStrings = ignoreEmptyStrings;
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will limit the number of times the splitting pattern is
	 * applied. If {@code limit} is strictly positive, then the pattern will
	 * be applied at most {@code limit - 1} times. If {@code limit} is
	 * non-positive then the splitting pattern will be applied as many times
	 * as possible.
	 *
	 * @param limit the splitting limit.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 */
	public Splitter limit(int limit)
	{
		return new Splitter(limit == 0 ? -1 : limit, trim, pattern,
			prefix, suffix, forEmpty, ignoreEmptyStrings);
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will trim splitting results.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 */
	public Splitter trimResults()
	{
		return new Splitter(limit, true, pattern, prefix, suffix,
			forEmpty, ignoreEmptyStrings);
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will ignore the given prefix.
	 *
	 * @param prefix the prefix to ignore.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code prefix} is {@code null}.
	 */
	public Splitter ignorePrefix(String prefix)
	{
		Parameters.checkNotNull(prefix);
		return new Splitter(limit, trim, pattern, prefix, suffix,
			forEmpty, ignoreEmptyStrings);
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will ignore the given suffix.
	 *
	 * @param suffix the suffix to ignore.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code suffix} is {@code null}.
	 */
	public Splitter ignoreSuffix(String suffix)
	{
		Parameters.checkNotNull(suffix);
		return new Splitter(limit, trim, pattern, prefix, suffix,
			forEmpty, ignoreEmptyStrings);
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will ignore empty results.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 */
	public Splitter ignoreEmptyStrings()
	{
		return new Splitter(limit, trim, pattern, prefix, suffix,
			forEmpty, true);
	}

	/**
	 * Returns a new {@code Splitter} that will behave as this one except
	 * that it will replace empty results by the specified {@code String}.
	 *
	 * @param forEmpty the value to use for empty {@code String}s.
	 *
	 * @return a new {@code Splitter} with the desired configuration.
	 */
	public Splitter replaceEmptyStringWith(String forEmpty)
	{
		return new Splitter(limit, trim, pattern, prefix, suffix,
			forEmpty, false);
	}

	/**
	 * Splits the given {@code String} into parts according to its
	 * configuration.
	 *
	 * @param s the {@code String} to split.
	 *
	 * @return the parts, as an unmodifiable {@code List}.
	 *
	 * @throws NullPointerException if {@code s} is {@code null}.
	 */
	public List<String> split(String s)
	{
		String[] parts = pattern.split(removePrefixAndSuffix(s), limit);
		List<String> results = new ArrayList<String>(parts.length);
		for (String part : parts) {
			String str = trim ? part.trim() : part;
			if (!str.isEmpty()) {
				results.add(str);
			} else if (!ignoreEmptyStrings) {
				results.add(forEmpty);
			}
		}
		return Collections.unmodifiableList(results);
	}

	private String removePrefixAndSuffix(String input)
	{
		String s = prefix != null && input.startsWith(prefix)
			? input.substring(prefix.length()) : input;
		return suffix != null && s.endsWith(suffix)
			? s.substring(0, s.length() - suffix.length()) : s;
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * Utilities for {@link Comparator}s.
 *
 * @author Osman KOCAK
 */
public final class Comparators
{
	/**
	 * Returns a {@code Comparator} that will call each {@code Comparator}
	 * in the given array until one of them returns a non-zero result (will
	 * return {@code 0} if they all return {@code 0}).
	 *
	 * @param <T> the parameters type of the returned {@code Comparator}.
	 * @param comparators the {@code Comparator}s to compose.
	 *
	 * @return the composed {@code Comparator}.
	 *
	 * @throws NullPointerException if {@code comparators} is {@code null}
	 *	of if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code comparators} is empty.
	 */
	public static <T> Comparator<T> compose(Comparator<? super T>... comparators)
	{
		return compose(Arrays.asList(comparators));
	}

	/**
	 * Returns a {@code Comparator} that will call each {@code Comparator}
	 * in the given {@code Iterable} until one of them returns a non-zero
	 * result (will return {@code 0} if they all return {@code 0}).
	 *
	 * @param <T> the parameters type of the returned {@code Comparator}.
	 * @param comparators the {@code Comparator}s to compose.
	 *
	 * @return the composed {@code Comparator}.
	 *
	 * @throws NullPointerException if {@code comparators} is {@code null}
	 *	of if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code comparators} is empty.
	 */
	public static <T> Comparator<T> compose(Iterable<? extends Comparator<? super T>> comparators)
	{
		return new CompositeComparator<T>(comparators);
	}

	/**
	 * Returns a {@code Comparator} that represents the reverse ordering of
	 * the given one. Namely, the returned {@code Comparator} will return
	 * a negative value if the original returns a positive value and,
	 * conversely, will return a positive value if it returns a negative
	 * value.
	 *
	 * @param <T> the parameters type of the {@code Comparator}s.
	 * @param comparator the comparator to invert.
	 *
	 * @return the inverted {@code Comparator}.
	 *
	 * @throws NullPointerException if {@code comparator} is {@code null}.
	 */
	public static <T> Comparator<T> reverse(final Comparator<T> comparator)
	{
		Parameters.checkNotNull(comparator);
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				return -comparator.compare(o1, o2);
			}
		};
	}

	/**
	 * Returns a {@code Comparator} that compares {@code Comparable} objects
	 * in natural order. The returned comparator doesn't accept {@code null}
	 * values.
	 *
	 * @param <T> the {@code Comparable} type.
	 *
	 * @return a {@code Comparator} that compares {@code Comparable} objects.
	 */
	public static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
	{
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				return o1.compareTo(Parameters.checkNotNull(o2));
			}
		};
	}

	/**
	 * Returns a new {@code Comparator} that considers {@code null} values
	 * as less than all other values and compares non-{@code null} values
	 * with the given {@code Comparator}.
	 *
	 * @param <T> the parameters type of the {@code Comparator}.
	 * @param comparator the non-{@code null} values {@code Comparator}
	 *
	 * @return the created {@code Comparator}.
	 *
	 * @throws NullPointerException if {@code comparator} is {@code null}.
	 */
	public static <T> Comparator<T> withNullsFirst(final Comparator<T> comparator)
	{
		Parameters.checkNotNull(comparator);
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				return o1 == o2 ? 0
					: o1 == null ? -1
					: o2 == null ? 1
					: comparator.compare(o1, o2);
			}
		};
	}

	/**
	 * Returns a new {@code Comparator} that considers {@code null} values
	 * as greater than all other values and compares non-{@code null} values
	 * with the given {@code Comparator}.
	 *
	 * @param <T> the parameters type of the {@code Comparator}.
	 * @param comparator the non-{@code null} values {@code Comparator}.
	 *
	 * @return the created {@code Comparator}.
	 *
	 * @throws NullPointerException if {@code comparator} is {@code null}.
	 */
	public static <T> Comparator<T> withNullsLast(final Comparator<T> comparator)
	{
		Parameters.checkNotNull(comparator);
		return new Comparator<T>()
		{
			@Override
			public int compare(T o1, T o2)
			{
				return o1 == o2 ? 0
					: o1 == null ? 1
					: o2 == null ? -1
					: comparator.compare(o1, o2);
			}
		};
	}

	private static final class CompositeComparator<T> implements Comparator<T>
	{
		private final List<Comparator<? super T>> comparators;

		CompositeComparator(Iterable<? extends Comparator<? super T>> comparators)
		{
			this.comparators = new ArrayList<Comparator<? super T>>();
			for (Comparator<? super T> comparator : comparators) {
				Parameters.checkNotNull(comparator);
				this.comparators.add(comparator);
			}
			Parameters.checkCondition(!this.comparators.isEmpty());
		}

		@Override
		public int compare(T o1, T o2)
		{
			for (Comparator<? super T> comparator : comparators) {
				int c = comparator.compare(o1, o2);
				if (c != 0) {
					return c;
				}
			}
			return 0;
		}
	}

	private Comparators()
	{
		/* ... */
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.util;

import java.util.Arrays;

/**
 * Joins multiple {@code Strings} into a single one. Instances of this class are
 * immutable.
 *
 * @see Splitter
 *
 * @author Osman KOCAK
 */
public final class Joiner
{
	/**
	 * Returns a new {@code Joiner} that will use the specified separator,
	 * no prefix, no suffix and {@code "null"} for {@code null} values.
	 *
	 * @param separator the separator.
	 *
	 * @return the created {@code Joiner}.
	 *
	 * @throws NullPointerException if {@code separator} is {@code null}.
	 */
	public static Joiner on(String separator)
	{
		return new Joiner(separator, "", "", false, false, false,
			"null", "");
	}

	private final String separator;
	private final String prefix;
	private final String suffix;
	private final boolean trim;
	private final boolean ignoreNull;
	private final boolean ignoreEmpty;
	private final String forNull;
	private final String forEmpty;

	private Joiner(String separator, String prefix, String suffix,
		boolean trim, boolean ignoreNull, boolean ignoreEmpty,
		String forNull, String forEmpty)
	{
		Parameters.checkNotNull(separator);
		Parameters.checkNotNull(prefix);
		Parameters.checkNotNull(suffix);
		Parameters.checkNotNull(forNull);
		Parameters.checkNotNull(forEmpty);
		this.separator = separator;
		this.prefix = prefix;
		this.suffix = suffix;
		this.trim = trim;
		this.ignoreNull = ignoreNull;
		this.ignoreEmpty = ignoreEmpty;
		this.forNull = forNull;
		this.forEmpty = forEmpty;
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will trim inputs before joining them.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 */
	public Joiner trimInputs()
	{
		return new Joiner(separator, prefix, suffix, true, ignoreNull,
			ignoreEmpty, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will ignore {@code null} input values.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 */
	public Joiner ignoreNulls()
	{
		return new Joiner(separator, prefix, suffix, trim, true,
			ignoreEmpty, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will ignore empty input {@code String}s.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 */
	public Joiner ignoreEmptyStrings()
	{
		return new Joiner(separator, prefix, suffix, trim, ignoreNull,
			true, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will replace {@code null} input values with the specified
	 * {@code String}.
	 *
	 * @param forNull the {@code String} to use for {@code null}s.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code forNull} is {@code null}.
	 */
	public Joiner replaceNullWith(String forNull)
	{
		return new Joiner(separator, prefix, suffix, trim, false,
			ignoreEmpty, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will replace empty input {@code String}s with the specified value.
	 *
	 * @param forEmpty the value to use for empty {@code String}s.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code forEmpty} is {@code null}.
	 */
	public Joiner replaceEmptyStringWith(String forEmpty)
	{
		return new Joiner(separator, prefix, suffix, trim, ignoreNull,
			false, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will use the specified prefix when joining {@code String}s.
	 *
	 * @param prefix the prefix.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code prefix} is {@code null}.
	 */
	public Joiner withPrefix(String prefix)
	{
		return new Joiner(separator, prefix, suffix,
			trim, ignoreNull, ignoreEmpty, forNull, forEmpty);
	}

	/**
	 * Returns a new {@code Joiner} that will behave as this one except that
	 * it will use the specified suffix when joining {@code String}s.
	 *
	 * @param suffix the suffix.
	 *
	 * @return a new {@code Joiner} with the desired configuration.
	 *
	 * @throws NullPointerException if {@code suffix} is {@code null}.
	 */
	public Joiner withSuffix(String suffix)
	{
		return new Joiner(separator, prefix, suffix,
			trim, ignoreNull, ignoreEmpty, forNull, forEmpty);
	}

	/**
	 * Joins the given {@code String}s according to its configuration.
	 *
	 * @param parts the parts to join.
	 *
	 * @return the created {@code String}.
	 *
	 * @throws NullPointerException if {@code parts} is {@code null}.
	 */
	public String join(Object... parts)
	{
		return join(Arrays.asList(parts));
	}

	/**
	 * Joins the given {@code String}s according to its configuration.
	 *
	 * @param parts the parts to join.
	 *
	 * @return the created {@code String}.
	 *
	 * @throws NullPointerException if {@code parts} is {@code null}.
	 */
	public String join(Iterable<?> parts)
	{
		boolean first = true;
		StringBuilder sb = new StringBuilder(prefix);
		for (Object part : parts) {
			String s = format(part);
			if (s != null) {
				if (first) {
					first = false;
				} else {
					sb.append(separator);
				}
				sb.append(s);
			}
		}
		return sb.append(suffix).toString();
	}

	private String format(Object o)
	{
		if (ignoreNull && o == null) {
			return null;
		}
		String s = XObjects.toString(o, forNull);
		s = trim ? s.trim() : s;
		return s.isEmpty() ? (ignoreEmpty ? null : forEmpty) : s;
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.security;

import org.kocakosm.pitaya.util.Parameters;

/**
 * Utility class to easily create instances of {@link Digest} and {@link MAC}.
 *
 * @author Osman KOCAK
 */
final class Factory
{
	/**
	 * Creates and returns a {@link Digest} instance corresponding to the
	 * given algorithm.
	 *
	 * @param algorithm the digest algorithm.
	 *
	 * @return the created {@link Digest} instance.
	 *
	 * @throws NullPointerException if {@code algorithm} is {@code null}.
	 * @throws IllegalArgumentException if the given algorithm is unknown.
	 */
	static Digest newDigest(Algorithm<Digest> algorithm)
	{
		Parameters.checkNotNull(algorithm);
		Digest digest;
		if (algorithm == Algorithm.MD2) {
			digest = Digests.md2();
		} else if (algorithm == Algorithm.MD4) {
			digest = Digests.md4();
		} else if (algorithm == Algorithm.MD5) {
			digest = Digests.md5();
		} else if (algorithm == Algorithm.SHA1) {
			digest = Digests.sha1();
		} else if (algorithm == Algorithm.SHA256) {
			digest = Digests.sha256();
		} else if (algorithm == Algorithm.SHA512) {
			digest = Digests.sha512();
		} else if (algorithm == Algorithm.KECCAK224) {
			digest = Digests.keccak224();
		} else if (algorithm == Algorithm.KECCAK256) {
			digest = Digests.keccak256();
		} else if (algorithm == Algorithm.KECCAK384) {
			digest = Digests.keccak384();
		} else if (algorithm == Algorithm.KECCAK512) {
			digest = Digests.keccak512();
		} else {
			throw new IllegalArgumentException("Unknown algorithm");
		}
		return digest;
	}

	/**
	 * Creates and returns a {@link MAC} instance corresponding to the given
	 * algorithm, initialized with the provided secret key.
	 *
	 * @param algorithm the MAC algorithm.
	 * @param key the secret key.
	 *
	 * @return the created {@link MAC} instance.
	 *
	 * @throws NullPointerException if {@code algorithm} is {@code null}.
	 * @throws IllegalArgumentException if the given algorithm is unknown.
	 */
	static MAC newMAC(Algorithm<MAC> algorithm, byte[] key)
	{
		Parameters.checkNotNull(algorithm);
		MAC mac;
		if (algorithm == Algorithm.HMAC_MD2) {
			mac = HMAC.md2(key);
		} else if (algorithm == Algorithm.HMAC_MD4) {
			mac = HMAC.md4(key);
		} else if (algorithm == Algorithm.HMAC_MD5) {
			mac = HMAC.md5(key);
		} else if (algorithm == Algorithm.HMAC_SHA1) {
			mac = HMAC.sha1(key);
		} else if (algorithm == Algorithm.HMAC_SHA256) {
			mac = HMAC.sha256(key);
		} else if (algorithm == Algorithm.HMAC_SHA512) {
			mac = HMAC.sha512(key);
		} else if (algorithm == Algorithm.HMAC_KECCAK224) {
			mac = HMAC.keccak224(key);
		} else if (algorithm == Algorithm.HMAC_KECCAK256) {
			mac = HMAC.keccak256(key);
		} else if (algorithm == Algorithm.HMAC_KECCAK384) {
			mac = HMAC.keccak384(key);
		} else if (algorithm == Algorithm.HMAC_KECCAK512) {
			mac = HMAC.keccak512(key);
		} else {
			throw new IllegalArgumentException("Unknown algorithm");
		}
		return mac;
	}

	private Factory()
	{
		/* ... */
	}
}


/*----------------------------------------------------------------------------*
 * This file is part of Pitaya.                                               *
 * Copyright (C) 2012-2016 Osman KOCAK <kocakosm@gmail.com>                   *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify it    *
 * under the terms of the GNU Lesser General Public License as published by   *
 * the Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                                 *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public     *
 * License for more details.                                                  *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 *----------------------------------------------------------------------------*/

package org.kocakosm.pitaya.collection;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * Immutable {@link Bag} implementation. Accepts {@code null} values.
 *
 * @param <E> the type of the {@code Bag}'s elements.
 *
 * @author Osman KOCAK
 */
public final class ImmutableBag<E> extends AbstractBag<E> implements Serializable
{
	private static final long serialVersionUID = 194306588498543706L;

	/**
	 * {@code ImmutableBag} builder. Not thread-safe.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 */
	public static final class Builder<E>
	{
		private final Bag<E> inner = new ArrayBag<E>();

		/**
		 * Adds the given values to the {@code Bag} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(E... values)
		{
			inner.addAll(Arrays.asList(values));
			return this;
		}

		/**
		 * Adds all the values contained in the given {@code Iterable}
		 * to the {@code Bag} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(Iterable<? extends E> values)
		{
			for (E value : values) {
				inner.add(value);
			}
			return this;
		}

		/**
		 * Adds all the values contained in the given {@code Iterator}
		 * to the {@code Bag} being built.
		 *
		 * @param values the values to add.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code values} is {@code null}.
		 */
		public Builder<E> add(Iterator<? extends E> values)
		{
			while (values.hasNext()) {
				inner.add(values.next());
			}
			return this;
		}

		/**
		 * Finalizes the creation of the {@code ImmutableBag}.
		 *
		 * @return an instance of {@code ImmutableBag}.
		 */
		public Bag<E> build()
		{
			return new ImmutableBag<E>(inner);
		}
	}

	/**
	 * Creates a new {@code ImmutableBag} from the given {@code Collection}.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 * @param c the source {@code Collection}.
	 *
	 * @return the created {@code ImmutableBag}.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static <E> Bag<E> copyOf(Collection<? extends E> c)
	{
		return new ImmutableBag<E>(new ArrayBag<E>(c));
	}

	/**
	 * Creates a new {@code ImmutableBag} from the given {@code Iterable}.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 * @param i the source {@code Iterable}.
	 *
	 * @return the created {@code ImmutableBag}.
	 *
	 * @throws NullPointerException if {@code i} is {@code null}.
	 */
	public static <E> Bag<E> copyOf(Iterable<? extends E> i)
	{
		return new ImmutableBag<E>(new ArrayBag<E>(i));
	}

	/**
	 * Creates a new {@code ImmutableBag} from the given {@code Iterator}.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 * @param i the source {@code Iterator}.
	 *
	 * @return the created {@code ImmutableBag}.
	 *
	 * @throws NullPointerException if {@code i} is {@code null}.
	 */
	public static <E> Bag<E> copyOf(Iterator<? extends E> i)
	{
		return new ImmutableBag<E>(new ArrayBag<E>(i));
	}

	/**
	 * Creates a new {@code ImmutableBag} containing the given elements.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 * @param values the {@code Bag}'s elements.
	 *
	 * @return the created {@code ImmutableBag}.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 */
	public static <E> Bag<E> copyOf(E[] values)
	{
		return new ImmutableBag<E>(new ArrayBag<E>(values));
	}

	/**
	 * Creates a new {@code ImmutableBag} containing the given elements.
	 *
	 * @param <E> the type of the {@code Bag}'s elements.
	 * @param values the {@code Bag}'s elements.
	 *
	 * @return the created {@code ImmutableBag}.
	 *
	 * @throws NullPointerException if {@code values} is {@code null}.
	 */
	public static <E> Bag<E> of(E... values)
	{
		return new ImmutableBag<E>(new ArrayBag<E>(values));
	}

	private final Bag<E> inner;

	private ImmutableBag(Bag<E> inner)
	{
		this.inner = inner;
	}

	@Override
	public int count(E e)
	{
		return inner.count(e);
	}

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

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

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

	@Override
	public Iterator<E> iterator()
	{
		return new UnmodifiableIterator<E>(inner.iterator());
	}

	@Override
	public Object[] toArray()
	{
		return inner.toArray();
	}

	@Override
	public <T> T[] toArray(T[] a)
	{
		return inner.toArray(a);
	}

	@Override
	public boolean add(E e)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean remove(Object o)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsAll(Collection<?> c)
	{
		return inner.containsAll(c);
	}

	@Override
	public boolean addAll(Collection<? extends E> c)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean removeAll(Collection<?> c)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean retainAll(Collection<?> c)
	{
		throw new UnsupportedOperationException();
	}

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


/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.hal.client;

import com.amazonaws.hal.Link;
import com.amazonaws.hal.ResourceInfo;
import com.amazonaws.hal.UriValue;
import com.amazonaws.hal.UriVariable;

import com.damnhandy.uri.template.MalformedUriTemplateException;
import com.damnhandy.uri.template.UriTemplate;
import com.damnhandy.uri.template.VariableExpansionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import static com.amazonaws.hal.client.ConversionUtil.convert;
import static com.amazonaws.hal.client.ConversionUtil.getCollectionType;
import static com.amazonaws.hal.client.ConversionUtil.getPropertyName;

class HalResourceInvocationHandler
        implements InvocationHandler {

    //-------------------------------------------------------------
    // Variables - Private
    //-------------------------------------------------------------

    private HalResource halResource;
    private String resourcePath;
    private HalClient halClient;

    private static Log log = LogFactory.getLog(HalResourceInvocationHandler.class);

    private static final Object[] EMPTY_ARGS = new Object[0];

    //-------------------------------------------------------------
    // Constructors
    //-------------------------------------------------------------

    HalResourceInvocationHandler(HalResource halResource, String resourcePath, HalClient halClient) {
        this.halResource = halResource;
        this.resourcePath = resourcePath;
        this.halClient = halClient;
    }

    //-------------------------------------------------------------
    // Implementation - InvocationHandler
    //-------------------------------------------------------------

    /**
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (halResource == null || !halResource.isDefined()) {
            halResource = halClient.getHalResource(resourcePath);
        }

        try {
            Method resourceInfoMethod = ResourceInfo.class.getMethod(method.getName(), method.getParameterTypes());

            return resourceInfoMethod.invoke(halResource, args);
        } catch (NoSuchMethodException ignore) {
            // If the method is not defined in ResourceInfo, we handle it below
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }

        Link link;
        if ((link = method.getAnnotation(Link.class)) != null) {
            switch (link.method()) {
            case GET:
                if (List.class.isAssignableFrom(method.getReturnType())) {
                    //noinspection unchecked
                    return new HalLinkList(halResource, link.relation(),
                                           (Class) getCollectionType(method.getGenericReturnType(), 0, ResourceInfo.class),
                                           halClient);
                } else if (Map.class.isAssignableFrom(method.getReturnType())) {
                    //noinspection unchecked
                    return new HalLinkMap(halResource, link.relation(), link.keyField(),
                                          (Class) getCollectionType(method.getGenericReturnType(), 1, ResourceInfo.class),
                                          halClient);
                } else {
                    return halClient.getResource(halResource, method.getReturnType(),
                                                 getRelationHref(link, args == null ? EMPTY_ARGS : args, method.getParameterAnnotations()), false);
                }

            case POST:
                if (args == null) {
                    throw new IllegalArgumentException("POST operations require a representation argument.");
                }

                return halClient.postResource(method.getReturnType(),
                                              getRelationHref(link, args, method.getParameterAnnotations()), args[0]);

            case PUT:
                if (args == null) {
                    throw new IllegalArgumentException("PUT operations require a representation argument.");
                }

                return halClient.putResource(method.getReturnType(),
                                             getRelationHref(link, args, method.getParameterAnnotations()), args[0]);

            case DELETE:
                return halClient.deleteResource(method.getReturnType(),
                                                getRelationHref(link, args == null ? EMPTY_ARGS : args, method.getParameterAnnotations()));

            case PATCH:
                if (args == null) {
                    throw new IllegalArgumentException("PATCH operations require a representation argument.");
                }

                return halClient.patchResource(method.getReturnType(),
                                               getRelationHref(link, args, method.getParameterAnnotations()), args[0]);

            default:
                throw new UnsupportedOperationException("Unexpected HTTP method: " + link.method());
            }

        } else if (method.getName().startsWith("get")) {
            String propertyName = getPropertyName(method.getName());
            Object property = halResource.getProperty(propertyName);
            Type returnType = method.getGenericReturnType();

            // When a value is accessed, it's intended type can either be a
            // class or some other type (like a ParameterizedType).
            //
            // If the target type is a class and the value is of that type,
            // we return it.  If the value is not of that type, we convert
            // it and store the converted value (trusting it was converted
            // properly) back to the backing store.
            //
            // If the target type is not a class, it may be ParameterizedType
            // like List<T> or Map<K, V>.  We check if the value is already
            // a converting type and if so, we return it.  If the value is
            // not, we convert it and if it's now a converting type, we store
            // the new value in the backing store.

            if (returnType instanceof Class) {
                if (!((Class) returnType).isInstance(property)) {
                    property = convert(returnType, property);

                    //noinspection unchecked
                    halResource.addProperty(propertyName, property);
                }
            } else {
                if (!(property instanceof ConvertingMap) && !(property instanceof ConvertingList)) {
                    property = convert(returnType, property);

                    if (property instanceof ConvertingMap || property instanceof ConvertingList) {
                        //noinspection unchecked
                        halResource.addProperty(propertyName, property);
                    }
                }
            }

            return property;
        } else if (method.getName().equals("toString") && args == null) {
            return resourcePath;
        } else if (method.getName().equals("equals") && args != null && args.length == 1) {
            HalResourceInvocationHandler other;

            try {
                other = (HalResourceInvocationHandler) Proxy.getInvocationHandler(args[0]);
            } catch (IllegalArgumentException e) {
                // argument is not a proxy
                return false;
            } catch (ClassCastException e) {
                // argument is the wrong type of proxy
                return false;
            }

            return resourcePath.equals(other.resourcePath);
        } else if (method.getName().equals("hashCode") && args == null) {
            return resourcePath.hashCode();
        }

        throw new UnsupportedOperationException("Don't know how to handle '" + method.getName() + "'");
    }

    //-------------------------------------------------------------
    // Methods - Package
    //-------------------------------------------------------------

    /**
     * The resource this InvocationHandler manages has been updated or deemed stale.
     *
     * @param halResource The new HalResource or null if the resource was stale and the new value is not yet known.
     */
    void resourceUpdated(HalResource halResource) {
        this.halResource = halResource;
    }

    //-------------------------------------------------------------
    // Methods - Private
    //-------------------------------------------------------------

    private String getRelationHref(Link link, Object[] args, Annotation[][] parameterAnnotations) {
        HalLink halLink = halResource.getLink(link.relation());

        if (halLink == null) {
            throw new UnsupportedOperationException(link.relation());
        }

        if (halLink.getDeprecation() != null) {
            log.warn("Link '" + link + "' has been deprecated: " + halLink.getDeprecation());
        }

        String href;

        if (halLink.isTemplated()) {
            try {
                UriTemplate uriTemplate = UriTemplate.fromTemplate(halLink.getHref());

                for (int i = 0; i < args.length; i++) {
                    for (Annotation annotation : parameterAnnotations[i]) {
                        if (annotation.annotationType() == UriVariable.class) {
                            UriVariable uriVariable = (UriVariable) annotation;

                            assignTemplateValue(uriTemplate, uriVariable.name(), args[i]);
                        }
                    }
                }

                for (int i = 0; i < link.uriValues().length; i++) {
                    UriValue uriValue = link.uriValues()[i];

                    assignTemplateValue(uriTemplate, uriValue.name(), uriValue.value());
                }

                href = uriTemplate.expand();
            } catch (MalformedUriTemplateException | VariableExpansionException e) {
                throw new RuntimeException(e);
            }
        } else {
            href = halLink.getHref();
        }

        return href;
    }

    private void assignTemplateValue(UriTemplate uriTemplate, String variableName, Object value) {
        if (uriTemplate.hasVariable(variableName)) {
            log.warn(String.format("Duplicate assignment to variable %s.  Current = '%s', skipping new value '%s'.",
                                   variableName, uriTemplate.get(variableName), value));

            return;
        }

        uriTemplate.set(variableName, value);
    }
}