/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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 org.opengion.hayabusa.taglib;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.util.StringUtil.nval ;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.jdbc.JDBCCategoryDataset;
import org.jfree.data.jdbc.JDBCPieDataset;
import org.jfree.data.jdbc.JDBCXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.util.TableOrder;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * BODY部に指定のSQLの検索結果をグラフ(円、棒、線)で表示するタグです。
 *
 * グラフ化には、JFreeChart (http://www.jfree.org/jfreechart/) を使用しています。
 * chartClass 属性には、ChartFactory.create****Chart( ････ ) の **** 部分を指定します。
 * それぞれのクラス毎に細かい設定値がありますが、初期レベルとしましては、主要チャートの
 * 主要属性のみサポートします。また、指定の chartClass では使用しない属性を指定した場合でも
 * 単に無視するだけで、警告等はありませんので、ご注意ください。
 *
 * 各属性は、{&#064;XXXX} 変数が使用できます。
 * これは、ServletRequest から、XXXX をキーに値を取り出し,この変数に
 * 割り当てます。つまり、このXXXXをキーにリクエストすれば、
 * この変数に値をセットすることができます。
 *
 * @og.formSample
 * ●形式：&lt;og:jfreeChart chartClass="…" … /&gt;
 * ●body：あり
 *
 * ●使用例
 *      &lt;og:jfreeChart
 *         chartClass    = &quot;実際に書き出すクラス名の略称(ChartFactory.create****Chart( ････ ) の **** 部分)をセットします。(必須)&quot;
 *         title         = &quot;チャートのタイトルをセットします。&quot;
 *         width         = &quot;チャートの横幅をセットします(初期値:200px)。&quot;
 *         height        = &quot;チャートの縦幅をセットします(初期値:200px)。&quot;
 *         legend        = &quot;チャートの凡例の表示可否(true/false)をセットします(初期値:true[表示する])。&quot;
 *         …
 *      &gt;
 *        &lt;jsp:text&gt; 実行するSQL文 &lt;/jsp:text&gt;
 *      &lt;/og:jfreeChart&gt;
 *
 * @og.rev 3.7.0.5 (2005/04/11) 新規追加
 * @og.group その他出力
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class JFreeChartTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/08/31)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/01/31)

	private static final Map<String,FileCache> cache = new WeakHashMap<String,FileCache>();
	private static final String[] CHART_LIST = new String[] {
									"Bar","XYLine","Pie","Pie3D","Area","Bar3D","Line","Line3D",
									"MultiplePie","MultiplePie3D","Polar","StackedArea",
									"StackedBar","StackedBar3D","TimeSeries","Waterfall",
									"XYArea","XYStepArea","XYStep" };

	private final String FILE_URL = HybsSystem.sys( "CHART_TEMP_DIR" );

	// 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
