/*
 * ao-dbc - Simplified JDBC access for simplified code.
 * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013, 2014, 2015, 2016, 2018  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-dbc.
 *
 * ao-dbc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ao-dbc 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ao-dbc.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.dbc;

import com.aoindustries.sql.AOConnectionPool;
import com.aoindustries.util.IntList;
import com.aoindustries.util.LongList;
import java.sql.Connection;
import java.sql.SQLData;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.logging.Logger;
import javax.sql.DataSource;

/**
 * Wraps and simplifies access to a JDBC database.  If used directly as a DatabaseAccess each individual call is a separate transaction.
 * If the current thread is already in a transaction, the calls will be performed using the connection associated with that transaction.
 * For transactions across multiple statements, use DatabaseConnection.
 *
 * @see  #createDatabaseConnection
 * @see  DatabaseConnection
 *
 * @author  AO Industries, Inc.
 */
public class Database extends AbstractDatabaseAccess {

	/**
	 * Only one connection pool is made to the database.
	 */
	private final AOConnectionPool pool;

	private final DataSource dataSource;
	private final Logger logger;

	public Database(String driver, String url, String user, String password, int numConnections, long maxConnectionAge, Logger logger) {
		this(new AOConnectionPool(driver, url, user, password, numConnections, maxConnectionAge, logger));
	}

	public Database(AOConnectionPool pool) {
		if(pool==null) throw new IllegalArgumentException("pool==null");
		this.pool = pool;
		this.dataSource = null;
		this.logger = null;
	}

	public Database(DataSource dataSource, Logger logger) {
		if(dataSource==null) throw new IllegalArgumentException("dataSource==null");
		if(logger==null) throw new IllegalArgumentException("logger==null");
		this.pool = null;
		this.dataSource = dataSource;
		this.logger = logger;
	}

	public DatabaseConnection createDatabaseConnection() {
		return new DatabaseConnection(this);
	}

	/**
	 * Gets the pool or null if using a DataSource.
	 */
	public AOConnectionPool getConnectionPool() {
		return pool;
	}

	/**
	 * Gets the data source or null if using an AOConnectionPool.
	 */
	public DataSource getDataSource() {
		return dataSource;
	}

	/**
	 * The custom types discovered via {@link ServiceLoader}.
	 */
	private volatile Map<String,Class<?>> sqlDataTypes;

	/**
	 * Loads the custom types when first needed and caches the results.
	 */
	private Map<String,Class<?>> getSqlDataTypes() throws SQLException {
		if(sqlDataTypes == null) {
			// Load custom types from ServiceLoader
			Map<String,Class<?>> newMap = new LinkedHashMap<>();
			Iterator<SQLData> iter = ServiceLoader.load(SQLData.class).iterator();
			while(iter.hasNext()) {
				SQLData sqlData = iter.next();
				newMap.put(sqlData.getSQLTypeName(), sqlData.getClass());
			}
			sqlDataTypes = newMap;
		}
		return sqlDataTypes;
	}

	private final Map<Connection,Map<String,Class<?>>> oldTypeMaps = new IdentityHashMap<>();

	public Connection getConnection(int isolationLevel, boolean readOnly, int maxConnections) throws SQLException {
		Connection conn;
		if(pool != null) {
			// From pool
			conn = pool.getConnection(isolationLevel, readOnly, maxConnections);
			boolean successful = false;
			try {
				initConnection(conn);
				successful = true;
			} finally {
				if(!successful) pool.releaseConnection(conn);
			}
		} else {
			// From dataSource
			conn = dataSource.getConnection();
			boolean successful = false;
			try {
				if(isolationLevel!=conn.getTransactionIsolation()) conn.setTransactionIsolation(isolationLevel);
				if(readOnly!=conn.isReadOnly()) conn.setReadOnly(readOnly);
				initConnection(conn);
				successful = true;
			} finally {
				if(!successful) conn.close();
			}
		}
		// Load custom types from ServiceLoader
		Map<String,Class<?>> newTypes = getSqlDataTypes();
		int size = newTypes.size();
		if(size != 0) {
			Map<String,Class<?>> typeMap = conn.getTypeMap();
			// Note: We get "null" back from PostgreSQL driver, despite documentation of returning empty
			if(typeMap == null) typeMap = new LinkedHashMap<>(size*4/3+1);
			oldTypeMaps.put(conn, new LinkedHashMap<>(typeMap));
			typeMap.putAll(newTypes);
			conn.setTypeMap(typeMap);
		}
		return conn;
	}

	public void releaseConnection(Connection conn) throws SQLException {
		// Restore custom types
		// TODO: Do not remove on release and avoid re-adding for performance?
		Map<String,Class<?>> oldTypeMap = oldTypeMaps.remove(conn);
		if(oldTypeMap != null) conn.setTypeMap(oldTypeMap);
		if(pool != null) {
			// From pool
			pool.releaseConnection(conn);
		} else {
			// From dataSource
			if(!conn.isClosed()) conn.close();
		}
	}

	public Logger getLogger() {
		if(pool!=null) {
			// From pool
			return pool.getLogger();
		} else {
			// From dataSource
			return logger;
		}
	}

