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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Text files utilities.
 *
 * @see XFiles
 *
 * @author Osman KOCAK
 */
public final class TextFiles
{
	/**
	 * Returns the first (up to 10) lines of the given {@code File} using
	 * the system's default charset. Named after the Unix command of the
	 * same name.
	 *
	 * @param f the {@code File} to read.
	 *
	 * @return the first lines 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 List<String> head(File f) throws IOException
	{
		return head(f, Charsets.DEFAULT);
	}

	/**
	 * Returns the first (up to 10) lines of the given {@code File} using
	 * the specified charset. Named after the Unix command of the same name.
	 *
	 * @param f the {@code File} to read.
	 * @param charset the charset to use.
	 *
	 * @return the first lines 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 List<String> head(File f, Charset charset) throws IOException
	{
		return head(f, 10, charset);
	}

	/**
	 * Returns the first (up to {@code n}) lines of the given {@code File}
	 * using the system's default charset. Named after the Unix command of
	 * the same name.
	 *
	 * @param f the {@code File} to read.
	 * @param n the maximum number of lines to read.
	 *
	 * @return the first lines 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 List<String> head(File f, int n) throws IOException
	{
		return head(f, n, Charsets.DEFAULT);
	}

	/**
	 * Returns the first (up to {@code n}) lines of the given {@code File}
	 * using the specified charset. Named after the Unix command of the same
	 * name.
	 *
	 * @param f the {@code File} to read.
	 * @param n the maximum number of lines to read.
	 * @param charset the charset to use.
	 *
	 * @return the first lines of the given {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments 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 List<String> head(File f, int n, Charset charset)
		throws IOException
	{
		Parameters.checkCondition(n >= 0);
		List<String> lines = new ArrayList<String>();
		BufferedReader reader = newReader(f, charset);
		try {
			String line = reader.readLine();
			while (line != null && lines.size() < n) {
				lines.add(line);
				line = reader.readLine();
			}
		} finally {
			IO.close(reader);
		}
		return Collections.unmodifiableList(lines);
	}

	/**
	 * Returns the last (up to 10) lines of the given {@code File} using the
	 * system's default charset. Named after the Unix command of the same
	 * name.
	 *
	 * @param f the {@code File} to read.
	 *
	 * @return the last lines 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 List<String> tail(File f) throws IOException
	{
		return tail(f, Charsets.DEFAULT);
	}

	/**
	 * Returns the last (up to 10) lines of the given {@code File} using the
	 * specified charset. Named after the Unix command of the same name.
	 *
	 * @param f the {@code File} to read.
	 * @param charset the charset to use.
	 *
	 * @return the last lines of the given {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments 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 List<String> tail(File f, Charset charset) throws IOException
	{
		return tail(f, 10, charset);
	}

	/**
	 * Returns the last (up to n) lines of the given {@code File} using the
	 * system's default charset. Named after the Unix command of the same
	 * name.
	 *
	 * @param f the {@code File} to read.
	 * @param n the maximum number of lines to read.
	 *
	 * @return the last lines 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 List<String> tail(File f, int n) throws IOException
	{
		return tail(f, n, Charsets.DEFAULT);
	}

	/**
	 * Returns the last (up to n) lines of the given {@code File} using the
	 * specified charset. Named after the Unix command of the same name.
	 *
	 * @param f the {@code File} to read.
	 * @param n the maximum number of lines to read.
	 * @param charset the charset to use.
	 *
	 * @return the last lines of the given {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments 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 List<String> tail(File f, int n, Charset charset)
		throws IOException
	{
		Parameters.checkCondition(n >= 0);
		if (n == 0) {
			return Collections.emptyList();
		}
		List<String> lines = new LinkedList<String>();
		BufferedReader reader = newReader(f, charset);
		try {
			String line = reader.readLine();
			while (line != null) {
				lines.add(line);
				if (lines.size() > n) {
					lines.remove(0);
				}
				line = reader.readLine();
			}
		} finally {
			IO.close(reader);
		}
		return Collections.unmodifiableList(lines);
	}

	/**
	 * Returns a new {@code BufferedReader} to read the given {@code File}
	 * using the system's default charset.
	 *
	 * @param f the file to read from.
	 *
	 * @return a {@code BufferedReader} to read 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 BufferedReader newReader(File f) throws FileNotFoundException
	{
		return newReader(f, Charsets.DEFAULT);
	}

	/**
	 * Returns a new {@code BufferedReader} to read the given {@code File}
	 * using the specified charset.
	 *
	 * @param f the file to read from.
	 * @param charset the charset to use.
	 *
	 * @return a {@code BufferedReader} to read the given {@code File}.
	 *
	 * @throws NullPointerException if one of the arguments 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 BufferedReader newReader(File f, Charset charset)
		throws FileNotFoundException
	{
		InputStream in = new FileInputStream(f);
		return new BufferedReader(new InputStreamReader(in, charset));
	}

	/**
	 * Returns a new {@code BufferedWriter} to write to the given
	 * {@code File} using the system's default charset.
	 *
	 * @param f the file to write to.
	 * @param options the write options.
	 *
	 * @return a {@code BufferedWriter} 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 BufferedWriter newWriter(File f, WriteOption... options)
		throws IOException
	{
		return newWriter(f, Charsets.DEFAULT, options);
	}

	/**
	 * Returns a new {@code BufferedWriter} to write to the given
	 * {@code File} using the specified charset.
	 *
	 * @param f the file to write to.
	 * @param charset the charset to use.
	 * @param options the write options.
	 *
	 * @return a {@code BufferedWriter} 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 BufferedWriter newWriter(File f, Charset charset,
		WriteOption... options) throws IOException
	{
		OutputStream out = XFiles.newOutputStream(f, options);
		return new BufferedWriter(new OutputStreamWriter(out, charset));
	}

	/**
	 * Reads the whole content of the given {@code File} as a {@code String}
	 * using the system's default charset.
	 *
	 * @param f the file to read.
	 *
	 * @return the file's content as a {@code String}.
	 *
	 * @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 String read(File f) throws IOException
	{
		return read(f, Charsets.DEFAULT);
	}

	/**
	 * Reads the whole content of the given {@code File} as a {@code String}
	 * using the specified charset.
	 *
	 * @param f the file to read.
	 * @param charset the charset to use.
	 *
	 * @return the file's content as a {@code String}.
	 *
	 * @throws NullPointerException if one of the arguments 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 String read(File f, Charset charset) throws IOException
	{
		BufferedReader in = newReader(f, charset);
		try {
			return CharStreams.read(in);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Reads all the lines from the given {@code File} using the system's
	 * default charset. Note that the returned {@code List} is immutable.
	 *
	 * @param f the file to read.
	 *
	 * @return the file's lines.
	 *
	 * @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 List<String> readLines(File f) throws IOException
	{
		return readLines(f, Charsets.DEFAULT);
	}

	/**
	 * Reads all the lines from the given {@code File} using the specified
	 * charset. Note that the returned {@code List} is immutable.
	 *
	 * @param f the file to read.
	 * @param charset the charset to use.
	 *
	 * @return the file's lines.
	 *
	 * @throws NullPointerException if one of the arguments 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 List<String> readLines(File f, Charset charset)
		throws IOException
	{
		BufferedReader in = newReader(f, charset);
		try {
			return CharStreams.readLines(in);
		} finally {
			IO.close(in);
		}
	}

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


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

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Represents a Uniform Resource Name (URN) as specified in RFC 2141. Instances
 * of this class are immutable.
 *
 * @see RFC 2141
 *
 * @author Osman KOCAK
 */
public final class URN implements Serializable
{
	private static final long serialVersionUID = 6295350526747423877L;
	private static final Pattern PATTERN;
	static {
		String regex = "(^urn):([a-z0-9][a-z0-9\\-]{0,31}):"
			+ "(([a-z0-9()+,\\-.:=@;$_!*']|%[0-9a-f]{2})+$)";
		PATTERN = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
	}

