[MAJOR: implementation of 'database' code generator. nickel@x9c.fr**20080920164745] { hunk ./build.xml 70 + hunk ./build.xml 115 - includes="**/*.class"/> + includes="**/*.class,**/*.clazz"/> addfile ./resources/dtds/dbmodule.dtd hunk ./resources/dtds/dbmodule.dtd 1 + + + + + + + + + + + + + + + + + + + addfile ./src/fr/x9c/nickel/database/CodeGenerator.java hunk ./src/fr/x9c/nickel/database/CodeGenerator.java 1 +/* + * This file is part of Nickel. + * Copyright (C) 2007-2008 Xavier Clerc. + * + * Nickel is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Nickel 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.x9c.nickel.database; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.sql.ParameterMetaData; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import fr.x9c.nickel.NickelException; +import fr.x9c.nickel.Parameters; +import fr.x9c.nickel.GenerateCCode; + +/** + * This class implements the actual code generation.
+ * This class contains the code generating OCaml source, generation of Java and + * C sources are done by using methods from {@link fr.x9c.nickel.database.GenerateJavaCode} + * and {@link fr.x9c.nickel.GenerateCCode}. + * + * @author Xavier Clerc + * @version 1.1 + * @since 1.1 + */ +final class CodeGenerator { + + /** Message printed when code generation starts, if verbose. */ + private static final String CODE_GEN_TRACE = "code generation ..."; + + /** Exception message for an i/o error. */ + private static final String EXCEPTION_IO = + "an i/o error occurred while writing files"; + + /** Exception message for a database error. */ + private static final String DATABASE_ERROR = + "a database error occurred while generating bindings"; + + /** Tabulation for OCaml sources. */ + private static final String OCAML_TABS = " "; + + /** Tabulation for Java and C sources. */ + private static final String TABS = " "; + + /** Parameters controlling code generation. */ + private final Parameters params; + + /** Name of generated module. */ + private final String module; + + /** Name of generated module (uncapitalized). */ + private final String uncapModule; + + /** + * List of statements to generate code for. + */ + private final List statements; + + /** Data of OCaml source, as bytes. */ + private final ByteArrayOutputStream mlSource; + + /** Data of Java source, as bytes. */ + private final ByteArrayOutputStream javaSource; + + /** Data of C source, as bytes. */ + private final ByteArrayOutputStream cSource; + + /** Output stream for OCaml source. */ + private final PrintStream ml; + + /** Output stream for Java source. */ + private final PrintStream java; + + /** Output stream for C source. */ + private final PrintStream c; + + /** + * Where comments should be printed during process + * (null if such print should not occur). + */ + private final PrintStream verbose; + + /** + * Where debug elements (e.g. stack traces) should be printed when + * an error occurs (null if such print should not occur). + */ + private final PrintStream debug; + + /** + * Constructs a code generator. + * @param p parameters for code generation - should not be null + * @param mdl name of generated module - should not be null + * @param stats list of statements to generate primitives for + * - should not be null
+ * passed class instances should also be resolved + * @throws NickelException if an error occurs + */ + CodeGenerator(final Parameters p, + final String mdl, + final List stats) + throws NickelException { + assert p != null : "null p"; + assert mdl != null : "null mdl"; + assert stats != null : "null stats"; + this.params = p; + this.module = mdl; + this.uncapModule = Character.toLowerCase(this.module.charAt(0)) + + this.module.substring(1); + this.statements = new ArrayList(stats); + + this.mlSource = new ByteArrayOutputStream(); + this.javaSource = new ByteArrayOutputStream(); + this.cSource = new ByteArrayOutputStream(); + this.ml = new PrintStream(this.mlSource); + this.java = new PrintStream(this.javaSource); + this.c = new PrintStream(this.cSource); + this.verbose = this.params.getVerboseStream(); + this.debug = this.params.getDebugStream(); + } // end constructor(Parameters, String, List) + + /** + * Actually generate the code, according to the elements passed to the + * constructor. + * @throws NickelException if any error occurs + */ + void generate() throws NickelException { + if (this.verbose != null) { + this.verbose.println(CodeGenerator.CODE_GEN_TRACE); + } // end if + + // headers + final String now = (new Date()).toString(); + GenerateCCode.forHeader(this.c, now); + this.ml.printf("(* Nickel-generated source file -- %s *)\n", now); + this.ml.printf("\n"); + GenerateJavaCode.forHeader(this.java, + this.module, + this.params.getJavaPackage(), + now, + CodeGenerator.TABS); + + // common methods + final String connectFunction = String.format("%scommon_%s_connect", + this.params.getPrimitivePrefix(), + this.uncapModule); + final String isConnectedFunction = String.format("%scommon_%s_is_connected", + this.params.getPrimitivePrefix(), + this.uncapModule); + final String disconnectFunction = String.format("%scommon_%s_disconnect", + this.params.getPrimitivePrefix(), + this.uncapModule); + GenerateJavaCode.forCommonMethods(this.java, + this.module, + connectFunction, + isConnectedFunction, + disconnectFunction, + this.statements, + CodeGenerator.TABS); + this.ml.printf("external connect : string -> string option -> string option -> unit = \"%s\"\n\n", + connectFunction); + GenerateCCode.forPrimitive(this.c, connectFunction, 3, CodeGenerator.TABS); + + this.ml.printf("external is_connected : unit -> bool = \"%s\"\n\n", + isConnectedFunction); + GenerateCCode.forPrimitive(this.c, isConnectedFunction, 1, CodeGenerator.TABS); + + this.ml.printf("external disconnect : unit -> unit = \"%s\"\n\n", + disconnectFunction); + GenerateCCode.forPrimitive(this.c, disconnectFunction, 1, CodeGenerator.TABS); + + // code generation + int index = 0; + try { + for (SQLStatement st : this.statements) { + generateStatement(st); + GenerateJavaCode.forStatement(this.java, + String.format("%sexecute_%s_", + this.params.getPrimitivePrefix(), + this.uncapModule), + index, + st, + CodeGenerator.TABS); + if (st.isPrepared()) { + index++; + } // end if + } /// end for + } catch (final SQLException se) { + if (debug != null) { + se.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.CODE_GENERATION, + CodeGenerator.DATABASE_ERROR); + } // end try/catch + + // footers + GenerateJavaCode.forUtilityMethods(this.java, this.module, CodeGenerator.TABS); + GenerateJavaCode.forFooter(this.java); + GenerateCCode.forFooter(this.c); + + // file save + writeFiles(); + } // end method 'generate()' + + /** + * Actually generate the code for a given statement, + * according to the elements passed to the constructor. + * @param statement statement to generate code for - should not be null + * @throws SQLException if a database error occurs + * @throws NickelException if any error occurs + */ + private void generateStatement(final SQLStatement statement) + throws SQLException, NickelException { + assert statement != null : "null statement"; + switch (statement.getKind()) { + case COMMAND: + final String commandFunction = String.format("%sexecute_%s_%s", + this.params.getPrimitivePrefix(), + this.uncapModule, + statement.getName()); + this.ml.printf("external %s : CadmiumJDBC.Connection.t%s%s -> int32 = %s\"%s\"\n\n", + statement.getName(), + statement.isPrepared() ? " option" : "", + generateSignature(statement.getParameterMetaData()), + 1 + statement.getParameterMetaData().getParameterCount() > 5 + ? ("\"" + commandFunction + "_bytecode\" ") + : "", + commandFunction); + GenerateCCode.forPrimitive(this.c, commandFunction, 1 + statement.getParameterMetaData().getParameterCount(), CodeGenerator.TABS); + if (1 + statement.getParameterMetaData().getParameterCount() > 5) { + final String synonymName = commandFunction + "_bytecode"; + GenerateCCode.forSynonym(this.c, + commandFunction, + synonymName, + 1 + statement.getParameterMetaData().getParameterCount(), + CodeGenerator.TABS); + } // end if + break; + case QUERY: + final String function = String.format("%sexecute_%s_%s", + this.params.getPrimitivePrefix(), + this.uncapModule, + statement.getName()); + final StringBuilder parametersBuilder = new StringBuilder(); + final int lenParams = statement.getParameterMetaData().getParameterCount(); + for (int i = 1; i <= lenParams; i++) { + parametersBuilder.append(" p"); + parametersBuilder.append(i); + } // end for + final String parameters = parametersBuilder.toString(); + this.ml.printf("external %s : CadmiumJDBC.Connection.t%s%s -> CadmiumJDBC.ResultSet.t = %s\"%s\"\n\n", + statement.getName(), + statement.isPrepared() ? " option" : "", + generateSignature(statement.getParameterMetaData()), + 1 + statement.getParameterMetaData().getParameterCount() > 5 + ? ("\"" + function + "_bytecode\" ") + : "", + function); + GenerateCCode.forPrimitive(this.c, function, 1 + statement.getParameterMetaData().getParameterCount(), CodeGenerator.TABS); + if (1 + statement.getParameterMetaData().getParameterCount() > 5) { + final String synonymName = function + "_bytecode"; + GenerateCCode.forSynonym(this.c, + function, + synonymName, + 1 + statement.getParameterMetaData().getParameterCount(), + CodeGenerator.TABS); + } // end if + this.ml.printf("class %s conn%s =\n", statement.getName(), parameters); + this.ml.printf("%sobject\n", OCAML_TABS); + this.ml.printf("%s%sval rs = %s conn%s\n", OCAML_TABS, OCAML_TABS, statement.getName(), parameters); + this.ml.printf("%s%smethod close = CadmiumJDBC.ResultSet.close rs\n", + OCAML_TABS, OCAML_TABS); + this.ml.printf("%s%smethod get_result_set = rs\n", + OCAML_TABS, OCAML_TABS); + this.ml.printf("%s%smethod next =\n", + OCAML_TABS, OCAML_TABS); + final ResultSetMetaData meta = statement.getResultSetMetaData(); + final int len = meta.getColumnCount(); + boolean existsNotNull = false; + for (int i = 1; i <= len; i++) { + existsNotNull = existsNotNull + || (meta.isNullable(i) == ResultSetMetaData.columnNoNulls); + } // end for + if (existsNotNull) { + this.ml.printf("%s%s%slet notnull = function Some x -> x | None -> failwith (\"Unexpected null value\") in\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS); + } // end if + this.ml.printf("%s%s%slet res = try CadmiumJDBC.ResultSet.next rs with _ -> false in\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS); + this.ml.printf("%s%s%sif res then\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS); + for (int i = 1; i <= len; i++) { + final boolean notnull = + meta.isNullable(i) == ResultSetMetaData.columnNoNulls; + this.ml.printf("%s%s%s%s%s(CadmiumJDBC.ResultSet.get_%s_idx rs %d)%s%s\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS, OCAML_TABS, + notnull ? "(notnull " : "", + getAccessor(meta.getColumnType(i)), i, + notnull ? ")" : "", + i < len ? "," : ""); + } // end for + this.ml.printf("%s%s%selse\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS); + this.ml.printf("%s%s%s%sraise Not_found\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS, OCAML_TABS); + if (statement.isUpdatable()) { + for (int i = 1; i <= len; i++) { + final boolean notnull = + meta.isNullable(i) == ResultSetMetaData.columnNoNulls; + this.ml.printf("%s%smethod update_%d x =\n", + OCAML_TABS, OCAML_TABS, i); + this.ml.printf("%s%s%s%sCadmiumJDBC.ResultSet.update_%s_idx rs %d %sx%s;\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS, OCAML_TABS, + getAccessor(meta.getColumnType(i)), + i, + notnull ? "(Some " : "", + notnull ? ")" : ""); + this.ml.printf("%s%s%s%sCadmiumJDBC.ResultSet.update_row rs\n", + OCAML_TABS, OCAML_TABS, OCAML_TABS, OCAML_TABS); + } // end for + } // end if + this.ml.printf("%send\n\n", OCAML_TABS); + break; + default: + // should not happen + assert false : "invalid case"; + } // end switch + } // end method 'generate(SQLStatement)' + + /** + * Actually writes the files and closes them. + * @throws NickelException if an i/o error occurs + */ + private void writeFiles() throws NickelException { + final IOException ioe1 = + writeFile(new File(this.params.getOCamlDirectory(), + this.uncapModule + ".ml"), + this.mlSource, + this.ml, + this.debug); + final IOException ioe2 = + writeFile(new File(this.params.getJavaDirectory(), + this.module + ".java"), + this.javaSource, + this.java, + this.debug); + final IOException ioe3 = + writeFile(new File(this.params.getCDirectory(), + this.uncapModule + ".c"), + this.cSource, + this.c, + this.debug); + if ((ioe1 != null) || (ioe2 != null) || (ioe3 != null)) { + throw new NickelException(NickelException.Phase.CODE_GENERATION, + CodeGenerator.EXCEPTION_IO); + } // end if + } // end method 'writeFiles()' + + /** + * Flushes a PrintStream, writes a ByteArrayOutputStream + * to a file and then flushes and closes the file. + * @param file file to write data to - should not be null + * @param byteStream stream to write to file - should not be null + * @param printStream stream to flush before file writing + * - should not be null + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @return the exception raise if any, null otherwise + */ + private static IOException writeFile(final File file, + final ByteArrayOutputStream byteStream, + final PrintStream printStream, + final PrintStream debug) { + assert file != null : "null file"; + assert byteStream != null : "null byteStream"; + assert printStream != null : "null printStream"; + FileOutputStream f = null; + try { + printStream.flush(); + f = new FileOutputStream(file); + f.write(byteStream.toByteArray()); + return null; + } catch (final IOException ioe) { + if (debug != null) { + ioe.printStackTrace(debug); + } // end if + return ioe; + } finally { + if (f != null) { + try { + f.close(); + } catch (final IOException ioe) { + // nothing to do on simple cleanup + } // end try/catch + } // end if + } // end try/catch/finally + } // end method 'writeFile(File, ByteArrayOutputStream, PrintStream, PrintStream)' + + /** + * Returns the accessor name for a JDBC type. + * @param type JDBC type + * @return the accessor name for a JDBC type + */ + private static String getAccessor(final int type) { + switch (type) { + case Types.ARRAY: + return "object"; + case Types.BIGINT: + return "long"; + case Types.BINARY: + return "bytes"; + case Types.BIT: + return "boolean"; + case Types.BLOB: + return "blob"; + case Types.BOOLEAN: + return "boolean"; + case Types.CHAR: + return "string"; + case Types.CLOB: + return "clob"; + case Types.DATALINK: + return "string"; + case Types.DATE: + return "date"; + case Types.DECIMAL: + return "big_decimal"; + case Types.DISTINCT: + return "object"; + case Types.DOUBLE: + return "double"; + case Types.FLOAT: + return "float"; + case Types.INTEGER: + return "int"; + case Types.JAVA_OBJECT: + return "object"; + case Types.LONGNVARCHAR: + return "string"; + case Types.LONGVARBINARY: + return "bytes"; + case Types.LONGVARCHAR: + return "string"; + case Types.NCHAR: + return "string"; + case Types.NCLOB: + return "nclob"; + case Types.NUMERIC: + return "big_decimal"; + case Types.NVARCHAR: + return "string"; + case Types.OTHER: + return "object"; + case Types.REAL: + return "double"; + case Types.REF: + return "ref"; + case Types.ROWID: + return "row_id"; + case Types.SMALLINT: + return "short"; + case Types.SQLXML: + return "sqlxml"; + case Types.STRUCT: + return "object"; + case Types.TIME: + return "time"; + case Types.TIMESTAMP: + return "timestamp"; + case Types.TINYINT: + return "byte"; + case Types.VARBINARY: + return "bytes"; + case Types.VARCHAR: + return "string"; + default: + // should not happen + assert false : "invalid case"; + return ""; + } // end switch + } // end method 'getAccessor(int)' + + /** + * Generates the OCaml signature from parameters. + * @param params parameters to generate signature from - should not be null + * @return the OCaml signature corresponding to the passed parameters + * @throws SQLException if a database error occurs + */ + private static String generateSignature(final ParameterMetaData params) + throws SQLException { + assert params != null : "null params"; + final int len = params.getParameterCount(); + final StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= len; i++) { + sb.append(" -> "); + switch (params.getParameterType(i)) { + case Types.ARRAY: + sb.append("Cadmium.java_object"); + break; + case Types.BIGINT: + sb.append("int64"); + break; + case Types.BINARY: + sb.append("string"); + break; + case Types.BIT: + sb.append("boolean"); + break; + case Types.BLOB: + sb.append("CadmiumJDBC.Blob.t"); + break; + case Types.BOOLEAN: + sb.append("boolean"); + break; + case Types.CHAR: + sb.append("string"); + break; + case Types.CLOB: + sb.append("CadmiumJDBC.Clob.t"); + break; + case Types.DATALINK: + sb.append("string"); + break; + case Types.DATE: + sb.append("CadmiumJDBC.Date.t"); + break; + case Types.DECIMAL: + sb.append("CadmiumMath.BigDecimal.t"); + break; + case Types.DISTINCT: + sb.append("Cadmium.java_object"); + break; + case Types.DOUBLE: + sb.append("float"); + break; + case Types.FLOAT: + sb.append("float"); + break; + case Types.INTEGER: + sb.append("int32"); + break; + case Types.JAVA_OBJECT: + sb.append("Cadmium.java_object"); + break; + case Types.LONGNVARCHAR: + sb.append("string"); + break; + case Types.LONGVARBINARY: + sb.append("string"); + break; + case Types.LONGVARCHAR: + sb.append("string"); + break; + case Types.NCHAR: + sb.append("string"); + break; + case Types.NCLOB: + sb.append("CadmiumJDBC.NClob.t"); + break; + case Types.NUMERIC: + sb.append("CadmiumMath.BigDecimal.t"); + break; + case Types.NVARCHAR: + sb.append("string"); + break; + case Types.OTHER: + sb.append("Cadmium.java_object"); + break; + case Types.REAL: + sb.append("float"); + break; + case Types.REF: + sb.append("CadmiumJDBC.Ref.t"); + break; + case Types.ROWID: + sb.append("CadmiumJDBC.RowId.t"); + break; + case Types.SMALLINT: + sb.append("int"); + break; + case Types.SQLXML: + sb.append("CadmiumJDBC.SQLXML.t"); + break; + case Types.STRUCT: + sb.append("Cadmium.java_object"); + break; + case Types.TIME: + sb.append("CadmiumJDBC.Time.t"); + break; + case Types.TIMESTAMP: + sb.append("CadmiumJDBC.Timestamp.t"); + break; + case Types.TINYINT: + sb.append("int"); + break; + case Types.VARBINARY: + sb.append("string"); + break; + case Types.VARCHAR: + sb.append("string"); + break; + default: + // should not happen + assert false : "invalid case"; + } // end switch + if (params.isNullable(i) != ParameterMetaData.parameterNoNulls) { + sb.append(" option"); + } // end if + } // end for + return sb.toString(); + } // end method 'generateSignature(ParameterMetaData)' + +} // end class 'CodeGenerator' addfile ./src/fr/x9c/nickel/database/Connector.java hunk ./src/fr/x9c/nickel/database/Connector.java 1 +/* + * This file is part of Nickel. + * Copyright (C) 2007-2008 Xavier Clerc. + * + * Nickel is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Nickel 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.x9c.nickel.database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * This class implements a class with a method connecting to a database.
+ * It is used to circumvent the fact that the caller of DriverManager.getConnection(-) + * should be in the same class loader than the driver it wants to use for connection.
+ * This class will hence be dynamically loaded by the same class loader that was used to + * load the driver. Then reflection will be used to create an instance of this class allowing + * to use connect as a synonym for DriverManager.getConnection(-), + * living in the appropriate class loader. + * + * @author Xavier Clerc + * @version 1.1 + * @since 1.1 + */ +public final class Connector { + + /** + * Empty constructor. + */ + public Connector() { + } // end empty constructor + + /** + * Connects to a database.
+ * Bare synonym for {@link DriverManager.getConnection(java.lang.String, java.lang.String, java.lang.String)}. + * @param url database URL - should not be null + * @param user database user + * @param password database password + * @return a connection to the database + */ + public Connection connect(final String url, final String user, final String password) + throws SQLException { + assert url != null : "null url"; + return DriverManager.getConnection(url, user, password); + } // end method 'connect(String, String, String)' + +} // end class 'Connector' hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 23 +import java.sql.Connection; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 41 + /** Message for invalid key. */ + private static final String INVALID_KEY = + "meta '%s' is not properly defined"; + + /** Message for invalid driver. */ + private static final String UNABLE_TO_LOAD_DRIVER = + "unable to load driver '%s'"; + + /** Message for connection error. */ + private static final String UNABLE_TO_CONNECT = + "unable to connect to database"; + + /** Message for close error. */ + private static final String UNABLE_TO_CLOSE = + "unable to close database"; + + /** The key of the 'driver' meta. */ + private static final String META_DRIVER = "DRIVER"; + + /** The key of the 'url' meta. */ + private static final String META_URL = "URL"; + + /** The key of the 'user' meta. */ + private static final String META_USER = "USER"; + + /** The key of the 'password' meta. */ + private static final String META_PASSWORD = "PASSWORD"; + + /** Connection to database.*/ + private Connection connection; + + /** List of statements to process. */ + private final List statements; + + /** Name of module to generate. */ + private String module; + hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 82 + this.connection = null; + this.statements = new LinkedList(); + this.module = null; hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 93 + this.module = ParseXML.parse(is, this.statements, this.meta, verbose, debug); hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 102 + final List drivers = getMeta(DatabaseModuleProducer.META_DRIVER); + if (drivers != null) { + for (String d : drivers) { + try { + Class.forName(d, true, cl); + } catch (final ClassNotFoundException cnfe) { + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_LOAD_DRIVER, d)); + } // end try/catch + } // end for + } // end if + try { + final String url = getMetaValue(DatabaseModuleProducer.META_URL, false); + final String user = getMetaValue(DatabaseModuleProducer.META_USER, true); + final String password = getMetaValue(DatabaseModuleProducer.META_PASSWORD, true); + final Class connector = cl.loadClass("fr.x9c.nickel.database.Connector"); + this.connection = + (Connection) connector.getMethod("connect", String.class, String.class, String.class).invoke(connector.newInstance(), url, user, password); + if (this.connection == null) { + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CONNECT)); + } // end if + } catch (final ClassNotFoundException cnfe) { + if (debug != null) { + cnfe.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CONNECT)); + } catch (final NoSuchMethodException nsme) { + if (debug != null) { + nsme.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CONNECT)); + } catch (final InstantiationException ie) { + if (debug != null) { + ie.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CONNECT)); + } catch (final IllegalAccessException iae) { + if (debug != null) { + iae.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CONNECT)); + } catch (final java.lang.reflect.InvocationTargetException ite) { + if (debug != null) { + ite.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + ite.getCause().getMessage()); + } // end try/catch + for (SQLStatement s : this.statements) { + s.resolve(this.connection, verbose, debug); + } // end for hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 166 + try { + final CodeGenerator gen = + new CodeGenerator(params, this.module, this.statements); + gen.generate(); + } finally { + try { + this.connection.close(); + } catch (final SQLException se) { + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.UNABLE_TO_CLOSE)); + } // end try/catch + } // end try/finally hunk ./src/fr/x9c/nickel/database/DatabaseModuleProducer.java 180 + /** + * Returns a meta value for a given key. + * @param key meta key - should not be null + * @param returnsNull whether null may be returned if the meta is no set + * @return the meta for the passed key + * @throws NickelException if the meta has multiple definition, + * or no definition and returnsNull is false + */ + private String getMetaValue(final String key, final boolean returnsNull) + throws NickelException { + assert key != null : "null key"; + final List l = this.meta.get(key); + if (l != null) { + if (l.size() == 1) { + return l.get(0); + } else if ((l.size() == 0) && returnsNull) { + return null; + } else { + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.INVALID_KEY, key)); + } // end if/else + } else { + if (returnsNull) { + return null; + } else { + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(DatabaseModuleProducer.INVALID_KEY, key)); + } // end if/else + } // end if/else + } // end method 'getMetaValue(String, boolean)' + addfile ./src/fr/x9c/nickel/database/GenerateJavaCode.java hunk ./src/fr/x9c/nickel/database/GenerateJavaCode.java 1 +/* + * This file is part of Nickel. + * Copyright (C) 2007-2008 Xavier Clerc. + * + * Nickel is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Nickel 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.x9c.nickel.database; + +import java.io.PrintStream; +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +/** + * This class provides utility methods for Java code generation. + * + * @author Xavier Clerc + * @version 1.1 + * @since 1.0 + */ +final class GenerateJavaCode { + + /** + * No instance of this class. + */ + private GenerateJavaCode() { + } // end empty constructor + + /** + * Generates the Java code for a primitive provider header. + * @param out where to print generated code - should not be null + * @param className class name of primitive provider - should not be null + * @param packageName package name of primitive provider - should not be null + * @param timestamp creation date, as a string - should not be null + * @param tabs tabulation value - should not be null + */ + static void forHeader(final PrintStream out, + final String className, + final String packageName, + final String timestamp, + final String tabs) { + assert out != null : "null out"; + assert className != null : "null className"; + assert packageName != null : "null packageName"; + assert timestamp != null : "null timestamp"; + assert tabs != null : "null tabs"; + + out.printf("/* Nickel-generated source file -- %s */\n", timestamp); + out.printf("package %s;\n", packageName); + out.printf("\n"); + out.printf("import java.sql.Connection;\n"); + out.printf("import java.sql.DriverManager;\n"); + out.printf("import java.sql.PreparedStatement;\n"); + out.printf("import java.sql.ResultSet;\n"); + out.printf("import java.sql.SQLException;\n"); + out.printf("\n"); + out.printf("import fr.x9c.cadmium.kernel.Block;\n"); + out.printf("import fr.x9c.cadmium.kernel.CodeRunner;\n"); + out.printf("import fr.x9c.cadmium.kernel.Custom;\n"); + out.printf("import fr.x9c.cadmium.kernel.Fail;\n"); + out.printf("import fr.x9c.cadmium.kernel.Primitive;\n"); + out.printf("import fr.x9c.cadmium.kernel.PrimitiveProvider;\n"); + out.printf("import fr.x9c.cadmium.kernel.Value;\n"); + out.printf("import fr.x9c.cadmium.primitives.cadmium.Cadmium;\n"); + out.printf("import fr.x9c.cadmium.primitives.cadmium.CustomObject;\n"); + out.printf("import fr.x9c.cadmium.primitives.cadmiumjdbc.JDBCSlot;\n"); + out.printf("\n"); + out.printf("@PrimitiveProvider\n"); + out.printf("public final class %s {\n", className); + out.printf("\n"); + out.printf("%sprivate %s() {\n", tabs, className); + out.printf("%s}\n", tabs); + out.printf("\n"); + } // end method 'forHeader(PrintStream, String, String, String)' + + /** + * Generates the Java code for a list of statements, and common methods. + * @param out where to print generated code - should not be null + * @param module module name - should not be null + * @param connectName name of connect primitive - should not be null + * @param isConnectedName name of is_connected primitive - should not be null + * @param disconnectName name of disconnect primitive - should not be null + * @param statements statements to generate code for - should not be null + * @param tabs tabulation value - should not be null + */ + static void forCommonMethods(final PrintStream out, + final String module, + final String connectName, + final String isConnectedName, + final String disconnectName, + final List statements, + final String tabs) { + assert out != null : "null out"; + assert module != null : "null module"; + assert connectName != null : "null connectName"; + assert isConnectedName != null : "null isConnectedName"; + assert disconnectName != null : "null disconnectName"; + assert statements != null : "null statements"; + assert tabs != null : "null tabs"; + + // connect + out.printf("%s@Primitive\n", tabs); + out.printf("%spublic static Value %s(final CodeRunner ctxt, final Value url, final Value user, final Value password) throws Fail.Exception {\n", + tabs, connectName); + out.printf("%s%s%s(ctxt, Value.UNIT);\n", tabs, tabs, disconnectName); + out.printf("%s%sfinal String urlString = url.asBlock().asString();\n", tabs, tabs); + out.printf("%s%sfinal String userString = user.isBlock() ? user.asBlock().get(0).asBlock().asString() : null;\n", tabs, tabs); + out.printf("%s%sfinal String passwordString = password.isBlock() ? password.asBlock().get(0).asBlock().asString() : null;\n", tabs, tabs); + out.printf("%s%stry {\n", tabs, tabs); + out.printf("%s%s%sfinal Connection conn = DriverManager.getConnection(urlString, userString, passwordString);\n", tabs, tabs, tabs); + out.printf("%s%s%sfinal PreparedStatement[] stats = {\n", tabs, tabs, tabs); + for (SQLStatement s : statements) { + if (s.isPrepared()) { + out.printf("%s%s%s%sconn.prepareStatement(\"%s\", ResultSet.TYPE_FORWARD_ONLY, %s),\n", tabs, tabs, tabs, tabs, s.getCode(), s.isUpdatable() ? "ResultSet.CONCUR_UPDATABLE" : "ResultSet.CONCUR_READ_ONLY"); + } // end if + } // end for + out.printf("%s%s%s};\n", tabs, tabs, tabs); + out.printf("%s%s%sctxt.getContext().registerSlot(%s.class, new JDBCSlot(conn, stats));\n", tabs, tabs, tabs, module); + out.printf("%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs); + out.printf("%s%s} catch (final Exception e) {\n", tabs, tabs); + out.printf("%s%s%sCadmium.fail(ctxt, e);\n", tabs, tabs, tabs); + out.printf("%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs); + out.printf("%s%s}\n", tabs, tabs); + out.printf("%s}\n", tabs); + out.printf("\n"); + + // is_connected + out.printf("%s@Primitive\n", tabs); + out.printf("%spublic static Value %s(final CodeRunner ctxt, final Value unit) {\n", + tabs, isConnectedName); + out.printf("%s%sfinal JDBCSlot slot = getSlot(ctxt);\n", tabs, tabs); + out.printf("%s%stry {\n", tabs, tabs); + out.printf("%s%s%sreturn (slot != null) && !slot.getConnection().isClosed()\n", tabs, tabs, tabs); + out.printf("%s%s%s ? Value.TRUE\n", tabs, tabs, tabs); + out.printf("%s%s%s : Value.FALSE;\n", tabs, tabs, tabs); + out.printf("%s%s} catch (final Exception e) {\n", tabs, tabs); + out.printf("%s%s%sreturn Value.FALSE;\n", tabs, tabs, tabs); + out.printf("%s%s}\n", tabs, tabs); + out.printf("%s}\n", tabs); + out.printf("\n"); + + // disconnect + out.printf("%s@Primitive\n", tabs); + out.printf("%spublic static Value %s(final CodeRunner ctxt, final Value unit) {\n", + tabs, disconnectName); + out.printf("%s%sfinal JDBCSlot slot = getSlot(ctxt);\n", tabs, tabs); + out.printf("%s%stry {\n", tabs, tabs); + out.printf("%s%s%sif ((slot != null) && !slot.getConnection().isClosed()) {\n", tabs, tabs, tabs); + out.printf("%s%s%s%sfor (PreparedStatement ps : slot.getStatements()) {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%stry {\n", tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%s%sps.close();\n", tabs, tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%s} catch (final Exception e) {\n", tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%s}\n", tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s}\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%stry {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%sslot.getConnection().close();\n", tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s} catch (final Exception e) {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s}\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s}\n", tabs, tabs, tabs); + out.printf("%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs); + out.printf("%s%s} catch (final Exception e) {\n", tabs, tabs); + out.printf("%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs); + out.printf("%s%s}\n", tabs, tabs); + out.printf("%s}\n", tabs); + out.printf("\n"); + } // end method 'forCommonMethods(PrintStream, String, String, String, String, List, String)' + + /** + * Generates the Java code for a statements. + * @param out where to print generated code - should not be null + * @param prefix prefix to primitive name - should not be null + * @param index index of statement in list being generated + * @param stat statement to generate code for - should not be null + * @param tabs tabulation value - should not be null + */ + static void forStatement(final PrintStream out, + final String prefix, + final int index, + final SQLStatement stat, + final String tabs) + throws SQLException { + assert out != null : "null out"; + assert stat != null : "null stat"; + assert tabs != null : "null tabs"; + out.printf("%s@Primitive\n", tabs); + out.printf("%spublic static Value %s%s(final CodeRunner ctxt, final Value c", + tabs, prefix, stat.getName()); + for (int i = 1; i <= stat.getParameterMetaData().getParameterCount(); i++) { + out.printf(", final Value p%d", i); + } // end for + out.printf(") throws Fail.Exception {\n"); + if (stat.isPrepared()) { + out.printf("%s%sif (c.isBlock()) {\n", tabs, tabs); + out.printf("%s%s%sfinal Connection conn = (Connection) c.asBlock().asCustom();\n", tabs, tabs, tabs); + } else { + out.printf("%s%sfinal Connection conn = (Connection) c.asBlock().asCustom();\n", tabs, tabs); + out.printf("%s%sif (conn == null) {\n", tabs, tabs); + } // end if/else + if (stat.getKind() == SQLStatement.Kind.COMMAND) { + out.printf("%s%s%sfinal Block res = Block.createCustom(Custom.INT_32_SIZE, Custom.INT_32_OPS);\n", tabs, tabs, tabs); + } // end if + out.printf("%s%s%stry {\n", tabs, tabs, tabs); + out.printf("%s%s%s%sfinal PreparedStatement ps = conn.prepareStatement(\"%s\", ResultSet.TYPE_FORWARD_ONLY, %s);\n", tabs, tabs, tabs, tabs, stat.getCode(), stat.isUpdatable() ? "ResultSet.CONCUR_UPDATABLE" : "ResultSet.CONCUR_READ_ONLY"); + for (int i = 1; i <= stat.getParameterMetaData().getParameterCount(); i++) { + final int t = stat.getParameterMetaData().getParameterType(i); + if (stat.getParameterMetaData().isNullable(i) != ParameterMetaData.parameterNoNulls) { + out.printf("%s%s%s%sif (p%d.isBlock()) {\n", + tabs, tabs, tabs, tabs, + i); + out.printf("%s%s%s%s%sps.set%s(%d, %s);\n", + tabs, tabs, tabs, tabs, tabs, + getAccessor(t), i, getConverter(t, "p" + i + ".asBlock().get(0)")); + out.printf("%s%s%s%s} else {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%sps.setNull(%d, %d);\n", + tabs, tabs, tabs, tabs, tabs, + i, t); + out.printf("%s%s%s%s}\n", tabs, tabs, tabs, tabs); + } else { + out.printf("%s%s%s%sps.set%s(%d, %s);\n", + tabs, tabs, tabs, tabs, + getAccessor(t), i, getConverter(t, "p" + i)); + } // end if/else + } // end for + switch (stat.getKind()) { + case COMMAND: + out.printf("%s%s%s%sres.setInt32(ps.executeUpdate());\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%sreturn Value.createFromBlock(res);\n", tabs, tabs, tabs, tabs); + break; + case QUERY: + out.printf("%s%s%s%sreturn Cadmium.createObject(ps.executeQuery());\n", tabs, tabs, tabs, tabs); + break; + default: + // should never be reached + assert false : "invalid case"; + break; + } // end switch + out.printf("%s%s%s} catch (final Exception e) {\n", tabs, tabs, tabs); + out.printf("%s%s%s%sCadmium.fail(ctxt, e);\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s}\n", tabs, tabs, tabs); + out.printf("%s%s} else {\n", tabs, tabs); + if (stat.isPrepared()) { + out.printf("%s%s%sfinal JDBCSlot slot = getSlot(ctxt);\n", tabs, tabs, tabs); + if (stat.getKind() == SQLStatement.Kind.COMMAND) { + out.printf("%s%s%sfinal Block res = Block.createCustom(Custom.INT_32_SIZE, Custom.INT_32_OPS);\n", tabs, tabs, tabs); + } // end if + out.printf("%s%s%stry {\n", tabs, tabs, tabs); + out.printf("%s%s%s%sif (slot == null) {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%sthrow new SQLException(\"not connected\");\n", tabs, tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s}\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%sfinal PreparedStatement ps = slot.getStatement(%d);\n", tabs, tabs, tabs, tabs, index); + for (int i = 1; i <= stat.getParameterMetaData().getParameterCount(); i++) { + final int t = stat.getParameterMetaData().getParameterType(i); + if (stat.getParameterMetaData().isNullable(i) != ParameterMetaData.parameterNoNulls) { + out.printf("%s%s%s%sif (p%d.isBlock()) {\n", + tabs, tabs, tabs, tabs, + i); + out.printf("%s%s%s%s%sps.set%s(%d, %s);\n", + tabs, tabs, tabs, tabs, tabs, + getAccessor(t), i, getConverter(t, "p" + i + ".asBlock().get(0)")); + out.printf("%s%s%s%s} else {\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%s%sps.setNull(%d, %d);\n", + tabs, tabs, tabs, tabs, tabs, + i, t); + out.printf("%s%s%s%s}\n", tabs, tabs, tabs, tabs); + } else { + out.printf("%s%s%s%sps.set%s(%d, %s);\n", + tabs, tabs, tabs, tabs, + getAccessor(t), i, getConverter(t, "p" + i)); + } // end if/else + } // end for + switch (stat.getKind()) { + case COMMAND: + out.printf("%s%s%s%sres.setInt32(ps.executeUpdate());\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%sreturn Value.createFromBlock(res);\n", tabs, tabs, tabs, tabs); + break; + case QUERY: + out.printf("%s%s%s%sreturn Cadmium.createObject(ps.executeQuery());\n", tabs, tabs, tabs, tabs); + break; + default: + // should never be reached + assert false : "invalid case"; + break; + } // end switch + out.printf("%s%s%s} catch (final Exception e) {\n", tabs, tabs, tabs); + out.printf("%s%s%s%sCadmium.fail(ctxt, e);\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs, tabs); + out.printf("%s%s%s}\n", tabs, tabs, tabs); + out.printf("%s%s}\n", tabs, tabs); + } else { + out.printf("%s%s%sFail.invalidArgument(\"invalid connection\");\n", + tabs, tabs, tabs); + out.printf("%s%s%sreturn Value.UNIT;\n", tabs, tabs, tabs); + out.printf("%s%s}\n", tabs, tabs); + } // end if/else + out.printf("%s}\n", tabs); + out.printf("\n"); + if (1 + stat.getParameterMetaData().getParameterCount() > 5) { + forSynonym(out, + prefix + stat.getName(), + prefix + stat.getName() + "_bytecode", + 1 + stat.getParameterMetaData().getParameterCount(), + tabs); + } // end if + } // end method 'forStatement(PrintStream, String, int, SQLStatement, String)' + + /** + * Generates the Java code for private utility methods. + * @param out where to print generated code - should not be null + * @param module module name - should not be null + * @param tabs tabulation value - should not be null + */ + static void forUtilityMethods(final PrintStream out, + final String module, + final String tabs) { + assert out != null : "null out"; + assert module != null : "null module"; + assert tabs != null : "null tabs"; + out.printf("%sprivate static JDBCSlot getSlot(final CodeRunner ctxt) {\n", tabs); + out.printf("%s%sreturn (JDBCSlot) ctxt.getContext().getSlot(%s.class);\n", + tabs, tabs, module); + out.printf("%s}\n", tabs); + out.printf("\n"); + } // end method 'forUtilityMethods(PrintStream, String, String)' + + /** + * Generates the Java code for a bytecode primitive being the synonym of a + * given native primitive. + * @param out where to print generated code - should not be null + * @param nativeId synonym identifier - should not be null + * @param bytecodeId primitive identifier - should not be null + * @param params number of primitive parameters + * - should be > 0 + * @param tabs tabulation value - should not be null + */ + static void forSynonym(final PrintStream out, + final String nativeId, + final String bytecodeId, + final int params, + final String tabs) { + assert out != null : "null out"; + assert nativeId != null : "null nativeId"; + assert bytecodeId != null : "null bytecodeId"; + assert params >= 0 : "params should be > 0"; + assert tabs != null : "null tabs"; + + out.printf("%s@Primitive\n", tabs); + out.printf("%spublic static Value %s(final CodeRunner ctxt", + tabs, + bytecodeId); + for (int i = 0; i < params; i++) { + out.printf(", final Value p%d", i); + } // end for + out.printf(") throws Fail.Exception {\n"); + out.printf("%s%sreturn %s(ctxt", tabs, tabs, nativeId); + for (int i = 0; i < params; i++) { + out.printf(", p%d", i); + } // end for + out.printf(");\n"); + out.printf("%s}\n", tabs); + out.printf("\n"); + } // end method 'forSynonym(PrintStream, String, String, int, String)' + + /** + * Generates the Java code for a primitive provider footer. + * @param out where to print generated code - should not be null + */ + static void forFooter(final PrintStream out) { + assert out != null : "null out"; + out.printf("}\n"); + } // end method 'forFooter(PrintStream)' + + /** + * Returns the accessor name for a given JDBC type. + * @param type JDBC type + * @return the accessor name for the passed JDBC type + */ + private static String getAccessor(final int type) { + switch (type) { + case Types.ARRAY: + return "Array"; + case Types.BIGINT: + return "Long"; + case Types.BINARY: + return "Bytes"; + case Types.BIT: + return "Boolean"; + case Types.BLOB: + return "Blob"; + case Types.BOOLEAN: + return "Boolean"; + case Types.CHAR: + return "String"; + case Types.CLOB: + return "Clob"; + case Types.DATALINK: + return "URL"; + case Types.DATE: + return "Date"; + case Types.DECIMAL: + return "BigDecimal"; + case Types.DISTINCT: + return "Object"; + case Types.DOUBLE: + return "Double"; + case Types.FLOAT: + return "Float"; + case Types.INTEGER: + return "Int"; + case Types.JAVA_OBJECT: + return "Object"; + case Types.LONGNVARCHAR: + return "NString"; + case Types.LONGVARBINARY: + return "Bytes"; + case Types.LONGVARCHAR: + return "String"; + case Types.NCHAR: + return "NString"; + case Types.NCLOB: + return "NClob"; + case Types.NUMERIC: + return "BigDecimal"; + case Types.NVARCHAR: + return "NString"; + case Types.OTHER: + return "Object"; + case Types.REAL: + return "Double"; + case Types.REF: + return "Ref"; + case Types.ROWID: + return "RowId"; + case Types.SMALLINT: + return "Short"; + case Types.SQLXML: + return "SQLXML"; + case Types.STRUCT: + return "Struct"; + case Types.TIME: + return "Time"; + case Types.TIMESTAMP: + return "Timestamp"; + case Types.TINYINT: + return "Byte"; + case Types.VARBINARY: + return "Bytes"; + case Types.VARCHAR: + return "String"; + default: + // should not happen + assert false : "invalid case"; + return ""; + } // end switch + } // end method 'getAccessor(int)' + + /** + * Returns the Java code converting an OCaml value into a given JDBC type. + * @param type JDBC type + * @param name of the variable holding the OCaml value - should not be null + * @return the Java code converting the passed value into the given JDBC type + */ + private static String getConverter(final int type, final String name) { + switch (type) { + case Types.ARRAY: + return name + ".asBlock().asCustom()"; + case Types.BIGINT: + return name + ".asBlock().asInt64()"; + case Types.BINARY: + return name + ".asBlock().getBytes()"; + case Types.BIT: + return name + " == Value.TRUE"; + case Types.BLOB: + return "(Blob) " + name + ".asBlock().asCustom()"; + case Types.BOOLEAN: + return name + " == Value.TRUE"; + case Types.CHAR: + return name + ".asBlock().asString()"; + case Types.CLOB: + return "(Clob) " + name + ".asBlock().asCustom()"; + case Types.DATALINK: + return name + ".asBlock().asString()"; + case Types.DATE: + return "(Date) " + name + ".asBlock().asCustom()"; + case Types.DECIMAL: + return "(BigDecimal) " + name + ".asBlock().asCustom()"; + case Types.DISTINCT: + return name + ".asBlock().asCustom()"; + case Types.DOUBLE: + return name + ".asBlock().asDouble()"; + case Types.FLOAT: + return "(float) " + name + ".asBlock().asDouble()"; + case Types.INTEGER: + return name + ".asBlock().asInt32()"; + case Types.JAVA_OBJECT: + return name + ".asBlock().asCustom()"; + case Types.LONGNVARCHAR: + return name + ".asBlock().asString()"; + case Types.LONGVARBINARY: + return name + ".asBlock().getBytes()"; + case Types.LONGVARCHAR: + return name + ".asBlock().asString()"; + case Types.NCHAR: + return name + ".asBlock().asString()"; + case Types.NCLOB: + return "(NClob) " + name + ".asBlock().asCustom()"; + case Types.NUMERIC: + return "(BigDecimal) " + name + ".asBlock().asCustom()"; + case Types.NVARCHAR: + return name + ".asBlock().asString()"; + case Types.OTHER: + return name + ".asBlock().asCustom()"; + case Types.REAL: + return name + ".asBlock().asDouble()"; + case Types.REF: + return "(Ref) " + name + ".asBlock().asCustom()"; + case Types.ROWID: + return "(RowId) " + name + ".asBlock().asCustom()"; + case Types.SMALLINT: + return "(short) " + name + ".asLong()"; + case Types.SQLXML: + return "(SQLXML) " + name + ".asBlock().asCustom()"; + case Types.STRUCT: + return name + ".asBlock().asCustom()"; + case Types.TIME: + return "(Time) " + name + ".asBlock().asCustom()"; + case Types.TIMESTAMP: + return "(Timestamp) " + name + ".asBlock().asCustom()"; + case Types.TINYINT: + return "(byte) " + name + ".asLong()"; + case Types.VARBINARY: + return name + ".asBlock().getBytes()"; + case Types.VARCHAR: + return name + ".asBlock().asString()"; + default: + // should not happen + assert false : "invalid case"; + return name; + } // end switch + } // end method 'getConverter(int, String)' + +} // end class 'GenerateJavaCode' addfile ./src/fr/x9c/nickel/database/ParseXML.java hunk ./src/fr/x9c/nickel/database/ParseXML.java 1 +/* + * This file is part of Nickel. + * Copyright (C) 2007-2008 Xavier Clerc. + * + * Nickel is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Nickel 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.x9c.nickel.database; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.FactoryConfigurationError; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import fr.x9c.nickel.CheckString; +import fr.x9c.nickel.NickelException; +import fr.x9c.nickel.StrictErrorHandler; + +/** + * This class implements a method parsing XML data (using dbmodule DTD) + * into a list of {@link fr.x9c.nickel.database.SQLStatement} objects. + * + * @author Xavier Clerc + * @version 1.1 + * @since 1.1 + */ +final class ParseXML { + + /** Message printed when xml parse starts, if verbose. */ + private static final String XML_PARSE_TRACE = "xml parse ..."; + + /** Directory of DTDs. */ + private static final String DTD_DIRECTORY = "/dtds"; + + /** DTD name. */ + private static final String DTD_NAME = "dbmodule"; + + /** DTD complete system ID. */ + private static final String DTD_SYSTEM_ID = "dtds/dbmodule.dtd"; + + /** Message for XML error. */ + private static final String XML_ERROR = "error during xml parsing"; + + /** Message for DTD error. */ + private static final String DTD_ERROR = "wrong dtd is used"; + + /** Message for XML parser error. */ + private static final String PARSER_ERROR = "cannot get parser"; + + /** Message for i/o error. */ + private static final String IO_ERROR = "i/o error"; + + /** Message for invalid OCaml module name. */ + private static final String INVALID_OCAML_MODULE_NAME = + "invalid OCaml module name: '%s'"; + + /** Message for invalid meta name. */ + private static final String INVALID_META_NAME = + "invalid meta name: '%s'"; + + /** Message for invalid OCaml name. */ + private static final String INVALID_NAME = + "invalid name: '%s'"; + + /** + * No instance of this class. + */ + private ParseXML() { + } // end empty constructor + + /** + * Parses XML data from a stream using 'dbmodule.dtd' DTD and converts it into + * a list of statements to be processed. + * @param stream stream to parse data from - should not be null
+ * closed before return + * @param statements list to add statements to - should not be null + * @param meta map to add meta definitions to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @return module name + * @throws NickelException if an error occurs during parse or conversion + * @throws NickelException if an i/o error occurs + */ + static String parse(final InputStream stream, + final List statements, + final Map< String, List > meta, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert stream != null : "null stream"; + assert statements != null : "null statements"; + assert meta != null : "null meta"; + + if (verbose != null) { + verbose.println(ParseXML.XML_PARSE_TRACE); + } // end if + + try { + final DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(false); + factory.setValidating(true); + final DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setErrorHandler(new StrictErrorHandler()); + final String systemID = + ParseXML.class.getResource(ParseXML.DTD_DIRECTORY).toString(); + final Document doc = builder.parse(stream, systemID); + final DocumentType type = doc.getDoctype(); + if (!type.getName().equals(ParseXML.DTD_NAME) + || (type.getPublicId() != null) + || !type.getSystemId().equals(ParseXML.DTD_SYSTEM_ID)) { + throw new NickelException(NickelException.Phase.XML_PARSE, + ParseXML.DTD_ERROR); + } // end if + return processDocument(doc, statements, meta, verbose, debug); + } catch (final SAXException se) { + final Exception e = se.getException(); + final String msg; + if (e != null) { + msg = e.getMessage(); + if (debug != null) { + e.printStackTrace(debug); + } // end if + } else { + msg = se.getMessage(); + if (debug != null) { + se.printStackTrace(debug); + } // end if + } // end if/else + throw new NickelException(NickelException.Phase.XML_PARSE, + msg != null ? msg : ParseXML.XML_ERROR); + } catch (final ParserConfigurationException pce) { + if (debug != null) { + pce.printStackTrace(debug); + } // end if + final String msg = pce.getMessage(); + throw new NickelException(NickelException.Phase.XML_PARSE, + msg != null ? msg : ParseXML.PARSER_ERROR); + } catch (final IOException ioe) { + if (debug != null) { + ioe.printStackTrace(debug); + } // end if + final String msg = ioe.getMessage(); + throw new NickelException(NickelException.Phase.XML_PARSE, + msg != null ? msg : ParseXML.IO_ERROR); + } catch (final FactoryConfigurationError fce) { + if (debug != null) { + fce.printStackTrace(debug); + } // end if + final String msg = fce.getMessage(); + throw new NickelException(NickelException.Phase.XML_PARSE, + msg != null ? msg : ParseXML.PARSER_ERROR); + } finally { + try { + stream.close(); + } catch (IOException ioe) { + // nothing to do on simple cleanup + } // end try/catch + } // end try/catch/finally + } // end method 'parse(InputStream, List, Map< String, List >, PrintStream, PrintStream)' + + /** + * Processes a document node. + * @param doc document node to process - should not be null + * @param statements list to add statements to - should not be null + * @param meta map to add meta definitions to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @return module name + * @throws NickelException if any error occurs + */ + private static String processDocument(final Document doc, + final List statements, + final Map< String, List > meta, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert doc != null : "null doc"; + assert statements != null : "null statements"; + assert meta != null : "null meta"; + final NodeList children = doc.getElementsByTagName("dbmodule"); + if (children.getLength() == 1) { + return processDbModule((Element) children.item(0), + statements, + meta, + verbose, + debug); + } else { + throw new NickelException(NickelException.Phase.XML_PARSE, + ParseXML.DTD_ERROR); + } // end if/else + } // end method 'processDocument(Document, List, Map< String, List >, PrintStream, PrintStream)' + + /** + * Processes a "dbmodule" node. + * @param module module node to process - should not be null + * @param statements list to add statements to - should not be null + * @param meta map to add meta definitions to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @return module name + * @throws NickelException if any error occurs + */ + private static String processDbModule(final Element module, + final List statements, + final Map< String, List > meta, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert module != null : "null module"; + assert statements != null : "null statements"; + assert meta != null : "null meta"; + + final String name = module.getAttribute("name"); + if (!CheckString.forOCamlModuleName(name)) { + throw new NickelException(NickelException.Phase.XML_PARSE, + String.format(ParseXML.INVALID_OCAML_MODULE_NAME, + name)); + } // end if + if (verbose != null) { + verbose.printf("... dbmodule '%s'\n", name); + } // end if + + // "meta" elements + final NodeList metas = module.getElementsByTagName("meta"); + final int lenMetas = metas.getLength(); + for (int i = 0; i < lenMetas; i++) { + processMeta((Element) metas.item(i), + meta, + verbose, + debug); + } // end for + + // "command" elements + final NodeList commands = module.getElementsByTagName("command"); + final int lenCommands = commands.getLength(); + for (int i = 0; i < lenCommands; i++) { + processCommand((Element) commands.item(i), + statements, + verbose, + debug); + } // end for + + // "query" elements + final NodeList queries = module.getElementsByTagName("query"); + final int lenQueries = queries.getLength(); + for (int i = 0; i < lenQueries; i++) { + processQuery((Element) queries.item(i), + statements, + verbose, + debug); + } // end for + + return name; + } // end method 'processDbModule(Element, List, Map< String, List >, PrintStream, PrintStream)' + + /** + * Processes a "meta" node. + * @param met meta node to process - should not be null + * @param meta map to add meta definitions to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @throws NickelException if any error occurs + */ + private static void processMeta(final Element met, + final Map< String, List > meta, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert met != null : "null met"; + assert meta != null : "null meta"; + + final String name = met.getAttribute("name"); + if (!CheckString.forMetaName(name)) { + throw new NickelException(NickelException.Phase.XML_PARSE, + String.format(ParseXML.INVALID_META_NAME, + name)); + } // end if + final String value = met.getAttribute("value"); + final List list = meta.get(name); + if (list != null) { + list.add(value); + } else { + final List newList = new LinkedList(); + newList.add(value); + meta.put(name, newList); + } // end if/else + if (verbose != null) { + verbose.printf("... meta named '%s' with value '%s'\n", name, value); + } // end if + } // end method 'processMeta(Element, Map< String, List >, PrintStream, PrintStream)' + + /** + * Processes a "command" node. + * @param command command node to process - should not be null + * @param statements list to add statements to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @throws NickelException if any error occurs + */ + private static void processCommand(final Element command, + final List statements, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert command != null : "null command"; + assert statements != null : "null statements"; + + final String name = command.getAttribute("name"); + if (!CheckString.forOCamlClassName(name)) { + throw new NickelException(NickelException.Phase.XML_PARSE, + String.format(ParseXML.INVALID_NAME, name)); + } // end if + final String code = command.getAttribute("code"); + final boolean prepare = "yes".equals(command.getAttribute("prepare")); + statements.add(new SQLStatement(SQLStatement.Kind.COMMAND, + name, + code == null ? "" : code, + prepare, + false)); + if (verbose != null) { + verbose.printf("... command '%s' with code '%s'\n", name, code); + } // end if + } // end method 'processCommand(Element, List, PrintStream, PrintStream)' + + /** + * Processes a "query" node. + * @param query query node to process - should not be null + * @param statements list to add statements to - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @throws NickelException if any error occurs + */ + private static void processQuery(final Element query, + final List statements, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert query != null : "null query"; + assert statements != null : "null statements"; + + final String name = query.getAttribute("name"); + if (!CheckString.forOCamlClassName(name)) { + throw new NickelException(NickelException.Phase.XML_PARSE, + String.format(ParseXML.INVALID_NAME, name)); + } // end if + final String code = query.getAttribute("code"); + final boolean prepare = "yes".equals(query.getAttribute("prepare")); + final boolean updatable = "yes".equals(query.getAttribute("updatable")); + statements.add(new SQLStatement(SQLStatement.Kind.QUERY, + name, + code == null ? "" : code, + prepare, + updatable)); + if (verbose != null) { + verbose.printf("... query '%s' with code '%s'\n", name, code); + } // end if + } // end method 'processQuery(Element, List, PrintStream, PrintStream)' + +} // end class 'ParseXML' addfile ./src/fr/x9c/nickel/database/SQLStatement.java hunk ./src/fr/x9c/nickel/database/SQLStatement.java 1 +/* + * This file is part of Nickel. + * Copyright (C) 2007-2008 Xavier Clerc. + * + * Nickel is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Nickel 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package fr.x9c.nickel.database; + +import java.io.PrintStream; +import java.sql.Connection; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +import fr.x9c.nickel.NickelException; + +/** + * This class defines a SQL statement to be run by a generated OCaml primitive. + * + * @author Xavier Clerc + * @version 1.1 + * @since 1.1 + */ +final class SQLStatement { + + /** The different kinds of SQL statements. */ + static enum Kind { + + /** SQL statement that do not return a value. */ + COMMAND, + + /** SQL statement that return a value. */ + QUERY + }; + + /** Beginning trace for statement resolution. */ + private static final String RESOLVING_TRACE = + "resolving %s ..."; + + /** Trace for error during statement resolution. */ + private static final String UNABLE_TO_EXECUTE = + "unable to execute %s: %s ..."; + + /** Kind of SQL statement. */ + private final Kind kind; + + /** Name of SQL statement. */ + private final String name; + + /** Code of SQL statement. */ + private final String code; + + /** Whether the SQL statement should be prepared upon connection. */ + private final boolean prepared; + + /** Whether the returned result sets should be updatable. */ + private final boolean updatable; + + /** Metadata for statement parameter, populated after (successful) resolution. */ + private ParameterMetaData parameterMetaData; + + /** Metadata for statement result set, populated after (successful) resolution. */ + private ResultSetMetaData resultSetMetaData; + + /** + * Constructs a SQL statement. + * @param k kind of statement - should not be null + * @param n name of statement - should not be null + * @param c code of statement - should not be null + * @param p whether the SQL statement should be prepared upon connection + * @param u whether the returned result sets should be updatable + */ + SQLStatement(final Kind k, + final String n, + final String c, + final boolean p, + final boolean u) { + assert k != null : "null k"; + assert n != null : "null n"; + assert c != null : "null c"; + assert (k == SQLStatement.Kind.QUERY) || !u : "incoherent parameters"; + this.kind = k; + this.name = n; + this.code = c; + this.prepared = p; + this.updatable = u; + this.parameterMetaData = null; + this.resultSetMetaData = null; + } // end constructor(Kind, String, String, boolean, boolean) + + /** + * Returns the kind of SQL statement. + * @return the kind of SQL statement + */ + Kind getKind() { + return this.kind; + } // end method 'getKind()' + + /** + * Returns the name of SQL statement. + * @return the name of SQL statement + */ + String getName() { + return this.name; + } // end method 'getName()' + + /** + * Returns the code of SQL statement. + * @return the code of SQL statement + */ + String getCode() { + return this.code; + } // end method 'getCode()' + + /** + * Tests whether the statement should be prepared upon connection. + * @return true if the statement should be prepared upon connection, + * false otherwise + */ + boolean isPrepared() { + return this.prepared; + } // end method 'isPrepared()' + + /** + * Tests whether the returned result sets should be updatable. + * @return true if the returned result sets should be updatable, + * false otherwise + */ + boolean isUpdatable() { + return this.updatable; + } // end method 'isUpdatable()' + + /** + * Returns the metadata for statement parameter. + * @return the metadata for statement parameter, + * null if resolution has not sucessfully completed + */ + ParameterMetaData getParameterMetaData() { + return this.parameterMetaData; + } // end method 'getParameterMetaData()' + + /** + * Returns the metadata for statement result set. + * @return the metadata for statement result set, + * null if resolution has not sucessfully completed + * or if statement does not return a value + */ + ResultSetMetaData getResultSetMetaData() { + return this.resultSetMetaData; + } // end method 'getResultSetMetaData()' + + /** + * Resolves a SQL, i.e. retrieves its related metadata. + * @param conn database connection - should not be null + * @param verbose where comments should be printed during process + * (null if such print should not occur) + * @param debug where debug elements (e.g. stack traces) + * should be printed when an error occurs + * (null if such print should not occur) + * @throws NickelException if an error occurs during resolution + */ + void resolve(final Connection conn, + final PrintStream verbose, + final PrintStream debug) + throws NickelException { + assert conn != null : "null conn"; + if (verbose != null) { + verbose.println(String.format(SQLStatement.RESOLVING_TRACE, this.name)); + } // end if + try { + final PreparedStatement ps = conn.prepareStatement(this.code); + this.parameterMetaData = ps.getParameterMetaData(); + switch (this.kind) { + case COMMAND: + this.resultSetMetaData = null; + break; + case QUERY: + this.resultSetMetaData = ps.getMetaData(); + break; + default: + // should never be reached + assert false : "invalid case"; + this.resultSetMetaData = null; + break; + } // end switch + } catch (final SQLException se) { + if (debug != null) { + se.printStackTrace(debug); + } // end if + throw new NickelException(NickelException.Phase.RESOLUTION, + String.format(SQLStatement.UNABLE_TO_EXECUTE, + this.name, + this.code)); + } // end try/catch + } // end method 'resolve(Connection, PrintStream, PrintStream)' + +} // end class 'SQLStatement' addfile ./src/fr/x9c/nickel/database/package.html hunk ./src/fr/x9c/nickel/database/package.html 1 + +This package contains the Nickel module producer whose goal is to generate both +OCaml and Java sources (C source is also generated to allow compilation) to +access to a database through a JDBC connection. These bridges are intended to be +used with the Cadmium runtime. + }