	@Override
	public boolean executeBooleanQuery(int isolationLevel, boolean readOnly, boolean rowRequired, String sql, Object ... params) throws NoRowException, SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeBooleanQuery(isolationLevel, readOnly, rowRequired, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				boolean value=conn.executeBooleanQuery(isolationLevel, readOnly, rowRequired, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public IntList executeIntListQuery(int isolationLevel, boolean readOnly, String sql, Object ... params) throws SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeIntListQuery(isolationLevel, readOnly, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				IntList value=conn.executeIntListQuery(isolationLevel, readOnly, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public int executeIntQuery(int isolationLevel, boolean readOnly, boolean rowRequired, String sql, Object ... params) throws NoRowException, SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeIntQuery(isolationLevel, readOnly, rowRequired, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				int value=conn.executeIntQuery(isolationLevel, readOnly, rowRequired, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public LongList executeLongListQuery(int isolationLevel, boolean readOnly, String sql, Object ... params) throws SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeLongListQuery(isolationLevel, readOnly, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				LongList value=conn.executeLongListQuery(isolationLevel, readOnly, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public long executeLongQuery(int isolationLevel, boolean readOnly, boolean rowRequired, String sql, Object ... params) throws NoRowException, SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeLongQuery(isolationLevel, readOnly, rowRequired, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				long value=conn.executeLongQuery(isolationLevel, readOnly, rowRequired, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public <T,E extends Exception> T executeObjectQuery(int isolationLevel, boolean readOnly, boolean rowRequired, Class<E> eClass, ObjectFactoryE<T,E> objectFactory, String sql, Object ... params) throws NoRowException, SQLException, E {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeObjectQuery(isolationLevel, readOnly, rowRequired, eClass, objectFactory, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				T value=conn.executeObjectQuery(isolationLevel, readOnly, rowRequired, eClass, objectFactory, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public <T,C extends Collection<? super T>,E extends Exception> C executeObjectCollectionQuery(int isolationLevel, boolean readOnly, C collection, Class<E> eClass, ObjectFactoryE<T,E> objectFactory, String sql, Object ... params) throws SQLException, E {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeObjectCollectionQuery(isolationLevel, readOnly, collection, eClass, objectFactory, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				C value=conn.executeObjectCollectionQuery(isolationLevel, readOnly, collection, eClass, objectFactory, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public <T,E extends Exception> T executeQuery(int isolationLevel, boolean readOnly, Class<E> eClass, ResultSetHandlerE<T,E> resultSetHandler, String sql, Object ... params) throws SQLException, E {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeQuery(isolationLevel, readOnly, eClass, resultSetHandler, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				T value = conn.executeQuery(isolationLevel, readOnly, eClass, resultSetHandler, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public short executeShortQuery(int isolationLevel, boolean readOnly, boolean rowRequired, String sql, Object ... params) throws NoRowException, SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeShortQuery(isolationLevel, readOnly, rowRequired, sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				short value=conn.executeShortQuery(isolationLevel, readOnly, rowRequired, sql, params);
				if(!readOnly) conn.commit();
				return value;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public int executeUpdate(String sql, Object ... params) throws SQLException {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return conn.executeUpdate(sql, params);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				int updateCount = conn.executeUpdate(sql, params);
				conn.commit();
				return updateCount;
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} finally {
				conn.releaseConnection();
			}
		}
	}

	private final ThreadLocal<DatabaseConnection> transactionConnection = new ThreadLocal<>();

	/**
	 * Checks if currently in a transaction.
	 *
	 * @see #executeTransaction(com.aoindustries.sql.DatabaseCallable)
	 * @see #executeTransaction(com.aoindustries.sql.DatabaseCallableE)
	 * @see #executeTransaction(com.aoindustries.sql.DatabaseRunnable)
	 * @see #executeTransaction(com.aoindustries.sql.DatabaseRunnableE)
	 */
	public boolean isInTransaction() {
		return transactionConnection.get()!=null;
	}

	/**
	 * @see  #executeTransaction(com.aoindustries.sql.DatabaseCallableE)
	 *
	 * @see #isInTransaction()
	 */
	public void executeTransaction(final DatabaseRunnable runnable) throws SQLException {
		executeTransaction(
			RuntimeException.class,
			new DatabaseCallableE<Void,RuntimeException>() {
				@Override
				public Void call(DatabaseConnection db) throws SQLException {
					runnable.run(db);
					return null;
				}
			}
		);
	}

	/**
	 * @see  #executeTransaction(com.aoindustries.sql.DatabaseCallableE)
	 *
	 * @see #isInTransaction()
	 */
	public <E extends Exception> void executeTransaction(
		Class<E> eClass,
		final DatabaseRunnableE<E> runnable
	) throws SQLException, E {
		executeTransaction(
			eClass,
			new DatabaseCallableE<Void,E>() {
				@Override
				public Void call(DatabaseConnection db) throws SQLException, E {
					runnable.run(db);
					return null;
				}
			}
		);
	}

   /**
	 * @see  #executeTransaction(com.aoindustries.sql.DatabaseCallableE)
	 *
	 * @see #isInTransaction()
	 */
	public <V> V executeTransaction(final DatabaseCallable<V> callable) throws SQLException {
		return executeTransaction(
			RuntimeException.class,
			new DatabaseCallableE<V,RuntimeException>() {
				@Override
				public V call(DatabaseConnection db) throws SQLException {
					return callable.call(db);
				}
			}
		);
	}

	/**
	 *

	 * Executes an arbitrary transaction, providing automatic commit, rollback, and connection management.
	 * Rolls-back the transaction on RuntimeException.
	 * Rolls-back the transaction on NoRowException on the outer-most transaction only.
	 * Rolls-back and closes the connection on all SQLException except NoRowException.
	 * Rolls-back the transaction on E.
	 *

	 *

	 * The connection allocated is stored as a ThreadLocal and will be automatically reused if
	 * another transaction is performed within this transaction.  Any nested transaction will automatically
	 * become part of the enclosing transaction.  For safety, a nested transaction will still rollback the
	 * entire transaction on any exception.
	 *

	 *
	 * @see #isInTransaction()
	 */
	public <V,E extends Exception> V executeTransaction(
		Class<E> eClass,
		DatabaseCallableE<V,E> callable
	) throws SQLException, E {
		DatabaseConnection conn = transactionConnection.get();
		if(conn!=null) {
			// Reuse existing connection
			try {
				return callable.call(conn);
			} catch(RuntimeException err) {
				conn.rollback();
				throw err;
			} catch(NoRowException err) {
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			}
		} else {
			// Create new connection
			conn=createDatabaseConnection();
			try {
				transactionConnection.set(conn);
				try {
					V result = callable.call(conn);
					conn.commit();
					return result;
				} finally {
					transactionConnection.remove();
				}
			} catch(RuntimeException | NoRowException err) {
				conn.rollback();
				throw err;
			} catch(SQLException err) {
				conn.rollbackAndClose();
				throw err;
			} catch(Exception e) {
				conn.rollback();
				if(eClass.isInstance(e)) throw eClass.cast(e);
				throw new SQLException(e);
			} finally {
				conn.releaseConnection();
			}
		}
	}

	@Override
	public String toString() {
		return "Database("+(pool!=null ? pool.toString() : dataSource.toString())+")";
	}

	/**
	 * Whenever a new connection is obtained from the pool or the dataSource,
	 * it is passed here for any initialization routine.
	 * This default implementation does nothing.
	 */
	protected void initConnection(Connection conn) throws SQLException {
		// Do nothing
	}
}


/*
 * ao-dbc - Simplified JDBC access for simplified code.
 * Copyright (C) 2009, 2010, 2011, 2013, 2015, 2016  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-dbc.
 *
 * ao-dbc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ao-dbc 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ao-dbc.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.dbc;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Creates instances of objects by using reflection and passing-in the parameters in the same
 * order as the matching constructor.  For unknown classes, will try to find any
 * valueOf(int) or valueOf(String) methods to create the object instance.
 *
 * @author  AO Industries, Inc.
 */
public class AutoObjectFactory<T> implements ObjectFactory<T> {

	static final String EOL = System.getProperty("line.separator");

	/**
	 * Concurrent map can't store nulls, uses this in place of null values when lookup fails.
	 */
	private static final Method notExists;
	static {
		try {
			notExists = AutoObjectFactory.class.getMethod("getValueOfStringMethod", Class.class);
		} catch(NoSuchMethodException err) {
			throw new RuntimeException(err);
		}
	}

	private static final ConcurrentMap<Class<?>,Method> valueOfIntMethods = new ConcurrentHashMap<>();

	/**
	 * Gets the valueOf(int) for the provided class or null if doesn't
	 * exist or is non-static or non-public.
	 */
	public static Method getValueOfIntMethod(Class<?> clazz) {
		Method existing = valueOfIntMethods.get(clazz);
		if(existing==null) {
			Method newMethod;
			try {
				newMethod = clazz.getMethod("valueOf", Integer.TYPE);
				int mod = newMethod.getModifiers();
				if(!Modifier.isStatic(mod) || !Modifier.isPublic(mod)) newMethod = notExists;
			} catch(NoSuchMethodException err) {
				newMethod = notExists;
			}
			existing = valueOfIntMethods.put(clazz, newMethod);
			if(existing==null) existing = newMethod;
		}
		return existing==notExists ? null : existing;
	}

	private static final ConcurrentMap<Class<?>,Method> valueOfStringMethods = new ConcurrentHashMap<>();

	/**
	 * Gets the valueOf(String) for the provided class or null if doesn't
	 * exist or is non-static or non-public.
	 */
	public static Method getValueOfStringMethod(Class<?> clazz) {
		//System.err.println("clazz="+clazz);
		Method existing = valueOfStringMethods.get(clazz);
		if(existing==null) {
			Method newMethod;
			try {
				newMethod = clazz.getMethod("valueOf", String.class);
				int mod = newMethod.getModifiers();
				if(!Modifier.isStatic(mod) || !Modifier.isPublic(mod)) newMethod = notExists;
			} catch(NoSuchMethodException err) {
				newMethod = notExists;
			}
			existing = valueOfStringMethods.put(clazz, newMethod);
			if(existing==null) existing = newMethod;
		}
		return existing==notExists ? null : existing;
	}

	private final Class<T> clazz;
	private final Object[] prefixParams;

	public AutoObjectFactory(Class<T> clazz, Object... prefixParams) {
		this.clazz = clazz;
		this.prefixParams = prefixParams;
	}

	/**
	 * Creates one object from the current values in the ResultSet.  Looks for a
	 * constructor that is assignable to the prefixParams and the result set values.
	 * The constructor must exactly match the number of prefixParams plus the
	 * result set.
	 */
	@Override
	public T createObject(ResultSet result) throws SQLException {
		try {
			ResultSetMetaData metaData = result.getMetaData();
			int numColumns = metaData.getColumnCount();
			int numParams = prefixParams.length + numColumns;
			Object[] params = new Object[numParams];

			// Find the candidate constructor
			List<String> warnings = null;
			Constructor<?>[] constructors = clazz.getConstructors();
		CONSTRUCTORS :
			for(Constructor<?> constructor : constructors) {
				Class<?>[] paramTypes = constructor.getParameterTypes();
				if(paramTypes.length==numParams) {
					for(int i=0;i<prefixParams.length;i++) {
						Class<?> paramType = paramTypes[i];
						if(!paramType.isAssignableFrom(prefixParams[i].getClass())) continue CONSTRUCTORS;
						params[i] = prefixParams[i];
						//System.err.println(paramType.getName()+" ? "+(params[i]==null ? "null" : params[i].getClass()));
					}
					// All remaining columns must be assignable from JDBC
					for(int c=1; c<=numColumns; c++) {
						int i = prefixParams.length + c-1;
						Class<?> paramType = paramTypes[i];
						// String first because it is commonly used
						if(paramType==String.class) {
							params[i] = result.getString(c);

						// Primitives
						} else if(paramType==Integer.TYPE) {
							int value = result.getInt(c);
							if(result.wasNull()) throw new SQLException(c+": "+metaData.getColumnName(c)+": null int");
							params[i] = value;
						} else if(paramType==Short.TYPE) {
							short value = result.getShort(c);
							if(result.wasNull()) throw new SQLException(c+": "+metaData.getColumnName(c)+": null short");
							params[i] = value;
						} else if(paramType==Boolean.TYPE) {
							boolean b = result.getBoolean(c);
							if(result.wasNull()) throw new SQLException(c+": "+metaData.getColumnName(c)+": null boolean");
							params[i] = b;
						} else if(paramType==Float.TYPE) {
							float value = result.getFloat(c);
							if(result.wasNull()) throw new SQLException(c+": "+metaData.getColumnName(c)+": null float");
							params[i] = value;
						} else if(paramType==Long.TYPE) {
							long value = result.getLong(c);
							if(result.wasNull()) throw new SQLException(c+": "+metaData.getColumnName(c)+": null long");
							params[i] = value;

						// Other types
						} else if(paramType==Date.class) {
							params[i] = result.getDate(c);
						} else if(paramType==Boolean.class) {
							boolean b = result.getBoolean(c);
							params[i] = result.wasNull() ? null : b;
						} else if(paramType==Timestamp.class) {
							params[i] = result.getTimestamp(c);
						} else if(paramType==Integer.class) {
							int value = result.getInt(c);
							params[i] = result.wasNull() ? null : value;
						} else if(paramType==Float.class) {
							float value = result.getFloat(c);
							params[i] = result.wasNull() ? null : value;
						} else if(paramType==Short.class) {
							short value = result.getShort(c);
							params[i] = result.wasNull() ? null : value;
						} else if(paramType==Long.class) {
							long value = result.getLong(c);
							params[i] = result.wasNull() ? null : value;
						} else {
							// Try to find valueOf(int) for unknown types
							Method valueOfIntMethod = getValueOfIntMethod(paramType);
							if(valueOfIntMethod!=null) {
								int value = result.getInt(c);
								if(result.wasNull()) params[i] = null;
								params[i] = valueOfIntMethod.invoke(null, value);
							} else {
								// Try to find valueOf(String) for unknown types
								Method valueOfStringMethod = getValueOfStringMethod(paramType);
								if(valueOfStringMethod!=null) {
									String value = result.getString(c);
									params[i] = result.wasNull() ? null : valueOfStringMethod.invoke(null, value);
								} else {
									if(warnings==null) warnings = new ArrayList<>();
									warnings.add("Unexpected parameter class: "+paramType.getName());
									continue CONSTRUCTORS;
								}
							}
						}
						//System.err.println(paramType.getName()+" ? "+(params[i]==null ? "null" : params[i].getClass()));
					}
					Object newInstance = constructor.newInstance(params);
					//System.err.println(newInstance.getClass().getName()+": "+newInstance);
					return clazz.cast(newInstance);
				}
			}
			StringBuilder message = new StringBuilder("Unable to find matching constructor");
			if(warnings!=null) for(String warning : warnings) message.append(EOL).append(warning);
			throw new SQLException(message.toString());
		} catch(InstantiationException | IllegalAccessException | InvocationTargetException err) {
			throw new SQLException(err);
		}
	}
}


/*
 * ao-dbc - Simplified JDBC access for simplified code.
 * Copyright (C) 2011, 2013, 2015, 2016  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-dbc.
 *
 * ao-dbc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ao-dbc 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ao-dbc.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.dbc.meta;

import com.aoindustries.dbc.NoRowException;
import com.aoindustries.table.IndexType;
import com.aoindustries.util.AoCollections;
import com.aoindustries.util.AutoGrowArrayList;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * A friendly wrapper around database meta data.
 *
 * @author  AO Industries, Inc.
 */
public class Table {

	private final Schema schema;
	private final String name;
	private final String tableType;
	private final int hashCode;

	protected Table(Schema schema, String name, String tableType) {
		this.schema = schema;
		if(name.indexOf('"')!=-1) throw new IllegalArgumentException();
		this.name = name;
		this.tableType = tableType;
		this.hashCode = schema.hashCode() * 31 + name.hashCode();
	}

	@Override
	public String toString() {
		return name;
	}

	/**
	 * Two tables are equal if they have the same schema name and table name.
	 */
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Table)) return false;
		Table other = (Table)obj;
		return
			hashCode==other.hashCode
			&& name.equals(other.name)
			&& schema.equals(other.schema)
		;
	}

	@Override
	public int hashCode() {
		return hashCode;
	}

	public Schema getSchema() {
		return schema;
	}

	public String getName() {
		return name;
	}

	public String getTableType() {
		return tableType;
	}

	private static class GetColumnMapLock {}
	private final GetColumnMapLock getColumnMapLock = new GetColumnMapLock();
	private SortedMap<String,Column> getColumnMapCache;

	private static Integer getInteger(ResultSet results, String columnName) throws SQLException {
		int val = results.getInt(columnName);
		if(results.wasNull()) return null;
		return val;
	}

	/**
	 * Gets all columns for this schema keyed by unique name.
	 *
	 * @see  java.sql.DatabaseMetaData#getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	public SortedMap<String,Column> getColumnMap() throws SQLException {
		synchronized(getColumnMapLock) {
			if(getColumnMapCache==null) {
				SortedMap<String,Column> newColumnMap = new TreeMap<>(DatabaseMetaData.getCollator());
				try (ResultSet results = schema.getCatalog().getMetaData().getMetaData().getColumns(schema.getCatalog().getName(), schema.getName(), name, null)) {
					while(results.next()) {
						Column newColumn = new Column(
							this,
							results.getString("COLUMN_NAME"),
							results.getInt("DATA_TYPE"),
							results.getString("TYPE_NAME"),
							getInteger(results, "COLUMN_SIZE"),
							getInteger(results, "DECIMAL_DIGITS"),
							results.getInt("NULLABLE"),
							results.getString("COLUMN_DEF"),
							getInteger(results, "CHAR_OCTET_LENGTH"),
							results.getInt("ORDINAL_POSITION"),
							results.getString("IS_NULLABLE"),
							results.getString("IS_AUTOINCREMENT")
						);
						if(newColumnMap.put(newColumn.getName(), newColumn)!=null) throw new AssertionError("Duplicate column: "+newColumn);
					}
				}
				getColumnMapCache = AoCollections.optimalUnmodifiableSortedMap(newColumnMap);
			}
			return getColumnMapCache;
		}
	}

	/**
	 * Gets the column of the provided name.
	 *
	 * @throws  NoRowException if the column doesn't exist
	 */
	public Column getColumn(String name) throws NoRowException, SQLException {
		Column column = getColumnMap().get(name);
		if(column==null) throw new NoRowException();
		return column;
	}

	private static class GetColumnsLock {}
	private final GetColumnsLock getColumnsLock = new GetColumnsLock();
	private List<Column> getColumnsCache;

	/**
	 * Gets all columns for this schema in their ordinal position order.
	 * Column with ordinal position one is at index zero.
	 *
	 * @see  #getColumnMap()
	 */
	public List<Column> getColumns() throws SQLException {
		synchronized(getColumnsLock) {
			if(getColumnsCache==null) {
				SortedMap<String,Column> columnMap = getColumnMap();
				List<Column> newColumns = new ArrayList<>(columnMap.size());
				for(int i=0; i<columnMap.size(); i++) newColumns.add(null);
				for(Column column : columnMap.values()) {
					int ordinalPosition = column.getOrdinalPosition();
					if(newColumns.set(ordinalPosition-1, column)!=null) throw new SQLException("Duplicate ordinal position: "+ordinalPosition);
				}
				for(int i=0; i<newColumns.size(); i++) {
					if(newColumns.get(i)==null) throw new SQLException("Missing ordinal position: "+(i+1));
				}
				getColumnsCache = AoCollections.optimalUnmodifiableList(newColumns);
			}
			return getColumnsCache;
		}
	}

	/**
	 * Gets the column of the provided ordinal position, where positions start at one.
	 *
	 * @throws  NoRowException if the column doesn't exist
	 */
	public Column getColumn(int ordinalPosition) throws NoRowException, SQLException {
		try {
			return getColumns().get(ordinalPosition-1);
		} catch(IndexOutOfBoundsException exc) {
			throw new NoRowException(exc);
		}
	}

	private static class GetPrimaryKeyLock {}
	private final GetPrimaryKeyLock getPrimaryKeyLock = new GetPrimaryKeyLock();
	private boolean getPrimaryKeyCached = false;
	private Index getPrimaryKeyCache;

	/**
	 * Gets the primary key for this table or null if not found.
	 */
	public Index getPrimaryKey() throws SQLException {
		synchronized(getPrimaryKeyLock) {
			if(!getPrimaryKeyCached) {
				String pkName = null;
				List<Column> columns = new AutoGrowArrayList<>();
				try (ResultSet results = schema.getCatalog().getMetaData().getMetaData().getPrimaryKeys(schema.getCatalog().getName(), schema.getName(), name)) {
					while(results.next()) {
						String columnName = results.getString("COLUMN_NAME");
						int keySeq = results.getInt("KEY_SEQ");
						String newPkName = results.getString("PK_NAME");
						if(newPkName!=null) {
							if(pkName==null) pkName = newPkName;
							else if(!newPkName.equals(pkName)) throw new SQLException("Mismatched PK_NAME values: "+newPkName+"!="+pkName);
						}
						if(columns.set(keySeq-1, getColumn(columnName))!=null) throw new SQLException("Duplicate key sequence: "+keySeq);
					}
				}
				if(columns.isEmpty()) {
					getPrimaryKeyCache = null;
					getPrimaryKeyCached = true;
				} else {
					// Make sure no gaps in the key sequence
					for(int i=0; i<columns.size(); i++) {
						if(columns.get(i)==null) throw new SQLException("Missing key sequence in index: "+(i+1));
					}
					getPrimaryKeyCache = new Index(this, pkName, IndexType.PRIMARY_KEY, columns);
					getPrimaryKeyCached = true;
				}
			}
			return getPrimaryKeyCache;
		}
	}

	private static class GetImportedTablesLock {}
	private final GetImportedTablesLock getImportedTablesLock = new GetImportedTablesLock();
	private Set<? extends Table> getImportedTablesCache;

	/**
	 * Gets the set of tables that this table depends on.
	 *
	 * This is based on getImportedKeys
	 */
	public Set<? extends Table> getImportedTables() throws SQLException {
		synchronized(getImportedTablesLock) {
			if(getImportedTablesCache==null) {
				Set
 newImportedTables = new LinkedHashSet<>();
				Catalog catalog = schema.getCatalog();
				DatabaseMetaData metaData = catalog.getMetaData();
				try (ResultSet results = schema.getCatalog().getMetaData().getMetaData().getImportedKeys(schema.getCatalog().getName(), schema.getName(), name)) {
					while(results.next()) {
						String pkCat = results.getString("PKTABLE_CAT");
						Catalog pkCatalog = pkCat==null ? catalog : metaData.getCatalog(pkCat);
						newImportedTables.add(
							pkCatalog
							.getSchema(results.getString("PKTABLE_SCHEM"))
							.getTable(results.getString("PKTABLE_NAME"))
						);
					}
				}
				getImportedTablesCache = AoCollections.optimalUnmodifiableSet(newImportedTables);
			}
			return getImportedTablesCache;
		}
	}

	private static class GetExportedTablesLock {}
	private final GetExportedTablesLock getExportedTablesLock = new GetExportedTablesLock();
	private Set<? extends Table> getExportedTablesCache;

	/**
	 * Gets the set of tables that depend on this table.
	 *
	 * This is based on getExportedKeys
	 */
	public Set<? extends Table> getExportedTables() throws SQLException {
		synchronized(getExportedTablesLock) {
			if(getExportedTablesCache==null) {
				Set
 newExportedTables = new LinkedHashSet<>();
				Catalog catalog = schema.getCatalog();
				DatabaseMetaData metaData = catalog.getMetaData();
				try (ResultSet results = schema.getCatalog().getMetaData().getMetaData().getExportedKeys(schema.getCatalog().getName(), schema.getName(), name)) {
					while(results.next()) {
						String fkCat = results.getString("FKTABLE_CAT");
						Catalog fkCatalog = fkCat==null ? catalog : metaData.getCatalog(fkCat);
						newExportedTables.add(
							fkCatalog
							.getSchema(results.getString("FKTABLE_SCHEM"))
							.getTable(results.getString("FKTABLE_NAME"))
						);
					}
				}
				getExportedTablesCache = AoCollections.optimalUnmodifiableSet(newExportedTables);
			}
			return getExportedTablesCache;
		}
	}
}


/*
 * ao-io-buffer - Output buffering library.
 * Copyright (C) 2013, 2014, 2015, 2016, 2019  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-io-buffer.
 *
 * ao-io-buffer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ao-io-buffer 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ao-io-buffer.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.io.buffer;

import com.aoindustries.util.AoArrays;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.ClosedChannelException;
import java.util.logging.Logger;

/**
 * Buffers all writes in segments.  This is to hold references to strings instead
 * of copying all the characters.
 *
 * This class is not thread safe.
 *
 * Future: If writing to another segmented buffer, the segments could be shared between
 * the two instances. (or arraycopy instead of writing each)
 *
 * @author  AO Industries, Inc.
 */
public class SegmentedWriter extends BufferWriter {

	private static final Logger logger = Logger.getLogger(SegmentedWriter.class.getName());

	/**
	 * The number of starting elements in segment arrays.
	 */
	private static final int START_LEN = 16;

	/**
	 * The set of internal types supported.
	 */
	static final byte
		TYPE_STRING = 1,
		TYPE_CHAR_NEWLINE = 2,
		TYPE_CHAR_QUOTE = 3,
		TYPE_CHAR_APOS = 4,
		TYPE_CHAR_OTHER = 5
	;

	/**
	 * Appends the segment with the given offset and length to the given writer.
	 */
	private void writeSegment(int segmentIndex, Writer out) throws IOException {
		switch(segmentTypes[segmentIndex]) {
			case TYPE_STRING :
				out.write((String)segmentValues[segmentIndex], segmentOffsets[segmentIndex], segmentLengths[segmentIndex]);
				break;
			case TYPE_CHAR_NEWLINE :
				assert segmentOffsets[segmentIndex]==0;
				assert segmentLengths[segmentIndex]==1;
				out.write('\n');
				break;
			case TYPE_CHAR_QUOTE :
				assert segmentOffsets[segmentIndex]==0;
				assert segmentLengths[segmentIndex]==1;
				out.write('"');
				break;
			case TYPE_CHAR_APOS :
				assert segmentOffsets[segmentIndex]==0;
				assert segmentLengths[segmentIndex]==1;
				out.write('\'');
				break;
			case TYPE_CHAR_OTHER :
				assert segmentOffsets[segmentIndex]==0;
				assert segmentLengths[segmentIndex]==1;
				out.write((Character)segmentValues[segmentIndex]);
				break;
			default :
				throw new AssertionError();
		}
	}

	/**
	 * The length of the writer is the sum of the length of all its segments.
	 * Once closed, this length will not be modified.
	 */
	private long length;

	/**
	 * The set of segments are maintained in an array.
	 * Once closed, these arrays will not be modified.
	 */
	private byte[] segmentTypes;
	private Object[] segmentValues;
	private int[] segmentOffsets;
	private int[] segmentLengths;
	private int segmentCount;

	/**
	 * Once closed, no further information may be written.
	 * Manipulations are only active once closed.
	 */
	private boolean isClosed;

	public SegmentedWriter() {
		this.length = 0;
		this.segmentTypes = AoArrays.EMPTY_BYTE_ARRAY;
		this.segmentValues = AoArrays.EMPTY_OBJECT_ARRAY;
		this.segmentOffsets = AoArrays.EMPTY_INT_ARRAY;
		this.segmentLengths = AoArrays.EMPTY_INT_ARRAY;
		this.segmentCount = 0;
		this.isClosed = false;
	}

	/**
	 * Adds a new segment.
	 */
	private void addSegment(byte type, Object value, int off, int len) {
		assert !isClosed;
		assert len>0 : "Empty segments should never be added";
		final int arraylen = segmentValues.length;
		if(segmentCount==arraylen) {
			// Need to grow
			if(arraylen==0) {
				this.segmentTypes = new byte[START_LEN];
				this.segmentValues = new Object[START_LEN];
				this.segmentOffsets = new int[START_LEN];
				this.segmentLengths = new int[START_LEN];
			} else {
				// Double capacity and copy
				int newLen = arraylen<<1;
				byte[] newTypes = new byte[newLen];
				System.arraycopy(segmentTypes, 0, newTypes, 0, arraylen);
				this.segmentTypes = newTypes;
				Object[] newValues = new Object[newLen];
				System.arraycopy(segmentValues, 0, newValues, 0, arraylen);
				this.segmentValues = newValues;
				int[] newOffsets = new int[newLen];
				System.arraycopy(segmentOffsets, 0, newOffsets, 0, arraylen);
				this.segmentOffsets = newOffsets;
				int[] newLengths = new int[newLen];
				System.arraycopy(segmentLengths, 0, newLengths, 0, arraylen);
				this.segmentLengths = newLengths;
			}
		}
		segmentTypes[segmentCount] = type;
		segmentValues[segmentCount] = value;
		segmentOffsets[segmentCount] = off;
		segmentLengths[segmentCount++] = len;
	}

	@Override
	public void write(int c) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		char ch = (char)c;
		switch(ch) {
			case '\n' :
				addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
				break;
			case '\'' :
				addSegment(TYPE_CHAR_APOS, null, 0, 1);
				break;
			case '"' :
				addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
				break;
			default :
				addSegment(TYPE_CHAR_OTHER, (Character)ch, 0, 1);
		}
		length++;
	}

	@Override
	public void write(char cbuf[]) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		final int len = cbuf.length;
		if(len>0) {
			if(len==1) {
				write(cbuf[0]);
			} else {
				addSegment(
					TYPE_STRING,
					new String(cbuf),
					0,
					len
				);
				length += len;
			}
		}
	}

	@Override
	public void write(char cbuf[], int off, int len) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		if(len>0) {
			if(len==1) {
				write(cbuf[off]);
			} else {
				addSegment(
					TYPE_STRING,
					new String(cbuf, off, len),
					0,
					len
				);
				length += len;
			}
		}
	}

	@Override
	public void write(String str) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		final int len = str.length();
		if(len>0) {
			if(len==1) {
				// Prefer character shortcuts
				switch(str.charAt(0)) {
					case '\n' :
						addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
						break;
					case '\'' :
						addSegment(TYPE_CHAR_APOS, null, 0, 1);
						break;
					case '"' :
						addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
						break;
					default :
						addSegment(TYPE_STRING, str, 0, 1);
				}
			} else {
				addSegment(TYPE_STRING, str, 0, len);
			}
			length += len;
		}
	}

	@Override
	public void write(String str, int off, int len) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		if(len>0) {
			if(len==1) {
				// Prefer character shortcuts
				switch(str.charAt(off)) {
					case '\n' :
						addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
						break;
					case '\'' :
						addSegment(TYPE_CHAR_APOS, null, 0, 1);
						break;
					case '"' :
						addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
						break;
					default :
						addSegment(TYPE_STRING, str, off, 1);
				}
			} else {
				addSegment(TYPE_STRING, str, off, len);
			}
			length += len;
		}
	}

	@Override
	public SegmentedWriter append(CharSequence csq) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		if(csq==null) {
			write("null");
		} else {
			final int len = csq.length();
			if(len>0) {
				if(len==1) {
					// Prefer character shortcuts
					switch(csq.charAt(0)) {
						case '\n' :
							addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
							break;
						case '\'' :
							addSegment(TYPE_CHAR_APOS, null, 0, 1);
							break;
						case '"' :
							addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
							break;
						default :
							addSegment(TYPE_STRING, csq.toString(), 0, 1);
					}
				} else {
					addSegment(TYPE_STRING, csq.toString(), 0, len);
				}
				length += len;
			}
		}
		return this;
	}

	@Override
	public SegmentedWriter append(CharSequence csq, int start, int end) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		if(csq==null) {
			write("null");
		} else {
			final int len = end-start;
			if(len>0) {
				if(len==1) {
					// Prefer character shortcuts
					char ch = csq.charAt(start);
					switch(ch) {
						case '\n' :
							addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
							break;
						case '\'' :
							addSegment(TYPE_CHAR_APOS, null, 0, 1);
							break;
						case '"' :
							addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
							break;
						default :
							if(
								ch <= 127 // Always cached
								|| !(csq instanceof String) // Use Character for all non-Strings
							) {
								addSegment(
									TYPE_CHAR_OTHER,
									(Character)ch,
									0,
									1
								);
							} else {
								// Use offset for String
								addSegment(
									TYPE_STRING,
									(String)csq,
									start,
									1
								);
							}
					}
				} else {
					if(csq instanceof String) {
						// Use offset for String
						addSegment(
							TYPE_STRING,
							(String)csq,
							start,
							len
						);
					} else {
						// Use subSequence().toString() for all non-Strings
						addSegment(
							TYPE_STRING,
							csq.subSequence(start, end).toString(),
							0,
							len
						);
					}
				}
				length += len;
			}
		}
		return this;
	}

	@Override
	public SegmentedWriter append(char c) throws ClosedChannelException {
		if(isClosed) throw new ClosedChannelException();
		switch(c) {
			case '\n' :
				addSegment(TYPE_CHAR_NEWLINE, null, 0, 1);
				break;
			case '\'' :
				addSegment(TYPE_CHAR_APOS, null, 0, 1);
				break;
			case '"' :
				addSegment(TYPE_CHAR_QUOTE, null, 0, 1);
				break;
			default :
				addSegment(TYPE_CHAR_OTHER, (Character)c, 0, 1);
		}
		length++;
		return this;
	}

	@Override
	public void flush() {
		// Nothing to do
	}

	//private static long biggest = 0;
	@Override
	public void close() {
		isClosed = true;
		/*
		long heap =
			segmentTypes.length * Byte.SIZE
			+ segmentValues.length * 8 // assume 64-bit data for worst-case numbers
			+ segmentOffsets.length * Integer.SIZE
			+ segmentLengths.length * Integer.SIZE
		;
		if(heap>biggest) {
			biggest = heap;
			System.err.println("SegmentedWriter: Biggest heap: " + biggest);
		}*/
	}

	@Override
	public long getLength() {
		return length;
	}

	@Override
	public String toString() {
		return "SegmentedWriter(length=" + length + ", count=" + segmentCount + ", capacity=" + segmentValues.length +")";
	}

	// The result is cached after first created
	private BufferResult result;

	@Override
	public BufferResult getResult() throws IllegalStateException {
		if(!isClosed) throw new IllegalStateException();
		if(result==null) {
			if(length==0) {
				result = EmptyResult.getInstance();
			} else {
				assert segmentCount>0 : "When not empty and using segments, must have at least one segment";
				int endSegmentIndex = segmentCount - 1;
				result = new SegmentedResult(
					segmentTypes,
					segmentValues,
					segmentOffsets,
					segmentLengths,
					0, // start
					0, // startSegmentIndex
					segmentOffsets[0],
					segmentLengths[0],
					length, // end
					endSegmentIndex,
					segmentOffsets[endSegmentIndex],
					segmentLengths[endSegmentIndex]
				);
			}
		}
		return result;
	}
}


/*
 * ao-io-buffer - Output buffering library.
 * Copyright (C) 2013, 2015, 2016  AO Industries, Inc.
 *     support@aoindustries.com
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of ao-io-buffer.
 *
 * ao-io-buffer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ao-io-buffer 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ao-io-buffer.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aoindustries.io.buffer;

import com.aoindustries.util.AtomicSequence;
import com.aoindustries.util.Sequence;
import com.aoindustries.util.WrappedException;
import java.io.IOException;
import java.io.Writer;

/**
 * Logs all write calls in a way that can be put into Java source code.
 * This is used to capture real-world scenarios for unit testing.
 *
 * This class is not thread safe.
 *
 * @author  AO Industries, Inc.
 */
public class LoggingWriter extends BufferWriter {

	private static final Sequence idSeq = new AtomicSequence();

	private final long id = idSeq.getNextSequenceValue();
	private final BufferWriter wrapped;
	private final Writer log;

	public LoggingWriter(BufferWriter wrapped, Writer log) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("] = factory.newBufferWriter();\n");
		log.flush();
		this.wrapped = wrapped;
		this.log = log;
	}

	public long getId() {
		return id;
	}

	/**
	 * Writes a character, unicode escaping as needed.
	 */
	private void log(char ch) throws IOException {
		if(ch=='\t') log.write("'\\t'");
		else if(ch=='\b') log.write("'\\b'");
		else if(ch=='\n') log.write("'\\n'");
		else if(ch=='\r') log.write("'\\r'");
		else if(ch=='\f') log.write("'\\f'");
		else if(ch=='\'') log.write("'\\'");
		else if(ch=='\\') log.write("'\\\\'");
		else if(ch=='"') log.write("'\\\"'");
		else if(ch<' ') {
			log.write("'\\u");
			String hex = Integer.toHexString(ch);
			for(int l=hex.length(); l<4; l++) log.write('0');
			log.write(hex);
			log.write('\'');
		} else {
			log.write('\'');
			log.write(ch);
			log.write('\'');
		}
	}

	/**
	 * Writes a String, unicode escaping as needed.
	 */
	private void log(String value) throws IOException {
		if(value==null) {
			log.write("(String)null");
		} else {
			log.write('"');
			for(int i=0, len=value.length(); i<len; i++) {
				char ch = value.charAt(i);
				if(ch=='\t') log.write("\\t");
				else if(ch=='\b') log.write("\\b");
				else if(ch=='\n') log.write("\\n");
				else if(ch=='\r') log.write("\\r");
				else if(ch=='\f') log.write("\\f");
				else if(ch=='\\') log.write("\\\\");
				else if(ch=='"') log.write("\\\"");
				else if(ch<' ') {
					log.write("\\u");
					String hex = Integer.toHexString(ch);
					for(int l=hex.length(); l<4; l++) log.write('0');
					log.write(hex);
				} else {
					log.write(ch);
				}
			}
			log.write('"');
		}
	}

	@Override
	public void write(int c) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(");
		log.write(Integer.toString(c));
		log.write(");\n");
		log.flush();
		wrapped.write(c);
	}

	@Override
	public void write(char cbuf[]) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(");
		log(new String(cbuf));
		log.write(".toCharArray());\n");
		/*
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(new char[] {");
		for(int i=0, end=cbuf.length; i<end; i++) {
			if((i%50)==0) log.write("\n    ");
			if(i>0) log.write(',');
			log(cbuf[i]);
		}
		log.write("\n});\n");
		*/
		log.flush();
		wrapped.write(cbuf);
	}

	@Override
	public void write(char cbuf[], int off, int len) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(");
		log(new String(cbuf, 0, off+len));
		log.write(".toCharArray(), ");
		log.write(Integer.toString(off));
		log.write(", ");
		log.write(Integer.toString(len));
		log.write(");\n");
		/*
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(new char[] {");
		for(int i=0, end=off+len; i<end; i++) {
			if((i%50)==0) log.write("\n    ");
			if(i>0) log.write(',');
			log(cbuf[i]);
		}
		log.write("\n}, ");
		log.write(Integer.toString(off));
		log.write(", ");
		log.write(Integer.toString(len));
		log.write(");\n");
		*/
		log.flush();
		wrapped.write(cbuf, off, len);
	}

	@Override
	public void write(String str) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(");
		log(str);
		log.write(");\n");
		log.flush();
		wrapped.write(str);
	}

	@Override
	public void write(String str, int off, int len) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].write(");
		log(str);
		log.write(", ");
		log.write(Integer.toString(off));
		log.write(", ");
		log.write(Integer.toString(len));
		log.write(");\n");
		log.flush();
		wrapped.write(str, off, len);
	}

	@Override
	public LoggingWriter append(CharSequence csq) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].append(");
		log(csq==null ? null : csq.toString());
		log.write(");\n");
		log.flush();
		wrapped.append(csq);
		return this;
	}

	@Override
	public LoggingWriter append(CharSequence csq, int start, int end) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].append(");
		log(csq==null ? null : csq.toString());
		log.write(", ");
		log.write(Integer.toString(start));
		log.write(", ");
		log.write(Integer.toString(end));
		log.write(");\n");
		log.flush();
		wrapped.append(csq, start, end);
		return this;
	}

	@Override
	public LoggingWriter append(char c) throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].append(");
		log(c);
		log.write(");\n");
		log.flush();
		wrapped.append(c);
		return this;
	}

	@Override
	public void flush() throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].flush();\n");
		log.flush();
		wrapped.flush();
	}

	@Override
	public void close() throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].close();\n");
		log.flush();
		wrapped.close();
	}

	@Override
	public long getLength() throws IOException {
		log.write("writer[");
		log.write(Long.toString(id));
		log.write("].getLength();\n");
		log.flush();
		return wrapped.getLength();
	}

	@Override
	public String toString() {
		try {
			log.write("writer[");
			log.write(Long.toString(id));
			log.write("].toString();\n");
			log.flush();
		} catch(IOException e) {
			throw new WrappedException(e);
		}
		return "LoggingWriter(" + wrapped.toString() + ")";
	}

	// The result is cached after first created
	private LoggingResult result;

	@Override
	public LoggingResult getResult() throws IllegalStateException, IOException {
		if(result==null) {
			result = new LoggingResult(wrapped.getResult(), log);
			log.write("result[");
			log.write(Long.toString(result.id));
			log.write("] = writer[");
			log.write(Long.toString(id));
			log.write("].getResult();\n");
			log.flush();
		}
		return result;
	}
}


