Java tutorial
// Copyright (c) 2009 - 2010, Uwe Finke. All rights reserved. // Subject to BSD License. See "license.txt" distributed with this package. package de.ufinke.cubaja.sql; import java.lang.reflect.Method; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.ufinke.cubaja.cafebabe.CodeAttribute; import de.ufinke.cubaja.cafebabe.GenClass; import de.ufinke.cubaja.cafebabe.GenMethod; import de.ufinke.cubaja.cafebabe.Generator; import de.ufinke.cubaja.cafebabe.Loader; import de.ufinke.cubaja.cafebabe.Type; import de.ufinke.cubaja.util.Text; import de.ufinke.cubaja.util.Util; import de.ufinke.cubaja.util.WarnMode; class ObjectFactoryGenerator implements Generator { static private class SearchEntry { String name; int position; int sqlType; boolean setterFound; SearchEntry(String name, int position, int sqlType) { this.name = name; this.position = position; this.sqlType = sqlType; setterFound = false; } } static private class SetterEntry { String name; ObjectFactoryType type; int position; SetterEntry(String methodName, ObjectFactoryType parameterType, int position) { this.name = methodName; this.type = parameterType; this.position = position; } } static private final Text text = Text.getPackageInstance(ObjectFactoryGenerator.class); static private final Log logger = LogFactory.getLog(ObjectFactoryGenerator.class); static private final Type objectFactoryType = new Type(ObjectFactory.class); static private final Type queryType = new Type(Query.class); static private final Type sqlExceptionType = new Type(SQLException.class); private Type dataClassType; private int singleColumnSqlType; private ObjectFactoryType builtin; private Map<String, SearchEntry> searchMap; private Map<String, SetterEntry> setterMap; private Map<Class<?>, ObjectFactory> factoryMap; ObjectFactoryGenerator(ResultSetMetaData metaData) throws SQLException { createSearchMap(metaData); factoryMap = new HashMap<Class<?>, ObjectFactory>(); } ObjectFactory getFactory(Class<?> dataClass, WarnMode warnMode) throws Exception { ObjectFactory factory = factoryMap.get(dataClass); if (factory != null) { return factory; } if (dataClass.isPrimitive()) { throw new SQLException(text.get("primitive")); } dataClassType = new Type(dataClass); builtin = null; if (singleColumnSqlType != 0) { TypeCombination combination = new TypeCombination(singleColumnSqlType, dataClass); builtin = ObjectFactoryType.getType(combination); } if (builtin == null) { createSetterMap(dataClass); checkSetterMap(dataClass, warnMode); } Class<?> factoryClass = Loader.createClass(this, "QueryObjectFactory", dataClass); factory = (ObjectFactory) factoryClass.newInstance(); factoryMap.put(dataClass, factory); setterMap = null; return factory; } public GenClass generate(String className) throws Exception { GenClass genClass = new GenClass(ACC_PUBLIC | ACC_FINAL, className, Type.OBJECT, objectFactoryType); genClass.createDefaultConstructor(); GenMethod method = genClass.createMethod(ACC_PUBLIC, Type.OBJECT, "createObject", queryType); method.addException(sqlExceptionType); if (builtin == null) { generateCode(method.getCode()); } else { generateBuiltin(method.getCode()); } return genClass; } private void generateCode(CodeAttribute code) { code.newObject(dataClassType); code.duplicate(); // required for setter or return code.invokeSpecial(dataClassType, Type.VOID, "<init>"); for (SetterEntry setter : setterMap.values()) { ObjectFactoryType type = setter.type; Type parmType = type.getType(); code.duplicate(); code.loadLocalReference(1); code.loadConstant(setter.position); code.invokeVirtual(queryType, parmType, type.getReaderMethod(), Type.INT); code.invokeVirtual(dataClassType, Type.VOID, setter.name, parmType); // operates on duplicated data object } code.returnReference(); // returns duplicated data object } private void generateBuiltin(CodeAttribute code) { code.loadLocalReference(1); // query code.loadConstant(1); // column #1 code.invokeVirtual(queryType, dataClassType, builtin.getReaderMethod(), Type.INT); code.returnReference(); } private void createSearchMap(ResultSetMetaData metaData) throws SQLException { int size = metaData.getColumnCount(); searchMap = new HashMap<String, SearchEntry>(); for (int i = 1; i <= size; i++) { String name = metaData.getColumnName(i).toLowerCase(); SearchEntry entry = new SearchEntry(name, i, metaData.getColumnType(i)); searchMap.put(Util.createMethodName(name, "set"), entry); } if (searchMap.size() == 1) { singleColumnSqlType = metaData.getColumnType(1); } } private void createSetterMap(Class<?> clazz) { setterMap = new HashMap<String, SetterEntry>(); for (Method method : clazz.getMethods()) { if (method.getReturnType() == Void.TYPE) { String methodName = method.getName(); SearchEntry searchEntry = searchMap.get(methodName); if (searchEntry != null && method.getReturnType() == Void.TYPE) { int position = searchEntry.position; Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { TypeCombination combination = new TypeCombination(searchEntry.sqlType, parameterTypes[0]); ObjectFactoryType type = ObjectFactoryType.getType(combination); if (type != null) { SetterEntry entry = setterMap.get(methodName); if (entry == null || type.getPriority() < entry.type.getPriority()) { setterMap.put(methodName, new SetterEntry(methodName, type, position)); searchEntry.setterFound = true; } } } } } } } private void checkSetterMap(Class<?> clazz, WarnMode warnMode) throws SQLException { if (warnMode == WarnMode.IGNORE) { return; } for (SearchEntry entry : searchMap.values()) { if (!entry.setterFound) { switch (warnMode) { case WARN: logger.warn(text.get("noSetter", clazz.getName(), entry.name)); break; case ERROR: throw new SQLException(text.get("noSetter", clazz.getName(), entry.name)); case IGNORE: break; } } } } }