	/**
	 * Creates a new {@code URN} by parsing the given {@code String}. This
	 * function invokes the {@link URN#URN(java.lang.String)} constructor;
	 * any {@code URNSyntaxException} thrown by the constructor is caught
	 * and wrapped in a new {@code IllegalArgumentException}, which is then
	 * thrown. This method is provided for use in situations where it is
	 * known that the given {@code String} is a legal {@code URN}.
	 *
	 * @param urn the {@code String} to be parsed into a {@code URN}.
	 *
	 * @return the created {@code URN}.
	 *
	 * @throws NullPointerException if {@code urn} is {@code null}.
	 * @throws IllegalArgumentException if {@code urn} violates RFC 2141.
	 */
	public static URN create(String urn)
	{
		try {
			return new URN(urn);
		} catch (URNSyntaxException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	private final String scheme;
	private final String nid;
	private final String nss;

	/**
	 * Creates a new {@code URN} by parsing the given {@code String} as
	 * specified by the grammar in RFC 2141.
	 *
	 * @param urn the {@code String} to be parsed into a {@code URN}.
	 *
	 * @throws NullPointerException if {@code urn} is {@code null}.
	 * @throws URNSyntaxException if {@code urn} violates RFC 2141.
	 */
	public URN(String urn) throws URNSyntaxException
	{
		Matcher m = PATTERN.matcher(Parameters.checkNotNull(urn));
		if (m.matches()) {
			this.scheme = m.group(1);
			this.nid = m.group(2);
			this.nss = m.group(3);
			checkNID(this.nid);
			checkNSS(this.nss);
		} else {
			throw new URNSyntaxException("Invalid URN: " + urn);
		}
	}

	/**
	 * Creates a new {@code URN} with the specified Namespace Identifier and
	 * Namespace Specific String.
	 *
	 * @param nid the Namespace Identifier.
	 * @param nss the Namespace Specific String.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws URNSyntaxException if the {@code URN} constructed from the
	 *	given components violates RFC 2141.
	 */
	public URN(String nid, String nss) throws URNSyntaxException
	{
		this("urn", nid, nss);
	}

	/**
	 * Creates a new {@code URN} with the specified scheme, Namespace
	 * Identifier and Namespace Specific String.
	 *
	 * @param scheme the scheme, must be equal to "urn", ignoring the case.
	 * @param nid the Namespace Identifier.
	 * @param nss the Namespace Specific String.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws URNSyntaxException if the {@code URN} constructed from the
	 *	given components violates RFC 2141.
	 */
	public URN(String scheme, String nid, String nss) throws URNSyntaxException
	{
		this(Parameters.checkNotNull(scheme)
			+ ":" + Parameters.checkNotNull(nid)
			+ ":" + Parameters.checkNotNull(nss));
	}

	private void checkNID(String nid) throws URNSyntaxException
	{
		if ("urn".equalsIgnoreCase(nid)) {
			throw new URNSyntaxException("Invalid NID: " + nid);
		}
	}

	private void checkNSS(String nss) throws URNSyntaxException
	{
		int len = nss.length();
		char[] chars = nss.toCharArray();
		StringBuilder sb = new StringBuilder(len);
		for (int i = 0; i < len; i++) {
			if (chars[i] == '%') {
				sb.append(chars[++i]).append(chars[++i]);
				if (i == len - 1 || chars[i + 1] != '%') {
					checkIsValidUTF8(decodeBase16(sb));
					sb.setLength(0);
				}
			}
		}
	}

	private byte[] decodeBase16(CharSequence hex)
	{
		return BaseEncoding.BASE_16.decode(hex.toString());
	}

	private void checkIsValidUTF8(byte[] bytes) throws URNSyntaxException
	{
		Charset utf8 = Charsets.UTF_8;
		byte[] reencoded = new String(bytes, utf8).getBytes(utf8);
		if (!Arrays.equals(bytes, reencoded)) {
			throw new URNSyntaxException(
				"Invalid NSS: Invalid UTF-8 encoding");
		}
	}

	/**
	 * Returns the scheme part of this {@code URN}, namely it returns "urn"
	 * modulo the case.
	 *
	 * @return this {@code URN}'s scheme.
	 */
	public String scheme()
	{
		return scheme;
	}

	/**
	 * Returns the Namespace Identifier part of this {@code URN}.
	 *
	 * @return this {@code URN}'s NID.
	 */
	public String nid()
	{
		return nid;
	}

	/**
	 * Returns the Namespace Specific String of this {@code URN}.
	 *
	 * @return this {@code URN}'s NSS.
	 */
	public String nss()
	{
		return nss;
	}

	/**
	 * Returns the normalized equivalent of this {@code URN}. Normalizing a
	 * {@code URN} consists in lower-casing its scheme, NID and percent
	 * encoded parts.
	 *
	 * @return the normalized equivalent of this {@code URN}.
	 */
	public URN normalized()
	{
		return create("urn:" + ASCII.toLowerCase(nid) + ":"
			+ normalizeNSS(nss));
	}

	private String normalizeNSS(String nss)
	{
		char[] chars = nss.toCharArray();
		StringBuilder sb = new StringBuilder(chars.length);
		for (int i = 0; i < chars.length; i++) {
			if (chars[i] == '%') {
				sb.append('%');
				sb.append(ASCII.toLowerCase(chars[++i]));
				sb.append(ASCII.toLowerCase(chars[++i]));
			} else {
				sb.append(chars[i]);
			}
		}
		return sb.toString();
	}

	/**
	 * Returns the {@code URI} equivalent to this {@code URN}.
	 *
	 * @return the {@code URI} equivalent to this {@code URN}.
	 */
	public URI toURI()
	{
		return URI.create(toString());
	}

	@Override
	public String toString()
	{
		return scheme + ":" + nid + ":" + nss;
	}

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

	@Override
	public boolean equals(Object o)
	{
		if (o == this) {
			return true;
		}
		if (!(o instanceof URN)) {
			return false;
		}
		URN u = normalized();
		URN t = ((URN) o).normalized();
		return u.nid.equals(t.nid) && u.nss.equals(t.nss);
	}
}


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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;

/**
 * A classpath resource.
 *
 * @author Osman KOCAK
 */
public final class Resource
{
	/**
	 * Finds and returns the resource having the given name using the
	 * {@linkplain Thread#getContextClassLoader() context class loader}.
	 * Note that if the context class loader is {@code null}, the class
	 * loader that loaded this class will be used instead.
	 *
	 * @param name the resource's name.
	 *
	 * @return the requested resource.
	 *
	 * @throws NullPointerException if {@code name} is {@code null}.
	 * @throws NotFoundException if the resource can't be found.
	 */
	public static Resource find(String name)
	{
		Parameters.checkNotNull(name);
		ClassLoader loader = XObjects.firstNonNull(
			Thread.currentThread().getContextClassLoader(),
			Resource.class.getClassLoader());
		URL url = loader.getResource(name);
		if (url != null) {
			return new Resource(url);
		}
		throw new NotFoundException(name);
	}

	/**
	 * Finds and returns the resource having the given name (relative to
	 * the specified class).
	 *
	 * @param name the resource's name.
	 * @param contextClass the context class.
	 *
	 * @return the requested resource.
	 *
	 * @throws NullPointerException if {@code name} is {@code null}.
	 * @throws NotFoundException if the resource can't be found.
	 */
	public static Resource find(String name, Class<?> contextClass)
	{
		Parameters.checkNotNull(name);
		URL url = contextClass.getResource(name);
		if (url != null) {
			return new Resource(url);
		}
		throw new NotFoundException(name);
	}

	private final URL url;

	private Resource(URL url)
	{
		this.url = url;
	}

	/**
	 * Returns this resource's {@code URL}.
	 *
	 * @return this resource's {@code URL}.
	 */
	public URL getURL()
	{
		return url;
	}

	/**
	 * Copies the content of this resource to the specified stream.
	 *
	 * @param out the stream to copy to.
	 *
	 * @throws NullPointerException if {@code out} is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public void copyTo(OutputStream out) throws IOException
	{
		InputStream in = url.openStream();
		try {
			ByteStreams.copy(in, out);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Copies the content of this resource to the specified stream.
	 *
	 * @param out the stream to copy to.
	 * @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 void copyTo(Writer out, Charset charset) throws IOException
	{
		InputStream in = url.openStream();
		try {
			CharStreams.copy(in, out, charset);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Reads and returns this resource's content as a {@code byte[]}.
	 *
	 * @return this resource's content.
	 *
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public byte[] read() throws IOException
	{
		InputStream in = url.openStream();
		try {
			return ByteStreams.read(in);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Reads and returns this resource's content as a {@code String}.
	 *
	 * @param charset the charset to use.
	 *
	 * @return this resource's content.
	 *
	 * @throws NullPointerException if {@code charset} is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public String read(Charset charset) throws IOException
	{
		InputStream in = url.openStream();
		try {
			return CharStreams.read(in, charset);
		} finally {
			IO.close(in);
		}
	}

	/**
	 * Reads and returns all this resource's lines. Note that the returned
	 * {@code List} is immutable.
	 *
	 * @param charset the charset to use.
	 *
	 * @return this resource's lines.
	 *
	 * @throws NullPointerException if {@code charset} is {@code null}.
	 * @throws IOException if an I/O error occurs during the process.
	 */
	public List<String> readLines(Charset charset) throws IOException
	{
		InputStream in = url.openStream();
		try {
			return CharStreams.readLines(in, charset);
		} finally {
			IO.close(in);
		}
	}

	@Override
	public String toString()
	{
		return url.toString();
	}

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

	@Override
	public boolean equals(Object o)
	{
		if (o == this) {
			return true;
		}
		if (!(o instanceof Resource)) {
			return false;
		}
		final Resource r = (Resource) o;
		return url.equals(r.url);
	}

	/** Thrown to indicate that a resource can't be found. */
	public static final class NotFoundException extends RuntimeException
	{
		private static final long serialVersionUID = 75062119139042997L;

		/**
		 * Creates a new {@code ResourceNotFoundException}.
		 *
		 * @param message the error message.
		 */
		NotFoundException(String message)
		{
			super(message);
		}
	}
}


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

package org.kocakosm.pitaya.time;

import static java.util.Calendar.*;

import org.kocakosm.pitaya.util.Parameters;

import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * {@link Date}s utilities.
 *
 * @author Osman KOCAK
 */
public final class Dates
{
	/**
	 * Returns a copy of the given {@code Date}.
	 *
	 * @param date the {@code Date} to copy.
	 *
	 * @return a copy of the given {@code Date} or {@code null} if it was
	 *	{@code null}.
	 */
	public static Date copy(Date date)
	{
		return date == null ? null : new Date(date.getTime());
	}

	/**
	 * Returns a new {@code Date} representing the current instant.
	 *
	 * @return a new {@code Date} representing the current instant.
	 */
	public static Date now()
	{
		return new Date();
	}

	/**
	 * Returns whether the given {@code Date}s represent the same instant.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same instant.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameInstant(Date date1, Date date2)
	{
		return date1.getTime() == date2.getTime();
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same instant.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same instant.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameInstant(Calendar cal1, Calendar cal2)
	{
		return isSameInstant(cal1.getTime(), cal2.getTime());
	}

	/**
	 * Returns whether the given {@code Date}s represent the same local time.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same local time.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameLocalTime(Date date1, Date date2)
	{
		return isSameLocalTime(toCalendar(date1), toCalendar(date2));
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same local time.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same local time.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameLocalTime(Calendar cal1, Calendar cal2)
	{
		return cal1.get(MILLISECOND) == cal2.get(MILLISECOND)
			&& cal1.get(SECOND) == cal2.get(SECOND)
			&& cal1.get(MINUTE) == cal2.get(MINUTE)
			&& cal1.get(HOUR_OF_DAY) == cal2.get(HOUR_OF_DAY)
			&& cal1.get(DAY_OF_YEAR) == cal2.get(DAY_OF_YEAR)
			&& cal1.get(YEAR) == cal2.get(YEAR)
			&& cal1.get(ERA) == cal2.get(ERA);
	}

	/**
	 * Returns whether the given {@code Date}s represent the same day.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same day.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameDay(Date date1, Date date2)
	{
		return isSameDay(toCalendar(date1), toCalendar(date2));
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same day.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same day.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameDay(Calendar cal1, Calendar cal2)
	{
		return cal1.get(ERA) == cal2.get(ERA)
			&& cal1.get(YEAR) == cal2.get(YEAR)
			&& cal1.get(DAY_OF_YEAR) == cal2.get(DAY_OF_YEAR);
	}

	/**
	 * Returns whether the given {@code Date}s represent the same week.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same week.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameWeek(Date date1, Date date2)
	{
		return isSameWeek(toCalendar(date1), toCalendar(date2));
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same week.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same week.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameWeek(Calendar cal1, Calendar cal2)
	{
		return cal1.get(ERA) == cal2.get(ERA)
			&& cal1.get(YEAR) == cal2.get(YEAR)
			&& cal1.get(WEEK_OF_YEAR) == cal2.get(WEEK_OF_YEAR);
	}

	/**
	 * Returns whether the given {@code Date}s represent the same month.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same month.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameMonth(Date date1, Date date2)
	{
		return isSameMonth(toCalendar(date1), toCalendar(date2));
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same month.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same month.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameMonth(Calendar cal1, Calendar cal2)
	{
		return cal1.get(ERA) == cal2.get(ERA)
			&& cal1.get(YEAR) == cal2.get(YEAR)
			&& cal1.get(MONTH) == cal2.get(MONTH);
	}

	/**
	 * Returns whether the given {@code Date}s represent the same year.
	 *
	 * @param date1 the first {@code Date}.
	 * @param date2 the second {@code Date}.
	 *
	 * @return whether the given {@code Date}s represent the same year.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameYear(Date date1, Date date2)
	{
		return isSameYear(toCalendar(date1), toCalendar(date2));
	}

	/**
	 * Returns whether the given {@code Calendar}s represent the same year.
	 *
	 * @param cal1 the first {@code Calendar}.
	 * @param cal2 the second {@code Calendar}.
	 *
	 * @return whether the given {@code Calendar}s represent the same year.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean isSameYear(Calendar cal1, Calendar cal2)
	{
		return cal1.get(ERA) == cal2.get(ERA)
			&& cal1.get(YEAR) == cal2.get(YEAR);
	}

	/**
	 * Parses a {@code String} representing a date into a {@code Date}
	 * instance. The given formats will be used sequentially until a
	 * successful parsing is obtained. A parsing is considered successful
	 * if it parses the whole input {@code String}.
	 *
	 * @param date the {@code String} to parse.
	 * @param formats the date format patterns to use.
	 *
	 * @return the parsed {@code Date}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}
	 *	or if {@code formats} contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code formats} is empty or if
	 *	{@code date} can't be parsed with any of the specified formats.
	 */
	public static Date parse(String date, String... formats)
	{
		return parse(date, Locale.getDefault(), formats);
	}

	/**
	 * Parses a {@code String} representing a date into a {@code Date}
	 * instance. The given formats will be used sequentially until a
	 * successful parsing is obtained. A parsing is considered successful
	 * if it parses the whole input {@code String}.
	 *
	 * @param date the {@code String} to parse.
	 * @param locale the {@code Locale} to use.
	 * @param formats the date format patterns to use.
	 *
	 * @return the parsed {@code Date}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}
	 *	or if {@code formats} contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code formats} is empty or if
	 *	{@code date} can't be parsed with any of the specified formats.
	 */
	public static Date parse(String date, Locale locale, String... formats)
	{
		Parameters.checkNotNull(date);
		Parameters.checkCondition(formats.length > 0);
		for (String format : formats) {
			DateFormat df = new SimpleDateFormat(format, locale);
			df.setLenient(true);
			ParsePosition position = new ParsePosition(0);
			Date d = df.parse(date, position);
			if (d != null && position.getIndex() == date.length()) {
				return d;
			}
		}
		throw new IllegalArgumentException("Unparseable date: " + date);
	}

	/**
	 * Returns the {@code String} representation of the given {@code Date}
	 * according to the specified format pattern.
	 *
	 * @param date the {@code Date} to format.
	 * @param format the format pattern to use.
	 *
	 * @return the {@code String} representation of {@code date}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static String format(Date date, String format)
	{
		Parameters.checkNotNull(date);
		return new SimpleDateFormat(format).format(date);
	}

	/**
	 * Converts a {@code Date} into a {@code Calendar}.
	 *
	 * @param date the {@code Date} to convert to a {@code Calendar}.
	 *
	 * @return the created {@code Calendar} instance.
	 *
	 * @throws NullPointerException if {@code date} is {@code null}.
	 */
	public static Calendar toCalendar(Date date)
	{
		Calendar calendar = getInstance();
		calendar.setTime(date);
		return calendar;
	}

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


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

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

/**
 * Static utility methods that operate on or return {@link Iterator}s.
 *
 * @author Osman KOCAK
 */
public final class Iterators
{
	/** The empty {@code Iterator}. */
	public static final Iterator EMPTY_ITERATOR = new EmptyIterator();

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

	/**
	 * Concatenates all the given {@code Iterator}s into a single one. The
	 * source {@code Iterator}s aren't polled until necessary. The returned
	 * {@code Iterator} supports {@link Iterator#remove()} when the
	 * corresponding input {@code Iterator} supports it.
	 *
	 * @param <E> the type of the returned {@code Iterator}'s elements.
	 * @param iterators the {@code Iterator}s to concatenate.
	 *
	 * @return the concatenated {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterators} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static <E> Iterator<E> concat(Iterator<? extends E>... iterators)
	{
		return concat(Arrays.asList(iterators).iterator());
	}

	/**
	 * Concatenates all the given {@code Iterator}s into a single one. The
	 * source {@code Iterator}s aren't polled until necessary. The returned
	 * {@code Iterator} supports {@link Iterator#remove()} when the
	 * corresponding input {@code Iterator} supports it.
	 *
	 * @param <E> the type of the returned {@code Iterator}'s elements.
	 * @param iterators the {@code Iterator}s to concatenate.
	 *
	 * @return the concatenated {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterators} is {@code null} or
	 *	if it contains a {@code null} reference.
	 */
	public static <E> Iterator<E> concat(Iterator<? extends Iterator<? extends E>> iterators)
	{
		return new ConcatIterator<E>(iterators);
	}

	/**
	 * Returns an {@code Iterator} that cycles indefinitely over the content
	 * of the given source {@code Iterator}. The source {@code Iterator} is
	 * not polled until necessary. The returned {@code Iterator} supports
	 * {@link Iterator#remove()}.
	 *
	 * @param <E> the type of the returned {@code Iterator}'s elements.
	 * @param iterator the source {@code Iterator}.
	 *
	 * @return the cyclic {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 */
	public static <E> Iterator<E> cycle(Iterator<? extends E> iterator)
	{
		return new LazyCyclicIterator<E>(iterator);
	}

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

	/**
	 * Returns whether the given {@code Iterator}s contain equal elements in
	 * the same order. Note that this method consumes entirely the input
	 * {@code Iterator}s.
	 *
	 * @param i1 the first {@code Iterator}.
	 * @param i2 the second {@code Iterator}.
	 *
	 * @return whether the given {@code Iterator} have the same content.
	 */
	public static boolean equal(Iterator<?> i1, Iterator<?> i2)
	{
		if (i1 == i2) {
			return true;
		}
		if (i1 == null || i2 == null) {
			return false;
		}
		while (i1.hasNext() && i2.hasNext()) {
			if (!XObjects.equal(i1.next(), i2.next())) {
				return false;
			}
		}
		return !(i1.hasNext() || i2.hasNext());
	}

	/**
	 * Creates an {@code Iterator} returning only the first {@code limit}
	 * elements of the given {@code Iterator}. The source {@code Iterator}
	 * is not polled until necessary. The returned {@code Iterator} supports
	 * {@link Iterator#remove()} if the source {@code Iterator} supports it.
	 *
	 * @param <E> the type of the returned {@code Iterator}'s elements.
	 * @param iterator the {@code Iterator} to limit.
	 * @param limit the number of elements in the returned {@code Iterator}.
	 *
	 * @return the limited {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 * @throws IllegalArgumentException if ({@code limit} is negative.
	 */
	public static <E> Iterator<E> limit(Iterator<? extends E> iterator,
		int limit)
	{
		return new LimitIterator<E>(iterator, limit);
	}

	/**
	 * Skips {@code n} elements from the given {@code Iterator}.
	 *
	 * @param iterator the {@code Iterator}.
	 * @param n the number of elements to skip.
	 *
	 * @return the actual number of elements that has been skipped.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 * @throws IllegalArgumentException if ({@code n} is negative.
	 */
	public static int skip(Iterator<?> iterator, int n)
	{
		Parameters.checkCondition(n >= 0);
		int skipped = 0;
		while (skipped < n && iterator.hasNext()) {
			iterator.next();
			skipped++;
		}
		return skipped;
	}

	/**
	 * Returns a {@code List} containing all the given {@code Iterator}'s
	 * elements. This method consumes entirely the input {@code Iterator}.
	 *
	 * @param <E> the type of the returned {@code List}'s elements.
	 * @param iterator the {@code Iterator} to consume.
	 *
	 * @return the {@code List} created from the given {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 */
	public static <E> List<E> toList(Iterator<? extends E> iterator)
	{
		List<E> list = new ArrayList<E>();
		while (iterator.hasNext()) {
			list.add(iterator.next());
		}
		return list;
	}

	/**
	 * Returns a {@code Set} containing all the given {@code Iterator}'s
	 * elements. The returned {@code Set} has the same iteration order as
	 * the given {@code Iterator}. This method consumes entirely the input
	 * {@code Iterator}.
	 *
	 * @param <E> the type of the returned {@code Set}'s elements.
	 * @param iterator the {@code Iterator} to consume.
	 *
	 * @return the {@code Set} created from the given {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 */
	public static <E> Set<E> toSet(Iterator<? extends E> iterator)
	{
		Set<E> set = new LinkedHashSet<E>();
		while (iterator.hasNext()) {
			set.add(iterator.next());
		}
		return set;
	}

	/**
	 * Returns a {@code Bag} containing all the given {@code Iterator}'s
	 * elements. The returned {@code Bag} has the same iteration order as
	 * the given {@code Iterator}. This method consumes entirely the input
	 * {@code Iterator}.
	 *
	 * @param <E> the type of the returned {@code Bag}'s elements.
	 * @param iterator the {@code Iterator} to consume.
	 *
	 * @return the {@code Bag} created from the given {@code Iterator}.
	 *
	 * @throws NullPointerException if {@code iterator} is {@code null}.
	 */
	public static <E> Bag<E> toBag(Iterator<? extends E> iterator)
	{
		return new ArrayBag<E>(iterator);
	}

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

	private static final class EmptyIterator<E> implements Iterator<E>
	{
		@Override
		public boolean hasNext()
		{
			return false;
		}

		@Override
		public E next()
		{
			throw new NoSuchElementException();
		}

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

	private static final class ConcatIterator<E> implements Iterator<E>
	{
		private final List<Iterator<? extends E>> iterators;
		private int index;

		ConcatIterator(Iterator<? extends Iterator<? extends E>> iterators)
		{
			this.iterators = new ArrayList<Iterator<? extends E>>();
			while (iterators.hasNext()) {
				Iterator<? extends E> iterator = iterators.next();
				if (iterator.hasNext()) {
					this.iterators.add(iterator);
				}
			}
		}

		@Override
		public boolean hasNext()
		{
			return (!finished() && current().hasNext())
				|| (nextIterator() && hasNext());
		}

		@Override
		public E next()
		{
			if (hasNext()) {
				return current().next();
			}
			throw new NoSuchElementException();
		}

		@Override
		public void remove()
		{
			current().remove();
		}

		private Iterator<? extends E> current()
		{
			return iterators.get(index);
		}

		private boolean finished()
		{
			return index >= iterators.size();
		}

		private boolean nextIterator()
		{
			index++;
			return !finished();
		}
	}

	private static final class LazyCyclicIterator<E> implements Iterator<E>
	{
		private final Iterator<? extends E> iterator;
		private final List<E> elements;
		private int index = -1;

		LazyCyclicIterator(Iterator<? extends E> iterator)
		{
			Parameters.checkNotNull(iterator);
			this.elements = new ArrayList<E>();
			this.iterator = iterator;
		}

		@Override
		public boolean hasNext()
		{
			return iterator.hasNext() || !elements.isEmpty();
		}

		@Override
		public E next()
		{
			if (iterator.hasNext()) {
				E element = iterator.next();
				elements.add(element);
				index++;
				return element;
			}
			if (!elements.isEmpty()) {
				if (++index >= elements.size()) {
					index = 0;
				}
				return elements.get(index);
			}
			throw new NoSuchElementException();
		}

		@Override
		public void remove()
		{
			if (index < 0) {
				throw new IllegalStateException();
			}
			elements.remove(index--);
		}
	}

	private static final class CyclicIterator<E> implements Iterator<E>
	{
		private final List<E> elements;
		private int index = -1;

		CyclicIterator(List<? extends E> elements)
		{
			this.elements = new ArrayList<E>(elements);
		}

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

		@Override
		public E next()
		{
			if (hasNext()) {
				return elements.get(nextIndex());
			}
			throw new NoSuchElementException();
		}

		@Override
		public void remove()
		{
			if (index < 0) {
				throw new IllegalStateException();
			}
			elements.remove(index--);
		}

		private int nextIndex()
		{
			if (++index >= elements.size()) {
				index = 0;
			}
			return index;
		}
	}

	private static final class LimitIterator<E> implements Iterator<E>
	{
		private final Iterator<? extends E> iterator;
		private final int limit;
		private int index;

		LimitIterator(Iterator<? extends E> iterator, int limit)
		{
			Parameters.checkNotNull(iterator);
			Parameters.checkCondition(limit >= 0);
			this.iterator = iterator;
			this.limit = limit;
		}

		@Override
		public boolean hasNext()
		{
			return iterator.hasNext() && index < limit;
		}

		@Override
		public E next()
		{
			if (hasNext()) {
				index++;
				return iterator.next();
			}
			throw new NoSuchElementException();
		}

		@Override
		public void remove()
		{
			iterator.remove();
		}
	}

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


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

package org.kocakosm.pitaya.util;

import java.util.Random;

/**
 * {@code String} utilities.
 *
 * @author Osman KOCAK
 */
public final class Strings
{
	private static final Random PRNG = new Random();

	/**
	 * Returns whether the given {@code String} only contains space, '\n',
	 * '\r' or '\t' characters.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return whether the given {@code String} is blank.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static boolean isBlank(String str)
	{
		for (char c : str.toCharArray()) {
			if (c != ' ' && c != '\r' && c != '\n' && c != '\t') {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns whether the given {@code String} is {@code null} or only
	 * contains space, '\n', '\r' or '\t' characters.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return whether the given {@code String} is {@code null} or blank.
	 */
	public static boolean isNullOrBlank(String str)
	{
		return str == null || isBlank(str);
	}

	/**
	 * Returns whether the given {@code String} only contains space
	 * characters.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return whether the given {@code String} contains only spaces.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static boolean isWhiteSpace(String str)
	{
		for (char c : str.toCharArray()) {
			if (c != ' ') {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns whether the given {@code String} is {@code null} or only
	 * contains space characters.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return whether the given {@code String} is {@code null} or contains
	 *	only spaces.
	 */
	public static boolean isNullOrWhiteSpace(String str)
	{
		return str == null || isWhiteSpace(str);
	}

	/**
	 * Returns whether the given {@code String} is {@code null} or empty.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return {@code true} if {@code str} is {@code null} or empty,
	 *	{@code false} otherwise.
	 */
	public static boolean isNullOrEmpty(String str)
	{
		return str == null || str.isEmpty();
	}

	/**
	 * Abbreviates the given {@code String} using "..." so that the returned
	 * {@code String}'s length is equal to {@code length}.
	 *
	 * @param str the {@code String} to abbreviate.
	 * @param length the desired length for the abbreviated {@code String}.
	 *
	 * @return the abbreviated {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code length} is too small.
	 */
	public static String abbreviate(String str, int length)
	{
		return abbreviate(str, length, "...");
	}

	/**
	 * Abbreviates the given {@code String} using the specified ellipsis so
	 * that the returned {@code String}'s length is equal to {@code length}.
	 *
	 * @param str the {@code String} to abbreviate.
	 * @param length the desired length for the abbreviated {@code String}.
	 * @param ellipsis the ellipsis to use to mark abbreviation.
	 *
	 * @return the abbreviated {@code String}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if {@code length} is too small.
	 */
	public static String abbreviate(String str, int length, String ellipsis)
	{
		Parameters.checkCondition(length > ellipsis.length());
		if (str.length() <= length) {
			return str;
		}
		return str.substring(0, length - ellipsis.length()) + ellipsis;
	}

	/**
	 * Concatenates the given {@code String}s.
	 *
	 * @param strings the {@code String}s to concatenate.
	 *
	 * @return the concatenated {@code String}.
	 *
	 * @throws NullPointerException if {@code strings} is {@code null}.
	 */
	public static String concat(String... strings)
	{
		StringBuilder sb = new StringBuilder(strings.length * 10);
		for (String string : strings) {
			sb.append(string);
		}
		return sb.toString();
	}

	/**
	 * Computes and returns the Damerau-Levenshtein distance between the
	 * given {@code String}s. This distance is obtained  by counting the
	 * minimum number of operations needed to transform one {@code String}
	 * into the other, where an operation is defined as an insertion,
	 * deletion, or substitution of a single character, or a transposition
	 * of two adjacent characters.
	 *
	 * @param str1 the first {@code String}.
	 * @param str2 the second {@code String}.
	 *
	 * @return the distance between {@code str1} and {@code str2}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static int distance(String str1, String str2)
	{
		char[] s1 = str1.toCharArray();
		char[] s2 = str2.toCharArray();
		int[][] d = new int[s1.length + 1][s2.length + 1];
		for (int i = 0; i <= s1.length; i++) {
			d[i][0] = i;
		}
		for (int i = 0; i <= s2.length; i++) {
			d[0][i] = i;
		}
		for (int i = 1; i <= s1.length; i++) {
			for (int j = 1; j <= s2.length; j++) {
				int c = s1[i - 1] == s2[j - 1] ? 0 : 1;
				d[i][j] = min(
					d[i - 1][j] + 1,
					d[i][j - 1] + 1,
					d[i - 1][j - 1] + c);
				if (i > 1 && j > 1
					&& s1[i - 1] == s2[j - 2]
					&& s1[i - 2] == s2[j - 1])
				{
					d[i][j] = min(
						d[i][j], d[i - 2][j - 2] + c);
				}
			}
		}
		return d[s1.length][s2.length];
	}

	private static int min(int... values)
	{
		int min = values[0];
		for (int i = 1; i < values.length; i++) {
			min = Math.min(min, values[i]);
		}
		return min;
	}

	/**
	 * Returns {@code null} if the given {@code String} is empty, returns
	 * the (unmodified) parameter otherwise.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return {@code null} if the given {@code String} is empty.
	 */
	public static String emptyToNull(String str)
	{
		return str == null ? null : str.isEmpty() ? null : str;
	}

	/**
	 * Returns an empty {@code String} if the given one is {@code null}, or
	 * returns the (unmodified) parameter otherwise.
	 *
	 * @param str the {@code String} to test.
	 *
	 * @return an empty {@code String} if the given one is {@code null}.
	 */
	public static String nullToEmpty(String str)
	{
		return str == null ? "" : str;
	}

	/**
	 * Returns a pseudo-random {@code String} of the specified size using
	 * characters from the given array.
	 *
	 * @param length the length of the {@code String} to produce.
	 * @param chars the characters to use to build the {@code String}.
	 *
	 * @return a pseudo-random {@code String}.
	 *
	 * @throws NullPointerException if {@code chars} is {@code null}.
	 * @throws IllegalArgumentException if {@code length} is negative or if
	 *	{@code chars} is empty.
	 */
	public static String random(int length, char... chars)
	{
		return random(length, PRNG, chars);
	}

	/**
	 * Returns a random {@code String} using the given characters and the
	 * specified source of randomness. All characters have equal likelihood
	 * to appear in the resulting {@code String} assuming that the source of
	 * randomness is fair.
	 *
	 * @param length the length of the {@code String} to produce.
	 * @param rnd the source of randomness to use.
	 * @param chars the characters to use to build the {@code String}.
	 *
	 * @return a random {@code String}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if {@code length} is negative or if
	 *	{@code chars} is empty.
	 */
	public static String random(int length, Random rnd, char... chars)
	{
		Parameters.checkCondition(length >= 0);
		Parameters.checkCondition(chars.length > 0);
		StringBuilder sb = new StringBuilder(length);
		for (int i = 0; i < length; i++) {
			sb.append(chars[rnd.nextInt(chars.length)]);
		}
		return sb.toString();
	}

	/**
	 * Returns the {@code String} obtained by repeating {@code count} times
	 * the given {@code String}.
	 *
	 * @param str the {@code String} to repeat.
	 * @param count the number of times the {@code str} has to be repeared.
	 *
	 * @return the created {@code String}.
	 *
	 * @throws IllegalArgumentException if {@code count} is negative.
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String repeat(String str, int count)
	{
		Parameters.checkCondition(count >= 0);
		StringBuilder sb = new StringBuilder(str.length() * count);
		for (int i = 0; i < count; i++) {
			sb.append(str);
		}
		return sb.toString();
	}

	/**
	 * Reverses the given {@code String}.
	 *
	 * @param str the {@code String} to reverse.
	 *
	 * @return the reversed {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String reverse(String str)
	{
		return new StringBuilder(str).reverse().toString();
	}

	/**
	 * Randomly permutes the characters from the given {@code String}. This
	 * implementation uses the optimized version of the Fisher-Yates shuffle
	 * algorithm (Fisher, Yates, Durstenfeld, Knuth) and thus runs in linear
	 * time.
	 *
	 * @param str the {@code String} to be shuffled.
	 *
	 * @return the shuffled {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String shuffle(String str)
	{
		return shuffle(str, PRNG);
	}

	/**
	 * Randomly permutes the characters from the given {@code String} using
	 * the specified source of randomness. All permutations occur with equal
	 * likelihood assuming that the source of randomness is fair. This
	 * implementation uses the optimized version of the Fisher-Yates shuffle
	 * algorithm (Fisher, Yates, Durstenfeld, Knuth) and thus runs in linear
	 * time.
	 *
	 * @param str the {@code String} to be shuffled.
	 * @param rnd the source of randomness to use.
	 *
	 * @return the shuffled {@code String}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static String shuffle(String str, Random rnd)
	{
		return new String(XArrays.shuffle(str.toCharArray(), rnd));
	}

	/**
	 * Returns the new {@code String} obtained by stripping the first and
	 * last {@code n} characters from the given one.
	 *
	 * @param str the {@code String} to be stripped.
	 * @param n the number of characters to strip.
	 *
	 * @return the stripped {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code n} is negative.
	 */
	public static String strip(String str, int n)
	{
		return stripRight(stripLeft(str, n), n);
	}

	/**
	 * Returns the new {@code String} obtained by stripping the first
	 * {@code n} characters from the given one.
	 *
	 * @param str the {@code String} to be stripped.
	 * @param n the number of characters to strip.
	 *
	 * @return the stripped {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code n} is negative.
	 */
	public static String stripLeft(String str, int n)
	{
		Parameters.checkCondition(n >= 0);
		int start = Math.min(str.length(), n);
		return str.substring(start);
	}

	/**
	 * Returns the new {@code String} obtained by stripping the last
	 * {@code n} characters from the given one.
	 *
	 * @param str the {@code String} to be stripped.
	 * @param n the number of characters to strip.
	 *
	 * @return the stripped {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code n} is negative.
	 */
	public static String stripRight(String str, int n)
	{
		Parameters.checkCondition(n >= 0);
		int end = Math.max(0, str.length() - n);
		return str.substring(0, end);
	}

	/**
	 * Returns a copy of the given {@code String} with leading and trailing
	 * whitespaces omitted.
	 *
	 * @param str the {@code String} to trim.
	 *
	 * @return the trimmed {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String trim(String str)
	{
		return str.trim();
	}

	/**
	 * Returns a copy of the given {@code String} with leading whitespaces
	 * omitted.
	 *
	 * @param str the {@code String} to trim.
	 *
	 * @return the trimmed {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String trimLeft(String str)
	{
		int start = 0;
		for (char c : str.toCharArray()) {
			if (c != ' ') {
				break;
			}
			start++;
		}
		return str.substring(start);
	}

	/**
	 * Returns a copy of the given {@code String} with trailing whitespaces
	 * omitted.
	 *
	 * @param str the {@code String} to trim.
	 *
	 * @return the trimmed {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String trimRight(String str)
	{
		int end = str.length();
		for (int i = str.length() - 1; i >= 0; i--) {
			if (str.charAt(i) != ' ') {
				break;
			}
			end--;
		}
		return str.substring(0, end);
	}

	/**
	 * Returns a {@code String} of length at least {@code len} created by
	 * prepending as many copies of {@code padChar} as necessary to reach
	 * that length.
	 *
	 * @param str the {@code String} to pad.
	 * @param len the result's minimum length.
	 * @param padChar the padding character.
	 *
	 * @return the padded {@code String}.
	 *
	 * @throws IllegalArgumentException if {@code len} is negative.
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String padLeft(String str, int len, char padChar)
	{
		Parameters.checkCondition(len >= 0);
		StringBuilder sb = new StringBuilder(len);
		while (sb.length() < len - str.length()) {
			sb.append(padChar);
		}
		sb.append(str);
		return sb.toString();
	}

	/**
	 * Returns a {@code String} of length at least {@code len} created by
	 * appending as many copies of {@code padChar} as necessary to reach
	 * that length.
	 *
	 * @param str the {@code String} to pad.
	 * @param len the result's minimum length.
	 * @param padChar the padding character.
	 *
	 * @return the padded {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 * @throws IllegalArgumentException if {@code len} is negative.
	 */
	public static String padRight(String str, int len, char padChar)
	{
		Parameters.checkNotNull(str);
		Parameters.checkCondition(len >= 0);
		StringBuilder sb = new StringBuilder(len);
		sb.append(str);
		while (sb.length() < len) {
			sb.append(padChar);
		}
		return sb.toString();
	}

	/**
	 * Quotes the given {@code String} with double quotes. If the given
	 * {@code String} has multiple quotes, only one of them is kept.
	 *
	 * @param str the {@code String} to quote.
	 *
	 * @return the quoted {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String quote(String str)
	{
		return new StringBuilder(str.length() + 2)
			.append("\"").append(unquote(str)).append("\"")
			.toString();
	}

	/**
	 * Unquotes the given {@code String}, that is, this method removes any
	 * leading or trailing double quotes. If the given {@code String} is not
	 * quoted, returns it unmodified.
	 *
	 * @param str the {@code String} to unquote.
	 *
	 * @return the unquoted {@code String}.
	 *
	 * @throws NullPointerException if {@code str} is {@code null}.
	 */
	public static String unquote(String str)
	{
		String unquoted = str;
		while (unquoted.startsWith("\"")) {
			unquoted = unquoted.substring(1);
		}
		while (unquoted.endsWith("\"")) {
			unquoted = unquoted.substring(0, unquoted.length() - 1);
		}
		return unquoted;
	}

	/**
	 * Counts the occurrences of the substring {@code sub} in {@code str}.
	 *
	 * @param str {@code String} to search in.
	 * @param sub {@code String} to search for.
	 *
	 * @return the number of occurrences of {@code sub} in {@code str}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static int countOccurrences(String str, String sub)
	{
		Parameters.checkNotNull(str);
		int n = 0;
		if (!sub.isEmpty()) {
			int start = 0;
			while (true) {
				start = str.indexOf(sub, start);
				if (start == -1) {
					break;
				}
				start += sub.length();
				n++;
			}
		}
		return n;
	}

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


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

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * URLs utilities.
 *
 * @author Osman KOCAK
 */
public final class URLs
{
	/**
	 * Creates a new {@code URL} by parsing the given {@code String}. This
	 * function invokes the {@link URL#URL(java.lang.String)} constructor;
	 * any {@code MalformedURLException} thrown by the constructor is caught
	 * and wrapped in a new {@code IllegalArgumentException}, which is then
	 * thrown. This method is provided for use in situations where it is
	 * known that the given {@code String} is a legal {@code URL}.
	 *
	 * @param url the {@code String} to be parsed into a {@code URL}.
	 *
	 * @return the created {@code URL}.
	 *
	 * @throws NullPointerException if {@code url} is {@code null}.
	 * @throws IllegalArgumentException if {@code url} is not a valid URL.
	 */
	public static URL create(String url)
	{
		Parameters.checkNotNull(url);
		try {
			return new URL(url);
		} catch (MalformedURLException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	/**
	 * Relativizes the specified full {@code URL} against the given base.
	 * Behaves as {@link java.net.URI#relativize(java.net.URI)}.
	 *
	 * @param base the base {@code URL}.
	 * @param full the full {@code URL}.
	 *
	 * @return the relativized path.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if the relativization fails.
	 */
	public static String relativize(URL base, URL full)
	{
		try {
			return base.toURI().relativize(full.toURI())
				.normalize().toString();
		} catch (URISyntaxException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	/**
	 * Resolves the specified path against the given base {@code URL}.
	 * Behaves as {@link java.net.URI#resolve(java.lang.String)}.
	 *
	 * @param base the base {@code URL}.
	 * @param path the path to resolve.
	 *
	 * @return the resolved {@code URL}.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 * @throws IllegalArgumentException if the path can't be resolved.
	 */
	public static URL resolve(URL base, String path)
	{
		try {
			return base.toURI().resolve(path).normalize().toURL();
		} catch (URISyntaxException ex) {
			throw new IllegalArgumentException(ex);
		} catch (MalformedURLException ex) {
			throw new IllegalArgumentException(ex);
		}
	}

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


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

package org.kocakosm.pitaya.util;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Utilities for {@code Class}es.
 *
 * @author Osman KOCAK
 */
public final class Classes
{
	/**
	 * Returns the qualified name of the given class (package name followed
	 * by a dot, followed by the class name). For array classes, this method
	 * returns the component type class name followed by as many "[]" as
	 * the array's dimension.
	 *
	 * @param c the class.
	 *
	 * @return the qualified name of the given class.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static String getQualifiedName(Class<?> c)
	{
		if (c.isArray()) {
			StringBuilder qname = new StringBuilder();
			while (c.isArray()) {
				c = c.getComponentType();
				qname.append("[]");
			}
			qname.insert(0, c.getName());
			return qname.toString();
		}
		return c.getName();
	}

	/**
	 * Returns only the name of the given class, that is, without its
	 * package name and without its eventual "outer" class name.
	 *
	 * @param c the class.
	 *
	 * @return the short name of the given class.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static String getShortName(Class<?> c)
	{
		String qname = getQualifiedName(c);
		int start = qname.lastIndexOf('$');
		if (start == -1) {
			start = qname.lastIndexOf('.');
		}
		return qname.substring(start + 1);
	}

	/**
	 * Returns the name of the package of the given class or the empty
	 * {@code String} if the class is defined in the default package.
	 *
	 * @param c the class.
	 *
	 * @return the package name of the given class.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static String getPackageName(Class<?> c)
	{
		String name = c.getName();
		int i = name.lastIndexOf('.');
		return i != -1 ? name.substring(0, i) : "";
	}

	/**
	 * Returns all the super types of the given class, that is, all the
	 * classes in which any instance of the given class can be cast into.
	 *
	 * @param c the class.
	 *
	 * @return all the given class's super types.
	 *
	 * @throws NullPointerException if {@code c} is {@code null}.
	 */
	public static Set<Class<?>> getSuperTypes(Class<?> c)
	{
		Set<Class<?>> classes = new HashSet<Class<?>>();
		for (Class<?> clazz : c.getInterfaces()) {
			classes.add(clazz);
			classes.addAll(getSuperTypes(clazz));
		}
		Class<?> sup = c.getSuperclass();
		if (sup != null) {
			classes.add(sup);
			classes.addAll(getSuperTypes(sup));
		}
		return Collections.unmodifiableSet(classes);
	}

	/**
	 * Returns the {@code Set} of common super types of the given classes,
	 * that is, all the classes in which any instance of the given classes
	 * can be cast into.
	 *
	 * @param classes the classes.
	 *
	 * @return the common super types of the given classes.
	 *
	 * @throws NullPointerException if {@code classes} is {@code null}, or
	 *	if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code classes} is empty.
	 */
	public static Set<Class<?>> getCommonSuperTypes(Class<?>... classes)
	{
		return getCommonSuperTypes(Arrays.asList(classes));
	}

	/**
	 * Returns the {@code Set} of common super types of the given classes,
	 * that is, all the classes in which any instance of the given classes
	 * can be cast into.
	 *
	 * @param classes the classes.
	 *
	 * @return the common super types of the given classes.
	 *
	 * @throws NullPointerException if {@code classes} is {@code null}, or
	 *	if it contains a {@code null} reference.
	 * @throws IllegalArgumentException if {@code classes} is empty.
	 */
	public static Set<Class<?>> getCommonSuperTypes(Iterable<Class<?>> classes)
	{
		Iterator<Class<?>> i = classes.iterator();
		Parameters.checkCondition(i.hasNext());
		Set<Class<?>> common = new HashSet<Class<?>>(getSuperTypes(i.next()));
		while (i.hasNext()) {
			common.retainAll(getSuperTypes(i.next()));
		}
		return Collections.unmodifiableSet(common);
	}

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


/*----------------------------------------------------------------------------*
 * 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.charset.UTF8;
import org.kocakosm.pitaya.util.ByteBuffer;
import org.kocakosm.pitaya.util.Strings;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;

/**
 * Passwords related utility functions.
 *
 * @author Osman KOCAK
 */
public final class Passwords
{
	private static final int R = 8;
	private static final int P = 1;
	private static final int N = 1 << 14;
	private static final int R_MIN = 8;
	private static final int P_MIN = 1;
	private static final int N_MIN = 1 << 14;
	private static final int SALT_LENGTH = 16;
	private static final int HASH_LENGTH = 32;
	private static final Random PRNG = new SecureRandom();
	private static final char[] ALPHABET = new char[] {
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
		'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
		'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
		'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
	};

	/**
	 * Generates a pseudo-random password (10 alphanumeric characters).
	 *
	 * @return a pseudo-random password.
	 */
	public static String generate()
	{
		return Strings.random(10, PRNG, ALPHABET);
	}

	/**
	 * Hashes the given password (using {@linkplain KDFs#scrypt SCrypt}).
	 * Hashing parameters are appended to the returned result.
	 *
	 * @param password the password to hash.
	 *
	 * @return the hashed password.
	 *
	 * @throws NullPointerException if {@code password} is {@code null}.
	 */
	public static byte[] hash(String password)
	{
		return hash(password, salt(), R, N, P);
	}

	/**
	 * Verifies that the given password matches the hashed one.
	 *
	 * @param password the password to verify.
	 * @param hash the hashed password.
	 *
	 * @return whether the given password matches the hashed one.
	 *
	 * @throws NullPointerException if one of the arguments is {@code null}.
	 */
	public static boolean verify(String password, byte[] hash)
	{
		byte[] h = Arrays.copyOf(hash, HASH_LENGTH + SALT_LENGTH + 3);
		int n = 1 << (h[HASH_LENGTH + SALT_LENGTH] & 0xFF);
		int r = h[HASH_LENGTH + SALT_LENGTH + 1] & 0xFF;
		int p = h[HASH_LENGTH + SALT_LENGTH + 2] & 0xFF;
		if (n > N || n < N_MIN || r > R || r < R_MIN || p > P || p < P_MIN) {
			n = N;
			r = R;
			p = P;
		}
		byte[] salt = new byte[SALT_LENGTH];
		System.arraycopy(h, HASH_LENGTH, salt, 0, SALT_LENGTH);
		byte[] expected = hash(password, salt, r, n, p);
		int result = 0;
		for (int i = 0; i < h.length; i++) {
			result |= h[i] ^ expected[i];
		}
		return result == 0;
	}

	private static byte[] hash(String password, byte[] salt, int r, int n, int p)
	{
		KDF scrypt = KDFs.scrypt(r, n, p, HASH_LENGTH);
		ByteBuffer buf = new ByteBuffer(HASH_LENGTH + SALT_LENGTH + 3);
		buf.append(scrypt.deriveKey(UTF8.encode(password), salt));
		buf.append(salt);
		buf.append((byte) Math.round(Math.log(n) / Math.log(2)));
		buf.append((byte) r, (byte) p);
		return buf.toByteArray();
	}

	private static byte[] salt()
	{
		byte[] salt = new byte[SALT_LENGTH];
		PRNG.nextBytes(salt);
		return salt;
	}

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


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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;

/**
 * Very simple utility class to easily read from the standard input. For a more
 * powerful API, use {@link java.util.Scanner}.
 *
 * @author Osman KOCAK
 */
public final class Scanf
{
	/**
	 * Reads the next line from the standard input using the system's
	 * default charset.
	 *
	 * @return the next line read from the standard input.
	 *
	 * @throws IOException if the standard input cannot be read.
	 */
	public static String readString() throws IOException
	{
		return readString(Charsets.DEFAULT);
	}

	/**
	 * Reads the next line from the standard input using the given charset.
	 *
	 * @param charset the charset to use.
	 *
	 * @return the next line read from the standard input.
	 *
	 * @throws NullPointerException if {@code charset} is {@code null}.
	 * @throws IOException if the standard input cannot be read.
	 */
	public static String readString(Charset charset) throws IOException
	{
		Reader in = new InputStreamReader(System.in, charset);
		BufferedReader reader = new BufferedReader(in);
		try {
			return reader.readLine();
		} finally {
			reader.close();
		}
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@code boolean} value. The strings "true", "on", "yes" (ignoring
	 * spaces and case) return {@code true}, all other strings will return
	 * {@code false}.
	 *
	 * @return the parsed {@code boolean} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 */
	public static boolean readBoolean() throws IOException
	{
		return Booleans.valueOf(readString());
	}

	/**
	 * Reads the next line from the standard input and parses it into an
	 * {@code int} value.
	 *
	 * @return the parsed {@code int} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static int readInt() throws IOException
	{
		return Integer.valueOf(readString());
	}

	/**
	 * Reads the next line from the standard input and parses it into an
	 * {@code int} value with the specified radix.
	 *
	 * @param radix the radix to be used in interpreting the read line.
	 *
	 * @return the parsed {@code int} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static int readInt(int radix) throws IOException
	{
		return Integer.valueOf(readString(), radix);
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@code long} value.
	 *
	 * @return the parsed {@code long} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static long readLong() throws IOException
	{
		return Long.valueOf(readString());
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@code long} value with the specified radix.
	 *
	 * @param radix the radix to be used in interpreting the read line.
	 *
	 * @return the parsed {@code long} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static long readLong(int radix) throws IOException
	{
		return Long.valueOf(readString(), radix);
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@link BigInteger} value.
	 *
	 * @return the parsed {@link BigInteger} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static BigInteger readBigInteger() throws IOException
	{
		return new BigInteger(readString());
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@link BigInteger} value with the specified radix.
	 *
	 * @param radix the radix to be used in interpreting the read line.
	 *
	 * @return the parsed {@link BigInteger} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static BigInteger readBigInteger(int radix) throws IOException
	{
		return new BigInteger(readString(), radix);
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@code double} value.
	 *
	 * @return the parsed {@code double} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static double readDouble() throws IOException
	{
		return Double.valueOf(readString());
	}

	/**
	 * Reads the next line from the standard input and parses it into a
	 * {@link BigDecimal} value.
	 *
	 * @return the parsed {@link BigDecimal} value.
	 *
	 * @throws IOException if the standard input cannot be read.
	 * @throws NumberFormatException if the line cannot be parsed.
	 */
	public static BigDecimal readBigDecimal() throws IOException
	{
		return new BigDecimal(readString());
	}

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


/*----------------------------------------------------------------------------*
 * 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.Serializable;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A buffer that stores a sequence of bytes. Not thread-safe.
 *
 * @author Osman KOCAK
 */
public final class ByteBuffer implements Iterable<Byte>, Serializable
{
	private static final long serialVersionUID = 5183155512871175888L;
	private static final int DEFAULT_CAPACITY = 512;

	private byte[] buf;
	private int count;
	private int capacity;

	/**
	 * Creates a new {@code ByteBuffer} with an initial capacity of 512
	 * bytes.
	 */
	public ByteBuffer()
	{
		this(DEFAULT_CAPACITY);
	}

	/**
	 * Creates a new {@code ByteBuffer} having the given initial capacity.
	 *
	 * @param capacity the initial capacity.
	 *
	 * @throws IllegalArgumentException if {@code capacity < 0}.
	 */
	public ByteBuffer(int capacity)
	{
		Parameters.checkCondition(capacity >= 0);
		this.buf = new byte[capacity];
		this.capacity = capacity;
	}

	/**
	 * Creates a new {@code ByteBuffer} containing the given data.
	 *
	 * @param bytes the buffer's initial content.
	 *
	 * @throws NullPointerException if {@code bytes} is {@code null}.
	 */
	public ByteBuffer(byte... bytes)
	{
		this.count = bytes.length;
		this.capacity = Math.max((count * 3) / 2 + 1, DEFAULT_CAPACITY);
		this.buf = new byte[capacity];
		System.arraycopy(bytes, 0, buf, 0, count);
	}

	/**
	 * Writes the given byte to this buffer.
	 *
	 * @param b the byte to write.
	 *
	 * @return this object.
	 */
	public ByteBuffer append(byte b)
	{
		int newCount = count + 1;
		ensureCapacity(newCount);
		buf[count] = b;
		count = newCount;
		return this;
	}

	/**
	 * Writes the given bytes to this buffer.
	 *
	 * @param bytes the bytes to write.
	 *
	 * @return this object.
	 *
	 * @throws NullPointerException if {@code bytes} is {@code null}.
	 */
	public ByteBuffer append(byte... bytes)
	{
		append(bytes, 0, bytes.length);
		return this;
	}

	/**
	 * Writes the given data to this buffer.
	 *
	 * @param bytes the data to write.
	 * @param off the offset.
	 * @param len the number of bytes to write.
	 *
	 * @return this object.
	 *
	 * @throws NullPointerException if {@code bytes} is {@code null}.
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than the length of
	 *	the {@code bytes} array.
	 */
	public ByteBuffer append(byte[] bytes, int off, int len)
	{
		int newCount = count + len;
		ensureCapacity(newCount);
		System.arraycopy(bytes, off, buf, count, len);
		count = newCount;
		return this;
	}

	/**
	 * Clears this buffer.
	 *
	 * @return this object.
	 */
	public ByteBuffer clear()
	{
		count = 0;
		buf = new byte[capacity];
		return this;
	}

	/**
	 * Returns the number of bytes contained in this buffer.
	 *
	 * @return the number of bytes contained in this buffer.
	 */
	public int size()
	{
		return count;
	}

	/**
	 * Returns the content of this buffer.
	 *
	 * @return the content of this buffer.
	 */
	public byte[] toByteArray()
	{
		byte[] data = new byte[count];
		System.arraycopy(buf, 0, data, 0, count);
		return data;
	}

	/**
	 * Returns the bytes contained in this buffer, starting at {@code off}.
	 *
	 * @param off the starting offset.
	 *
	 * @return the requested data bytes.
	 *
	 * @throws IndexOutOfBoundsException if {@code off} is negative or
	 *	if {@code off} is greater than {@code size()}.
	 */
	public byte[] toByteArray(int off)
	{
		return toByteArray(off, count - off);
	}

	/**
	 * Returns {@code len} bytes from this buffer, starting at {@code off}.
	 *
	 * @param off the starting offset.
	 * @param len the number of bytes to return.
	 *
	 * @return the requested data bytes.
	 *
	 * @throws IndexOutOfBoundsException if {@code off} or {@code len} is
	 *	negative or if {@code off + len} is greater than {@code size()}.
	 */
	public byte[] toByteArray(int off, int len)
	{
		if (off < 0 || len < 0 || off + len > count) {
			throw new IndexOutOfBoundsException();
		}
		byte[] data = new byte[len];
		System.arraycopy(buf, off, data, 0, len);
		return data;
	}

	@Override
	public Iterator<Byte> iterator()
	{
		return new ByteBufferIterator();
	}

	@Override
	public String toString()
	{
		return BaseEncoding.BASE_16.encode(buf, 0, count);
	}

	@Override
	public boolean equals(Object o)
	{
		if (o == this) {
			return true;
		}
		if (!(o instanceof ByteBuffer)) {
			return false;
		}
		final ByteBuffer buffer = (ByteBuffer) o;
		if (count != buffer.count) {
			return false;
		}
		for (int i = 0; i < count; i++) {
			if (buf[i] != buffer.buf[i]) {
				return false;
			}
		}
		return true;
	}

	@Override
	public int hashCode()
	{
		int hash = 7;
		if (count == 0) {
			return hash;
		}
		hash = 17 * hash + count;
		hash = 17 * hash + buf[0];
		hash = 17 * hash + buf[count];
		return hash;
	}

	private void ensureCapacity(int capacity)
	{
		int oldSize = buf.length;
		if (capacity > oldSize) {
			int newSize = Math.max((oldSize * 3) / 2 + 1, capacity);
			byte[] newBuf = new byte[newSize];
			System.arraycopy(buf, 0, newBuf, 0, count);
			buf = newBuf;
		}
	}

	private final class ByteBufferIterator implements Iterator<Byte>
	{
		private int index;

		@Override
		public boolean hasNext()
		{
			return index < count;
		}

		@Override
		public Byte next()
		{
			if (index >= count) {
				throw new NoSuchElementException();
			}
			return buf[index++];
		}

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