/*
 * Copyright 2013 Harald Wellmann
 *
 * 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.ops4j.pax.shiro.cdi.impl;

import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * Provides access to the current bean manager. If the bean manager cannot be found under the
 * canonical JNDI name (e.g. when the application is not running in a Java EE environment), this
 * class returns a singleton which must be set explicitly by the user with
 * {@link #setBeanManager(BeanManager)}.
 * <p>
 * TODO In CDI 1.1, CDI.current().getBeanManager() should be used instead.
 *
 * @author Harald Wellmann
 *
 */
public class BeanManagerProvider {

    private static BeanManager beanManager;

    /** Hidden constructor. */
    private BeanManagerProvider() {
    }

    /**
     * Looks up the current bean manager in JNDI, or returns the value set by
     * {@link #setBeanManager(BeanManager)} as fallback.
     *
     * @return
     */
    public static BeanManager getBeanManager() {
        try {
            InitialContext initialContext = new InitialContext();
            return (BeanManager) initialContext.lookup("java:comp/BeanManager");
        }
        catch (NamingException e) {
            if (beanManager != null) {
                return beanManager;
            }
            throw new IllegalStateException(
                "BeanManager not found in JNDI and not set via setBeanManager()");
        }
    }

    /**
     * @param beanManager
     *            the beanManager to set
     */
    public static void setBeanManager(BeanManager beanManager) {
        BeanManagerProvider.beanManager = beanManager;
    }

}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  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 javascalautils;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.EitherCompanion.Left;

