package io.lumify.palantir.sqlrunner; import org.apache.commons.io.IOUtils; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.sql.Blob; import java.sql.Clob; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class SqlRunnerQueryIterable<T> implements Iterable<T> { private final ResultSet rs; private final Class<T> clazz; private final Map<String, Method> setters = new HashMap<>(); private final Map<String, Method> columnNameToSetter = new HashMap<>(); private Method[] columnSetters; private int columnCount; public SqlRunnerQueryIterable(ResultSet rs, Class<T> clazz) throws SQLException { this.rs = rs; this.clazz = clazz; for (Method m : clazz.getMethods()) { if ((m.getName().startsWith("set") || m.getName().startsWith("is")) && m.getParameterTypes().length == 1) { setters.put(m.getName().substring("set".length()), m); } } columnCount = rs.getMetaData().getColumnCount(); columnSetters = new Method[columnCount]; for (int i = 1; i <= columnCount; i++) { columnSetters[i - 1] = getSetter(rs.getMetaData().getColumnName(i)); } } @Override public Iterator<T> iterator() { return new Iterator<T>() { private T next; private T current; @Override public boolean hasNext() { loadNext(); return next != null; } @Override public T next() { loadNext(); if (this.next == null) { throw new IllegalStateException("iterable doesn't have a next element"); } this.current = this.next; this.next = null; return this.current; } private void loadNext() { if (this.next != null) { return; } try { if (rs.next()) { this.next = toClass(rs, clazz); } } catch (Exception e) { throw new RuntimeException("Could not fetch next", e); } } @Override public void remove() { throw new RuntimeException("not supported"); } }; } private T toClass(ResultSet rs, Class<T> clazz) throws Exception { T obj = clazz.newInstance(); for (int i = 1; i <= columnCount; i++) { Method setter = columnSetters[i - 1]; Object columnValue = rs.getObject(i); setClassProperty(obj, setter, columnValue); } return obj; } private void setClassProperty(T obj, Method setter, Object columnValue) throws InvocationTargetException, IllegalAccessException { try { Class<?> parameterType = setter.getParameterTypes()[0]; if (columnValue instanceof BigDecimal && (parameterType == long.class || parameterType == Long.class)) { columnValue = ((BigDecimal) columnValue).longValue(); } else if (columnValue instanceof BigDecimal && parameterType == boolean.class) { BigDecimal bd = (BigDecimal) columnValue; if (bd.longValue() == 0) { columnValue = false; } else if (bd.longValue() == 1) { columnValue = true; } } else if (columnValue instanceof Clob && parameterType == String.class) { Clob clob = (Clob) columnValue; columnValue = IOUtils.toString(clob.getAsciiStream()); } else if (columnValue instanceof Blob && parameterType == byte[].class) { Blob blob = (Blob) columnValue; columnValue = IOUtils.toByteArray(blob.getBinaryStream()); } else if (columnValue instanceof Blob && parameterType == InputStream.class) { Blob blob = (Blob) columnValue; columnValue = blob.getBinaryStream(); } setter.invoke(obj, columnValue); } catch (Throwable ex) { throw new RuntimeException("Could not call setter " + setter + " on obj " + obj + " with value " + columnValue + " (" + (columnValue == null ? "null" : columnValue.getClass().getName()) + ")", ex); } } private Method getSetter(String columnName) { Method setter = columnNameToSetter.get(columnName); if (setter != null) { return setter; } String setterName = toSetterName(columnName); setter = setters.get(setterName); if (setter != null) { columnNameToSetter.put(columnName, setter); return setter; } throw new RuntimeException("Could not find setter on " + clazz.getName() + " for column name " + columnName); } private String toSetterName(String columnName) { StringBuilder result = new StringBuilder(); columnName = columnName.toLowerCase(); for (int i = 0; i < columnName.length(); i++) { char ch = columnName.charAt(i); if (ch == '_') { i++; ch = Character.toUpperCase(columnName.charAt(i)); result.append(ch); } else { if (i == 0) { ch = Character.toUpperCase(ch); } result.append(ch); } } return result.toString(); } }