import java.sql.*;
import java.util.*;

public class ConnectionPool implements Runnable {
  private String 	driver, url, username, password;
  private int 		maxConnections;
  private boolean 	waitIfBusy;
  private Vector 	availableConnections, busyConnections;
  private boolean 	connectionPending = false;

//----------------------------------------------------------------------------------------------------
	public ConnectionPool(String  driver, 			
   						  String  url,
                          String  username, 			
                          String  password,
                          int 	  initialConnections,
                          int 	  maxConnections,
                          boolean waitIfBusy)			throws SQLException {
                        	
    	this.driver 		= driver;
    	this.url 			= url;
    	this.username 		= username;
    	this.password 		= password;
    	this.maxConnections = maxConnections;
		this.waitIfBusy 	= waitIfBusy;
    
		if (initialConnections > maxConnections) initialConnections = maxConnections;
    
		availableConnections = new Vector(initialConnections);
		busyConnections = new Vector();
    
    	availableConnections.addElement(makeNewConnection());
    	
		for(int i=0; i<initialConnections - 1; i++) {
			makeBackgroundConnection();
		}
	}
  
//----------------------------------------------------------------------------------------------------  
	public synchronized Connection getConnection() throws SQLException {
		
		if (!availableConnections.isEmpty()) {
			
      		Connection existingConnection = (Connection)availableConnections.lastElement();
			int lastIndex = availableConnections.size() - 1;
			availableConnections.removeElementAt(lastIndex);
			
			/**
			* Se a conexão retornada está fechada, então notifique a todos que uma conexão estará
			* disponível e tente novamente pegar uma conexão  na lista de conexões estabelecidas.
      		* Caso contrário será retornada a conexão selecionada par aser utilizada.
      		*/
      		      		
      		if (existingConnection.isClosed()) {
        		notifyAll(); // Libera todas as threads que estiverem esperando
        		return(getConnection());
      		} else {
        		busyConnections.addElement(existingConnection);
        		System.out.println("conexao locada   \t" + totalConnections() + "\tLivres:" + availableConnections.size() + "\tBusy:" + busyConnections.size());
        		return(existingConnection);
      		}
		} else {
			
      		/**
		    *  Três casos possíveis:
      		*  1) Não foi ultrapassado o limite máximo de conexões e se não existe nenhuma conexão
      		*     sendo estabelecida no memento. Então uma conexão será estabelecida em segundo plano.
      		*  2) Se foi ultrapassado o limite do conexões e o flag waitIfBusy está como false então
       		*   "dispare" Throw SQLException.
      		*  3) Foi ultrapassado o limite máximo de conexõesand e o flag waitIfBusy está como true;
      		*     então espere a liberação de uma nova conexão como no passo 2.
      	    */
      	    
			if ((totalConnections() < maxConnections) && !connectionPending) {   // 1)
	        	makeBackgroundConnection();
	      	
      		} else if (!waitIfBusy) {		// 2)
		        throw new SQLException("Connection limit reached");
      		}
    	}
   	
   		try {				// 3)
			wait();
   		} catch(InterruptedException ie) {}
	      	return(getConnection());  // alguém liberou um conexão, então tente denovo...	
	}

//----------------------------------------------------------------------------------------------------
  /**
  * Cria uma conexão em segundo plano, desta forma a thread pode acessar a conexão que for liberada
  * antes, seja a que esta sendo estabelecida ou uma outra que for liberado por outro usuário.
  */
  private void makeBackgroundConnection() {
    connectionPending = true;
    try {
      Thread connectThread = new Thread(this);
      connectThread.start();
    } catch(OutOfMemoryError oome) {
      // Give up on new connection
    }
  }

//----------------------------------------------------------------------------------------------------
  public void run() {
    try {
      Connection connection = makeNewConnection();
      
      synchronized(this) {  
      		availableConnections.addElement(connection);
        	connectionPending = false;
        	notifyAll();
      }
    
    } catch(Exception e) { // SQLException or OutOfMemory
      // Give up on new connection and wait for existing one
      // to free up.
    }
  }

//----------------------------------------------------------------------------------------------------  
	/**
	* Cria de fato uma nova conexão; esta função é chamada pelo construtor desta classe ou pelas suas
	* threads, para a criação de conexões em segundo plano.
	*/
	private Connection makeNewConnection() throws SQLException {
    	try {
	
      	Class.forName(driver);
      	Connection connection = DriverManager.getConnection(url, username, password);
      		System.out.println("conexao aberta");//------------------------------------------------------------
	      return(connection);
	
	    } catch(ClassNotFoundException cnfe) {
      		throw new SQLException("Can't find class for driver: " + driver);
    	}	
  	}

//----------------------------------------------------------------------------------------------------
  	public synchronized void free(Connection connection) {

		int lastIndex = busyConnections.size() - 1;
		busyConnections.removeElementAt(lastIndex);		

	    availableConnections.addElement(connection);
	    notifyAll(); //Libera threads que estão esperando por uma conexão livre...
	    System.out.print("conexao liberada \t");//------------------------------------------------------------
	    System.out.println("" + totalConnections() + "\tLivres:" + availableConnections.size() + "\tBusy:" + busyConnections.size());
  	}
    
//----------------------------------------------------------------------------------------------------
  	public synchronized int totalConnections() {
	    return(availableConnections.size() + busyConnections.size());
  	}

//----------------------------------------------------------------------------------------------------
	  public synchronized void closeAllConnections() {
	    closeConnections(availableConnections);
    	availableConnections = new Vector();
	    closeConnections(busyConnections);
    	busyConnections = new Vector();
  	}

//----------------------------------------------------------------------------------------------------
	private void closeConnections(Vector connections) {
		try {
      		for(int i=0; i<connections.size(); i++) {
        		Connection connection =  (Connection) connections.elementAt(i);
        		if (!connection.isClosed()) {
          			connection.close();
        		}
      		}	
    	} catch(SQLException sqle) {
      		// Ignore errors; garbage collect anyhow
    	}
  	}
  
//----------------------------------------------------------------------------------------------------
  public synchronized String toString() {
	return ("ConnectionPool(" + url + "," + username + ")" +
      		", available=" 	  + availableConnections.size() +
      		", busy=" 		  + busyConnections.size() +
      		", max=" 		  + maxConnections				);
  }

//----------------------------------------------------------------------------------------------------
}// FIM DA CLASSE ConnectionPool