/**
 * This is a left-biased wrapper for an instance of {@link Either}.
 *
 * @author Peter Nerg
 * @since 1.1
 */
public class LeftProjection<L, R> extends Projection<L> implements Iterable<L>, Serializable {
    private static final long serialVersionUID = 4251047373391313192L;
    private final Either<L, R> either;

    /**
     * Creates an instance of the projection.
     *
     * @param either
     *            The instance to wrap.
     * @since 1.1
     */
    LeftProjection(Either<L, R> either) {
        this.either = either;
    }

    /**
     * Returns false if {@link Right} or returns the result of applying the predicate to the {@link Left} value.
     *
     * @param predicate
     *            The predicate to apply
     * @return If this is a {@link Left} and the predicate matches the value
     * @since 1.1
     */
    public boolean exists(Predicate<L> predicate) {
        return either.isLeft() && predicate.test(orNull());
    }

    /**
     * Returns the value if this is a {@link Left} else the value provided by the supplier.
     *
     * @param supplier
     *            The supplier
     * @return The value
     * @since 1.1
     */
    @Override
    public L getOrElse(Supplier<L> supplier) {
        return either.fold(v -> v, v -> supplier.get());
    }

    /**
     * Returns a {@link Some} wrapping the {@link Either} if it's a {@link Left} and the value of the {@link Left} matches the predicate, else {@link None} .
     *
     * @param predicate
     *            The predicate to apply
     * @return The resulting Option of the filter operation
     * @since 1.1
     */
    public Option<Either<L, R>> filter(Predicate<L> predicate) {
        return Option.apply(exists(predicate) ? either : null);
    }

