/*
 * Paraselene
 * Copyright (c) 2009  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.tag.form;



import java.util.*;
import java.io.*;
import paraselene.tag.*;
import paraselene.supervisor.*;

/**
 * ファイル。<br>
 * アップロードファイルのGUIコントロール自体を表すと共に、アップロードされた
 * ファイルのライフサイクルを管理します。<br>
 * アップロードされたファイルは、OSが提供するテンポラリディレクトリに保存されます。
 * このアップロードファイル(以降、一時ファイルと呼びます)は次の何れかのタイミングで
 * 削除されます。
 * <UL>
 * <LI>JAVA VMが正常終了した時(サーブレットコンテナの終了)。
 * <LI>このUploadFileインスタンスがJAVA VMによりメモリから解放される時。
 * <LI>このUploadFileインスタンスのdeleteメソッドを呼んだ時。
 * <LI>このUploadFileインスタンスのmoveメソッドが正常終了した時。
 * </UL>
 * 一時ファイルが削除されると、クライアントが指定したファイル名やコンテントタイプは
 * 取得できなくなります。<br>
 * クライアントが指定したファイル名はgetValueStringで取得できます。
 * この時得られるファイル名はパスを含まないファイル名です。
 * クライアントの絶対パスを得たい場合は、RequestParameterから直接得て下さい。
 */
public class UploadFile extends Input {
	private static final long serialVersionUID = 1L;
	private File	file;
	private String	mime;

	/**
	 * コンストラクタ。
	 */
	public UploadFile() {
		super( Type.FILE );
	}

	protected Tag newReplica() {
		return new UploadFile();
	}

	void setRequest( RequestItem item ) {
		delete();
		int	cnt = item.getCount();
		for ( int i = 0; i < cnt; i++ ) {
			if ( !item.isFile( i ) )	continue;
			String	path = item.getValue( i );
			if ( path == null ) continue;
			if ( path.isEmpty() )	continue;
			setValueString( new File( path ).getName() );
			file = item.getFile( i );
			mime = item.getContentType( i );
			return;
		}
	}

	/**
	 * コンテントタイプの取得。
	 * @return コンテントタイプ。
	 */
	public String getContentType() {
		return mime;
	}

	/**
	 * 一時ファイルの削除。既に削除されていれば何もしません。
	 */
	public void delete() {
		if ( file != null ) {
			file.delete();
			file = null;
			setValueString( null );
			mime = null;
		}
	}

	/**
	 * 拡張子の検査。
	 * アップロードされたファイルが無い(または一時ファイルが削除されている)場合、
	 * 正常終了します。
	 * @param mes ControlException用メッセージ。
	 * @param ext 許可する拡張子。ピリオドはあっても無くても構いません。
	 * 大文字小文字は同一視します。
	 * @exception ControlException 許可する拡張子の何れでもない。
	 */
	public void checkExtension( String mes, String ... ext ) throws ControlException {
		String	name = getValueString();
		if ( name == null )	return;
		int	name_len = name.length();
		for ( int i = 0; i < ext.length; i++ ) {
			int	ext_len = ext[i].length();
			if ( name_len <= ext_len )	continue;
			if ( name.substring( name_len - ext_len ).equalsIgnoreCase( ext[i] ) )	return;
		}
		throw new ControlException( mes, this );
	}

	/**
	 * コンテントタイプの検査。
	 * アップロードされたファイルが無い(または一時ファイルが削除されている)場合、
	 * コンテンツタイプが取得できていない場合、正常終了します。
	 * @param mes ControlException用メッセージ。
	 * @param type 許可するコンテントタイプ。大文字小文字は同一視します。
	 * スペースが登場すると、それ以降は無視します。
	 * @exception ControlException 許可するコンテントタイプの何れでもない。
	 */
	public void checkContentType( String mes, String ... type ) throws ControlException {
		String	myself = getContentType();
		if ( myself == null )	return;
		myself = myself.split( " " )[0];
		for ( int i = 0; i < type.length; i++ ) {
			type[i] = type[i].split( " " )[0];
			if ( myself.equalsIgnoreCase( type[i] ) )	return;
		}
		throw new ControlException( mes, this );
	}

	/**
	 * ファイルの移動。一時ファイルは消滅します。<br>
	 * 最初にOSのリネーム機能を試行し、それが不可能ならファイルオープンを行い
	 * 目的ファイルに書き込みを行います。
	 * @param des 移動先。
	 * @exception FileNotFoundException 一時ファイルが無い、移動先がオープンできない。
	 * @exception IOException ファイル入出力時にエラーが発生。
	 */
	public void move( File des ) throws FileNotFoundException, IOException {
		des.delete();
		if ( file.renameTo( des ) ) {
			delete();
			return;
		}

		BufferedOutputStream	out = null;
		InputStream				in = null;
		byte[]					buf = new byte[4096];
		FileNotFoundException	fnf_ex = null;
		IOException				io_ex = null;
		try {
			out = new BufferedOutputStream( new FileOutputStream( des ) );
			in = getInputStream();
			while( true ) {
				int	size = in.read( buf );
				if ( size <= 0 )	break;
				out.write( buf, 0, size );
				if ( size < buf.length )	break;
			}
			in.close();
			out.close();
			delete();
		}
		catch( FileNotFoundException e1 ) {
			fnf_ex = e1;
		}
		catch( IOException e2 ) {
			io_ex = e2;
		}
		try {
			in.close();
		}
		catch( Exception e ) {}
		try {
			out.close();
		}
		catch( Exception e ) {}
		if ( fnf_ex != null )	throw fnf_ex;
		if ( io_ex != null )	throw io_ex;
	}

	/**
	 * 一時ファイルからのストリーム取得。実際に返すのはBufferedInputStreamです。
	 * メインルーチン側で必ずcloseして下さい。
	 * @return 入力ストリーム。
	 * @exception FileNotFoundException 一時ファイルが存在しない。
	 */
	public InputStream getInputStream() throws FileNotFoundException {
		if ( file == null ) throw new FileNotFoundException();
		return new BufferedInputStream( new FileInputStream( file ) );
	}

	protected void finalize() throws Throwable {
		delete();
	}
}