//	private String	dbid			= "DEFAULT";
	private String	dbid			= null ;
	private String	chartClass		= null ;
	private String	title			= null ;
	private String  width           = "200";		// 4.0.0 (2006/12/05) 数字から文字に変更
	private String  height          = "200";		// 4.0.0 (2006/12/05) 数字から文字に変更
	private boolean legend			= true;
	private boolean tooltips		= true;
	private boolean urls			= true;
	private String	sql				= null;
	private String	xlabel			= null;
	private String	ylabel			= null;
	private String	orientation		= "VERTICAL" ;		// VERTICAL(or V) , HORIZONTAL(or H)
	private int		cacheTime		= -1;				// 画像ファイルのキャッシュ

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return	int
	 */
	@Override
	public int doStartTag() {
		return( EVAL_BODY_BUFFERED );	// Body を評価する。（ extends BodyTagSupport 時）
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @og.rev 3.8.6.3 (2006/11/30) SQL 文の前後のスペースを取り除きます。
	 *
	 * @return	int
	 */
	@Override
	public int doAfterBody() {
		sql = getBodyString();
		if( sql != null && sql.length() > 0 ) {
			sql = sql.trim();
		}
		return(SKIP_BODY);
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return  int
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)

		String filename = null;
		String cacheKey = null;
		// キャッシュ制御の確認と取り出し。時間切れの場合は、null となります。
		if( cacheTime > 0 ) {
			cacheKey = chartClass + sql;
			FileCache fc = cache.get( cacheKey );
			if( fc != null ) {
				filename = fc.getFileURL();
				if( filename == null ) { cache.remove( cacheKey ); }	// 不要なキャッシュを削除
			}
		}

		if( filename == null ) {
			File file = getTempFile( FILE_URL );
			create( file,sql );
			filename = getContextPath() + "/" + FILE_URL + file.getName() ;
			// キャッシュ制御している場合は、キャッシュにセットする。
			if( cacheTime > 0 ) {
				FileCache fc = new FileCache( filename,cacheTime );
				cache.put( cacheKey,fc );
			}
		}

		jspPrint( makeTag( filename ) );

		return(EVAL_PAGE);
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 * @og.rev 3.5.5.0 (2004/03/12) baseURL , fileURL の削除（ fileURLは、final static 化する為）
	 *
	 * @og.rev 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
	 */
	@Override
	protected void release2() {
		super.release2();
//		dbid			= "DEFAULT";
		dbid			= null ;
		chartClass		= null ;	// 3.5.4.2 (2003/12/15) 追加
		title			= null ;
		width           = "200";		// 4.0.0 (2006/12/05) 数字から文字に変更
		height          = "200";		// 4.0.0 (2006/12/05) 数字から文字に変更
		legend			= true;
		tooltips		= true;
		urls			= true;
		orientation		= "VERTICAL" ;		// VERTICAL(or V) , HORIZONTAL(or H)
		sql				= null;
		xlabel			= null;
		ylabel			= null;
		cacheTime		= -1;
	}

	/**
	 * チャートを表示するためのタグを作成します。
	 *
	 * @param   filename String 画像ファイル
	 * @return	タグ文字列
	 */
	protected String makeTag( final String filename ) {
		StringBuilder rtn = new StringBuilder( HybsSystem.BUFFER_MIDDLE );

		rtn.append( "<img width = \"" ).append( width ).append( "\"");
		rtn.append( " height = \"" ).append( height ).append( "\"");
		rtn.append( " src=\"" ).append( filename ).append( "\" /> ");

		return rtn.toString();
	}

	/**
	 * TableWriter の実オブジェクトを生成して，PrintWriter に書き込みます。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfo オブジェクトを設定
	 *
	 * @param   out PrintWriter
	 */
	private void create( final File file,final String query )  {
		JFreeChart chart = null;
		Connection conn = null;
		boolean errFlag = true;
		PlotOrientation orient = ( orientation.startsWith( "V" ) ) ?
										PlotOrientation.VERTICAL : PlotOrientation.HORIZONTAL ;

		try {
			conn = ConnectionFactory.connection( dbid,getApplicationInfo() );	// 3.8.7.0 (2006/12/15)

			if( "Area".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createAreaChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			if( "StackedArea".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createStackedAreaChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Bar".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createBarChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Bar3D".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createBarChart3D(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "StackedBar".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createStackedBarChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "StackedBar3D".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createStackedBarChart3D(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Line".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createLineChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Line3D".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createLineChart3D(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Line3D".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createLineChart3D(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "Waterfall".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createWaterfallChart(
							title,xlabel,ylabel,cData,orient,legend, tooltips, urls);
			}
			else if( "MultiplePie".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createMultiplePieChart(
							title,cData,TableOrder.BY_COLUMN,legend, tooltips, urls);
			}
			else if( "MultiplePie3D".equalsIgnoreCase( chartClass ) ) {
				CategoryDataset cData = new JDBCCategoryDataset( conn, sql );
				chart = ChartFactory.createMultiplePieChart3D(
							title,cData,TableOrder.BY_COLUMN,legend, tooltips, urls);
			}
			else if( "Pie".equalsIgnoreCase( chartClass ) ) {
				JDBCPieDataset pData = new JDBCPieDataset( conn, sql );
				chart = ChartFactory.createPieChart(
							title,pData,legend, tooltips, urls);
			}
			else if( "Pie3D".equalsIgnoreCase( chartClass ) ) {
				JDBCPieDataset pData = new JDBCPieDataset( conn, sql );
				chart = ChartFactory.createPieChart3D(
							title,pData,legend, tooltips, urls);
			}
			else if( "XYLine".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createXYLineChart(
							title,xlabel,ylabel,xyData,orient,legend, tooltips, urls);
			}
			else if( "XYArea".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createXYAreaChart(
							title,xlabel,ylabel,xyData,orient,legend, tooltips, urls);
			}
			else if( "XYStepArea".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createXYStepAreaChart(
							title,xlabel,ylabel,xyData,orient,legend, tooltips, urls);
			}
			else if( "XYStep".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createXYStepChart(
							title,xlabel,ylabel,xyData,orient,legend, tooltips, urls);
			}
			else if( "TimeSeries".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createTimeSeriesChart(
							title,xlabel,ylabel,xyData,legend, tooltips, urls);
			}
			else if( "Polar".equalsIgnoreCase( chartClass ) ) {
				XYDataset xyData = new JDBCXYDataset( conn, sql );
				chart = ChartFactory.createPolarChart(
							title,xyData,legend, tooltips, urls);
			}
			ChartUtilities.saveChartAsPNG( file, chart, TaglibUtil.changeInt( width ), TaglibUtil.changeInt( height ) );
			errFlag = false;
		}
		catch( SQLException ex ) {
			String errMsg = "データベース処理を実行できませんでした。"
						 + HybsSystem.CR + query + HybsSystem.CR
						 + "err=[" + ex.getSQLState() + "]"
						 + ex.getMessage();
			throw new HybsSystemException( errMsg,ex );
		}
		catch( IOException ex ) {
			String errMsg = "ファイル I/O が実行できませんでした。"
						 + HybsSystem.CR + file + HybsSystem.CR
						 + ex.getMessage();
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
			if( errFlag ) { ConnectionFactory.remove( conn,dbid ); }
			else		  { ConnectionFactory.close( conn,dbid );  }
		}
	}

	/**
	 * テンポラリFile を取得します。
	 *
	 * ここでは、一般的なファイル出力を考慮した テンポラリFile を作成します。
	 *
	 * @og.rev 3.7.1.1 (2005/05/23) フォルダがない場合は、複数階層分のフォルダを自動で作成します。
	 *
	 * @param   fileURL String ファイルを作成するディレクトリ
	 */
	private File getTempFile( final String fileURL ) {

		String directory = HybsSystem.url2dir( fileURL );
		File dir = new File( directory );
		if( ! dir.exists() && ! dir.mkdirs() ) {
			String errMsg = "ディレクトリの作成に失敗しました。[" + directory + "]";
			throw new HybsSystemException( errMsg );
		}

		final File file ;
		try {
			file = File.createTempFile( "JFree",".png",dir );
			file.deleteOnExit();
		}
		catch( IOException ex ) {
			String errMsg = "ファイル名がオープン出来ませんでした。"
								+ " Url:" + fileURL 
								+ HybsSystem.CR
								+ ex.getMessage();			// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new HybsSystemException( errMsg,ex );
		}

		return file ;
	}

	/**
	 * 【TAG】(通常は使いません)Datasetオブジェクトを作成する時のDB接続IDを指定します。
	 *
	 * @og.tag
	 * Datasetオブジェクトを作成する時のDB接続IDを指定します。
	 * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
	 * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
	 * データベースにアクセスできます。
	 *
	 * @param	id データベース接続ID
	 */
	public void setDbid( final String id ) {
		dbid = nval( getRequestParameter( id ),dbid );
	}

	/**
	 * 【TAG】実際に書き出すクラス名の略称(ChartFactory.create****Chart( ････ ) の **** 部分)をセットします。
	 *
	 * @og.tag
	 * グラフ化には、JFreeChart (http://www.jfree.org/jfreechart/) を使用しています。
	 * chartClass 属性には、ChartFactory.createXXXXChart( ････ ) の XXXX 部分を指定します。
	 *
	 * @param   chartClass クラス名（の略称）
	 */
	public void setChartClass( final String chartClass ) {
		this.chartClass = nval( getRequestParameter( chartClass ),this.chartClass );

		if( ! check( this.chartClass, CHART_LIST ) ) {
			String errMsg = "指定のクラスは、現在サポートされていません。[" + chartClass + "]"
								+ HybsSystem.CR
								+ "Chart Class List=[" + StringUtil.array2csv( CHART_LIST ) + "]" ;
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】チャートのタイトルをセットします。
	 *
	 * @og.tag チャートのタイトルをセットします。
	 *
	 * @param   title タイトル
	 */
	public void setTitle( final String title ) {
		this.title = nval( getRequestParameter( title ),this.title );
	}

	/**
	 * 【TAG】チャートの横幅をセットします(初期値:200)。
	 *
	 * @og.tag
	 * タイトルや凡例も含んだ大きさです。データ領域は自動計算されます。
	 *
	 * @param   width 横幅
	 */
	public void setWidth( final String width ) {
		this.width = nval( getRequestParameter( width ),this.width );
	}

	/**
	 * 【TAG】チャートの縦幅をセットします(初期値:200)。
	 *
	 * @og.tag
	 * タイトルや凡例も含んだ大きさです。データ領域は自動計算されます。
	 *
	 * @param   height 縦幅
	 */
	public void setHeight( final String height ) {
		this.height = nval( getRequestParameter( height ),this.height );
	}

	/**
	 * 【TAG】チャートの凡例の表示可否(true/false)をセットします(初期値:true[表示する])。
	 *
	 * @og.tag
	 * 初期値は、表示する（true) です。
	 *
	 * @param   legend 凡例の表示可否 ： 表示する（true) /しない（その他）
	 */
	public void setLegend( final String legend ) {
		this.legend = nval( getRequestParameter( legend ),this.legend );
	}

	/**
	 * 【TAG】チャートのツールチップの表示可否(true/false)をセットします(初期値:true[表示する])。
	 *
	 * @og.tag
	 * 初期値は、表示する（true) です。
	 *
	 * @param   tooltips ツールチップの表示可否 ： 表示する（true) /しない（その他）
	 */
	public void setTooltips( final String tooltips ) {
		this.tooltips = nval( getRequestParameter( tooltips ),this.tooltips );
	}

	/**
	 * 【TAG】チャートのURLの表示可否(true/false)をセットします(初期値:true[表示する])。
	 *
	 * @og.tag
	 * 初期値は、表示する（true) です。
	 *
	 * @param   urls URLの表示可否 ： 表示する（true) /しない（その他）
	 */
	public void setUrls( final String urls ) {
		this.urls = nval( getRequestParameter( urls ),this.urls );
	}

	/**
	 * 【TAG】チャートの表示方向を、VERTICAL(or V) , HORIZONTAL(or H)で指定します(初期値:VERTICAL)。
	 *
	 * @og.tag
	 * 内部では、先頭１文字のみで判定しています。
	 * 初期値は、VERTICAL です。
	 *
	 * @param   orientation 表示方向 ： VERTICAL(or V) , HORIZONTAL(or H)
	 */
	public void setOrientation( final String orientation ) {
		this.orientation = nval( getRequestParameter( orientation ),this.orientation );
	}

	/**
	 * 【TAG】チャートのXラベルを指定します。
	 *
	 * @og.tag
	 * チャートのXラベルを指定します。
	 *
	 * @param   xlbl チャートのXラベル
	 */
	public void setXlabel( final String xlbl ) {
		String lbl = nval( getRequestParameter( xlbl ),xlabel );
		if( lbl != null ) { xlabel = getLabel( lbl ); }
	}

	/**
	 * 【TAG】チャートのYラベルを指定します。
	 *
	 * @og.tag
	 * チャートのYラベルを指定します。
	 *
	 * @param   ylbl チャートのXラベル
	 */
	public void setYlabel( final String ylbl ) {
		String lbl = nval( getRequestParameter( ylbl ),ylabel );
		if( lbl != null ) { ylabel = getLabel( lbl ); }
	}

	/**
	 * 【TAG】画像ファイルをキャッシュする時間を秒で指定します(初期値：キャッシュしない)
	 *
	 * @og.tag
	 * 検索結果のグラフ表示において、毎回、要求ごとにファイルを作成していては
	 * 処理時間やCPUパワーの増加が発生します。データベースの値が変わらなければ、
	 * SQL結果も、画像ファイルも変わりません。ある程度変更頻度が少ない場合は、
	 * 画像ファイルをキャッシュして、再利用します。そのキャッシュ時間を指定します。
	 * 初期値は、キャッシュしないです。
	 *
	 * @param   cacheTime 画像ファイルのキャッシュ時間(秒)
	 */
	public void setCacheTime( final String cacheTime ) {
		this.cacheTime = nval( getRequestParameter( cacheTime ),this.cacheTime );
	}

	/**
	 * ファイルをキャッシュするクラスです。
	 * キャッシュ時間をセットしておき、取り出し時に判定します。
	 *
	 * @og.group その他出力
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class FileCache {
		private final long   timeLimit ;
		private final String fileUrl ;

		/**
		 * コンストラクタ
		 * キャッシュするファイル名とキャッシュ時間をセットします。
		 *
		 * @param fileUrl String セットするファイル名
		 * @param cacheTime int  キャッシュ時間(秒)
		 */
		FileCache( final String fileUrl,final int cacheTime ) {
			this.fileUrl = fileUrl;
			timeLimit	 = cacheTime * 1000L + System.currentTimeMillis();
		}

		/**
		 * ファイルをキャッシュするクラスです。。
		 * キャッシュ時間をセットしておき、取り出し時に判定します。
		 * 時間切れの場合は、null を返します。
		 *
		 * @return String キャッシュされたファイル名(時間切れ時は、null)
		 */
		String getFileURL() {
			if( timeLimit > System.currentTimeMillis() ) {
				return fileUrl;
			}
			else {
				return null;
			}
		}
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"		,VERSION	)
				.println( "dbid"		,dbid		)
				.println( "chartClass"	,chartClass	)
				.println( "title"		,title		)
				.println( "width"		,width		)
				.println( "height"		,height		)
				.println( "legend"		,legend		)
				.println( "tooltips"	,tooltips	)
				.println( "urls"		,urls		)
				.println( "sql"			,sql		)
				.println( "xlabel"		,xlabel		)
				.println( "ylabel"		,ylabel		)
				.println( "orientation"	,orientation)
				.println( "cacheTime"	,cacheTime	)
				.println( "CHART_LIST"	,CHART_LIST	)
				.println( "FILE_URL"	,FILE_URL	)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
