/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.utils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;



/**
 * コレクションに関するユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドを含みます。
 * 
 * @author nakamura
 * 
 */
public final class Utils {

	/**
	 * 値の個数に関して原子的であるクラスの一覧のデフォルト値です。処理の原子性とは無関係です。
	 */
	public static final Set<Class<?>> ATOM_SET = Collections.unmodifiableSet(new HashSet<Class<?>>(
			Utils.<Class<?>>
			list(boolean.class)
			.list(char.class)
			.list(byte.class)
			.list(short.class)
			.list(int.class)
			.list(long.class)
			.list(float.class)
			.list(double.class)
			.list(Boolean.class)
			.list(Character.class)
			.list(Byte.class)
			.list(Short.class)
			.list(Integer.class)
			.list(Long.class)
			.list(Float.class)
			.list(Double.class)
			.list(BigInteger.class)
			.list(BigDecimal.class)
			.list(java.util.Date.class)
			.list(java.sql.Date.class)
			.list(java.sql.Time.class)
			.list(java.sql.Timestamp.class)
			.list(String.class)
			.list(Number.class)));

	private Utils() {
	}

	/**
	 * 逆写像の{@link Map}のビューを返します。
	 * 引数が単射でなくなるキーと値の組を設定した場合は
	 * {@link IllegalStateException}を投げます。
	 * 
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param before 逆写像の作成元。本メソッドはこの引数の状態を変化させません。
	 * @return 逆写像の{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 * @throws IllegalStateException 引数が単射でない場合。
	 */
	public static <K, V> Map<V, K> inverseMap(final Map<K, V> before) {
		return new InverseMap<V, K>(before);
	}

