	private void generate(String schema, String table, JSONObject columns, JSONArray pks,
			List<FK> fks, List<FK> fksIn, String dataSourceName, String callbackPackage,
			JSONObject enums)
	throws IOException, JSONException {
		String className = genTableClassName(table);
		Set<String> pkSet = new HashSet<String>();
		if (pks == null) pks = new JSONArray();
		for (int i=0; i<pks.length(); ++i) {
			pkSet.add(pks.getString(i));
		}
		int fieldCount = columns.keySet().size();
		String pkgDir = Misc.join("/", pkg.split("[.]"));
		new File(Misc.join("/", dir, pkgDir, schema)).mkdirs();
		File file = new File(Misc.join("/", dir, pkgDir, schema, className+".java"));
		System.out.println("writing: "+ file.getAbsolutePath());
		BufferedWriter br = new BufferedWriter(new FileWriter(file));
		br.write("package "+ pkg +"."+ schema +";\n\n");
		br.write("import java.lang.reflect.Method;\n");
		br.write("import java.lang.reflect.InvocationTargetException;\n");
		br.write("import java.sql.SQLException;\n");
		br.write("import javax.sql.DataSource;\n");
		br.write("import java.util.Map;\n\n");
		br.write("import java.util.HashMap;\n\n");
		br.write("import org.nosco.Field;\n");
		br.write("import org.nosco.Query;\n");
		br.write("import org.nosco.QueryFactory;\n");
		br.write("import org.nosco.Table;\n");
		br.write("\n");
		br.write("public class "+ className +" extends Table implements Comparable<"+ className +"> {\n\n");
		int index = 0;
		for (String column : columns.keySet()) {
			br.write("\tpublic static final Field<");
			br.write(getFieldType(schema, table, column, columns.getString(column)));
			br.write("> "+ getFieldName(column));
			br.write(" = new Field<"+ getFieldType(schema, table, column, columns.getString(column)));
			br.write(">("+ index +", "+ className +".class, \""+ column);
			br.write("\", "+ getFieldType(schema, table, column, columns.getString(column)) +".class");
			br.write(");\n");
			++index;
		}
		br.write("\n");
		br.write("\tpublic static Field.PK<"+ className +"> PK = new Field.PK<"+ className +">(");
		for (int i=0; i<pks.length(); ++i) {
			br.write(pks.getString(i).toUpperCase());
			if (i<pks.length()-1) br.write(", ");
		}
		br.write(");\n");
		br.write("\tpublic org.nosco.Field.PK PK() { return PK; }\n\n");
		for (FK fk : fks) {
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
			}
			String fkName = genFKName(fk.columns.keySet(), referencedTable);
			br.write("\tpublic static final Field.FK<"+ referencedTableClassName +"> FK_"+ fkName);
			br.write(" = new Field.FK<"+ referencedTableClassName +">("+ index +", "+ className +".class, ");
			br.write(referencedTableClassName +".class");
			for (Entry<String, String> e : fk.columns.entrySet()) {
				br.write(", "+ e.getKey().toUpperCase());
			}
			for (Entry<String, String> e : fk.columns.entrySet()) {
				br.write(", "+ referencedTableClassName +".");
				br.write(e.getValue().toUpperCase());
			}
			br.write(");\n");
			++index;
		}
		br.write("\n");
		for (String column : columns.keySet()) {
			String enumKey = schema +"."+ table +"."+ column;
			if (!enums.has(enumKey)) continue;
			JSONObject instances = enums.optJSONObject(enumKey);
			boolean simple = pkSet.size() == 1;
			if (simple) {
				String pk = pkSet.iterator().next();
				String pkType = getFieldType(schema, table, pk, columns.getString(pk));
				br.write("\tpublic enum PKS implements Table.__SimplePrimaryKey<"+ className +", "+ pkType +"> {\n\n");
				int count = 0;
				for (String name : instances.keySet()) {
					++ count;
					String value = instances.optJSONArray(name).getString(0);
					if ("java.lang.String".equals(pkType)) value = "\""+ value +"\"";
					br.write("\t\t"+ name.toUpperCase().replaceAll("\\W", "_"));
					br.write("("+ value +")");
					if (count < instances.keySet().size()) br.write(",\n");
				}
				br.write(";\n\n");
				br.write("\t\tpublic final "+ pkType +" "+ getFieldName(pk) +";\n");
				br.write("\t\tPKS("+ pkType +" v) {\n");
				br.write("\t\t\t"+ getFieldName(pk) +" = v;\n");
				br.write("\t\t}\n");
				br.write("\t\t@SuppressWarnings(\"unchecked\")\n");
				br.write("\t\t@Override\n");
				br.write("\t\tpublic <R> R get(Field<R> field) {\n");
				br.write("\t\t\tif (field=="+ className +"."+ getFieldName(pk)
						+") return (R) Integer.valueOf("+ getFieldName(pk) +"); \n");
				br.write("\t\t\tif ("+ className +"."+ getFieldName(pk)
						+".sameField(field)) return (R) Integer.valueOf("
						+ getFieldName(pk) +");\n");
				br.write("\t\t\tthrow new RuntimeException(\"field \"+ field +\" is not part of this primary key\");\n");
				br.write("\t\t}\n");
				br.write("\t\t@Override\n");
				br.write("\t\tpublic "+ pkType +" value() {\n");
				br.write("\t\t\treturn "+ getFieldName(pk)+ ";\n");
				br.write("\t\t}\n\n");
				br.write("\t}\n");
			}
		}
		for (String column : columns.keySet()) {
			br.write("\tprivate "+ getFieldType(schema, table, column, columns.getString(column)));
			br.write(" "+ getInstanceFieldName(column) + " = null;\n");
		}
		br.write("\n");
		br.write("\tpublic "+ className +"() {}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tprotected "+ className +"(Field[] fields, Object[] objects, int start, int end) {\n");
		br.write("\t\tif (fields.length != objects.length)\n\t\t\tthrow new IllegalArgumentException(");
		br.write("\"fields.length != objects.length => \"+ fields.length +\" != \"+ objects.length");
		br.write(" +\"\");\n");
		br.write("\t\tfor (int i=start; i<end; ++i) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\t\tif (fields[i]=="+ getFieldName(column) +") {\n");
			br.write("\t\t\t\t"+ getInstanceFieldName(column) +" = ");
			String assignment = convertToActualType(schema, table, column,
					columns.getString(column),
					"("+ getFieldClassType(columns.getString(column)).getName()+ ") objects[i]");
			br.write(assignment);
			br.write(";\n");
			br.write("\t\t\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t\t\tcontinue;\n");
			br.write("\t\t\t}\n");
		}
		br.write("\t\t}\n\t}\n\n");
		br.write("\tpublic String SCHEMA_NAME() {\n\t\treturn \""+ schema +"\";\n\t}\n\n");
		br.write("\tpublic String TABLE_NAME() {\n\t\treturn \""+ table +"\";\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic Field[] FIELDS() {\n\t\tField[] fields = {");
		for (String column : columns.keySet()) {
			br.write(getFieldName(column)+",");
		}
		br.write("};\n\t\treturn fields;\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic Field.FK[] FKS() {\n\t\tField.FK[] fields = {");
		for (FK fk : fks) {
			String referencedTable = fk.reffed[1];
			br.write("FK_" + genFKName(fk.columns.keySet(), referencedTable) + ",");
		}
		br.write("};\n\t\treturn fields;\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"unchecked\")\n");
		br.write("\tpublic <S> S get(Field<S> _field) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (_field=="+ getFieldName(column) +") ");
			br.write("return (S) "+ getInstanceFieldName(column) +";\n");
		}
		br.write("\t\tthrow new IllegalArgumentException(\"unknown field \"+ _field);\n");
		br.write("\t}\n\n");
		br.write("\tpublic <S> void set(Field<S> _field, S _value) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (_field=="+ getFieldName(column) +") ");
			br.write(getInstanceFieldName(column) +" = ("+ getFieldType(schema, table, column, columns.getString(column)) +") _value;\n");
		}
		br.write("\t\tthrow new IllegalArgumentException(\"unknown field \"+ _field);\n");
		br.write("\t}\n\n");
		br.write("\tpublic static final Query<"+ className +"> ALL = QueryFactory.IT.getQuery("
		+ className +".class).use("+ pkg +"."+ dataSourceName +"."+ schema.toUpperCase() +");\n\n");
		br.write("\t public String toString() {\n");
		br.write("\t\treturn \"["+ className);
		for (int i=0; i<pks.length(); ++i) {
			String pk = pks.getString(i);
			br.write(" "+ pk+":");
			br.write("\"+"+ getInstanceFieldName(pk));
			br.write("+\"");
		}
		for (String column : columns.keySet()) {
			if (!"name".equalsIgnoreCase(column)) continue;
			br.write(" "+ column +":");
			br.write("\"+"+ getInstanceFieldName(column));
			br.write("+\"");
		}
		br.write("]\";\n");
		br.write("\t}\n\n");
		br.write("\t public String toStringDetailed() {\n");
		br.write("\t\treturn \"["+ className);
		for (String column : columns.keySet()) {
			br.write(" "+ column +":");
			br.write("\"+"+ getInstanceFieldName(column));
			br.write("+\"");
		}
		br.write("]\";\n");
		br.write("\t}\n\n");
		for (String column : columns.keySet()) {
			String cls = getFieldType(schema, table, column, columns.getString(column));
			br.write("\tpublic "+ cls +" get"+ getInstanceMethodName(column) +"() {\n");
			br.write("\t\tif (!__NOSCO_FETCHED_VALUES.get("+ getFieldName(column) +".INDEX)) {\n");
			br.write("\t\t\t"+ className +" _tmp = ALL.onlyFields(");
			br.write(getFieldName(column)+")");
			for (String pk : pkSet) {
				br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
			}
			br.write(".getTheOnly();\n");
			br.write("\t\t\t"+ getInstanceFieldName(column) +" = _tmp == null ? null : _tmp.get"
					+ getInstanceMethodName(column) +"();");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t}\n");
			br.write("\t\treturn "+ getInstanceFieldName(column) +";\n\t}\n\n");
			br.write("\tpublic "+ className +" set"+ getInstanceMethodName(column));
			br.write("("+ cls +" v) {\n");
			br.write("\t\t"+ getInstanceFieldName(column) +" = v;\n");
			br.write("\t\tif (__NOSCO_UPDATED_VALUES == null) __NOSCO_UPDATED_VALUES = new java.util.BitSet();\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\treturn this;\n");
			br.write("\t}\n\n");
			if (!cls.equals(getFieldClassType(columns.getString(column)).getName())) {
				br.write("\tpublic "+ className +" set"+ getInstanceMethodName(column));
				br.write("("+ getFieldClassType(columns.getString(column)).getName() +" v) {\n");
				br.write("\t\treturn set"+ getInstanceMethodName(column) +"("+
						this.convertToActualType(schema, table, column,
								columns.getString(column), "v") +");\n");
				br.write("\t}\n\n");
			}
		}
		for (FK fk : fks) {
			String referencedSchema = fk.reffed[0];
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
		}
			String methodName = genFKMethodName(fk.columns.keySet(), referencedTable);
			String cachedObjectName = "_NOSCO_FK_"+ underscoreToCamelCase(fk.columns.keySet(), false);
			br.write("\tprivate "+ referencedTableClassName +" "+ cachedObjectName +" = null;\n\n");
			br.write("\tpublic "+ referencedTableClassName +" get"+ methodName +"() {\n");
			br.write("\t\tif (!__NOSCO_FETCHED_VALUES.get(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX)) {\n");
			br.write("\t\t\t"+ cachedObjectName +" = "+ referencedTableClassName +".ALL");
			br.write(".where("+ referencedTableClassName +"."+ getFieldName(fk.columns.values()) +".eq("+ underscoreToCamelCase(fk.columns.keySet(), false) +"))");
			br.write(".getTheOnly();\n");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\t}\n");
			br.write("\t\treturn "+ cachedObjectName +";\n\t}\n\n");
			br.write("\tpublic "+ className +" set"+ methodName +"("+ referencedTableClassName +" v) {\n");
			br.write("\t\t"+ underscoreToCamelCase(fk.columns.keySet(), false) +" = v.get"+ underscoreToCamelCase(fk.columns.values(), true) +"();\n");
			br.write("\t\tif (__NOSCO_UPDATED_VALUES == null) __NOSCO_UPDATED_VALUES = new java.util.BitSet();\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set("+ getFieldName(fk.columns.keySet()) +".INDEX);\n");
			br.write("\t\t"+ cachedObjectName +" = v;\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\treturn this;\n");
			br.write("\n\t}\n\n");
		}
		br.write("\tprotected void SET_FK(Field.FK<?> field, Object v) {\n");
		br.write("\t\tif (false);\n");
		for (FK fk : fks) {
			String cachedObjectName = "_NOSCO_FK_"+ underscoreToCamelCase(fk.columns.keySet(), false);
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
		}
			br.write("\t\telse if (field == FK_"+ genFKName(fk.columns.keySet(), referencedTable) +") {\n");
			br.write("\t\t\t"+ cachedObjectName +" = ("+ referencedTableClassName +") v;\n");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\t}\n");
		}
		br.write("\t\telse {throw new RuntimeException(\"unknown FK\");}\n");
		br.write("\t}\n\n");
		br.write("\t@SuppressWarnings(\"unchecked\")\n");
		br.write("\tprotected void SET_FK_SET(Field.FK<?> fk, Query<?> v) {\n");
		br.write("\t\tif (false);\n");
		for (FK fk : fksIn) {
		    String relatedSchema = fk.reffing[0];
		    String relatedTable = fk.reffing[1];
		    String relatedTableClassName = this.genTableClassName(relatedTable);
			if (!schema.equals(relatedSchema)) {
				relatedTableClassName = pkg +"."+ relatedSchema +"."+ relatedTableClassName;
			}
			String fkName = "FK_"+ genFKName(fk.columns.keySet(), fk.reffed[1]);
		    String localVar = "__NOSCO_CACHED_FK_SET___"+ relatedTable + "___"
		    		+ Misc.join("__", fk.columns.keySet());
		    br.write("\t\telse if ("+ relatedTableClassName +"."+ fkName +".equals(fk)) {\n");
		    br.write("\t\t\t"+ localVar +" = (Query<"+ relatedTableClassName +">) v;\n");
		    br.write("\t\t}\n");
		}
		br.write("\t\telse {throw new RuntimeException(\"unknown FK\");}\n");
		br.write("\t}\n\n");
		Map<String, Integer> reffingCounts = new HashMap<String,Integer>();
		for (FK fk : fksIn) {
		    String relatedTable = fk.reffing[1];
		    Integer c = reffingCounts.get(relatedTable);
		    if (c == null) c = 0;
		    reffingCounts.put(relatedTable, c+1);
		}
		for (FK fk : fksIn) {
		    String relatedSchema = fk.reffing[0];
		    String relatedTable = fk.reffing[1];
		    String relatedTableClassName = this.genTableClassName(relatedTable);
			if (!schema.equals(relatedSchema)) {
				relatedTableClassName = pkg +"."+ relatedSchema +"."+ relatedTableClassName;
			}
		    String method = getInstanceMethodName(relatedTable);
		    if (reffingCounts.get(relatedTable) > 1) {
			    String tmp = Misc.join("_", fk.columns.keySet());
				method = method + "_" + getInstanceMethodName(tmp);
		    }
		    String localVar = "__NOSCO_CACHED_FK_SET___"+ relatedTable + "___"
		    		+ Misc.join("__", fk.columns.keySet());
		    br.write("\tprivate Query<"+ relatedTableClassName +"> "+ localVar +" = null;\n");
		    br.write("\tpublic Query<"+ relatedTableClassName +"> get"+ method +"Set() {\n");
		    br.write("\t\tif ("+ localVar +" != null) return "+ localVar + ";\n");
		    br.write("\t\telse return "+ relatedTableClassName +".ALL");
		    for (Entry<String, String> e : fk.columns.entrySet()) {
			String relatedColumn = e.getKey();
			String column = e.getValue();
			br.write(".where("+ relatedTableClassName +"."+ getFieldName(relatedColumn) +".eq(get"+ getInstanceMethodName(column) +"()))");
		    }
		    br.write(";\n");
		    br.write("\t}\n\n");
		}
		br.write("\tpublic boolean save() throws SQLException {\n");
		br.write("\t\t return save(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean save(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		br.write(";\n");
		if (pkSet == null || pkSet.isEmpty()) {
			br.write("\t\tthrow new RuntimeException(\"save() is ambiguous on objects without PKs - use insert() or update()\");\n");
		} else {
			br.write("\t\tint size = query.size();\n");
			br.write("\t\tif (size == 0) return this.insert(ds);\n");
			br.write("\t\telse if (size == 1) return this.update(ds);\n");
			br.write("\t\telse throw new RuntimeException(\"more than one result was returned " +
					"for a query that specified all the PKs.  this is bad.\");\n");
			br.write("\t\t\n");
		}
		br.write("\t}\n");
		br.write("\tpublic boolean update() throws SQLException {\n");
		br.write("\t\t return update(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean update(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		if (pkSet == null || pkSet.size() == 0) {
			for (String column : columns.keySet()) {
				br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
			}
		}
		br.write(";\n");
		br.write("\t\tif (__NOSCO_CALLBACK_UPDATE_PRE!=null) "
				+ "try { __NOSCO_CALLBACK_UPDATE_PRE.invoke(null, this); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\tMap<Field<?>,Object> updates = new HashMap<Field<?>,Object>();\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (__NOSCO_UPDATED_VALUES.get("+ getFieldName(column) +".INDEX)) {\n");
			br.write("\t\t\tupdates.put("+ getFieldName(column) +", "
			+ convertToOriginalType(schema, table, column, columns.getString(column), getInstanceFieldName(column)) +");\n");
			br.write("\t\t}\n");
		}
		br.write("\t\tquery = query.set(updates);\n");
		br.write("\t\tint count = query.update();\n");
		br.write("\t\tif (__NOSCO_CALLBACK_UPDATE_POST!=null) "
				+ "try { __NOSCO_CALLBACK_UPDATE_POST.invoke(null, this, ds); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\treturn count==1;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean delete() throws SQLException {\n");
		br.write("\t\t return delete(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean delete(DataSource ds) throws SQLException {\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		if (pkSet == null || pkSet.size() == 0) {
			for (String column : columns.keySet()) {
				br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
			}
		}
		br.write(";\n");
		br.write("\t\tint count = query.deleteAll();\n");
		br.write("\t\treturn count==1;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean insert() throws SQLException {\n");
		br.write("\t\t return insert(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean insert(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		br.write(";\n");
		if (!pkSet.isEmpty()) {
			br.write("\t\tif (__NOSCO_CALLBACK_INSERT_PRE!=null) "
					+ "try { __NOSCO_CALLBACK_INSERT_PRE.invoke(null, this); }"
					+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
					+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		}
		br.write("\t\tMap<Field<?>,Object> updates = new HashMap<Field<?>,Object>();\n");
		for (String column : columns.keySet()) {
			br.write("\t\tupdates.put("+ getFieldName(column) +", "
		+ convertToOriginalType(schema, table, column, columns.getString(column), getInstanceFieldName(column)) +");\n");
		}
		br.write("\t\tquery = query.set(updates);\n");
		br.write("\t\t\tquery.insert();\n");
		br.write("\t\t\tif (__NOSCO_CALLBACK_INSERT_POST!=null) "
				+ "try { __NOSCO_CALLBACK_INSERT_POST.invoke(null, this, ds); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\t\treturn true;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean exists() throws SQLException {\n");
		br.write("\t\t return exists(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean exists(DataSource ds) throws SQLException {\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
		}
		br.write(";\n");
		br.write("\t\tint size = query.size();\n");
		br.write("\t\treturn size > 0;\n");
		br.write("\t}\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_INSERT_PRE = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_INSERT_POST = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_UPDATE_PRE = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_UPDATE_POST = null;\n");
		br.write("\tstatic {\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_INSERT_PRE = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"preInsert\", "
				+ className +".class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_INSERT_POST = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"postInsert\", "
				+ className +".class, DataSource.class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_UPDATE_PRE = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"preUpdate\", "
				+ className +".class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_UPDATE_POST = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"postUpdate\", "
				+ className +".class, DataSource.class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t}\n");
		br.write("\t/**\n");
		br.write("\t * Returns a table alias.  This is used when specifying manual joins\n");
		br.write("\t * to reference later using Field.from(alias) in where() conditions.\n");
		br.write("\t */\n");
		br.write("\tpublic static Table.__Alias<"+ className +"> as(String alias) {\n");
		br.write("\t\treturn new Table.__Alias<"+ className +">("+ className +".class, alias);\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic int hashCode() {\n");
		br.write("\t\tfinal int prime = 31;\n");
		br.write("\t\tint result = 1;\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\tresult = prime * result + (("+ getInstanceFieldName(column)
					+" == null) ? 0 : "+ getInstanceFieldName(column) +".hashCode());\n");
		}
		br.write("\t\treturn result;\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic boolean equals(Object other) {\n");
		br.write("\t\treturn (other == this) || ((other != null) \n");
		br.write("\t\t\t&& (other instanceof "+ className +")\n");
		br.write("\t\t\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\t\t&& (("+ getInstanceFieldName(column) +" == null) ? ((("
					+ className +")other)."+ getInstanceFieldName(column) +" == null) : ("
					+ getInstanceFieldName(column) +".equals((("+ className +")other)."
					+ getInstanceFieldName(column) +")))\n");
		}
		br.write("\t\t);\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic int compareTo("+ className +" o) {\n");
		br.write("\t\tint v = 0;\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\tv = "+ getInstanceFieldName(column) +"==null ? (o."
					+ getInstanceFieldName(column) +"==null ? 0 : -1) : "
					+ getInstanceFieldName(column) +".compareTo(o."
					+ getInstanceFieldName(column) +");\n");
			br.write("\t\tif (v != 0) return v;\n");
		}
		br.write("\t\treturn 0;\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic java.lang.Object __NOSCO_PRIVATE_mapType(java.lang.Object o) {\n");
		Set<String> coveredTypes = new HashSet<String>();
		for (String origType : classTypeMappings.keySet()) {
			String actualType = classTypeMappings.optString(origType);
			br.write("\t\tif (o instanceof "+ actualType +") return ");
			br.write(typeMappingFunctions.optString(actualType +" "+ origType)
					.replaceAll("[%]s", "(("+ actualType +")o)"));
			br.write(";\n");
			coveredTypes.add(actualType);
		}
		for (String actualType : this.schemaTypeMappings.values()) {
			if (coveredTypes.contains(actualType)) continue;
			String origType = null;
			for (String column : columns.keySet()) {
				String type = columns.getString(column);
				if (actualType.equals(this.getFieldType(schema, table, column, type))) {
					origType = this.getFieldClassType(type).getName();
					break;
				}
			}
			if (origType == null) continue;
			br.write("\t\tif (o instanceof "+ actualType +") return ");
			br.write(typeMappingFunctions.optString(actualType +" "+ origType)
					.replaceAll("[%]s", "(("+ actualType +")o)"));
			br.write(";\n");
			coveredTypes.add(actualType);
		}
		br.write("\t\treturn o;\n");
		br.write("\t}\n\n");
		br.write("}\n");
		br.close();
	}

	private void generate(String schema, String table, JSONObject columns, JSONArray pks,
			List<FK> fks, List<FK> fksIn, String dataSourceName, String callbackPackage,
			JSONObject enums)
	throws IOException, JSONException {
		String className = genTableClassName(table);
		Set<String> pkSet = new HashSet<String>();
		if (pks == null) pks = new JSONArray();
		for (int i=0; i<pks.length(); ++i) {
			pkSet.add(pks.getString(i));
		}
		int fieldCount = columns.keySet().size();
		String pkgDir = Misc.join("/", pkg.split("[.]"));
		new File(Misc.join("/", dir, pkgDir, schema)).mkdirs();
		File file = new File(Misc.join("/", dir, pkgDir, schema, className+".java"));
		System.out.println("writing: "+ file.getAbsolutePath());
		BufferedWriter br = new BufferedWriter(new FileWriter(file));
		br.write("package "+ pkg +"."+ schema +";\n\n");
		br.write("import java.lang.reflect.Method;\n");
		br.write("import java.lang.reflect.InvocationTargetException;\n");
		br.write("import java.sql.SQLException;\n");
		br.write("import javax.sql.DataSource;\n");
		br.write("import java.util.Map;\n\n");
		br.write("import java.util.HashMap;\n\n");
		br.write("import org.nosco.Field;\n");
		br.write("import org.nosco.Query;\n");
		br.write("import org.nosco.QueryFactory;\n");
		br.write("import org.nosco.Table;\n");
		br.write("\n");
		br.write("public class "+ className +" extends Table implements Comparable<"+ className +"> {\n\n");
		int index = 0;
		for (String column : columns.keySet()) {
			br.write("\tpublic static final Field<");
			br.write(getFieldType(schema, table, column, columns.getString(column)));
			br.write("> "+ getFieldName(column));
			br.write(" = new Field<"+ getFieldType(schema, table, column, columns.getString(column)));
			br.write(">("+ index +", "+ className +".class, \""+ column);
			br.write("\", "+ getFieldType(schema, table, column, columns.getString(column)) +".class");
			br.write(");\n");
			++index;
		}
		br.write("\n");
		br.write("\tpublic static Field.PK<"+ className +"> PK = new Field.PK<"+ className +">(");
		for (int i=0; i<pks.length(); ++i) {
			br.write(pks.getString(i).toUpperCase());
			if (i<pks.length()-1) br.write(", ");
		}
		br.write(");\n");
		br.write("\tpublic org.nosco.Field.PK PK() { return PK; }\n\n");
		for (FK fk : fks) {
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
			}
			String fkName = genFKName(fk.columns.keySet(), referencedTable);
			br.write("\tpublic static final Field.FK<"+ referencedTableClassName +"> FK_"+ fkName);
			br.write(" = new Field.FK<"+ referencedTableClassName +">("+ index +", "+ className +".class, ");
			br.write(referencedTableClassName +".class");
			for (Entry<String, String> e : fk.columns.entrySet()) {
				br.write(", "+ e.getKey().toUpperCase());
			}
			for (Entry<String, String> e : fk.columns.entrySet()) {
				br.write(", "+ referencedTableClassName +".");
				br.write(e.getValue().toUpperCase());
			}
			br.write(");\n");
			++index;
		}
		br.write("\n");
		for (String column : columns.keySet()) {
			String enumKey = schema +"."+ table +"."+ column;
			if (!enums.has(enumKey)) continue;
			JSONObject instances = enums.optJSONObject(enumKey);
			boolean simple = pkSet.size() == 1;
			if (simple) {
				String pk = pkSet.iterator().next();
				String pkType = getFieldType(schema, table, pk, columns.getString(pk));
				br.write("\tpublic enum PKS implements Table.__SimplePrimaryKey<"+ className +", "+ pkType +"> {\n\n");
				int count = 0;
				for (String name : instances.keySet()) {
					++ count;
					String value = instances.optJSONArray(name).getString(0);
					if ("java.lang.String".equals(pkType)) value = "\""+ value +"\"";
					br.write("\t\t"+ name.toUpperCase().replaceAll("\\W", "_"));
					br.write("("+ value +")");
					if (count < instances.keySet().size()) br.write(",\n");
				}
				br.write(";\n\n");
				br.write("\t\tpublic final "+ pkType +" "+ getFieldName(pk) +";\n");
				br.write("\t\tPKS("+ pkType +" v) {\n");
				br.write("\t\t\t"+ getFieldName(pk) +" = v;\n");
				br.write("\t\t}\n");
				br.write("\t\t@SuppressWarnings(\"unchecked\")\n");
				br.write("\t\t@Override\n");
				br.write("\t\tpublic <R> R get(Field<R> field) {\n");
				br.write("\t\t\tif (field=="+ className +"."+ getFieldName(pk)
						+") return (R) Integer.valueOf("+ getFieldName(pk) +"); \n");
				br.write("\t\t\tif ("+ className +"."+ getFieldName(pk)
						+".sameField(field)) return (R) Integer.valueOf("
						+ getFieldName(pk) +");\n");
				br.write("\t\t\tthrow new RuntimeException(\"field \"+ field +\" is not part of this primary key\");\n");
				br.write("\t\t}\n");
				br.write("\t\t@Override\n");
				br.write("\t\tpublic "+ pkType +" value() {\n");
				br.write("\t\t\treturn "+ getFieldName(pk)+ ";\n");
				br.write("\t\t}\n\n");
				br.write("\t}\n");
			}
		}
		for (String column : columns.keySet()) {
			br.write("\tprivate "+ getFieldType(schema, table, column, columns.getString(column)));
			br.write(" "+ getInstanceFieldName(column) + " = null;\n");
		}
		br.write("\n");
		br.write("\tpublic "+ className +"() {}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tprotected "+ className +"(Field[] fields, Object[] objects, int start, int end) {\n");
		br.write("\t\tif (fields.length != objects.length)\n\t\t\tthrow new IllegalArgumentException(");
		br.write("\"fields.length != objects.length => \"+ fields.length +\" != \"+ objects.length");
		br.write(" +\"\");\n");
		br.write("\t\tfor (int i=start; i<end; ++i) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\t\tif (fields[i]=="+ getFieldName(column) +") {\n");
			br.write("\t\t\t\t"+ getInstanceFieldName(column) +" = ");
			String assignment = convertToActualType(schema, table, column,
					columns.getString(column),
					"("+ getFieldClassType(columns.getString(column)).getName()+ ") objects[i]");
			br.write(assignment);
			br.write(";\n");
			br.write("\t\t\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t\t\tcontinue;\n");
			br.write("\t\t\t}\n");
		}
		br.write("\t\t}\n\t}\n\n");
		br.write("\tpublic String SCHEMA_NAME() {\n\t\treturn \""+ schema +"\";\n\t}\n\n");
		br.write("\tpublic String TABLE_NAME() {\n\t\treturn \""+ table +"\";\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic Field[] FIELDS() {\n\t\tField[] fields = {");
		for (String column : columns.keySet()) {
			br.write(getFieldName(column)+",");
		}
		br.write("};\n\t\treturn fields;\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic Field.FK[] FKS() {\n\t\tField.FK[] fields = {");
		for (FK fk : fks) {
			String referencedTable = fk.reffed[1];
			br.write("FK_" + genFKName(fk.columns.keySet(), referencedTable) + ",");
		}
		br.write("};\n\t\treturn fields;\n\t}\n\n");
		br.write("\t@SuppressWarnings(\"unchecked\")\n");
		br.write("\tpublic <S> S get(Field<S> _field) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (_field=="+ getFieldName(column) +") ");
			br.write("return (S) "+ getInstanceFieldName(column) +";\n");
		}
		br.write("\t\tthrow new IllegalArgumentException(\"unknown field \"+ _field);\n");
		br.write("\t}\n\n");
		br.write("\tpublic <S> void set(Field<S> _field, S _value) {\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (_field=="+ getFieldName(column) +") ");
			br.write(getInstanceFieldName(column) +" = ("+ getFieldType(schema, table, column, columns.getString(column)) +") _value;\n");
		}
		br.write("\t\tthrow new IllegalArgumentException(\"unknown field \"+ _field);\n");
		br.write("\t}\n\n");
		br.write("\tpublic static final Query<"+ className +"> ALL = QueryFactory.IT.getQuery("
		+ className +".class).use("+ pkg +"."+ dataSourceName +"."+ schema.toUpperCase() +");\n\n");
		br.write("\t public String toString() {\n");
		br.write("\t\treturn \"["+ className);
		for (int i=0; i<pks.length(); ++i) {
			String pk = pks.getString(i);
			br.write(" "+ pk+":");
			br.write("\"+"+ getInstanceFieldName(pk));
			br.write("+\"");
		}
		for (String column : columns.keySet()) {
			if (!"name".equalsIgnoreCase(column)) continue;
			br.write(" "+ column +":");
			br.write("\"+"+ getInstanceFieldName(column));
			br.write("+\"");
		}
		br.write("]\";\n");
		br.write("\t}\n\n");
		br.write("\t public String toStringDetailed() {\n");
		br.write("\t\treturn \"["+ className);
		for (String column : columns.keySet()) {
			br.write(" "+ column +":");
			br.write("\"+"+ getInstanceFieldName(column));
			br.write("+\"");
		}
		br.write("]\";\n");
		br.write("\t}\n\n");
		for (String column : columns.keySet()) {
			String cls = getFieldType(schema, table, column, columns.getString(column));
			br.write("\tpublic "+ cls +" get"+ getInstanceMethodName(column) +"() {\n");
			br.write("\t\tif (!__NOSCO_FETCHED_VALUES.get("+ getFieldName(column) +".INDEX)) {\n");
			br.write("\t\t\t"+ className +" _tmp = ALL.onlyFields(");
			br.write(getFieldName(column)+")");
			for (String pk : pkSet) {
				br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
			}
			br.write(".getTheOnly();\n");
			br.write("\t\t\t"+ getInstanceFieldName(column) +" = _tmp == null ? null : _tmp.get"
					+ getInstanceMethodName(column) +"();");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t}\n");
			br.write("\t\treturn "+ getInstanceFieldName(column) +";\n\t}\n\n");
			br.write("\tpublic "+ className +" set"+ getInstanceMethodName(column));
			br.write("("+ cls +" v) {\n");
			br.write("\t\t"+ getInstanceFieldName(column) +" = v;\n");
			br.write("\t\tif (__NOSCO_UPDATED_VALUES == null) __NOSCO_UPDATED_VALUES = new java.util.BitSet();\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\t__NOSCO_FETCHED_VALUES.set("+ getFieldName(column) +".INDEX);\n");
			br.write("\t\treturn this;\n");
			br.write("\t}\n\n");
			if (!cls.equals(getFieldClassType(columns.getString(column)).getName())) {
				br.write("\tpublic "+ className +" set"+ getInstanceMethodName(column));
				br.write("("+ getFieldClassType(columns.getString(column)).getName() +" v) {\n");
				br.write("\t\treturn set"+ getInstanceMethodName(column) +"("+
						this.convertToActualType(schema, table, column,
								columns.getString(column), "v") +");\n");
				br.write("\t}\n\n");
			}
		}
		for (FK fk : fks) {
			String referencedSchema = fk.reffed[0];
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
		}
			String methodName = genFKMethodName(fk.columns.keySet(), referencedTable);
			String cachedObjectName = "_NOSCO_FK_"+ underscoreToCamelCase(fk.columns.keySet(), false);
			br.write("\tprivate "+ referencedTableClassName +" "+ cachedObjectName +" = null;\n\n");
			br.write("\tpublic "+ referencedTableClassName +" get"+ methodName +"() {\n");
			br.write("\t\tif (!__NOSCO_FETCHED_VALUES.get(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX)) {\n");
			br.write("\t\t\t"+ cachedObjectName +" = "+ referencedTableClassName +".ALL");
			br.write(".where("+ referencedTableClassName +"."+ getFieldName(fk.columns.values()) +".eq("+ underscoreToCamelCase(fk.columns.keySet(), false) +"))");
			br.write(".getTheOnly();\n");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\t}\n");
			br.write("\t\treturn "+ cachedObjectName +";\n\t}\n\n");
			br.write("\tpublic "+ className +" set"+ methodName +"("+ referencedTableClassName +" v) {\n");
			br.write("\t\t"+ underscoreToCamelCase(fk.columns.keySet(), false) +" = v.get"+ underscoreToCamelCase(fk.columns.values(), true) +"();\n");
			br.write("\t\tif (__NOSCO_UPDATED_VALUES == null) __NOSCO_UPDATED_VALUES = new java.util.BitSet();\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set("+ getFieldName(fk.columns.keySet()) +".INDEX);\n");
			br.write("\t\t"+ cachedObjectName +" = v;\n");
			br.write("\t\t__NOSCO_UPDATED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\treturn this;\n");
			br.write("\n\t}\n\n");
		}
		br.write("\tprotected void SET_FK(Field.FK<?> field, Object v) {\n");
		br.write("\t\tif (false);\n");
		for (FK fk : fks) {
			String cachedObjectName = "_NOSCO_FK_"+ underscoreToCamelCase(fk.columns.keySet(), false);
			String referencedTable = fk.reffed[1];
			String referencedTableClassName = genTableClassName(referencedTable);
			if (!schema.equals(fk.reffed[0])) {
				referencedTableClassName = pkg +"."+ fk.reffed[0] +"."+ referencedTableClassName;
		}
			br.write("\t\telse if (field == FK_"+ genFKName(fk.columns.keySet(), referencedTable) +") {\n");
			br.write("\t\t\t"+ cachedObjectName +" = ("+ referencedTableClassName +") v;\n");
			br.write("\t\t\t__NOSCO_FETCHED_VALUES.set(FK_"+ genFKName(fk.columns.keySet(), referencedTable) +".INDEX);\n");
			br.write("\t\t}\n");
		}
		br.write("\t\telse {throw new RuntimeException(\"unknown FK\");}\n");
		br.write("\t}\n\n");
		br.write("\t@SuppressWarnings(\"unchecked\")\n");
		br.write("\tprotected void SET_FK_SET(Field.FK<?> fk, Query<?> v) {\n");
		br.write("\t\tif (false);\n");
		for (FK fk : fksIn) {
		    String relatedSchema = fk.reffing[0];
		    String relatedTable = fk.reffing[1];
		    String relatedTableClassName = this.genTableClassName(relatedTable);
			if (!schema.equals(relatedSchema)) {
				relatedTableClassName = pkg +"."+ relatedSchema +"."+ relatedTableClassName;
			}
			String fkName = "FK_"+ genFKName(fk.columns.keySet(), fk.reffed[1]);
		    String localVar = "__NOSCO_CACHED_FK_SET___"+ relatedTable + "___"
		    		+ Misc.join("__", fk.columns.keySet());
		    br.write("\t\telse if ("+ relatedTableClassName +"."+ fkName +".equals(fk)) {\n");
		    br.write("\t\t\t"+ localVar +" = (Query<"+ relatedTableClassName +">) v;\n");
		    br.write("\t\t}\n");
		}
		br.write("\t\telse {throw new RuntimeException(\"unknown FK\");}\n");
		br.write("\t}\n\n");
		Map<String, Integer> reffingCounts = new HashMap<String,Integer>();
		for (FK fk : fksIn) {
		    String relatedTable = fk.reffing[1];
		    Integer c = reffingCounts.get(relatedTable);
		    if (c == null) c = 0;
		    reffingCounts.put(relatedTable, c+1);
		}
		for (FK fk : fksIn) {
		    String relatedSchema = fk.reffing[0];
		    String relatedTable = fk.reffing[1];
		    String relatedTableClassName = this.genTableClassName(relatedTable);
			if (!schema.equals(relatedSchema)) {
				relatedTableClassName = pkg +"."+ relatedSchema +"."+ relatedTableClassName;
			}
		    String method = getInstanceMethodName(relatedTable);
		    if (reffingCounts.get(relatedTable) > 1) {
			    String tmp = Misc.join("_", fk.columns.keySet());
				method = method + "_" + getInstanceMethodName(tmp);
		    }
		    String localVar = "__NOSCO_CACHED_FK_SET___"+ relatedTable + "___"
		    		+ Misc.join("__", fk.columns.keySet());
		    br.write("\tprivate Query<"+ relatedTableClassName +"> "+ localVar +" = null;\n");
		    br.write("\tpublic Query<"+ relatedTableClassName +"> get"+ method +"Set() {\n");
		    br.write("\t\tif ("+ localVar +" != null) return "+ localVar + ";\n");
		    br.write("\t\telse return "+ relatedTableClassName +".ALL");
		    for (Entry<String, String> e : fk.columns.entrySet()) {
			String relatedColumn = e.getKey();
			String column = e.getValue();
			br.write(".where("+ relatedTableClassName +"."+ getFieldName(relatedColumn) +".eq(get"+ getInstanceMethodName(column) +"()))");
		    }
		    br.write(";\n");
		    br.write("\t}\n\n");
		}
		br.write("\tpublic boolean save() throws SQLException {\n");
		br.write("\t\t return save(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean save(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		br.write(";\n");
		if (pkSet == null || pkSet.isEmpty()) {
			br.write("\t\tthrow new RuntimeException(\"save() is ambiguous on objects without PKs - use insert() or update()\");\n");
		} else {
			br.write("\t\tint size = query.size();\n");
			br.write("\t\tif (size == 0) return this.insert(ds);\n");
			br.write("\t\telse if (size == 1) return this.update(ds);\n");
			br.write("\t\telse throw new RuntimeException(\"more than one result was returned " +
					"for a query that specified all the PKs.  this is bad.\");\n");
			br.write("\t\t\n");
		}
		br.write("\t}\n");
		br.write("\tpublic boolean update() throws SQLException {\n");
		br.write("\t\t return update(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean update(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		if (pkSet == null || pkSet.size() == 0) {
			for (String column : columns.keySet()) {
				br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
			}
		}
		br.write(";\n");
		br.write("\t\tif (__NOSCO_CALLBACK_UPDATE_PRE!=null) "
				+ "try { __NOSCO_CALLBACK_UPDATE_PRE.invoke(null, this); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\tMap<Field<?>,Object> updates = new HashMap<Field<?>,Object>();\n");
		for (String column : columns.keySet()) {
			br.write("\t\tif (__NOSCO_UPDATED_VALUES.get("+ getFieldName(column) +".INDEX)) {\n");
			br.write("\t\t\tupdates.put("+ getFieldName(column) +", "
			+ convertToOriginalType(schema, table, column, columns.getString(column), getInstanceFieldName(column)) +");\n");
			br.write("\t\t}\n");
		}
		br.write("\t\tquery = query.set(updates);\n");
		br.write("\t\tint count = query.update();\n");
		br.write("\t\tif (__NOSCO_CALLBACK_UPDATE_POST!=null) "
				+ "try { __NOSCO_CALLBACK_UPDATE_POST.invoke(null, this, ds); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\treturn count==1;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean delete() throws SQLException {\n");
		br.write("\t\t return delete(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean delete(DataSource ds) throws SQLException {\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		if (pkSet == null || pkSet.size() == 0) {
			for (String column : columns.keySet()) {
				br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
			}
		}
		br.write(";\n");
		br.write("\t\tint count = query.deleteAll();\n");
		br.write("\t\treturn count==1;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean insert() throws SQLException {\n");
		br.write("\t\t return insert(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean insert(DataSource ds) throws SQLException {\n");
		br.write("\t\tif (!dirty()) return false;\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String pk : pkSet) {
			br.write(".where("+ getFieldName(pk) +".eq("+ getInstanceFieldName(pk) +"))");
		}
		br.write(";\n");
		if (!pkSet.isEmpty()) {
			br.write("\t\tif (__NOSCO_CALLBACK_INSERT_PRE!=null) "
					+ "try { __NOSCO_CALLBACK_INSERT_PRE.invoke(null, this); }"
					+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
					+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		}
		br.write("\t\tMap<Field<?>,Object> updates = new HashMap<Field<?>,Object>();\n");
		for (String column : columns.keySet()) {
			br.write("\t\tupdates.put("+ getFieldName(column) +", "
		+ convertToOriginalType(schema, table, column, columns.getString(column), getInstanceFieldName(column)) +");\n");
		}
		br.write("\t\tquery = query.set(updates);\n");
		br.write("\t\t\tquery.insert();\n");
		br.write("\t\t\tif (__NOSCO_CALLBACK_INSERT_POST!=null) "
				+ "try { __NOSCO_CALLBACK_INSERT_POST.invoke(null, this, ds); }"
				+ "catch (IllegalAccessException e) { e.printStackTrace(); } "
				+ "catch (InvocationTargetException e) { e.printStackTrace(); }\n");
		br.write("\t\t\treturn true;\n");
		br.write("\t}\n");
		br.write("\tpublic boolean exists() throws SQLException {\n");
		br.write("\t\t return exists(ALL.getDataSource());\n");
		br.write("\t}\n");
		br.write("\t@SuppressWarnings(\"rawtypes\")\n");
		br.write("\tpublic boolean exists(DataSource ds) throws SQLException {\n");
		br.write("\t\tQuery<"+ className +"> query = ALL.use(ds)");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write(".where("+ getFieldName(column) +".eq("+ getInstanceFieldName(column) +"))");
		}
		br.write(";\n");
		br.write("\t\tint size = query.size();\n");
		br.write("\t\treturn size > 0;\n");
		br.write("\t}\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_INSERT_PRE = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_INSERT_POST = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_UPDATE_PRE = null;\n");
		br.write("\tprivate static Method __NOSCO_CALLBACK_UPDATE_POST = null;\n");
		br.write("\tstatic {\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_INSERT_PRE = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"preInsert\", "
				+ className +".class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_INSERT_POST = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"postInsert\", "
				+ className +".class, DataSource.class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_UPDATE_PRE = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"preUpdate\", "
				+ className +".class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t\ttry {\n\t\t\t __NOSCO_CALLBACK_UPDATE_POST = Class.forName(\""+ callbackPackage
				+"."+ schema +"."+ className +"CB\").getMethod(\"postUpdate\", "
				+ className +".class, DataSource.class);\n\t\t} catch (Exception e) { /* ignore */ }\n");
		br.write("\t}\n");
		br.write("\t/**\n");
		br.write("\t * Returns a table alias.  This is used when specifying manual joins\n");
		br.write("\t * to reference later using Field.from(alias) in where() conditions.\n");
		br.write("\t */\n");
		br.write("\tpublic static Table.__Alias<"+ className +"> as(String alias) {\n");
		br.write("\t\treturn new Table.__Alias<"+ className +">("+ className +".class, alias);\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic int hashCode() {\n");
		br.write("\t\tfinal int prime = 31;\n");
		br.write("\t\tint result = 1;\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\tresult = prime * result + (("+ getInstanceFieldName(column)
					+" == null) ? 0 : "+ getInstanceFieldName(column) +".hashCode());\n");
		}
		br.write("\t\treturn result;\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic boolean equals(Object other) {\n");
		br.write("\t\treturn (other == this) || ((other != null) \n");
		br.write("\t\t\t&& (other instanceof "+ className +")\n");
		br.write("\t\t\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\t\t&& (("+ getInstanceFieldName(column) +" == null) ? ((("
					+ className +")other)."+ getInstanceFieldName(column) +" == null) : ("
					+ getInstanceFieldName(column) +".equals((("+ className +")other)."
					+ getInstanceFieldName(column) +")))\n");
		}
		br.write("\t\t);\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic int compareTo("+ className +" o) {\n");
		br.write("\t\tint v = 0;\n");
		for (String column : pkSet == null || pkSet.size() == 0 ? columns.keySet() : pkSet) {
			br.write("\t\tv = "+ getInstanceFieldName(column) +"==null ? (o."
					+ getInstanceFieldName(column) +"==null ? 0 : -1) : "
					+ getInstanceFieldName(column) +".compareTo(o."
					+ getInstanceFieldName(column) +");\n");
			br.write("\t\tif (v != 0) return v;\n");
		}
		br.write("\t\treturn 0;\n");
		br.write("\t}\n\n");
		br.write("\t@Override\n");
		br.write("\tpublic java.lang.Object __NOSCO_PRIVATE_mapType(java.lang.Object o) {\n");
		Set<String> coveredTypes = new HashSet<String>();
		for (String origType : classTypeMappings.keySet()) {
			String actualType = classTypeMappings.optString(origType);
			br.write("\t\tif (o instanceof "+ actualType +") return ");
			br.write(typeMappingFunctions.optString(actualType +" "+ origType)
					.replaceAll("[%]s", "(("+ actualType +")o)"));
			br.write(";\n");
			coveredTypes.add(actualType);
		}
		for (String actualType : this.schemaTypeMappings.values()) {
			if (coveredTypes.contains(actualType)) continue;
			String origType = null;
			for (String column : columns.keySet()) {
				String type = columns.getString(column);
				if (actualType.equals(this.getFieldType(schema, table, column, type))) {
					origType = this.getFieldClassType(type).getName();
					break;
				}
			}
			if (origType == null) continue;
			br.write("\t\tif (o instanceof "+ actualType +") return ");
			br.write(typeMappingFunctions.optString(actualType +" "+ origType)
					.replaceAll("[%]s", "(("+ actualType +")o)"));
			br.write(";\n");
			coveredTypes.add(actualType);
		}
		for (String actualType : this.schemaTypeMappings.values()) {
			for (String x : typeMappingFunctions.keySet()) {
				if (coveredTypes.contains(actualType)) continue;
				String[] y = x.split(" ");
				if (y==null || y.length!=2) {
					throw new RuntimeException("bad key in type_mappings: '"+ x +"'. must be of " +
							"the form 'com.something.ClassA com.else.ClassB'");
				}
				if (actualType.equals(y[0])) {
					br.write("\t\tif (o instanceof "+ actualType +") return ");
					br.write(typeMappingFunctions.optString(actualType +" "+ y[1])
							.replaceAll("[%]s", "(("+ actualType +")o)"));
					br.write(";\n");
					coveredTypes.add(actualType);
				}
			}
		}
		br.write("\t\treturn o;\n");
		br.write("\t}\n\n");
		br.write("}\n");
		br.close();
	}
