All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.gkutiel.flip.db.FlipDB Maven / Gradle / Ivy

package com.github.gkutiel.flip.db;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;

public class FlipDB {
	static class Magic implements InvocationHandler {
		private final List pool = new LinkedList<>();
		private final String url;

		public Magic(final String url) {
			this.url = url;
		}

		private Object buildReturnType(final Class returnType, final ResultSet rs) throws SQLException {
			if (returnType.equals(ResultSet.class)) return rs;
			if (returnType.isArray()) {
				final Class componentType = returnType.getComponentType();
				final List result = new ArrayList<>();
				while (rs.next())
					result.add(this.objectFromResultSet(componentType, rs));
				return result.toArray((Object[]) Array.newInstance(componentType, result.size()));
			}
			return this.objectFromResultSet(returnType, rs);
		}

		private PreparedStatement buildStmt(final PreparedStatement ps, final Object[] args) {
			if (args == null) return ps;
			for (int i = 0; i < args.length; i++) {
				final int index = i + 1;
				accept(args[i], new ObjectVisitor() {

					@Override
					public void visit(final byte[] bs) throws SQLException {
						ps.setBinaryStream(index, new ByteInputStream(bs, bs.length));
					}

					@Override
					public void visit(final Double d) throws SQLException {
						ps.setDouble(index, d);
					}

					@Override
					public void visit(final Integer i) throws SQLException {
						ps.setInt(index, i);
					}

					@Override
					public void visit(final Long l) throws SQLException {
						ps.setLong(index, l);
					}

					@Override
					public void visit(final String s) throws SQLException {
						ps.setString(index, s);
					}
				});
			}
			return ps;
		}

		private Connection getConnection() throws SQLException {
			synchronized (this.pool) {
				if (this.pool.isEmpty()) return DriverManager.getConnection(this.url);
				return this.pool.remove(0);
			}
		}

		@Override
		public Object invoke(final Object proxy, final Method m, final Object[] args) {
			Connection con = null;
			try {
				con = this.getConnection();
				final PreparedStatement ps = this.buildStmt(con.prepareStatement(m.getAnnotation(Query.class).value()), args);
				final Class returnType = m.getReturnType();
				if (returnType.equals(void.class)) {
					ps.executeUpdate();
					return null;
				}
				final ResultSet rs = ps.executeQuery();
				return this.buildReturnType(returnType, rs);
			} catch (final SQLException e) {
				throw new RuntimeException(e);
			} finally {
				this.releseConnection(con);
			}
		}

		private Object objectFromResultSet(final Class returnType, final ResultSet rs) {
			try {
				final Object obj = returnType.newInstance();
				final Field[] fields = returnType.getDeclaredFields();
				AccessibleObject.setAccessible(fields, true);
				for (final Field field : fields)
					try {
						field.set(obj, rs.getObject(field.getName()));
					} catch (final SQLException e) {}

				return obj;
			} catch (InstantiationException | IllegalAccessException | SecurityException | IllegalArgumentException e) {
				throw new RuntimeException(e);
			}
		}

		private void releseConnection(final Connection con) {
			synchronized (this.pool) {
				this.pool.add(con);
			}
		}
	}

	interface ObjectVisitor {

		void visit(byte[] obj) throws SQLException;

		void visit(Double d) throws SQLException;

		void visit(Integer i) throws SQLException;

		void visit(Long l) throws SQLException;

		void visit(String s) throws SQLException;

	}

	static final Map, String> JAVA_TO_SQL = new HashMap, String>() {
		{
			this.put(int.class, "INTEGER");
			this.put(Integer.class, "INTEGER");
			this.put(long.class, "BIGINT");
			this.put(Long.class, "BIGINT");
			this.put(double.class, "DOUBLE");
			this.put(Double.class, "DOUBLE");
			this.put(String.class, "VARCHAR");
			this.put(byte[].class, "BLOB");
		}
	};

	static void accept(final Object obj, final ObjectVisitor visitor) {
		try {
			if (obj instanceof Integer) visitor.visit((Integer) obj);
			else if (obj instanceof Long) visitor.visit((Long) obj);
			else if (obj instanceof Double) visitor.visit((Double) obj);
			else if (obj instanceof String) visitor.visit((String) obj);
			else if (obj instanceof byte[]) visitor.visit((byte[]) obj);
			else throw new UnsupportedOperationException("Unsupported object type: " + obj.getClass());
		} catch (final SQLException e) {
			throw new RuntimeException(e);
		}
	}

	private final String url;

	public FlipDB(@SuppressWarnings("unused") final Driver driver, final String url) {
		this.url = url;
	}

	@SafeVarargs
	final public FlipDB createTables(final Class... tables) {
		try {
			Connection con = null;
			try {
				con = DriverManager.getConnection(this.url);
				for (final Class table : tables) {
					con.createStatement().execute(SqlUtils.createTableSql(table));
					SqlUtils.createIndexes(con, table);
				}
			} finally {
				if (con != null) con.close();
			}
		} catch (final SQLException e) {
			throw new RuntimeException(e);
		}
		return this;
	}

	@SuppressWarnings("unchecked")
	public  T getImplementation(final Class forInterface) {
		return (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] { forInterface }, new Magic(this.url));
	}

}