package charactermanaj.util;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;

/**
 * リソースをロードするためのクラス.
 *
 * @author seraphy
 */
public class ResourceLoader {

	private final ClassLoader classLoader;

	/**
	 * ローカル優先でリソースを探索するリソースローダーを構築します。
	 */
	public ResourceLoader() {
		this(true);
	}

	/**
	 * ローカル優先か、クラス優先のいずれかを指定してリソースローダーを構築します。
	 * @param preferredLocal
	 */
	public ResourceLoader(boolean preferredLocal) {
		this(getUsersResourceDirClassLoader(getDefaultClassLoader(), preferredLocal));
	}

	/**
	 * クラスローダーを指定してリソースローダーを構築します。
	 * @param classLoader
	 */
	public ResourceLoader(ClassLoader classLoader) {
		if (classLoader == null) {
			throw new IllegalArgumentException("classLoader is required.");
		}
		this.classLoader = classLoader;
	}

	/**
	 * クラスローダを取得する.<br>
	 * まずローカルファイル上のリソースディレクトリがあれば、それを検索する.<br>
	 * つぎにスレッドに関連づけられているコンテキストクラスローダか、もしなければ、このクラスをロードしたクラスローダを用いて検索する.<br>
	 *
	 * @return クラスローダ
	 */
	public ClassLoader getClassLoader() {
		return classLoader;
	}

	/**
	 * クラスローダによりリソースをロードする.<br>
	 * 該当するリソースが存在しない場合はnullを返す.<br>
	 * リソース名がnullの場合もnullを返す.<br>
	 *
	 * @param name
	 *            リソース名またはnull
	 * @return リソースがあれば、そのURL。なければnull
	 */
	public URL getResource(String name) {
		if (name == null) {
			return null;
		}
		return getClassLoader().getResource(name);
	}

	/**
	 * クラスローダを取得する.<br>
	 * スレッドに関連づけられているコンテキストクラスローダか、もしなければ、このクラスをロードしたクラスローダを返す.<br>
	 *
	 * @return クラスローダ
	 */
	public static ClassLoader getDefaultClassLoader() {
		return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
			public ClassLoader run() {
				ClassLoader cl = Thread.currentThread().getContextClassLoader();
				if (cl == null) {
					cl = ResourceLoader.class.getClassLoader();
				}
				return cl;
			}
		});
	}

	/**
	 * ユーザー用のローカルファイル上のリソースディレクトリにアクセスするクラスローダ取得する.<br>
	 * 作成されていなければparentをそのまま返す.<br>
	 * ローカル優先の場合、リソースはローカルファイル上のパスで検索されたのちにparentで検索されます.(標準のURLClassLoaderとは違う探索方法)<br>
	 * ローカル優先ではない場合は通常どおり、親クラスローダを優先して検索されます.<br>
	 *
	 * @param parent
	 *            親クラスローダ、nullの場合は親の探索をしない.
	 * @param preferredLocal
	 *            ローカル優先か？
	 * @return ローカルシステム上のリソースディレクトリにアクセスするクラスローダ、なければparentのまま
	 */
	public static ClassLoader getUsersResourceDirClassLoader(final ClassLoader parent, final boolean preferredLocal) {
		try {
			File baseDir = ConfigurationDirUtilities.getUserDataDir();
			SetupLocalization localize = new SetupLocalization(baseDir);
			final File resourceDir = localize.getResourceDir();
			if (!resourceDir.exists() || !resourceDir.isDirectory()) {
				return parent;
			}
			URLClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction<URLClassLoader>() {
				public URLClassLoader run() throws MalformedURLException {
					URL[] urls = new URL[] { resourceDir.toURI().toURL() };
					if (preferredLocal) {
						// リソースの探索順序をローカル優先にするURLクラスローダ
						return new URLClassLoader(urls, parent) {
							@Override
							public URL getResource(String name) {
								URL url = findResource(name); // 子が優先 (標準と逆)
								if (url == null) {
									ClassLoader parent = getParent();
									if (parent != null) {
										url = parent.getResource(name);
									}
								}
								return url;
							}
						};
					} else {
						// リソースの探索順序を親優先(標準)にするURLクラスローダー
						return new URLClassLoader(urls, parent);
					}
				}
			});
			return cl;

		} catch (Exception ex) {
			ex.printStackTrace();
			return null;
		}
	}
}
