import java.sql.*;
import java.util.*;

/**
* Database connection pool. This class employs factory design pattern to control the pool instantination. Please use the method getPool to get the right pool instance.
 * @author Jan Seda
 */
public class JConnectionPool {

	/** Default connection count. */
	private final int DEFAULT_CNT = 10;
	/** Holds value of property driver. */
	private String driver;
	/** Holds value of property database. */
	private String database;
	/** Holds value of property user. */
	private String user;
	/** Holds value of property password. */
	private String password;
	/** Holds value of property size. */
	private int size;
	/** Stack with the connections */
	Stack pool = new Stack();
	/** Static pool HashMap */
	private static HashMap poolStore = new HashMap();
  
	/** Creates new JConnectionPool
	* @param driver the database driver class
	* @param database the jdbc url of the database
	* @param user username
	* @param password password
	* @param n the size of the pool (-1 is default)
	* @throws ClassNotFoundException If there is bad driver specified
	* @throws SQLException if there is a problem with connection
	* @throws InstantiationException if there is a problem with driver instantiation
	* @throws IllegalAccessException if there is a problem with connection instantiation
	*/
	protected JConnectionPool(String driver, String database, String user, String password, int n) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
		this.setDriver(driver);
		this.setDatabase(database);
		this.setUser(user);
		this.setPassword(password);
		this.setSize((n>0)?(n):(this.DEFAULT_CNT));
		this.reAlloc();
	}

	/** Getter for property driver. 
	* @return Value of property driver.
	*/
	public synchronized String getDriver() {
		return driver;
	}
    
  /** Setter for property driver. Method reAlloc must be called to employ the change.
    * @param driver New value of property driver.
    */
public synchronized void setDriver(String driver) {
	this.driver = driver;
}
    
  /** Getter for property database.
   * @return Value of property database.
   */
public synchronized String getDatabase() {
	return database;
}
    
  /** Setter for property database. Method reAlloc must be called to employ the change.
   * @param database New value of property database.
   */
public synchronized void setDatabase(String database) {
	this.database = database;
}
    
  /** Getter for property user.
   * @return Value of property user.
   */
public synchronized String getUser() {
	return user;
}
    
  /** Setter for property user. Method reAlloc must be called to employ the change.
    * @param user New value of property user.
    */
public synchronized void setUser(String user) {
	this.user = user;
}
    
  /** Getter for property password.
    * @return Value of property password.
    */
public synchronized String getPassword() {
	return password;
}
    
  /** Setter for property password. Method reAlloc must be called to employ the change.
    * @param password New value of property password.
    */
public synchronized void setPassword(String password) {
	this.password = password;
}
  
  /** Getter for property size.
   * @return Value of property size.
   */
public synchronized int getSize() {
	return size;
}
  
  /** Setter for property size. Method reAlloc must be called to employ the change.
   * @param size New value of property size.
   */
public synchronized void setSize(int size) {
	this.size = size;
}
  
  /** Realocates the connection pool according to new parameters
   * @throws ClassNotFoundException If there is bad driver specified
   * @throws SQLException if there is a problem with connection
   * @throws InstantiationException if there is a problem with driver instantiation
   * @throws IllegalAccessException if there is a problem with connection instantiation
   */
public synchronized void reAlloc() throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
	DriverManager.registerDriver((Driver)Class.forName(this.driver).newInstance());
	String db = this.getDatabase();
	String usr = this.getUser();
	String psw = this.getPassword();
	int cnt = this.getSize();
	for(int i=0;i<cnt;i++) {
		Connection con = DriverManager.getConnection(db,usr,psw);
		pool.push(con);
	}
}
 
  /** Creates the new Connection pool
   * @param name name of the pool
   * @param driver the database driver class
   * @param n the size of the pool (-1 is default)
   * @param db the database jdbc url
   * @param usr username
   * @param psw password
   * @throws ClassNotFoundException If there is bad driver specified
   * @throws SQLException if there is a problem with connection
   * @throws InstantiationException if there is a problem with driver instantiation
   * @throws IllegalAccessException if there is a problem with connection instantiation
   */
  public static synchronized void createPool(String name,String driver, String db, String usr, String psw, int n) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException {
    String signature = name;
    JConnectionPool pool = (JConnectionPool)poolStore.get(signature);
    if(pool==null) {
      pool = new JConnectionPool(driver,db,usr,psw,n);
      poolStore.put(signature,pool);
    }
  }
  
  /** Gets the Connection pool
   * @param name name of the pool
   * @return appropriate connection pool 
  */
   public static synchronized JConnectionPool getPool(String name) {
      JConnectionPool pool = (JConnectionPool)poolStore.get(name);
      return pool;
   }
  
  /** Gets the connection from the pool
   * @param wait wait if there is no connection in the pool
   * @param timeout timeout of the wait
   * @return connection
   */
  protected synchronized Connection getConnection(boolean wait,long timeout) {
    try {
      //System.out.println("Getting connection");
      Connection con = (Connection)pool.pop();
      //System.out.println("Got connection");
      return con;
    }
    catch (EmptyStackException e) {
      if(wait) {
	try {
	  wait(timeout);
	  return getConnection(wait,timeout);
	}
	catch (InterruptedException e0) {
	  e0.printStackTrace();
	  return null;
	}
      }
      else
      return null;
    }
  }
  
  /** Gets the connection from the pool
   * @param wait wait if there is no connection in the pool
   * @return connection
   */
  public synchronized Connection getConnection(boolean wait) {
    return getConnection(wait,0);
  }
  
  /** Returns the connection back to the pool
   * @param con returned connection
  */
  public synchronized void releaseConnection(Connection con) {
    pool.push(con);
    //System.out.println("Connection released");
    this.notify();
  }
  
  /** Returns unique id of the component
   * @return id as String
   */
  public String getId() {
    return this.getClass().getName()+"::"+driver+database+user+password;
  }
}