	/**
	 * 単射であることを維持する{@link Map}のビューを返します。
	 * 単射とは「map#keySet()に含まれるすべての k0 と k1 について k0 != k1 ならば map#get(k0) != map#get(k1)」
	 * であることを意味します。
	 * または対偶をとって
	 * 「map#keySet()に含まれるすべての k0 と k1 について map#get(k0) == map#get(k1) ならば k0 == k1」
	 * であることを意味します。
	 * 登録済の値を異なるキーで設定した場合は
	 * {@link IllegalStateException}を投げます。
	 * 
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map 単射であることを維持する対象の{@link Map}。本メソッドはこの引数の状態を変化させません。
	 * @return 単射であることを維持された{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <K, V> Map<K, V> injectiveMap(final Map<K, V> map) {
		map.getClass();
		return new InjectiveMap<K, V>(map);
	}

	/**
	 * キーに関して上書き禁止した{@link Map}のビューを返します。
	 * 登録済のキーを設定した場合は{@link IllegalStateException}を投げます。
	 * 
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map キーに関して上書き禁止する対象の{@link Map}。本メソッドはこの引数の状態を変化させません。
	 * @return キーに関して上書き禁止された{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <K, V> Map<K, V> unoverwritableMap(final Map<K, V> map) {
		map.getClass();
		return new DelegationMap<K, V>(map) {
			private void check(final K key) {
				if (super.containsKey(key)) {
					throw new IllegalStateException(
							"already registered:" + key);
				}
			}

			@Override
			public V put(final K key, final V value) {
				check(key);
				return super.put(key, value);
			}

			@Override
			public void putAll(final Map<? extends K, ? extends V> t) {
				for (final K key : t.keySet()) {
					check(key);
				}
				super.putAll(t);
			}
		};
	}

	/**
	 * キー・値が null 値をとることを禁止する{@link Map}のビューを返します。
	 * キーまたは値が null 値である組を設定した場合は{@link NullPointerException}を投げます。
	 * 
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map キー・値がnull値をとることを禁止する対象の{@link Map}。本メソッドはこの引数の状態を変化させません。
	 * @return キー・値がnull値をとることを禁止された{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <K, V> Map<K, V> notNullMap(final Map<K, V> map) {
		map.getClass();
		return new NotNullMap<K, V>(map);
	}

	/**
	 * キーの文字数がゼロであることを禁止する{@link Map}のビューを返します。
	 * 文字数がゼロのキーを設定した場合は
	 * {@link StringIndexOutOfBoundsException}を投げます。
	 * 
	 * @param <V> 値。
	 * @param map キーの文字数がゼロであることを禁止する対象の{@link Map}。本メソッドはこの引数の状態を変化させます。
	 * @return キーの文字数がゼロであることを禁止された{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <V> Map<String, V> keyNotEmptyMap(final Map<String, V> map) {
		map.getClass();
		return new KeyNotEmptyMap<V>(map);
	}

	/**
	 * 類似の名前を同一とみなす{@link Map}のビューを返します。
	 * 類似の名前とは、大文字区切り(先頭文字の大文字と小文字の区別なし)と
	 * アンダーバー区切り(大文字と小文字の区別なし)の相互変換の範囲とします。
	 * 
	 * @param <V> 値。
	 * @param map 類似の名前を同一とみなす対象の{@link Map}。本メソッドはこの引数の状態を変化させません。
	 * @return 類似の名前を同一とみなす{@link Map}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <V> Map<String, V> similarKeyMap(final Map<String, V> map) {
		map.getClass();
		return new SimilarPropertyNameMap<V>(map);
	}

	/**
	 * {@link ConcurrentMap}で定義しているメソッドのみを lock で同期化する{@link ConcurrentMap}のビューを返します。
	 * DCLP(Double checked locking)パターンを使用しているためインスタンスが複数生成されることを抑止することはできませんが、
	 * ひとつのインスタンスのみが複数のスレッドから参照されることを保証することができます。
	 * {@link Map}で定義されているメソッドは同期化しないので引数の
	 * map が同期化されている必要があります。
	 * 
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map 対象の{@link Map}。本メソッドはこの引数の状態を変化させません。
	 * @param lock ロックオブジェクト。
	 * @return ロックオブジェクトで原子化した{@link ConcurrentMap}。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static <K, V> ConcurrentMap<K, V> concurrentMap(final Map<K, V> map, final Object lock) {
		map.getClass();
		return new SynchronizedConcurrentMap<K, V>(map, lock);
	}

	/**
	 * 変更禁止の{@link Iterable}のビューを返します。
	 * 
	 * @param <E> 汎用型。
	 * @param iterable 変更禁止対象の{@link Iterable}。本メソッドはこの引数の状態を変化させません。
	 * @return 変更禁止された{@link Iterable}。引数の状態変化はこの返却値に伝播します。
	 */
	public static <E> Iterable<E> unmodifiableIterable(
			final Iterable<E> iterable) {
		return new Iterable<E>() {
			public Iterator<E> iterator() {
				final Iterator<E> it = iterable.iterator();
				return new Iterator<E>() {
					public boolean hasNext() {
						return it.hasNext();
					}
					public E next() {
						return it.next();
					}
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
	
	/**
	 * 配列を変換した{@link List}のビューを返します。
	 * @param value 配列。プリミティブ型の配列にも対応しています。
	 * @return 配列を変換した{@link List}のビュー。返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static List<Object> arrayToList(final Object value) {
		if (value.getClass().getComponentType().isPrimitive()) {
			return new AbstractList<Object>() {
				@Override
				public Object get(final int index) {
					return Array.get(value, index);
				}
				@Override
				public int size() {
					return Array.getLength(value);
				}
				@Override
				public Object set(final int index, final Object after) {
					final Object before = get(index);
					Array.set(value, index, after);
					return before;
				}
			};
		} else {
			return Arrays.asList((Object[]) value);
		}
	}

	/**
	 * オブジェクトを{@link Iterable}に変換します。
	 * 変換元が配列なら{@link Iterable}に変換します。
	 * 変換元が{@link Iterable}ならそのまま返します。
	 * 上記以外ならその値ひとつの{@link Iterable}を返します。
	 * このクラスは LSP(The Liskov Substitution Principle) を満たしません。
	 * 
	 * @param value 変換元。
	 * @return 変換された{@link Iterable}。
	 * 変換元が配列または{@link Iterable}
	 * の場合は返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static Iterable<?> toIterable(final Object value) {
		if (value instanceof Iterable) {
			return (Iterable<?>) value;
		}
		return toListPrivate(value);
	}

	/**
	 * オブジェクトを{@link Collection}に変換します。
	 * 変換元が配列なら{@link Collection}に変換します。
	 * 変換元が{@link Collection}ならそのまま返します。
	 * 上記以外ならその値ひとつの{@link Collection}を返します。
	 * このクラスは LSP(The Liskov Substitution Principle) を満たしません。
	 * 
	 * @param value 変換元。
	 * @return 変換された{@link Collection}。
	 * 変換元が配列または{@link Collection}
	 * の場合は返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static Collection<?> toCollection(final Object value) {
		if (value instanceof Collection) {
			return (Collection<?>) value;
		}
		return toListPrivate(value);
	}

	/**
	 * オブジェクトを{@link List}に変換します。
	 * 変換元が配列なら{@link List}に変換します。
	 * 変換元が{@link List}ならそのまま返します。
	 * 上記以外ならその値ひとつの{@link List}を返します。
	 * このクラスは LSP(The Liskov Substitution Principle) を満たしません。
	 * 
	 * @param value 変換元。
	 * @return 変換された{@link List}。変換元が配列または
	 * {@link List}の場合は返却値の状態変化と引数の状態変化は相互に伝播します。
	 */
	public static List<?> toList(final Object value) {
		if (value instanceof List) {
			return (List<?>) value;
		}
		return toListPrivate(value);
	}

	private static List<Object> toListPrivate(final Object value) {
		if (value != null) {
			if (value.getClass().isArray()) {
				return arrayToList(value);
			} else if (value instanceof Iterable){
				final List<Object> list = new ArrayList<Object>();
				for(final Object element : (Iterable) value) {
					list.add(element);
				}
				return list;
			}
		}
		return Collections.singletonList(value);
	}

	/**
	 * {@link Throwable#getCause()}をたどって
	 * {@link Throwable#getLocalizedMessage()}の一覧に変換します。
	 * 中間の{@link Throwable}の場合、
	 * メッセージが null でないかつメッセージが原因の
	 * {@link Throwable#toString()}と同一でない場合にメッセージとして追加します。
	 * 終端の{@link Throwable}の場合、
	 * メッセージが null の場合は{@link Throwable#toString()}をメッセージとして追加し、
	 * メッセージが null でない場合はそれをメッセージとして追加します。
	 * 
	 * @param throwable 基点。
	 * @return メッセージの一覧。
	 */
	public static List<String> toMessageList(final Throwable throwable) {
		final List<String> list = new ArrayList<String>();
		for (Throwable t = throwable; t != null; t = t.getCause()) {
			final String s = t.getLocalizedMessage();
			if (t.getCause() == null) {
				if (s != null) {
					list.add(s);
				} else {
					list.add(t.toString());
				}
			} else {
				if (s != null && !t.getCause().toString().equals(s)) {
					list.add(s);
				}
			}
		}
		return list;
	}

	/**
	 * Javaの汎用型の推論を使用して{@link List}を簡易に生成するための開始位置です。
	 * @param <V>
	 * @param v 追加する値。
	 * @return {@link BuildableArrayList}のインスタンス。
	 */
	public static <V> BuildableArrayList<V> list(final V v) {
		return new BuildableArrayList<V>().list(v);
	}
	
	/**
	 * Javaの汎用型の推論を使用して{@link List}を簡易に生成するための開始位置です。
	 * @param <V>
	 * @param c コピー元。
	 * @return {@link BuildableArrayList}のインスタンス。
	 */
	public static <V> BuildableArrayList<V> list(final Collection<? extends V> c) {
		return new BuildableArrayList<V>().merge(c);
	}

	/**
	 * Javaの汎用型の推論を使用して{@link Map}を簡易に生成するための開始位置です。
	 * @param <K>
	 * @param <V>
	 * @param key 追加するキー。
	 * @param value 追加するキーに対応する値。
	 * @return {@link BuildableHashMap}のインスタンス。
	 */
	public static <K, V> BuildableHashMap<K, V> map(final K key, final V value) {
		return new BuildableHashMap<K, V>().map(key, value);
	}
	
	/**
	 * Javaの汎用型の推論を使用して{@link Map}を簡易に生成するための開始位置です。
	 * @param <K>
	 * @param <V>
	 * @param t コピー元。
	 * @return {@link BuildableHashMap}のインスタンス。
	 */
	public static <K, V> BuildableHashMap<K, V> map(final Map<? extends K, ? extends V> t) {
		return new BuildableHashMap<K, V>().merge(t);
	}

	/**
	 * Javaの汎用型の推論を使用して{@link java.util.LinkedHashMap}を簡易に生成するための開始位置です。
	 * @param <K>
	 * @param <V>
	 * @param key 追加するキー。
	 * @param value 追加するキーに対応する値。
	 * @return {@link BuildableLinkedHashMap}のインスタンス。
	 */
	public static <K, V> BuildableLinkedHashMap<K, V> linkedMap(final K key, final V value) {
		return new BuildableLinkedHashMap<K, V>().map(key, value);
	}

	/**
	 * Javaの汎用型の推論を使用して{@link java.util.LinkedHashMap}を簡易に生成するための開始位置です。
	 * @param <K>
	 * @param <V>
	 * @param t コピー元。
	 * @return {@link BuildableLinkedHashMap}のインスタンス。
	 */
	public static <K, V> BuildableLinkedHashMap<K, V> linkedMap(final Map<? extends K, ? extends V> t) {
		return new BuildableLinkedHashMap<K, V>().merge(t);
	}
}