    /**
     * Returns true if {@link Right} or returns the result of applying the predicate to the {@link Left} value.
     *
     * @param predicate
     *            The predicate to apply
     * @return If it is a match
     * @since 1.1
     */
    public boolean forAll(Predicate<L> predicate) {
        return !either.isLeft() || predicate.test(orNull());
    }

    /**
     * If this projection contains a {@link Left} then a new {@link Left} is returned containing the value from the original {@link Right} mapped via the
     * provided function, else the contained Either is returned as is.
     *
     * @param <T>
     *            The type to return as the new {@link Left}
     * @param function
     *            The function
     * @return Mapped Either
     * @since 1.1
     */
    @SuppressWarnings("unchecked")
    public <T> Either<T, R> map(Function<L, T> function) {
        return either.isLeft() ? Left(function.apply(get())) : (Either<T, R>) either;
    }

    /**
     * If this projection contains a {@link Left} then a new {@link Left} is returned containing the value from the original {@link Right} mapped via the
     * provided function, else the contained Either is returned as is.
     *
     * @param <T>
     *            The type to return as the new {@link Left}
     * @param function
     *            The function
     * @return Mapped Either
     * @since 1.2
     */
    @SuppressWarnings("unchecked")
    public <T> Either<T, R> flatMap(Function<L, Left<T, R>> function) {
        return either.isLeft() ? function.apply(get()) : (Either<T, R>) either;
    }

}


