/*
 * 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>(Arrays.asList(
			(Class) boolean.class,
			char.class,
			byte.class,
			short.class,
			int.class,
			long.class,
			float.class,
			double.class,
			Boolean.class,
			Character.class,
			Byte.class,
			Short.class,
			Integer.class,
			Long.class,
			Float.class,
			Double.class,
			BigInteger.class,
			BigDecimal.class,
			java.util.Date.class,
			java.sql.Date.class,
			String.class,
			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 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 && value.getClass().isArray()) {
			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);
			}
		}
		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 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 java.util.LinkedHashMap}を簡易に生成するための開始位置です。
	 * @param <K>
	 * @param <V>
	 * @param key 追加するキー。
	 * @param value 追加するキーに対応する値。
	 * @return {@link BuildableHashMap}のインスタンス。
	 */
	public static <K, V> BuildableLinkedHashMap<K, V> linkedMap(final K key, final V value) {
		return new BuildableLinkedHashMap<K, V>().map(key, value);
	}
}
