/*----------------------------------------------------------------------------*
 * 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.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Utilities for {@link Throwable}s.
 *
 * @author Osman KOCAK
 */
public final class Throwables
{
	/**
	 * Returns the root cause of the given {@code Throwable}. If the given
	 * {@code Throwable} is not the result of a previous one, it is
	 * considered as the root.
	 *
	 * @param t the {@code Throwable} to get the root cause.
	 *
	 * @return the root cause of the given {@code Throwable}.
	 *
	 * @throws NullPointerException if {@code t} is {@code null}.
	 */
	public static Throwable getRootCause(Throwable t)
	{
		Throwable cause = t.getCause();
		return cause == null ? t : getRootCause(cause);
	}

	/**
	 * Returns the chain of {@code Throwable}s that caused the given one.
	 * The given {@code Throwable} is the first element in the returned
	 * {@code List}, the last one is the root cause.
	 *
	 * @param t the {@code Throwable} to get the chain.
	 *
	 * @return the chain of {@code Throwable} that caused {@code t}.
	 *
	 * @throws NullPointerException if {@code t} is {@code null}.
	 */
	public static List<Throwable> getCauseChain(Throwable t)
	{
		List<Throwable> chain = new ArrayList<Throwable>();
		chain.add(Parameters.checkNotNull(t));
		Throwable cause = t.getCause();
		while (cause != null) {
			chain.add(cause);
			cause = cause.getCause();
		}
		return Collections.unmodifiableList(chain);
	}

	/**
	 * Returns the stack trace of the given {@code Throwable} as a
	 * {@code String}.
	 *
	 * @param t the {@code Throwable} to get the stack trace.
	 *
	 * @return the stack trace of the given {@code Throwable}.
	 *
	 * @throws NullPointerException if {@code t} is {@code null}.
	 */
	public static String getStackTrace(Throwable t)
	{
		StringWriter out = new StringWriter();
		PrintWriter writer = new PrintWriter(out);
		t.printStackTrace(writer);
		writer.flush();
		return out.toString();
	}

	/**
	 * Returns the stack frames of the given {@code Throwable}. The stack
	 * frames are simply obtained from calling {@code toString} on each
	 * {@link StackTraceElement} of {@code t}.
	 *
	 * @param t the {@code Throwable} to get the stack frames.
	 *
	 * @return the stack frames of the given {@code Throwable}.
	 *
	 * @throws NullPointerException if {@code t} is {@code null}.
	 *
	 * @see Throwable#getStackTrace()
	 */
	public static List<String> getStackFrames(Throwable t)
	{
		StackTraceElement[] elements = t.getStackTrace();
		List<String> frames = new ArrayList<String>(elements.length);
		for (StackTraceElement element : elements) {
			frames.add(element.toString());
		}
		return Collections.unmodifiableList(frames);
	}

	/**
	 * Re-throws the given {@code Throwable} if it is already an instance of
	 * {@code RuntimeException} or {@link Error}, and, if not, wraps it in a
	 * {@code RuntimeException} before throwing it.
	 *
	 * @param t the {@code Throwable} to propagate.
	 *
	 * @return nothing (this method always throws a {@code Throwable}).
	 *
	 * @throws NullPointerException if {@code t} is {@code null}.
	 * @throws RuntimeException if {@code t} is already an instance of
	 *	{@code RuntimeException} or if {@code t} is neither an instance
	 *	of {@code RuntimeException} nor an instance of {@code Error}.
	 * @throws Error if {@code t} is an instance of {@code Error}.
	 */
	public static RuntimeException propagate(Throwable t)
	{
		Parameters.checkNotNull(t);
		if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else if (t instanceof Error) {
			throw (Error) t;
		}
		throw new RuntimeException(t);
	}

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


/*----------------------------------------------------------------------------*
 * 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.io;

import org.kocakosm.pitaya.util.Parameters;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Serialization utilities.
 *
 * @author Osman KOCAK
 */
public final class ObjectCodec
{
	/**
	 * Serializes the given {@code Serializable} object into an array of
	 * {@code byte}s.
	 *
	 * @param object the object to encode.
	 *
	 * @return the given object's encoding.
	 *
	 * @throws NullPointerException if {@code object} is {@code null}.
	 * @throws EncodingException if the given object cannot be serialized.
	 */
	public static byte[] encode(Serializable object)
	{
		Parameters.checkNotNull(object);
		ObjectOutputStream oos = null;
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(out);
			oos.writeObject(object);
			oos.flush();
			return out.toByteArray();
		} catch (IOException ex) {
			throw new EncodingException(ex);
		} finally {
			IO.close(oos);
		}
	}

	/**
	 * Deserializes the given array of {@code byte}s into an object.
	 *
	 * @param bytes the object's encoding.
	 *
	 * @return the deserialized object.
	 *
	 * @throws NullPointerException if {@code bytes} is {@code null}.
	 * @throws DecodingException if the deserialization fails.
	 */
	public static Serializable decode(byte[] bytes)
	{
		return decode(bytes, Serializable.class);
	}

	/**
	 * Deserializes the given array of {@code byte}s into an object.
	 *
	 * @param <T> the object's expected type.
	 * @param bytes the object's encoding.
	 * @param t the object's expected type's {@code Class}.
	 *
	 * @return the deserialized object.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws DecodingException if the deserialization fails.
	 * @throws ClassCastException if the deserialized object is not an
	 *	instance of the expected class.
	 */
	public static <T extends Serializable> T decode(byte[] bytes, Class<T> t)
	{
		Parameters.checkNotNull(t);
		Parameters.checkNotNull(bytes);
		ObjectInputStream ois = null;
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		try {
			ois = new ObjectInputStream(in);
			return Parameters.checkType(ois.readObject(), t);
		} catch (IOException ex) {
			throw new DecodingException(ex);
		} catch (ClassNotFoundException ex) {
			throw new DecodingException(ex);
		} finally {
			IO.close(ois);
		}
	}

	/** Thrown on serialization error. */
	public static final class EncodingException extends RuntimeException
	{
		private static final long serialVersionUID = 27396917846938764L;

		private EncodingException(Throwable cause)
		{
			super(cause);
		}
	}

	/** Thrown on deserialization error. */
	public static final class DecodingException extends RuntimeException
	{
		private static final long serialVersionUID = 39875873692107851L;

		private DecodingException(Throwable cause)
		{
			super(cause);
		}
	}

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


/*----------------------------------------------------------------------------*
 * 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.charset;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

/**
 * Static utility methods to encode and decode UTF-8 {@code String}s.
 *
 * @author Osman KOCAK
 */
public final class UTF8
{
	/**
	 * Returns whether the given {@code String} can be encoded into UTF-8.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return whether {@code str} can be encoded into UTF-8.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static boolean canEncode(String str)
	{
		return Charsets.UTF_8.newEncoder().canEncode(str);
	}

	/**
	 * Returns whether the specified range in the given {@code String} can
	 * be encoded into UTF-8.
	 *
	 * @param str the input {@code String}.
	 * @param off the start index, inclusive.
	 * @param len the number of characters to test.
	 *
	 * @return whether the specified range in {@code str} can be encoded
	 *	into UTF-8.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than {@code str}'s
	 *	length.
	 */
	public static boolean canEncode(String str, int off, int len)
	{
		return canEncode(str.substring(off, off + len));
	}

	/**
	 * Returns whether the given byte array represents valid UTF-8 encoded
	 * characters.
	 *
	 * @param input the bytes to test.
	 *
	 * @return whether {@code input} repesents UTF-8 encoded characters.
	 *
	 * @throws NullPointerException if {@code input} is {@code null}.
	 */
	public static boolean canDecode(byte... input)
	{
		return canDecode(input, 0, input.length);
	}

	/**
	 * Returns whether the specified range in the given byte array represents
	 * valid UTF-8 encoded characters.
	 *
	 * @param input the input buffer.
	 * @param off the start index, inclusive.
	 * @param len the number of bytes to test.
	 *
	 * @return whether the specified range in {@code input} represents UTF-8
	 *	encoded characters.
	 *
	 * @throws NullPointerException if {@code input} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than {@code input}'s
	 *	length.
	 */
	public static boolean canDecode(byte[] input, int off, int len)
	{
		try {
			decode(input, off, len);
		} catch (IllegalArgumentException ex) {
			return false;
		}
		return true;
	}

