/***************************************************************************
 *   Copyright (C) 2010 by Fabrizio Montesi <famontesi@gmail.com>          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   For details about the authors of this software, see the AUTHORS file. *
 ***************************************************************************/

package joliex.db.impl;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jolie.runtime.Value;


/**
 * Commodity class for converting a parametrized
 * query in a valid JDBC {@link PreparedStatement}.
 *
 * @author Fabrizio Montesi
 * @see PreparedStatement
 */
public class NamedStatementParser
{
	private static class TypeKeywords {
		private final static String DATE = "Date";
		private final static String TIMESTAMP = "Timestamp";
		private final static String TIME = "Time";
	}

	private final Map< String, List< Integer > > parameterPositions = new HashMap< String, List< Integer > >();
	private final PreparedStatement statement;
	private GregorianCalendar cal = new GregorianCalendar();

	public NamedStatementParser( Connection connection, String sql, Value parameters )
		throws SQLException
	{
		String jdbcSql = parse( sql );

		statement = connection.prepareStatement( jdbcSql );
		Value v;
		for( Entry< String, List< Integer > > entry : parameterPositions.entrySet() ) {
			v = parameters.getFirstChild( entry.getKey() );
			if ( v.isInt() ) {
				for( Integer index : entry.getValue() ) {
					statement.setInt( index, v.intValue() );
				}
			} else if ( v.isDouble() ) {
				for( Integer index : entry.getValue() ) {
					statement.setDouble( index, v.doubleValue() );
				}
			} else if ( v.isLong() ) {
				for( Integer index : entry.getValue() ) {
					statement.setLong( index, v.longValue() );
				}
			} else if ( v.isBool() ) {
				for( Integer index : entry.getValue() ) {
					statement.setBoolean( index, v.boolValue() );
				}
			} else if ( v.isByteArray() ) {
				for( Integer index : entry.getValue() ) {
					statement.setBytes( index, v.byteArrayValue().getBytes() );
				}
			} else {
				if ( v.hasChildren( TypeKeywords.DATE ) ) {
					Value date = v.getFirstChild( TypeKeywords.DATE );
					for( Integer index : entry.getValue() ) {
						String month = String.valueOf( date.getFirstChild( "month").intValue() );
						String day = String.valueOf( date.getFirstChild( "day").intValue() );
						if ( month.length() < 2 ) {
							month = "0" + month;
						}
						if ( day.length() < 2 ) {
							day = "0" + day;
						}
						statement.setDate( index,
							Date.valueOf(
								date.getFirstChild( "year").intValue()
								+ "-" + month
								+ "-" +day
							)
						);
					}
				} else if ( v.hasChildren( TypeKeywords.TIME ) ) {
					Value time = v.getFirstChild( TypeKeywords.TIME );
					for( Integer index : entry.getValue() ) {
						String hour = String.valueOf( time.getFirstChild( "hour" ).intValue() );
						String minute = String.valueOf( time.getFirstChild( "minute" ).intValue() );
						String second = String.valueOf( time.getFirstChild( "second" ).intValue() );
						if ( hour.length() < 2 ) {
							hour = "0" + hour;
						}
						statement.setTime( index,
							Time.valueOf( hour
							+ ":" + minute
							+ ":" + second ) );
					}
				} else if ( v.hasChildren( TypeKeywords.TIMESTAMP ) ) {
					Value timestampValue = v.getFirstChild( TypeKeywords.TIMESTAMP );					
					Timestamp timestamp = new Timestamp( timestampValue.getFirstChild( "epoch" ).longValue()  );
					for( Integer index : entry.getValue() ) {
						statement.setTimestamp( index, timestamp );
					}
				} else {
					for( Integer index : entry.getValue() ) {
						statement.setString( index, v.strValue() );
					}
				}
			}
		}
	}

	private String parse( String sql )
	{
		int length = sql.length();
		int index = 1;
		boolean inDoubleQuote = false;
		boolean inSingleQuote = false;
		char c;
		StringBuilder builder = new StringBuilder( length );
		int j;
		String name;
		List< Integer > positions;

		for( int i = 0; i < length; i++ ) {
			c = sql.charAt( i );
			if ( inSingleQuote ) {
				if ( c == '\'' ) {
					inSingleQuote = false;
				}
			} else if ( inDoubleQuote ) {
				if ( c == '"' ) {
					inDoubleQuote = false;
				}
			} else {
				if ( c == '\'' ) {
					inSingleQuote = true;
				} else if ( c == '"' ) {
					inDoubleQuote = true;
				} else if (
					c == ':' && i+1 < length && Character.isJavaIdentifierPart( sql.charAt( i+1 ) )
					&& ( i == 0 || sql.charAt( i - 1 ) != ':' )
				) {
					j = i + 2;
					while( j < length && Character.isJavaIdentifierPart( sql.charAt( j ) ) ) {
						j++;
					}
					name = sql.substring( i + 1, j );
					c = '?';
					i += name.length();

					positions = getParameterPositions( name );
					positions.add( index++ );
				}
			}

			builder.append( c );
		}

		return builder.toString();
	}

	private List< Integer > getParameterPositions( String parameterName )
	{
		List< Integer > ret = parameterPositions.get( parameterName );
		if ( ret == null ) {
			ret = new ArrayList< Integer >();
			parameterPositions.put( parameterName, ret );
		}
		return ret;
	}

	public PreparedStatement getPreparedStatement()
	{
		return statement;
	}
}

@@ -2467,16 +2467,22 @@
 private 
+final 
 Gregoria

Make cal final.