/**
 * Copyright 2015 Peter Nerg
 *
 *  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 javascalautils.concurrent;

import javascalautils.Try;
import javascalautils.Validator;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

import static javascalautils.TryCompanion.*;

/**
 * The Promise implementation.

 * Provides a reusable implementation for safely publishing results from a Promise into a Future.
 *
 * @author Peter Nerg
 * @since 1.2
 */
final class PromiseImpl<T> implements Promise<T> {

    private final FutureImpl<T> future = new FutureImpl<>();
    private final AtomicBoolean completed = new AtomicBoolean(false);

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#future()
     */
    @Override
    public Future<T> future() {
        return future;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#isCompleted()
     */
    @Override
    public boolean isCompleted() {
        return completed.get();
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#success(java.lang.Object)
     */
    @Override
    public void success(T object) {
        complete(Success(object));
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#failure(java.lang.Throwable)
     */
    @Override
    public void failure(Throwable throwable) {
        complete(Failure(throwable));
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#complete(javascalautils.Try)
     */
    @Override
    public void complete(Try<T> result) {
        if (!tryComplete(result)) {
            throw new IllegalStateException("Attempt to complete an already completed Promise");
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#completeWith(javascalautils.concurrent.Future)
     */
    @Override
    public void completeWith(Future<T> result) {
        if (!tryCompleteWith(result)) {
            throw new IllegalStateException("Attempt to complete an already completed Promise");
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#tryComplete(javascalautils.Try)
     */
    @Override
    public boolean tryComplete(Try<T> result) {
        Validator.requireNonNull(result, "Must provide a valid result");
        return tryComplete(future -> future.complete(result));
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#tryCompleteWith(javascalautils.concurrent.Future)
     */
    @Override
    public boolean tryCompleteWith(Future<T> result) {
        return tryComplete(future -> result.onComplete(response -> future.complete(response)));
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#trySuccess(java.lang.Object)
     */
    @Override
    public boolean trySuccess(T result) {
        return tryComplete(Success(result));
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Promise#tryFailure(java.lang.Throwable)
     */
    @Override
    public boolean tryFailure(Throwable throwable) {
        return tryComplete(Failure(throwable));
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.2
     */
    @Override
    public String toString() {
        return "Promise:" + future;
    }

    /**
     * Internal try complete method that takes a consumer to apply the Future this Promise holds.

     * Performs a check if this Promise already has been fulfilled or not.
     *
     * @param c The consumer
     * @return true if completed, false if already completed
     */
    private boolean tryComplete(Consumer<FutureImpl<T>> c) {
        if (completed.compareAndSet(false, true)) {
        	//This Try is only here to manage any unforeseen exception raised by the Future event listener
        	//See issue#41
            Try(() -> c.accept(future));
            return true;
        }
        return false;
    }
}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  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 javascalautils;

import java.util.Objects;

/**
 * Used internally to validate parameters
 *
 * @author Peter Nerg
 * @since 1.3
 */
public final class Validator {

    /**
     * Inhibitive constructor.
     */
    private Validator() {
    }

    /**
     * Checks that the specified object reference is not null and and throws {@link IllegalArgumentException} if it is.

     * Essentially this is very much the same as {@link Objects#requireNonNull(Object)} but throws an {@link IllegalArgumentException} as opposed to the
     * {@link NullPointerException} the SDK class throws.
     *
     * @param <T>
     *            The type of the object
     * @param obj
     *            The object to be validated
     * @return The provided object in case it was non-null
     * @throws IllegalArgumentException
     *             If the provided object was null
     * @since 1.3
     */
    public static <T> T requireNonNull(T obj) {
        return requireNonNull(obj, "Failed validation, the validated parameter was null");
    }

    /**
     * Checks that the specified object reference is not {@code null} and throws a customized {@link IllegalArgumentException} if it is.

     * Essentially this is very much the same as {@link Objects#requireNonNull(Object, String)} but throws an {@link IllegalArgumentException} as opposed to the
     * {@link NullPointerException} the SDK class throws.
     *
     * @param <T>
     *            The type of the object
     * @param obj
     *            The object to be validated
     * @param message
     *            detail message to be used in the event that a {@code IllegalArgumentException} is thrown
     * @return The provided object in case it was non-null
     * @throws IllegalArgumentException
     *             If the provided object was null
     * @since 1.3
     */
    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null) {
            throw new IllegalArgumentException(message);
        }
        return obj;
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  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 javascalautils;

import java.io.Serializable;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.EitherCompanion.Left;
import static javascalautils.EitherCompanion.Right;

/**
 * Represents an empty {@link Option}.

 * The {@link None} is a replacement for null values representing a non-existing value.

 * One can consider {@link None} as a collection of size 0.

 * Instances of None should not be created directly, rather use the factory methods provided on {@link Option}.
 *

 *
{@link Option#empty()}

 *
{@link Option#None()}

 *

 * Since {@link None} anyways cannot represent a value it is preferable to use it as a singleton saving unnecessary instance creation.

 * For examples on usage refer to the documentation for {@link Option}.
 *
 * @author Peter Nerg
 * @since 1.0
 * @param <T>
 *            The type of the value represented by this instance
 */
public final class None<T> extends EmptyContainer<T> implements Option<T>, Serializable {

    private static final long serialVersionUID = -5169653193196761412L;

    /**
     * Always throws {@link NoSuchElementException} since None cannot per definition hold any value.
     *
     * @since 1.0
     */
    @Override
    public T get() {
        throw new NoSuchElementException("None cannot hold values");
    }

    /**
     * Always the value provided by the supplier.
     *
     * @since 1.0
     */
    public T getOrElse(Supplier<T> supplier) {
        return supplier.get();
    }

    /**
     * Always returns false.

     * I.e. the predicate is never used as {@link None} represents nothing/no value.
     *
     * @since 1.0
     */
    public boolean exists(Predicate<T> predicate) {
        return false;
    }

    /**
     * Always returns this.

     * I.e. the function is never used as {@link None} represents nothing/no value.
     *
     * @since 1.0
     */
    @SuppressWarnings("unchecked")
    public <R> Option<R> map(ThrowableFunction1<T, R> function) {
        return (Option<R>) this;
    }

    /**
     * Always returns this.

     * I.e. the function is never used as {@link None} represents nothing/no value.
     *
     * @since 1.2
     * @since 1.0
     */
    @Override
    public <R> Option<R> flatMap(ThrowableFunction1<T, Option<R>> function) {
        // uses the Map method as it anyways produces the same result
        return map(null);
    }

    /**
     * Always the value provided by the supplier.
     *
     * @return The value provided by the supplier
     * @since 1.0
     */
    public Option<T> orElse(Supplier<Option<T>> supplier) {
        return supplier.get();
    }

    /**
     * Returns a {@link Right} containing the value from the provided supplier.
     *
     * @since 1.4
     * @since 1.0
     */
    @Override
    public <R> Either<T, R> toLeft(Supplier<R> right) {
        return Right(right.get());
    }

    /**
     * Returns a {@link Left} containing the value from the provided supplier.
     *
     * @since 1.4
     * @since 1.0
     */
    @Override
    public <L> Either<L, T> toRight(Supplier<L> left) {
        return Left(left.get());
    }

    /**
     * Returns true if other is a {@link None} as {@link None} is stateless comparing it to some other None is therefore always the same,
     * false otherwise.
     *
     * @param other
     *            The other object to compare to
     * @return true if other is {@link None}, false otherwise
     * @since 1.0
     */
    @Override
    public boolean equals(Object other) {
        return other instanceof None;
    }

    /**
     * Always returns 0 as None is stateless and has no value.
     *
     * @return 0
     * @since 1.0
     */
    @Override
    public int hashCode() {
        return 0;
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.0
     */
    @Override
    public String toString() {
        return "None";
    }
}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  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 javascalautils.concurrent;

import javascalautils.*;

/**
 * Acts as a Scala type companion object for {@link Future}.

 * The primary purpose is to get the Scala feel of instantiating classes.

 * In Scala you can define a companion object for a class, acting as a static reference/singleton for that class allowing you do define factory methods.

 * One use case is to define methods with the same name as the class and let these methods invoke the constructor thus creating a nice way to create instances
 * without using the word "new".

 * This can be achieved in java by statically importing a method and then using it.

 * The limitation is that classes may not have method with the same name as the class itself hence new companion classes have to be created.

 * To be able to use it in a neat concise way one needs to statically import the method.

 *
 *

 * import static javascalautils.FutureCompanion.Future;
 *
 * Future<Integer> resultSuccess = Future(() -> 9 / 3); // The Future will at some point contain: Success(3)
 * Future<Integer> resultFailure = Future(() -> 9 / 0); // The Future will at some point contain: Failure(ArithmeticException)
 *
 * //code that performs a side effect but has not return type can be written like this
 * Future<Unit> f = Future(() -> {
 *    database.delete(someId);
 * });
 *

 *
 *

 *
 * @author Peter Nerg
 * @since 1.3
 */
public final class FutureCompanion {
    private FutureCompanion() {
    }

    /**
     * Allows for easy creation of asynchronous computations that will be executed in the future.

     * The purpose is to allow the user to invoke a side-effecting function that may succeed/fail but has now return type.

     * E.g. deleting something from a database may not have an interesting return type. One is only interested of the outcome, {@link Success}/{@link Failure}.

     * The method relays the execution to {@link Future#apply(ThrowableFunction0)}.

     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.concurrent.FutureCompanion.Future;
     *
     * Future<Unit> t = Future(() -> {
     *    database.delete(someId);
     * });
     *

     *

     *
     * @param function
     *            The function to render either the value T or raise an exception.
     * @return The future that will hold either {@link Unit} or an exception.
     * @since 1.9
     * @see Future#apply(ThrowableFunction0)
     */
    public static Future<Unit> Future(VoidFunction0 function) {
    	return Future(() -> {
    		function.apply();
    		return Unit.Instance;
    	});
    }

    /**
     * Allows for easy creation of asynchronous computations that will be executed in the future.

     * The purpose is to allow the user to invoke a side-effecting function that may succeed/fail but has now return type.

     * E.g. deleting something from a database may not have an interesting return type. One is only interested of the outcome, {@link Success}/{@link Failure}.

     * The method relays the execution to {@link Future#apply(ThrowableFunction0)}.

     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.concurrent.FutureCompanion.Future;
     *
     * Future<Unit> t = Future(() -> {
     *    database.delete(someId);
     * }, someExecutor);
     *

     *

     *
     * @param function
     *            The function to render either the value T or raise an exception.
     * @param executor
     *            The executor to use to compute/execute the Future holding the provided function
     * @return The future that will hold either {@link Unit} or an exception.
     * @since 1.9
     * @see Future#apply(ThrowableFunction0)
     */
    public static Future<Unit> Future(VoidFunction0 function, Executor executor) {
    	return Future(() -> {
    		function.apply();
    		return Unit.Instance;
    	}, executor);
    }

    /**
     * Allows for easy creation of asynchronous computations that will be executed in the future.

     * The method relays the execution to {@link Future#apply(ThrowableFunction0)}.

     * Best used in conjunction with statically importing this method.
     *
     *

     *
     *

     * import static javascalautils.concurrent.FutureCompanion.Future;
     *
     * Future<Integer> resultSuccess = Future(() -> 9 / 3); // The Future will at some point contain: Success(3)
     * Future<Integer> resultFailure = Future(() -> 9 / 0); // The Future will at some point contain: Failure(ArithmeticException)
     *

     *
     *

     *
     * @param <T>
     *            The type for the Future
     * @param function
     *            The function to render either the value T or raise an exception.
     * @return The future that will hold the result provided by the function
     * @since 1.3
     * @see Future#apply(ThrowableFunction0)
     */
    public static <T> Future<T> Future(ThrowableFunction0<T> function) {
        return Future.apply(function);
    }

    /**
     * Allows for easy creation of asynchronous computations that will be executed in the future.

     * The method relays the execution to {@link Future#apply(ThrowableFunction0)}.

     * Best used in conjunction with statically importing this method.
     *
     *

     *
     *

     * import static javascalautils.concurrent.FutureCompanion.Future;
     *
     * Future<Integer> resultSuccess = Future(() -> 9 / 3, someExecutor); // The Future will at some point contain: Success(3)
     * Future<Integer> resultFailure = Future(() -> 9 / 0, someExecutor); // The Future will at some point contain: Failure(ArithmeticException)
     *

     *
     *

     *
     * @param <T>
     *            The type for the Future
     * @param function
     *            The function to render either the value T or raise an exception.
     * @param executor
     *            The executor to use to compute/execute the Future holding the provided function
     * @return The future that will hold the result provided by the function
     * @since 1.4
     * @see Future#apply(ThrowableFunction0, Executor)
     */
    public static <T> Future<T> Future(ThrowableFunction0<T> function, Executor executor) {
        return Future.apply(function, executor);
    }

    /**
     * Allows for converting a blocking JDK {@link java.util.concurrent.Future Future} into a non-blocking future.

     * This will use the default executor to wait/block on the provided future.
     *
     * @param <T>
     *            The type for the Future
     * @param future
     *            The blocking future
     * @return The future that will hold the result provided by the future
     * @since 1.5
     */
    public static <T> Future<T> Future(java.util.concurrent.Future<T> future) {
        return Future.apply(() -> future.get());
    }

    /**
     * Allows for converting a blocking JDK {@link java.util.concurrent.Future Future} into a non-blocking future.
     *
     * @param <T>
     *            The type for the Future
     * @param future
     *            The blocking future
     * @param executor
     *            The executor to use to compute/execute the Future holding the provided function
     * @return The future that will hold the result provided by the future
     * @since 1.5
     */
    public static <T> Future<T> Future(java.util.concurrent.Future<T> future, Executor executor) {
        return Future.apply(() -> future.get(), executor);
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  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 javascalautils.concurrent;

import javascalautils.Try;

import java.util.concurrent.ThreadFactory;

/**
 * Factory for creating {@link Executor} instances.
 *
 * @author Peter Nerg
 * @since 1.2
 */
public final class Executors {

    /** The default executor. */
    private static final Executor DefaultExecutor = createDefaultExecutor();

    /**
     * Inhibitive constructor.
     */
    private Executors() {
    }

    /**
     * Get the default thread executor.

     * Unless specified the default one is created from {@link #createCachedThreadPoolExecutor(ThreadFactory)}.
     *
     * @return The default thread executor
     * @since 1.3
     */
    public static Executor getDefault() {
        return DefaultExecutor;
    }

    /**
     * Creates an executor that uses the {@link java.util.concurrent.Executors#newCachedThreadPool(ThreadFactory)}.
     *
     * @param threadFactory
     *            The thread factory to use.
     * @return An instance of this Executor
     * @since 1.2
     */
    public static Executor createCachedThreadPoolExecutor(ThreadFactory threadFactory) {
        return create(java.util.concurrent.Executors.newCachedThreadPool(threadFactory));
    }

    /**
     * Creates an executor that uses the {@link java.util.concurrent.Executors#newFixedThreadPool(int,ThreadFactory)}
     *
     * @param threads
     *            The amount of threads to allow in the thread pool
     * @param threadFactory
     *            The thread factory to use.
     * @return An instance of this Executor
     * @since 1.2
     */
    public static Executor createFixedThreadPoolExecutor(int threads, ThreadFactory threadFactory) {
        return create(java.util.concurrent.Executors.newFixedThreadPool(threads, threadFactory));
    }

    /**
     * Creates an executor that uses the provided Java concurrent executor for the actual job processing
     *
     * @param threadPool
     *            The java.util.Concurrent executor to use as thread pool
     * @return An instance of this Executor
     * @since 1.2
     */
    public static Executor create(java.util.concurrent.Executor threadPool) {
        return new ExecutorImpl(threadPool);
    }

    /**
     * Creates the default {@link Executor} instance.

     * Either by getting the provider class from the system property or using the default.
     *
     * @return The created executor
     */
    private static Executor createDefaultExecutor() {
        Try<Executor> t = ReflectionUtil.newInstance(System.getProperty("javascalautils.concurrent.executorprovider"));
        return t.getOrElse(() -> createCachedThreadPoolExecutor(new NamedSequenceThreadFactory("Executors-Default")));
    }
}