	/**
	 * Returns the UTF-8 encoding of the given {@code String}.
	 *
	 * @param str the {@code String} to encode.
	 *
	 * @return the UTF-8 encoding of the given {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code str} can't be encoded into
	 *	UTF-8.
	 */
	public static byte[] encode(String str)
	{
		CharsetEncoder encoder = Charsets.UTF_8.newEncoder();
		try {
			ByteBuffer out = encoder.encode(CharBuffer.wrap(str));
			byte[] bytes = new byte[out.limit()];
			out.get(bytes);
			return bytes;
		} catch (CharacterCodingException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	/**
	 * Returns the UTF-8 encoding of the specified character sequence.
	 *
	 * @param str the input {@code String}.
	 * @param off the start index, inclusive.
	 * @param len the number of characters to encode.
	 *
	 * @return the UTF-8 encoding of the specified characters.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than {@code str}'s
	 *	length.
	 * @throws IllegalArgumentException if {@code str} can't be encoded into
	 *	UTF-8.
	 */
	public static byte[] encode(String str, int off, int len)
	{
		return encode(str.substring(off, off + len));
	}

	/**
	 * Decodes the given UTF-8 encoded characters.
	 *
	 * @param input the UTF-8 encoded characters to decode.
	 *
	 * @return the decoded characters.
	 *
	 * @throws NullPointerException if {@code input} is {@code null}.
	 * @throws IllegalArgumentException if {@code input} doesn't represent
	 *	valid UTF-8 encoded characters.
	 */
	public static String decode(byte... input)
	{
		return decode(input, 0, input.length);
	}

	/**
	 * Decodes {@code len} UTF-8 encoded bytes from the given input buffer,
	 * starting at {@code off}.
	 *
	 * @param input the input buffer.
	 * @param off the starting offset.
	 * @param len the number of bytes to decode.
	 *
	 * @return the decoded characters.
	 *
	 * @throws NullPointerException if {@code input} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than {@code input}'s
	 *	length.
	 * @throws IllegalArgumentException if {@code input} doesn't represent
	 *	valid UTF-8 encoded characters.
	 */
	public static String decode(byte[] input, int off, int len)
	{
		CharsetDecoder decoder = Charsets.UTF_8.newDecoder();
		ByteBuffer buf = ByteBuffer.wrap(input, off, len);
		try {
			CharBuffer out = decoder.decode(buf);
			char[] chars = new char[out.limit()];
			out.get(chars);
			return new String(chars);
		} catch (CharacterCodingException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

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


/*----------------------------------------------------------------------------*
 * 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;

/**
 * Little-endian encoding and decoding.
 *
 * @author Osman KOCAK
 */
public final class LittleEndian
{
	/**
	 * Encodes the given {@code int} value using little-endian byte ordering
	 * convention.
	 *
	 * @param n the {@code int} value to encode.
	 *
	 * @return the encoded value.
	 */
	public static byte[] encode(int n)
	{
		byte[] out = new byte[4];
		encode(n, out, 0);
		return out;
	}

	/**
	 * Encodes the given {@code int} value using little-endian byte ordering
	 * convention into the given array, starting at the given offset.
	 *
	 * @param n the {@code int} value to encode.
	 * @param out the output buffer.
	 * @param off the output offset.
	 *
	 * @throws NullPointerException if {@code out} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} is negative or if
	 *	{@code out}'s length is lower than {@code off + 4}.
	 */
	public static void encode(int n, byte[] out, int off)
	{
		out[off] = (byte) n;
		out[off + 1] = (byte) (n >>> 8);
		out[off + 2] = (byte) (n >>> 16);
		out[off + 3] = (byte) (n >>> 24);
	}

	/**
	 * Encodes the given {@code long} value using little-endian byte
	 * ordering convention.
	 *
	 * @param n the {@code long} value to encode.
	 *
	 * @return the encoded value.
	 */
	public static byte[] encode(long n)
	{
		byte[] out = new byte[8];
		encode(n, out, 0);
		return out;
	}

	/**
	 * Encodes the given {@code long} value using little-endian byte
	 * ordering convention into the given array, starting at the given
	 * offset.
	 *
	 * @param n the {@code long} value to encode.
	 * @param out the output buffer.
	 * @param off the output offset.
	 *
	 * @throws NullPointerException if {@code out} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} is negative or if
	 *	{@code out}'s length is lower than {@code off + 8}.
	 */
	public static void encode(long n, byte[] out, int off)
	{
		out[off] = (byte) n;
		out[off + 1] = (byte) (n >>> 8);
		out[off + 2] = (byte) (n >>> 16);
		out[off + 3] = (byte) (n >>> 24);
		out[off + 4] = (byte) (n >>> 32);
		out[off + 5] = (byte) (n >>> 40);
		out[off + 6] = (byte) (n >>> 48);
		out[off + 7] = (byte) (n >>> 56);
	}

	/**
	 * Decodes the first 4 bytes of the given array into an {@code int}
	 * value using little-endian byte ordering convention.
	 *
	 * @param in the encoded value.
	 *
	 * @return the decoded {@code int} value.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code in}'s length is lower
	 *	than {@code 4}.
	 */
	public static int decodeInt(byte[] in)
	{
		return decodeInt(in, 0);
	}

	/**
	 * Decodes the first 4 bytes starting at {@code off} of the given array
	 * into an {@code int} value using little-endian byte ordering
	 * convention.
	 *
	 * @param in the encoded value.
	 * @param off the input offset.
	 *
	 * @return the decoded {@code int} value.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} is negative or if
	 *	{@code in}'s length is lower than {@code off + 4}.
	 */
	public static int decodeInt(byte[] in, int off)
	{
		return (in[off] & 0xFF)
			| ((in[off + 1] & 0xFF) << 8)
			| ((in[off + 2] & 0xFF) << 16)
			| ((in[off + 3] & 0xFF) << 24);
	}

	/**
	 * Decodes the first 8 bytes of the given array into a {@code long}
	 * value using little-endian byte ordering convention.
	 *
	 * @param in the encoded value.
	 *
	 * @return the decoded {@code long} value.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code in}'s length is lower
	 *	than {@code 8}.
	 */
	public static long decodeLong(byte[] in)
	{
		return decodeLong(in, 0);
	}

	/**
	 * Decodes the first 8 bytes starting at {@code off} of the given array
	 * into a {@code long} value using little-endian byte ordering
	 * convention.
	 *
	 * @param in the encoded value.
	 * @param off the input offset.
	 *
	 * @return the decoded {@code long} value.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} is negative or if
	 *	{@code in}'s length is lower than {@code off + 8}.
	 */
	public static long decodeLong(byte[] in, int off)
	{
		return (long) (in[off] & 0xFF)
			| ((long) (in[off + 1] & 0xFF) << 8)
			| ((long) (in[off + 2] & 0xFF) << 16)
			| ((long) (in[off + 3] & 0xFF) << 24)
			| ((long) (in[off + 4] & 0xFF) << 32)
			| ((long) (in[off + 5] & 0xFF) << 40)
			| ((long) (in[off + 6] & 0xFF) << 48)
			| ((long) (in[off + 7] & 0xFF) << 56);
	}

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


/*----------------------------------------------------------------------------*
 * 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.io;

import org.kocakosm.pitaya.charset.Charsets;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Character streams utilities.
 *
 * @author Osman KOCAK
 */
public final class CharStreams
{
	/**
	 * Concatenates the given {@code Reader}s into a single one. The given
	 * streams will be read sequentially. Closing the concatenated stream
	 * will close all the source streams. Note that the returned
	 * {@code Reader} is not thread-safe and does not support
	 * {@link Reader#mark(int)} and {@link Reader#reset()}.
	 *
	 * @param streams the streams to concatenate.
	 *
	 * @return the concatenated {@code Reader}.
	 *
	 * @throws NullPointerException if {@code streams} is {@code null} or
	 *	if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code streams} is empty.
	 */
	public static Reader concat(Reader... streams)
	{
		return new ConcatReader(streams);
	}

	/**
	 * Concatenates the given {@code Reader}s into a single one. The given
	 * streams will be read sequentially. Closing the concatenated stream
	 * will close all the source streams. Note that the returned
	 * {@code Reader} is not thread-safe and does not support
	 * {@link Reader#mark(int)} and {@link Reader#reset()}.
	 *
	 * @param streams the streams to concatenate.
	 *
	 * @return the concatenated {@code Reader}.
	 *
	 * @throws NullPointerException if {@code streams} is {@code null} or
	 *	if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code streams} is empty.
	 */
	public static Reader concat(Iterable<? extends Reader> streams)
	{
		return new ConcatReader(streams);
	}

	/**
	 * Copies the content of the given {@code Reader} into the given
	 * {@code Writer}.
	 *
	 * @param in the {@code Reader} to read from.
	 * @param out the {@code Reader} to write on.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static void copy(Reader in, Writer out) throws IOException
	{
		char[] buf = new char[4096];
		int len = in.read(buf);
		while (len >= 0) {
			out.write(buf, 0, len);
			len = in.read(buf);
		}
		out.flush();
	}

	/**
	 * Copies the content of the given {@code InputStream} into the given
	 * {@code Writer} using the system's default charset.
	 *
	 * @param in the {@code InputStream} to read from.
	 * @param out the {@code Reader} to write on.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static void copy(InputStream in, Writer out) throws IOException
	{
		copy(new InputStreamReader(in, Charsets.DEFAULT), out);
	}

	/**
	 * Copies the content of the given {@code InputStream} into the given
	 * {@code Writer} using the specified charset.
	 *
	 * @param in the {@code InputStream} to read from.
	 * @param out the {@code Reader} to write on.
	 * @param charset the charset to use.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static void copy(InputStream in, Writer out, Charset charset)
		throws IOException
	{
		copy(new InputStreamReader(in, charset), out);
	}

	/**
	 * Copies the content of the given {@code Reader} into the given
	 * {@code OutputStream} using the system's default charset.
	 *
	 * @param in the {@code Reader} to read from.
	 * @param out the {@code OutputStream} to write on.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static void copy(Reader in, OutputStream out) throws IOException
	{
		copy(in, new OutputStreamWriter(out, Charsets.DEFAULT));
	}

	/**
	 * Copies the content of the given {@code Reader} into the given
	 * {@code OutputStream} using the specified charset.
	 *
	 * @param in the {@code Reader} to read from.
	 * @param out the {@code OutputStream} to write on.
	 * @param charset the charset to use.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static void copy(Reader in, OutputStream out, Charset charset)
		throws IOException
	{
		copy(in, new OutputStreamWriter(out, charset));
	}

	/**
	 * Returns whether the given streams have the same content.
	 *
	 * @param in1 the first stream.
	 * @param in2 the second stream.
	 *
	 * @return {@code true} if the streams have the same content.
	 *
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public static boolean equal(Reader in1, Reader in2) throws IOException
	{
		if (in1 == in2) {
			return true;
		}
		if (in1 == null || in2 == null) {
			return false;
		}
		in1 = buffer(in1);
		in2 = buffer(in2);
		int c1 = in1.read();
		int c2 = in2.read();
		while (c1 != -1 && c2 != -1 && c1 == c2) {
			c1 = in1.read();
			c2 = in2.read();
		}
		return in1.read() == -1 && in2.read() == -1;
	}

	private static BufferedReader buffer(Reader in)
	{
		if (in instanceof BufferedReader) {
			return (BufferedReader) in;
		}
		return new BufferedReader(in);
	}

	/**
	 * Returns an {@code Reader} that will only supply characters from the
	 * given {@code Reader} up to the specified limit. The returned
	 * {@code Reader} is thread-safe.
	 *
	 * @param in the source stream.
	 * @param limit the maximum number of characters to be read from {@code in}.
	 *
	 * @return the length-limited {@code Reader}.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IllegalArgumentException if {@code limit} is negative.
	 */
	public static Reader limit(Reader in, long limit)
	{
		return new LimitReader(in, limit);
	}

	/**
	 * Reads the whole content of the given {@code Reader} into a
	 * {@code String}.
	 *
	 * @param in the stream to read.
	 *
	 * @return the stream's content as a {@code String}.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static String read(Reader in) throws IOException
	{
		Writer out = new StringWriter();
		copy(in, out);
		return out.toString();
	}

	/**
	 * Reads the whole content of the given {@code InputStream} into a
	 * {@code String} using the system's default charset.
	 *
	 * @param in the stream to read.
	 *
	 * @return the stream's content as a {@code String}.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static String read(InputStream in) throws IOException
	{
		return read(new InputStreamReader(in, Charsets.DEFAULT));
	}

	/**
	 * Reads the whole content of the given {@code InputStream} into a
	 * {@code String} using the specified charset.
	 *
	 * @param in the stream to read.
	 * @param charset the stream's charset.
	 *
	 * @return the stream's content as a {@code String}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static String read(InputStream in, Charset charset)
		throws IOException
	{
		return read(new InputStreamReader(in, charset));
	}

	/**
	 * Reads all the lines from the given source {@code Reader}. Note that
	 * the returned {@code List} is immutable.
	 *
	 * @param in the stream to read.
	 *
	 * @return the stream's lines.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static List<String> readLines(Reader in) throws IOException
	{
		BufferedReader reader = buffer(in);
		List<String> lines = new ArrayList<String>();
		String line = reader.readLine();
		while (line != null) {
			lines.add(line);
			line = reader.readLine();
		}
		return Collections.unmodifiableList(lines);
	}

	/**
	 * Reads all the lines from the given source {@code InputStream} using
	 * the system's default charset. Note that the returned {@code List} is
	 * immutable.
	 *
	 * @param in the stream to read.
	 *
	 * @return the stream's lines.
	 *
	 * @throws NullPointerException if {@code in} is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static List<String> readLines(InputStream in) throws IOException
	{
		return readLines(in, Charsets.DEFAULT);
	}

	/**
	 * Reads all the lines from the given source {@code InputStream} using
	 * the specified charset. Note that the returned {@code List} is immutable.
	 *
	 * @param in the stream to read.
	 * @param charset the charset to use.
	 *
	 * @return the stream's lines.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if the stream cannot be read.
	 */
	public static List<String> readLines(InputStream in, Charset charset)
		throws IOException
	{
		return readLines(new InputStreamReader(in, charset));
	}

	/**
	 * Returns a {@code Writer} that writes all characters written to it
	 * to the given underlying streams. Named after the Unix 'tee' command.
	 * The returned instance is not thread-safe.
	 *
	 * @param streams the streams to write to.
	 *
	 * @return the 'tee' {@code Writer}.
	 *
	 * @throws NullPointerException if {@code streams} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static Writer tee(Writer... streams)
	{
		return new TeeWriter(streams);
	}

	/**
	 * Returns a {@code Writer} that writes all characters written to it
	 * to the given underlying streams. Named after the Unix 'tee' command.
	 * The returned instance is not thread-safe.
	 *
	 * @param streams the streams to write to.
	 *
	 * @return the 'tee' {@code Writer}.
	 *
	 * @throws NullPointerException if {@code streams} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static Writer tee(Iterable<? extends Writer> streams)
	{
		return new TeeWriter(streams);
	}

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


/*----------------------------------------------------------------------------*
 * 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;

/**
 * Somme commonly used key derivation function algorithms.
 *
 * @author Osman KOCAK
 */
public final class KDFs
{
	/**
	 * Creates and returns a new immutable {@code KDF} instance implementing
	 * the PBKDF1 algorithm (RFC 2898).
	 *
	 * @param digest the digest algorithm to use.
	 * @param iterationCount the desired number of iterations.
	 * @param dkLen the desired length for derived keys, in bytes.
	 *
	 * @return the created {@code KDF} instance.
	 *
	 * @throws NullPointerException if {@code digest} is {@code null}.
	 * @throws IllegalArgumentException if {@code iterationCount} or
	 *	{@code dkLen} is negative, or if {@code dkLen} is greater than
	 *	the digest algorithm's output length, or if the digest algorithm
	 *	is unknown.
	 */
	public static KDF pbkdf1(Algorithm<Digest> digest, int iterationCount,
		int dkLen)
	{
		return new PBKDF1(digest, iterationCount, dkLen);
	}

	/**
	 * Creates and returns a new immutable {@code KDF} instance implementing
	 * the PBKDF2 algorithm (RFC 2898).
	 *
	 * @param mac the MAC algorithm to use.
	 * @param iterationCount the desired number of iterations.
	 * @param dkLen the desired length for derived keys, in bytes.
	 *
	 * @return the created {@code KDF} instance.
	 *
	 * @throws NullPointerException if {@code mac} is {@code null}.
	 * @throws IllegalArgumentException if {@code iterationCount} or
	 *	{@code dkLen} is negative, or if the MAC algorithm is unknown.
	 */
	public static KDF pbkdf2(Algorithm<MAC> mac, int iterationCount,
		int dkLen)
	{
		return new PBKDF2(mac, iterationCount, dkLen);
	}

	/**
	 * Creates and returns a new immutable {@code KDF} instance implementing
	 * the HKDF algorithm (RFC 5869).
	 *
	 * @param mac the MAC algorithm to use.
	 * @param info optional context and application specific information,
	 *	may be {@code null} or empty.
	 * @param dkLen the desired length for derived keys, in bytes.
	 *
	 * @return the created {@code KDF} instance.
	 *
	 * @throws NullPointerException if {@code mac} is {@code null}.
	 * @throws IllegalArgumentException if {@code dkLen} is negative, or if
	 *	the MAC algorithm is unknown, or if {@code dkLen} is greater
	 *	than 255 * MAC algorithm's output length.
	 */
	public static KDF hkdf(Algorithm<MAC> mac, byte[] info, int dkLen)
	{
		return new HKDF(mac, info, dkLen);
	}

	/**
	 * Creates and returns a new immutable {@code KDF} instance implementing
	 * the SCrypt algorithm (RFC 7914).
	 *
	 * @param r the block size parameter.
	 * @param n the CPU/Memory cost parameter.
	 * @param p the parallelization parameter.
	 * @param dkLen the desired length for derived keys, in bytes.
	 *
	 * @return the created {@code KDF} instance.
	 *
	 * @throws IllegalArgumentException if {@code r, dkLen} or {@code p} is
	 *	negative, or if {@code n} is not greater than 1 or if it is not
	 *	a power of 2 or if it is not less than 2 ^ (128 * r / 8), or if
	 *	{@code p} is greater than ((2 ^ 32 - 1) * 32) / (128 * r).
	 */
	public static KDF scrypt(int r, int n, int p, int dkLen)
	{
		return new SCrypt(r, n, p, dkLen);
	}

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


/*----------------------------------------------------------------------------*
 * 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.io;

import static org.kocakosm.pitaya.io.WriteOption.*;

import org.kocakosm.pitaya.collection.ImmutableSet;
import org.kocakosm.pitaya.util.Parameters;
import org.kocakosm.pitaya.util.XArrays;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.util.Set;

/**
 * {@link File}s utilities.
 *
 * @see TextFiles
 *
 * @author Osman KOCAK
 */
public final class XFiles
{
	/**
	 * Copies the contents of {@code src} to {@code dst}. If {@code dst}
	 * doesn't exist, it will be created. If it already exists, it can be
	 * either a directory or a regular file if {@code src} is also a regular
	 * file. Named after the Unix command of the same name.
	 *
	 * @param src the source {@code File}.
	 * @param dst the target {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if {@code src} is a directory and
	 *	{@code dst} is a regular file.
	 * @throws IOException if {@code src} doesn't exist or if {@code src} is
	 *	neither a regular file nor a directory or if an I/O error occurs
	 *	during the process.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code src} or write access to {@code dst}.
	 */
	public static void cp(File src, File dst) throws IOException
	{
		if (!src.exists()) {
			throw new FileNotFoundException(src + " doesn't exist");
		}
		Parameters.checkCondition((!dst.exists() || dst.isDirectory())
			|| (src.isFile() && dst.isFile()));
		if (src.isDirectory()) {
			copyDirectory(src, dst);
		} else if (src.isFile()) {
			copyFile(src, dst);
		} else {
			throw new IOException(
				src + " is neither a directory nor a regular file");
		}
	}

	private static void copyDirectory(File src, File dst) throws IOException
	{
		if (dst.isDirectory()) {
			cp(src, new File(dst, src.getName()));
		} else {
			mkdir(dst);
			for (File f : src.listFiles()) {
				cp(f, new File(dst, f.getName()));
			}
		}
	}

	private static void copyFile(File src, File dst) throws IOException
	{
		if (dst.isDirectory()) {
			cp(src, new File(dst, src.getName()));
		} else {
			InputStream in = null;
			OutputStream out = null;
			try {
				in = newInputStream(src);
				out = newOutputStream(dst);
				ByteStreams.copy(in, out);
			} finally {
				IO.close(in);
				IO.close(out);
			}
		}
	}

	/**
	 * Copies the contents of the given input {@code File} to the given
	 * {@code OutputStream}. Named after the Unix command of the same name.
	 *
	 * @param src the file to copy from.
	 * @param dst the stream to write to.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if {@code src} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be opened
	 *	for reading, or if an I/O error occurs during the process.
	 * @throws SecurityException if a security manager exists denies read
	 *	access to {@code src}.
	 */
	public static void cp(File src, OutputStream dst) throws IOException
	{
		InputStream in = newInputStream(src);
		try {
			ByteStreams.copy(in, dst);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Copies the contents of the given {@code InputStream} to the given
	 * output {@code File}. Named after the Unix command of the same name.
	 *
	 * @param src the stream to read from.
	 * @param dst the file to write to.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IOException if {@code dst} exists but is a directory rather
	 *	than a regular file, or if it does not exist but cannot be
	 *	created, or if an I/O error occurs during the process.
	 * @throws SecurityException if a security manager exists denies write
	 *	access to {@code dst}.
	 */
	public static void cp(InputStream src, File dst) throws IOException
	{
		OutputStream out = newOutputStream(dst);
		try {
			ByteStreams.copy(src, out);
		} finally {
			IO.close(out);
		}
	}

	/**
	 * Creates the directories specified by the given paths. This method
	 * will also create any necessary parent directories for a particular
	 * path. Named after the Unix command of the same name.
	 *
	 * @param paths the directories to create.
	 *
	 * @throws NullPointerException if {@code paths} is {@code null}, or if
	 *	it contains a {@code null} reference.
	 * @throws IOException if one of the specified path already exists and
	 *	represents a regular file, or if the requested directory can't
	 *	be created.
	 * @throws SecurityException if a security manager exists and denies
	 *	read/write access to one of the specified paths.
	 */
	public static void mkdir(File... paths) throws IOException
	{
		for (File path : paths) {
			createDirectory(path);
		}
	}

	private static void createDirectory(File f) throws IOException
	{
		if (f.exists()) {
			if (!f.isDirectory()) {
				throw new IOException(
					f + " exists and is not a directory");
			}
		} else if (!f.mkdirs()) {
			throw new IOException("Failed to create " + f);
		}
	}

	/**
	 * Moves a file from one path to another. Named after the Unix command
	 * of the same name.
	 *
	 * @param src the original file.
	 * @param dst the destination path.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if {@code src} is a directory and
	 *	{@code dst} is a regular file.
	 * @throws IOException if {@code src} can't be moved to {@code dst}.
	 * @throws SecurityException if a security manager exists and denies
	 *	read/write access to {@code src}/{@code dst}.
	 */
	public static void mv(File src, File dst) throws IOException
	{
		Parameters.checkNotNull(dst);
		if (!src.equals(dst)) {
			cp(src, dst);
			try {
				rm(src);
			} catch (IOException e) {
				rm(dst);
				throw new IOException("Can't move " + src, e);
			}
		}
	}

	/**
	 * Deletes the given {@code File}s. Directories will be recursively
	 * deleted. Named after the Unix command of the same name.
	 *
	 * @param files the {@code File}s to delete.
	 *
	 * @throws NullPointerException if {@code files} is {@code null} or if
	 *	it contains a {@code null} reference.
	 * @throws IOException if one of the specified {@code File}s can't be
	 *	deleted.
	 * @throws SecurityException if a security manager exists and denies
	 *	read/write access to one of the specified files or its children.
	 */
	public static void rm(File... files) throws IOException
	{
		for (File f : files) {
			if (f.exists()) {
				if (f.isDirectory()) {
					rm(f.listFiles());
				}
				if (!f.delete()) {
					throw new IOException("Can't delete " + f);
				}
			}
		}
	}

	/**
	 * Creates empty files at the specified paths or updates the last
	 * modification time of the files at the specified paths. Named after
	 * the Unix command of the same name.
	 *
	 * @param files the files to touch.
	 *
	 * @throws IOException if an I/O error occurs during the process.
	 * @throws SecurityException if a security manager exists and denies
	 *	read/write access to one of the specified files.
	 */
	public static void touch(File... files) throws IOException
	{
		long now = System.currentTimeMillis();
		for (File f : files) {
			if (!f.createNewFile() && !f.setLastModified(now)) {
				throw new IOException("Failed to touch " + f);
			}
		}
	}

	/**
	 * Reads the first (up to {@code 512}) bytes of the given {@code File}.
	 * Named after the Unix command of the same name.
	 *
	 * @param f the file to read.
	 *
	 * @return the first bytes of the given {@code File}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws IOException if {@code f} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be read.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static byte[] head(File f) throws IOException
	{
		return head(f, 512);
	}

	/**
	 * Reads the first (up to {@code n}) bytes of the given {@code File}.
	 * Named after the Unix command of the same name.
	 *
	 * @param f the file to read.
	 * @param n the maximum number of bytes to read.
	 *
	 * @return the first bytes of the given {@code File}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws IllegalArgumentException if {@code n} is negative.
	 * @throws IOException if {@code f} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be read.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static byte[] head(File f, int n) throws IOException
	{
		Parameters.checkCondition(n >= 0);
		InputStream in = newInputStream(f);
		byte[] buf = new byte[n];
		try {
			return XArrays.copyOf(buf, 0, in.read(buf));
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Reads the last (up to {@code 512}) bytes of the given {@code File}.
	 * Named after the Unix command of the same name.
	 *
	 * @param f the file to read.
	 *
	 * @return the last bytes of the given {@code File}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws IOException if {@code f} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be read.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static byte[] tail(File f) throws IOException
	{
		return tail(f, 512);
	}

	/**
	 * Reads the last (up to {@code n}) bytes of the given {@code File}.
	 * Named after the Unix command of the same name.
	 *
	 * @param f the file to read.
	 * @param n the maximum number of bytes to read.
	 *
	 * @return the last bytes of the given {@code File}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws IOException if {@code f} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be read.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static byte[] tail(File f, int n) throws IOException
	{
		Parameters.checkCondition(n >= 0);
		RandomAccessFile file = new RandomAccessFile(f, "r");
		file.seek(file.length() - n);
		byte[] data = new byte[n];
		file.read(data);
		return data;
	}

	/**
	 * Returns whether the given {@code File}s have the same content. Two
	 * regular files are considered equal if they contain the same bytes.
	 * Two directories are considered equal if they both contain the same
	 * items where an item is either a directory or a regular file (items
	 * must have the same name and content in both directories).
	 *
	 * @param f1 the first {@code File}.
	 * @param f2 the second {@code File}.
	 *
	 * @return whether the given {@code File}s have the same content.
	 *
	 * @throws IOException if the content of the {@code File}s can't be read.
	 */
	public static boolean equal(File f1, File f2) throws IOException
	{
		if (f1 == f2) {
			return true;
		}
		if (f1 == null || f2 == null || !haveSameType(f1, f2)) {
			return false;
		}
		return f1.equals(f2) ? true : haveSameContent(f1, f2);
	}

	private static boolean haveSameType(File f1, File f2)
	{
		return f1.isFile() == f2.isFile()
			&& f1.isDirectory() == f2.isDirectory();
	}

	private static boolean haveSameContent(File f1, File f2)
		throws IOException
	{
		if (f1.isFile()) {
			InputStream in1 = null;
			InputStream in2 = null;
			try {
				in1 = newInputStream(f1);
				in2 = newInputStream(f2);
				return ByteStreams.equal(in1, in2);
			} finally {
				IO.close(in1);
				IO.close(in2);
			}
		}
		File[] files = f1.listFiles();
		if (files.length != f2.listFiles().length) {
			return false;
		}
		for (File f : files) {
			if (!equal(f, new File(f2, f.getName()))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns the file name without its path or extension. This method is
	 * named after the 'basename' Unix command.
	 *
	 * @param f the file to get the base name.
	 *
	 * @return {@code f}'s extension-free name.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 */
	public static String getBaseName(File f)
	{
		String fileName = f.getName();
		int index = fileName.lastIndexOf('.');
		return index == -1 ? fileName : fileName.substring(0, index);
	}

	/**
	 * Returns the extension of the given file, or the empty {@code String}
	 * if the file has no extension. The returned extension does not include
	 * the leading dot.
	 *
	 * @param f the file to get the extension.
	 *
	 * @return {@code f}'s extension.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 */
	public static String getExtension(File f)
	{
		String fileName = f.getName();
		int index = fileName.lastIndexOf('.');
		return index == -1 ? "" : fileName.substring(index + 1);
	}

	/**
	 * Returns an {@code InputStream} to read from the given {@code File}.
	 *
	 * @param f the file to read from.
	 *
	 * @return an {@code InputStream} to read from the given {@code File}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws FileNotFoundException if {@code f} doesn't exist, or if it is
	 *	a directory rather than a regular file, or if it can't be opened
	 *	for reading.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static BufferedInputStream newInputStream(File f)
		throws FileNotFoundException
	{
		return new BufferedInputStream(new FileInputStream(f));
	}

	/**
	 * Returns an {@code OutputStream} to write to the given {@code File}.
	 *
	 * @param f the file to write to.
	 * @param options the write options.
	 *
	 * @return an {@code OutputStream} to write to the given {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if incompatible options are given.
	 * @throws FileNotFoundException if {@code f} exists but is a directory
	 *	rather than a regular file, or if it does not exist but cannot
	 *	be created, or if it cannot be opened for any other reason.
	 * @throws IOException if the {@link WriteOption#CREATE} option is given
	 *	and the specified file already exists.
	 * @throws SecurityException if a security manager exists and denies
	 *	write access to {@code f}.
	 */
	public static BufferedOutputStream newOutputStream(File f, WriteOption... options)
		throws IOException
	{
		Set<WriteOption> opts = ImmutableSet.of(options);
		checkWriteOptions(opts);
		checkFileExistence(f, opts);
		return new BufferedOutputStream(newOutputStream(f, opts));
	}

	private static void checkWriteOptions(Set<WriteOption> options)
	{
		if ((options.contains(APPEND) && options.contains(OVERWRITE))
			|| (options.contains(CREATE) && options.size() > 1))
		{
			throw new IllegalArgumentException(
				"Incompatible write options: " + options);
		}
	}

	private static void checkFileExistence(File f, Set<WriteOption> options)
		throws IOException
	{
		if (options.contains(CREATE) && f.exists()) {
			throw new IOException(
				CREATE + ": " + f + " already exists");
		}
		if (options.contains(UPDATE) && !f.exists()) {
			throw new FileNotFoundException(
				UPDATE + ": " + f + " doesn't exist");
		}
	}

	private static OutputStream newOutputStream(File f, Set<WriteOption> options)
		throws FileNotFoundException
	{
		if (options.contains(APPEND)) {
			return new FileOutputStream(f, true);
		}
		if (options.contains(OVERWRITE)) {
			RandomAccessFile file = new RandomAccessFile(f, "rw");
			FileChannel channel = file.getChannel();
			return Channels.newOutputStream(channel);
		}
		return new FileOutputStream(f);
	}

	/**
	 * Reads the content of the given {@code File}.
	 *
	 * @param f the file to read.
	 *
	 * @return the file's content as a {@code byte[]}.
	 *
	 * @throws NullPointerException if {@code f} is {@code null}.
	 * @throws IOException if {@code f} does not exist, or if it is a
	 *	directory rather than a regular file, or if it can't be read.
	 * @throws SecurityException if a security manager exists and denies
	 *	read access to {@code f}.
	 */
	public static byte[] read(File f) throws IOException
	{
		InputStream in = newInputStream(f);
		try {
			return ByteStreams.read(in);
		} finally {
			IO.close(in);
		}
	}

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


/*----------------------------------------------------------------------------*
 * 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.net;

import org.kocakosm.pitaya.charset.ASCII;
import org.kocakosm.pitaya.collection.ImmutableMap;
import org.kocakosm.pitaya.util.Joiner;
import org.kocakosm.pitaya.util.Parameters;
import org.kocakosm.pitaya.util.Strings;
import org.kocakosm.pitaya.util.XObjects;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Represents an Internet Media Type. This class also supports the concept of
 * media ranges defined by HTTP/1.1. The {@code *} character is treated as a
 * wildcard and is used to represent any acceptable type or subtype value. All
 * values for type, subtype, parameter attributes or parameter values must be
 * valid according to RFCs 2045 and 2046. All portions of the media type that
 * are case-insensitive (type, subtype, parameter attributes) are normalized to
 * lowercase. Parameters' values are not modified except for the {@code charset}
 * parameter which is normalised to uppercase. Instances of this class are
 * immutable.
 *
 * @see RFC 2045
 * @see RFC 2046
 * @see RFC 2616 (§ 14.1)
 *
 * @since 0.3
 *
 * @author Osman KOCAK
 */
public final class MediaType implements Serializable
{
	private static final long serialVersionUID = 30386013071308392L;
	private static final String CHARSET;
	private static final String WILDCARD;
	private static final Joiner PARAMS_JOINER;
	private static final Pattern TOKEN_PATTERN;
	private static final Pattern MEDIA_TYPE_PATTERN;
	static {
		WILDCARD = "*";
		CHARSET = "charset";
		PARAMS_JOINER = Joiner.on("; ").withPrefix("; ");
		String token = "[\\p{ASCII}&&[^\\p{Cntrl}\\s\\(\\)<>@,;:\"/\\[\\]\\?=\\\\]]+";
		TOKEN_PATTERN = Pattern.compile(token);
		String quotedString = "\"([\\p{ASCII}&&[^\"\\\\]]|\\\\\\p{ASCII})*\"";
		String regex = Strings.concat("(", token, ")/(", token, ")",
			"((;[ \t\r\n]*", token, "=(", token, "|", quotedString, "))*)");
		MEDIA_TYPE_PATTERN = Pattern.compile(regex);
	}

	/** */* */
	public static final MediaType ANY_TYPE = create(WILDCARD, WILDCARD);

	/** {@code text/*} */
	public static final MediaType ANY_TEXT_TYPE = create("text", WILDCARD);

	/** {@code image/*} */
	public static final MediaType ANY_IMAGE_TYPE = create("image", WILDCARD);

	/** {@code audio/*} */
	public static final MediaType ANY_AUDIO_TYPE = create("audio", WILDCARD);

	/** {@code video/*} */
	public static final MediaType ANY_VIDEO_TYPE = create("video", WILDCARD);

	/** {@code application/*} */
	public static final MediaType ANY_APPLICATION_TYPE = create("application", WILDCARD);

	/** {@code image/jpeg} */
	public static final MediaType JPEG = create("image", "jpeg");

	/** {@code image/png} */
	public static final MediaType PNG = create("image", "png");

	/** {@code image/gif} */
	public static final MediaType GIF = create("image", "gif");

	/** {@code text/css} */
	public static final MediaType CSS = create("text", "css");

	/** {@code text/html} */
	public static final MediaType HTML = create("text", "html");

	/** {@code text/plain} */
	public static final MediaType PLAIN_TEXT = create("text", "plain");

	/** {@code application/json} */
	public static final MediaType JSON = create("application", "json");

	/** {@code application/xml} */
	public static final MediaType XML = create("application", "xml");

	/** {@code application/octet-stream} */
	public static final MediaType OCTET_STREAM = create("application", "octet-stream");

	/**
	 * Parses a {@code MediaType} from its {@code String} representation.
	 *
	 * @param input the input {@code String} to be parsed.
	 *
	 * @return the parsed {@code MediaType} instance.
	 *
	 * @throws NullPointerException if {@code input} is {@code null}.
	 * @throws IllegalArgumentException if {@code input} can't be parsed or
	 *	if a wildcard is used for the type, but not for the subtype.
	 */
	public static MediaType parse(String input)
	{
		Parameters.checkNotNull(input);
		Matcher m = MEDIA_TYPE_PATTERN.matcher(input);
		Parameters.checkCondition(m.matches());
		String type = m.group(1);
		String subtype = m.group(2);
		String params = m.group(3);
		ParametersParser parser = new ParametersParser();
		return create(type, subtype, parser.parse(params));
	}

	/**
	 * Creates a new {@code MediaType} with the given type and subtype.
	 *
	 * @param type the type.
	 * @param subtype the subtype.
	 *
	 * @return the created {@code MediaType} instance.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if type or subtype is invalid or if
	 *	a wildcard is used for the type, but not for the subtype.
	 */
	public static MediaType create(String type, String subtype)
	{
		Map<String, String> params = Collections.emptyMap();
		return create(type, subtype, params);
	}

	private static MediaType create(String type, String subtype,
		Map<String, String> parameters)
	{
		Parameters.checkCondition(!WILDCARD.equals(type) || WILDCARD.equals(subtype));
		String normalizedType = normalizeToken(type);
		String normalizedSubtype = normalizeToken(subtype);
		Map<String, String> params = normalizeParameters(parameters);
		return new MediaType(normalizedType, normalizedSubtype, params);
	}

	private static String normalizeToken(String token)
	{
		Parameters.checkNotNull(token);
		Parameters.checkCondition(TOKEN_PATTERN.matcher(token).matches());
		return ASCII.toLowerCase(token);
	}

	private static Map<String, String> normalizeParameters(Map<String, String> parameters)
	{
		Map<String, String> params = new LinkedHashMap<String, String>();
		for (Entry<String, String> entry : parameters.entrySet()) {
			String attribute = normalizeToken(entry.getKey());
			String value = CHARSET.equals(attribute)
				? ASCII.toUpperCase(entry.getValue())
				: Parameters.checkNotNull(entry.getValue());
			params.put(attribute, value);
		}
		return params;
	}

	private final String type;
	private final String subtype;
	private final Map<String, String> parameters;

	private MediaType(String type, String subtype, Map<String, String> parameters)
	{
		this.type = type;
		this.subtype = subtype;
		this.parameters = ImmutableMap.copyOf(parameters);
	}

	/**
	 * Returns the top-level media type.
	 *
	 * @return the top-level media type.
	 */
	public String type()
	{
		return type;
	}

	/**
	 * Returns the media subtype.
	 *
	 * @return the media subtype.
	 */
	public String subtype()
	{
		return subtype;
	}

	/**
	 * Returns the parameters of this media type. The returned {@code Map}
	 * is immutable.
	 *
	 * @return the parameters of this media type.
	 */
	public Map<String, String> parameters()
	{
		return parameters;
	}

	/**
	 * Returns whether the type or the subtype of this media type is the
	 * wildcard.
	 *
	 * @return whether this media type has a wildcard.
	 */
	public boolean hasWildcard()
	{
		return WILDCARD.equals(type) || WILDCARD.equals(subtype);
	}

	/**
	 * Returns the value of the charset parameter if it is specified, or
	 * {@code null} otherwise.
	 *
	 * @return the charset parameter's value if present, or {@code null}
	 *	otherwise.
	 *
	 * @throws IllegalCharsetNameException if the charset is illegal.
	 * @throws UnsupportedCharsetException if the charset is not supported.
	 */
	public Charset charset()
	{
		String charset = parameters.get(CHARSET);
		return charset == null ? null : Charset.forName(charset);
	}

	/**
	 * Returns a new {@code MediaType} instance similar to this one but with
	 * the {@code charset} parameter set to the {@link Charset#name name} of
	 * the given charset.
	 *
	 * @param charset the charset.
	 *
	 * @return the created {@code MediaType}.
	 *
	 * @throws NullPointerException if {@code charset} is {@code null}.
	 */
	public MediaType withCharset(Charset charset)
	{
		return withParameter(CHARSET, charset.name());
	}

	/**
	 * Returns a new {@code MediaType} instance similar to this one but with
	 * the specified parameter set to the given value.
	 *
	 * @param attribute the attribute of the parameter to add.
	 * @param value the value of the parameter to add.
	 *
	 * @return the created {@code MediaType}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if {@code attribute} is invalid.
	 */
	public MediaType withParameter(String attribute, String value)
	{
		Map<String, String> params = new HashMap<String, String>();
		params.put(attribute, value);
		return withParameters(params);
	}

	/**
	 * Returns a new {@code MediaType} instance having the same type and
	 * subtype as this one but whose parameters is the "union" of this
	 * instances' parameters with the given ones (the given parameters have
	 * precedence over this instance's parameters).
	 *
	 * @param parameters the parameters.
	 *
	 * @return the created {@code MediaType}.
	 *
	 * @throws NullPointerException if {@code parameters} is {@code null} or
	 *	if it contains the {@code null} key or a {@code null} value.
	 * @throws IllegalArgumentException if {@code parameters} contains an
	 *	invalid attribute.
	 */
	public MediaType withParameters(Map<String, String> parameters)
	{
		Map<String, String> params = new LinkedHashMap<String, String>();
		params.putAll(this.parameters);
		params.putAll(normalizeParameters(parameters));
		return new MediaType(type, subtype, params);
	}

	/**
	 * Returns a new {@code MediaType} instance similar to this one but
	 * without the specified parameter.
	 *
	 * @param attribute the attribute of the parameter to remove.
	 *
	 * @return the created {@code MediaType}.
	 *
	 * @throws NullPointerException if {@code attribute} is {@code null}.
	 */
	public MediaType withoutParameter(String attribute)
	{
		Parameters.checkNotNull(attribute);
		Map<String, String> params = new LinkedHashMap<String, String>();
		params.putAll(parameters);
		params.remove(attribute);
		return new MediaType(type, subtype, params);
	}

	/**
	 * Returns a new {@code MediaType} instance with the same type and
	 * subtype as this instance but without any parameters.
	 *
	 * @return the created {@code MediaType}.
	 */
	public MediaType withoutParameters()
	{
		Map<String, String> params = Collections.emptyMap();
		return new MediaType(type, subtype, params);
	}

	/**
	 * Returns whether this media type is within the specified media range.
	 * Namely, it returns {@code true} if the type of {@code range} is the
	 * wildcard or is equal to the type of this instance, and, if the
	 * subtype of {@code range} is the wildcard or is equal to the subtype
	 * of this instance, and, if all the parameters present in {@code range}
	 * are also present in this instance.
	 *
	 * @param range the range to check this media type against.
	 *
	 * @return whether this media type is within the specified range.
	 *
	 * @throws NullPointerException if {@code range} is {@code null}.
	 */
	public boolean is(MediaType range)
	{
		return (range.type.equals(WILDCARD) || range.type.equals(type))
			&& (range.subtype.equals(WILDCARD) || range.subtype.equals(subtype))
			&& parameters.entrySet().containsAll(range.parameters.entrySet());
	}

	@Override
	public boolean equals(Object o)
	{
		if (o == this) {
			return true;
		}
		if (!(o instanceof MediaType)) {
			return false;
		}
		MediaType mediaType = (MediaType) o;
		return type.equals(mediaType.type)
			&& subtype.equals(mediaType.subtype)
			&& parameters.equals(mediaType.parameters);
	}

	@Override
	public int hashCode()
	{
		return XObjects.hashCode(type, subtype, parameters);
	}

	@Override
	public String toString()
	{
		if (parameters.isEmpty()) {
			return Strings.concat(type, "/", subtype);
		}
		List<String> params = new ArrayList<String>();
		for (Entry<String, String> parameter : parameters.entrySet()) {
			String attribute = parameter.getKey();
			String value = parameter.getValue();
			if (!TOKEN_PATTERN.matcher(value).matches()) {
				value = escapeAndQuote(value);
			}
			params.add(attribute + "=" + value);
		}
		return Strings.concat(type, "/", subtype, PARAMS_JOINER.join(params));
	}

	private String escapeAndQuote(String value)
	{
		StringBuilder escaped = new StringBuilder(value.length() * 2);
		escaped.append('"');
		for (char c : value.toCharArray()) {
			if (c == '\\' || c == '"') {
				escaped.append('\\');
			}
			escaped.append(c);
		}
		return escaped.append('"').toString();
	}

	private static final class ParametersParser
	{
		private String params;
		private int index;

		Map<String, String> parse(String params)
		{
			this.params = params;
			Map<String, String> m = new LinkedHashMap<String, String>();
			while (index < params.length() - 1) {
				m.put(readAttribute(), readValue());
			}
			return m;
		}

		private String readAttribute()
		{
			StringBuilder attribute = new StringBuilder();
			char c = params.charAt(++index);
			while (c != '=') {
				attribute.append(c);
				c = params.charAt(++index);
			}
			return attribute.toString().trim();
		}

		private String readValue()
		{
			char c = params.charAt(++index);
			boolean quoted = c == '"';
			c = quoted ? params.charAt(++index) : c;
			boolean escaped = false;
			StringBuilder value = new StringBuilder();
			while ((c != ';' || quoted) && (c != '"' || escaped)) {
				if (c == '\\' && !escaped) {
					escaped = true;
				} else {
					escaped = false;
					value.append(c);
				}
				if (index == params.length() - 1) {
					break;
				}
				c = params.charAt(++index);
			}
			index += quoted ? 1 : 0;
			return value.toString();
		}
	}
}


/*----------------------------------------------------------------------------*
 * 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 org.kocakosm.pitaya.util.Parameters;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Static utility methods that operate on or return {@link Iterable}s.
 *
 * @author Osman KOCAK
 */
public final class Iterables
{
	/** The empty {@code Iterable}. */
	public static final Iterable EMPTY_ITERABLE = new EmptyIterable();

	/**
	 * Returns the empty {@code Iterable} for a particular type (type-safe).
	 * Note that unlike this method, the like-named static field does not
	 * provide type safety.
	 *
	 * @param <T> the type of the {@code Iterable}'s elements.
	 *
	 * @return the empty {@code Iterable}.
	 */
	public static <T> Iterable<T> emptyIterable()
	{
		return (Iterable<T>) EMPTY_ITERABLE;
	}

	/**
	 * Concatenates the given {@code Iterable}s into a single one. Note that
	 * the returned {@code Iterable} is only a view, that is, any subsequent
	 * update on any of the input {@code Iterable}s will affect the returned
	 * view. The input {@code Iterable}s will not be polled until necessary.
	 * The returned view's {@code Iterator} supports {@link Iterator#remove()}
	 * when the corresponding input {@code Iterator} supports it.
	 *
	 * @param <T> the type of the returned {@code Iterable}s' elements.
	 * @param iterables the {@code Iterable}s to concatenate.
	 *
	 * @return the concatenated {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterables} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static <T> Iterable<T> concat(Iterable<? extends T>... iterables)
	{
		return concat(Arrays.asList(iterables));
	}

	/**
	 * Concatenates the given {@code Iterable}s into a single one. Note that
	 * the returned {@code Iterable} is only a view, that is, any subsequent
	 * update on any of the input {@code Iterable}s will affect the returned
	 * view. The input {@code Iterable}s will not be polled until necessary.
	 * The returned view's {@code Iterator} supports {@link Iterator#remove()}
	 * when the corresponding input {@code Iterator} supports it.
	 *
	 * @param <T> the type of the returned {@code Iterable}s' elements.
	 * @param iterables the {@code Iterable}s to concatenate.
	 *
	 * @return the concatenated {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterables} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static <T> Iterable<T> concat(Iterable<? extends Iterable<? extends T>> iterables)
	{
		return new ConcatIterable<T>(iterables);
	}

	/**
	 * Returns a cyclic {@code Iterable} that cycles indefinitely over the
	 * given {@code Iterable}'s elements. The returned {@code Iterable}'s
	 * {@code Iterator} supports {@code Iterator#remove()}.
	 *
	 * @param <T> the type of the returned {@code Iterable}'s elements.
	 * @param iterable the {@code Iterable} containing the elements to
	 *	cycle over.
	 *
	 * @return the cyclic {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static <T> Iterable<T> cycle(Iterable<? extends T> iterable)
	{
		return new CyclicIterable<T>(toList(iterable));
	}

	/**
	 * Returns an {@code Iterable} that cycles indefinitely over the given
	 * elements. The returned {@code Iterable}'s {@code Iterator} supports
	 * {@link Iterator#remove()}.
	 *
	 * @param <T> the type of the returned {@code Iterable}'s elements.
	 * @param elements the elements to cycle over.
	 *
	 * @return the cyclic {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static <T> Iterable<T> cycle(T... elements)
	{
		return new CyclicIterable<T>(new ArrayList<T>(Arrays.asList(elements)));
	}

	/**
	 * Returns whether the given {@code Iterable}s contain equal elements in
	 * the same order.
	 *
	 * @param i1 the first {@code Iterable}.
	 * @param i2 the second {@code Iterable}.
	 *
	 * @return whether the given {@code Iterables} have the same content.
	 */
	public static boolean equal(Iterable<?> i1, Iterable<?> i2)
	{
		if (i1 == i2) {
			return true;
		}
		if (i1 == null || i2 == null) {
			return false;
		}
		return Iterators.equal(i1.iterator(), i2.iterator());
	}

	/**
	 * Returns an {@code Iterable} view of the given {@code Iterable} that
	 * will only return its first {@code limit} items. The returned view's
	 * {@code Iterator}s support {@link Iterator#remove()} if the original
	 * {@code Iterator} does.
	 *
	 * @param <T> the type of the returned {@code Iterable}'s elements.
	 * @param iterable the {@code Iterable} to limit.
	 * @param limit the number of elements in the returned {@code Iterable}.
	 *
	 * @return the limited {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 * @throws IllegalArgumentException if ({@code limit} is negative.
	 */
	public static <T> Iterable<T> limit(Iterable<? extends T> iterable, int limit)
	{
		return new LimitIterable<T>(iterable, limit);
	}

	/**
	 * Returns an {@code Iterable} view of the given {@code Iterable} that
	 * skips its first {@code n} elements. The returned {@code Iterable}'s
	 * {@code Iterator} supports {@link Iterator#remove()} if the original
	 * {@code Iterator} does.
	 *
	 * @param <T> the type of the {@code Iterable}'s elements.
	 * @param iterable the input {@code Iterable}.
	 * @param n the number of items to skip.
	 *
	 * @return the skipping {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 * @throws IllegalArgumentException if ({@code n} is negative.
	 */
	public static <T> Iterable<T> skip(Iterable<T> iterable, int n)
	{
		return new SkipIterable<T>(iterable, n);
	}

	/**
	 * Returns a {@code List} containing all the given {@code Iterable}'s
	 * elements.
	 *
	 * @param <T> the type of the returned {@code List}'s elements.
	 * @param iterable the source {@code Iterable}.
	 *
	 * @return the {@code List} created from the given {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static <T> List<T> toList(Iterable<? extends T> iterable)
	{
		List<T> list = new ArrayList<T>();
		for (T e : iterable) {
			list.add(e);
		}
		return list;
	}

	/**
	 * Returns a {@code Set} containing all the given {@code Iterable}'s
	 * elements. The returned {@code Set} has the same iteration order as
	 * the source {@code Iterable}.
	 *
	 * @param <T> the type of the returned {@code Set}'s elements.
	 * @param iterable the source {@code Iterable}.
	 *
	 * @return the {@code Set} created from the given {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static <T> Set<T> toSet(Iterable<? extends T> iterable)
	{
		Set<T> set = new LinkedHashSet<T>();
		for (T e : iterable) {
			set.add(e);
		}
		return set;
	}

	/**
	 * Returns a {@code Bag} containing all the given {@code Iterable}'s
	 * elements. The returned {@code Bag} has the same iteration order as
	 * the source {@code Iterable}.
	 *
	 * @param <T> the type of the returned {@code Bag}'s elements.
	 * @param iterable the source {@code Iterable}.
	 *
	 * @return the {@code Bag} created from the given {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static <T> Bag<T> toBag(Iterable<? extends T> iterable)
	{
		return new ArrayBag<T>(iterable);
	}

	/**
	 * Returns a {@code String} representation of the given {@code Iterable}
	 * using the "[e1, e2, ..., en]" format.
	 *
	 * @param iterable the {@code Iterable}.
	 *
	 * @return the {@code String} created from the given {@code Iterable}.
	 *
	 * @throws NullPointerException if {@code iterable} is {@code null}.
	 */
	public static String toString(Iterable<?> iterable)
	{
		return toList(iterable).toString();
	}

	private static final class EmptyIterable<T> implements Iterable<T>
	{
		@Override
		public Iterator<T> iterator()
		{
			return Iterators.emptyIterator();
		}
	}

	private static final class ConcatIterable<T> implements Iterable<T>
	{
		private final List<Iterable<? extends T>> iterables;

		ConcatIterable(Iterable<? extends Iterable<? extends T>> iterables)
		{
			this.iterables = new ArrayList<Iterable<? extends T>>();
			for (Iterable<? extends T> iterable : iterables) {
				Parameters.checkNotNull(iterable);
				this.iterables.add(iterable);
			}
		}

		@Override
		public Iterator<T> iterator()
		{
			List<Iterator<? extends T>> iterators = new ArrayList<Iterator<? extends T>>();
			for (Iterable<? extends T> iterable : iterables) {
				iterators.add(iterable.iterator());
			}
			return Iterators.concat(iterators.iterator());
		}
	}

	private static final class CyclicIterable<T> implements Iterable<T>
	{
		private final Iterable<? extends T> iterable;

		CyclicIterable(Iterable<? extends T> iterable)
		{
			Parameters.checkNotNull(iterable);
			this.iterable = iterable;
		}

		@Override
		public Iterator<T> iterator()
		{
			return Iterators.cycle(iterable.iterator());
		}
	}

	private static final class LimitIterable<T> implements Iterable<T>
	{
		private final int limit;
		private final Iterable<? extends T> iterable;

		LimitIterable(Iterable<? extends T> iterable, int limit)
		{
			Parameters.checkNotNull(iterable);
			Parameters.checkCondition(limit >= 0);
			this.iterable = iterable;
			this.limit = limit;
		}

		@Override
		public Iterator<T> iterator()
		{
			return Iterators.limit(iterable.iterator(), limit);
		}
	}

	private static final class SkipIterable<T> implements Iterable<T>
	{
		private final int n;
		private final Iterable<T> iterable;

		SkipIterable(Iterable<T> iterable, int n)
		{
			Parameters.checkNotNull(iterable);
			Parameters.checkCondition(n >= 0);
			this.iterable = iterable;
			this.n = n;
		}

		@Override
		public Iterator<T> iterator()
		{
			Iterator<T> iterator = iterable.iterator();
			Iterators.skip(iterator, n);
			return iterator;
		}
	}

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


/*----------------------------------------------------------------------------*
 * 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.LittleEndian;

/**
 * The MD4 digest algorithm. Instances of this class are not thread safe.
 *
 * @author Osman KOCAK
 */
final class MD4 extends AbstractDigest
{
	private static final int DIGEST_LENGTH = 16;
	private static final int BLOCK_LENGTH = 64;

	/** Number of bytes processed so far. */
	private long counter;

	/** Current digest value (4 32-bit words). */
	private final int[] value;

	/** Input buffer (16 32-bit words). */
	private final byte[] buffer;

	/** Number of bytes in the input buffer. */
	private int bufferLen;

	/** Creates a new ready to use {@code MD4}. */
	MD4()
	{
		super("MD4", DIGEST_LENGTH);
		this.value = new int[4];
		this.buffer = new byte[BLOCK_LENGTH];
		reset();
	}

	@Override
	public Digest reset()
	{
		counter = 0L;
		value[0] = 0x67452301;
		value[1] = 0xEFCDAB89;
		value[2] = 0x98BADCFE;
		value[3] = 0x10325476;
		bufferLen = 0;
		return this;
	}

	@Override
	public Digest update(byte input)
	{
		buffer[bufferLen] = input;
		if (++bufferLen == BLOCK_LENGTH) {
			processBuffer();
		}
		return this;
	}

	@Override
	public Digest update(byte[] input, int off, int len)
	{
		while (len > 0) {
			int cpLen = Math.min(BLOCK_LENGTH - bufferLen, len);
			System.arraycopy(input, off, buffer, bufferLen, cpLen);
			bufferLen += cpLen;
			off += cpLen;
			len -= cpLen;
			if (bufferLen == BLOCK_LENGTH) {
				processBuffer();
			}
		}
		return this;
	}

	@Override
	public byte[] digest()
	{
		addPadding();
		byte[] res = new byte[DIGEST_LENGTH];
		LittleEndian.encode(value[0], res, 0);
		LittleEndian.encode(value[1], res, 4);
		LittleEndian.encode(value[2], res, 8);
		LittleEndian.encode(value[3], res, 12);
		reset();
		return res;
	}

	/** Adds the padding bits and the message length to the input data. */
	private void addPadding()
	{
		int len = BLOCK_LENGTH - bufferLen;
		if (len < 9) {
			len += BLOCK_LENGTH;
		}
		byte[] buf = new byte[len];
		buf[0] = (byte) 0x80;
		for (int i = 1; i < len - 8; i++) {
			buf[i] = (byte) 0x00;
		}
		counter = (counter + (long) bufferLen) * 8L;
		LittleEndian.encode(counter, buf, len - 8);
		update(buf);
	}

	private void processBuffer()
	{
		int A = value[0];
		int B = value[1];
		int C = value[2];
		int D = value[3];

		int[] X = new int[16];
		for (int i = 0; i < 16; i++) {
			X[i] = LittleEndian.decodeInt(buffer, 4 * i);
		}

		/* Round 1 */
		A = FF(A, B, C, D, X[ 0], 3);
		D = FF(D, A, B, C, X[ 1], 7);
		C = FF(C, D, A, B, X[ 2], 11);
		B = FF(B, C, D, A, X[ 3], 19);
		A = FF(A, B, C, D, X[ 4], 3);
		D = FF(D, A, B, C, X[ 5], 7);
		C = FF(C, D, A, B, X[ 6], 11);
		B = FF(B, C, D, A, X[ 7], 19);
		A = FF(A, B, C, D, X[ 8], 3);
		D = FF(D, A, B, C, X[ 9], 7);
		C = FF(C, D, A, B, X[10], 11);
		B = FF(B, C, D, A, X[11], 19);
		A = FF(A, B, C, D, X[12], 3);
		D = FF(D, A, B, C, X[13], 7);
		C = FF(C, D, A, B, X[14], 11);
		B = FF(B, C, D, A, X[15], 19);

		/* Round 2 */
		A = GG(A, B, C, D, X[ 0], 3);
		D = GG(D, A, B, C, X[ 4], 5);
		C = GG(C, D, A, B, X[ 8], 9);
		B = GG(B, C, D, A, X[12], 13);
		A = GG(A, B, C, D, X[ 1], 3);
		D = GG(D, A, B, C, X[ 5], 5);
		C = GG(C, D, A, B, X[ 9], 9);
		B = GG(B, C, D, A, X[13], 13);
		A = GG(A, B, C, D, X[ 2], 3);
		D = GG(D, A, B, C, X[ 6], 5);
		C = GG(C, D, A, B, X[10], 9);
		B = GG(B, C, D, A, X[14], 13);
		A = GG(A, B, C, D, X[ 3], 3);
		D = GG(D, A, B, C, X[ 7], 5);
		C = GG(C, D, A, B, X[11], 9);
		B = GG(B, C, D, A, X[15], 13);

		/* Round 3 */
		A = HH(A, B, C, D, X[ 0], 3);
		D = HH(D, A, B, C, X[ 8], 9);
		C = HH(C, D, A, B, X[ 4], 11);
		B = HH(B, C, D, A, X[12], 15);
		A = HH(A, B, C, D, X[ 2], 3);
		D = HH(D, A, B, C, X[10], 9);
		C = HH(C, D, A, B, X[ 6], 11);
		B = HH(B, C, D, A, X[14], 15);
		A = HH(A, B, C, D, X[ 1], 3);
		D = HH(D, A, B, C, X[ 9], 9);
		C = HH(C, D, A, B, X[ 5], 11);
		B = HH(B, C, D, A, X[13], 15);
		A = HH(A, B, C, D, X[ 3], 3);
		D = HH(D, A, B, C, X[11], 9);
		C = HH(C, D, A, B, X[ 7], 11);
		B = HH(B, C, D, A, X[15], 15);

		value[0] += A;
		value[1] += B;
		value[2] += C;
		value[3] += D;

		counter += 64L;
		bufferLen = 0;
	}

	private int FF(int a, int b, int c, int d, int x, int s)
	{
		return Integer.rotateLeft(a + F(b, c, d) + x, s);
	}

	private int GG(int a, int b, int c, int d, int x, int s)
	{
		return Integer.rotateLeft(a + G(b, c, d) + x + 0x5A827999, s);
	}

	private int HH(int a, int b, int c, int d, int x, int s)
	{
		return Integer.rotateLeft(a + H(b, c, d) + x + 0x6ED9EBA1, s);
	}

	private int F(int x, int y, int z)
	{
		return (y & x) | (z & ~x);
	}

	private int G(int x, int y, int z)
	{
		return (x & y) | (x & z) | (y & z);
	}

	private int H(int x, int y, int z)
	{
		return x ^ y ^ z;
	}
}


/*----------------------------------------------------------------------------*
 * 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.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.RandomAccess;

/**
 * Immutable random access {@link List} implementation. Accepts {@code null}
 * values.
 *
 * @param <E> the type of the {@code List}'s elements.
 *
 * @author Osman KOCAK
 */
public final class ImmutableList<E>
	extends AbstractList<E> implements RandomAccess, Serializable
{
	private static final long serialVersionUID = 5355899738233743694L;

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

		/**
		 * Adds the given values to the {@code List} 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 List} 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 List} 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;
		}

		/**
		 * Sorts the elements currently in the {@code List} being built
		 * into ascending order, according to the natural ordering of
		 * its elements (all elements in this builder must implement the
		 * {@link Comparable} interface).
		 *
		 * @return this object.
		 *
		 * @throws ClassCastException if this builder contains elements
		 *	that are not mutually comparable.
		 * @throws IllegalArgumentException if the natural ordering of
		 *	the elements in this builder is found to violate the
		 *	{@link Comparable} contract.
		 */
		public Builder<E> sort()
		{
			E[] values = (E[]) inner.toArray();
			Arrays.sort(values);
			return new Builder<E>().add(values);
		}

		/**
		 * Sorts the elements currently in the {@code List} being built
		 * into ascending order, according to the order induced by the
		 * given {@code Comparator}.
		 *
		 * @param c the {@code Comparator} to use.
		 *
		 * @return this object.
		 *
		 * @throws ClassCastException if this builder contains elements
		 *	that are not mutually comparable using the specified
		 *	{@code Comparator}.
		 * @throws IllegalArgumentException if the comparator is found
		 *	to violate the {@code Comparator} contract.
		 */
		public Builder<E> sort(Comparator<? super E> c)
		{
			Collections.sort(inner, c);
			return this;
		}

		/**
		 * Reverses the order of the elements currently in the
		 * {@code List} being built.
		 *
		 * @return this object.
		 */
		public Builder<E> reverse()
		{
			Collections.reverse(inner);
			return this;
		}

		/**
		 * Rotates the elements currently in the {@code List} being
		 * built by the specified distance.
		 *
		 * @param n the distance to rotate.
		 *
		 * @return this object.
		 */
		public Builder<E> rotate(int n)
		{
			Collections.rotate(inner, n);
			return this;
		}

		/**
		 * Randomly permutes the elements currently in the {@code List}
		 * being built using a default source of randomness. All
		 * permutations occur with approximately equal likelihood.
		 *
		 * @return this object.
		 */
		public Builder<E> shuffle()
		{
			Collections.shuffle(inner);
			return this;
		}

		/**
		 * Randomly permutes the elements currently in the {@code List}
		 * being built using the specified source of randomness. All
		 * permutations occur with equal likelihood assuming that the
		 * source of randomness is fair.
		 *
		 * @param random the source of randomness to use.
		 *
		 * @return this object.
		 *
		 * @throws NullPointerException if {@code random} is {@code null}.
		 */
		public Builder<E> shuffle(Random random)
		{
			Collections.shuffle(inner, random);
			return this;
		}

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

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

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

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

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

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

	private final List<E> inner;

	private ImmutableList(List<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 UnmodifiableListIterator<E>(inner.listIterator());
	}

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

	@Override
	public <E> E[] toArray(E[] 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 addAll(int index, 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();
	}

	@Override
	public E get(int index)
	{
		return inner.get(index);
	}

	@Override
	public E set(int index, E element)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public void add(int index, E element)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public E remove(int index)
	{
		throw new UnsupportedOperationException();
	}

	@Override
	public int indexOf(Object o)
	{
		return inner.indexOf(o);
	}

	@Override
	public int lastIndexOf(Object o)
	{
		return inner.lastIndexOf(o);
	}

	@Override
	public ListIterator<E> listIterator()
	{
		return new UnmodifiableListIterator<E>(inner.listIterator());
	}

	@Override
	public ListIterator<E> listIterator(int index)
	{
		return new UnmodifiableListIterator<E>(inner.listIterator(index));
	}

	@Override
	public List<E> subList(int fromIndex, int toIndex)
	{
		return new ImmutableList<E>(inner.subList(fromIndex, toIndex));
	}

	private static final class UnmodifiableListIterator<E> implements ListIterator<E>
	{
		private final ListIterator<E> inner;

		UnmodifiableListIterator(ListIterator<E> iterator)
		{
			this.inner = iterator;
		}

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

		@Override
		public E next()
		{
			return inner.next();
		}

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

		@Override
		public E previous()
		{
			return inner.previous();
		}

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

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

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

		@Override
		public void set(E e)
		{
			throw new UnsupportedOperationException();
		}

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