// Copyright (c) 2001 Dustin Sallings <dustin@spy.net> package net.spy.util; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintWriter; import java.lang.reflect.Field; import java.math.BigDecimal; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; import net.spy.SpyObject; import net.spy.db.QuerySelector; /** * Generator for .spt->.java. */ public class SPGen extends SpyObject { private BufferedReader in=null; private PrintWriter out=null; private String classname=null; private boolean isInterface=true; private boolean wantsResultSet=false; private boolean wantsCursor=false; private String section=""; private String description=""; private String procname=""; private String pkg=""; private String superclass=null; private String dbcpSuperclass=null; private String dbspSuperclass=null; private String superinterface=null; private long cachetime=0; private Map<String, List<String>> queries=null; private String currentQuery=QuerySelector.DEFAULT_QUERY; private List<Result> results=null; List<Parameter> args=null; private Set<String> interfaces=null; private Set<String> imports=null; private int timeout=0; private static Set<String> types=null; static Map<String, String> javaTypes=null; static Map<String, String> javaResultTypes=null; boolean looseTypes=false; private boolean typeDbsp=false; private boolean typeDbcp=false; private boolean verbose=true; /** * Get a new SPGen from the given BufferedReader. * * @param cn the name of the class to generate * @param i the stream containing the spt source * @param o the stream to which the java code will be written */ public SPGen(String cn, BufferedReader i, PrintWriter o) { super(); this.in=i; this.out=o; this.classname=cn; queries=new TreeMap<String, List<String>>(); results=new ArrayList<Result>(); args=new ArrayList<Parameter>(); interfaces=new HashSet<String>(); imports=new TreeSet<String>(); if(types==null) { initTypes(); } } /** * Set the verbosity flag. */ public void setVerbose(boolean to) { this.verbose=to; } /** * Set the superclass of the generated java class. */ public void setSuperclass(String sc) { if (sc!=null) { this.superclass=sc; } } /** * Add an interface to the implements line. * @param intf the fully qualified name of the interface to implement */ public void addInterface(String intf) { interfaces.add(intf); } /** * Add a collection of interfaces. * @param set the set of interfaces */ public void addInterfaces(Collection<String> set) { interfaces.addAll(set); } /** * Set the DBCP superclass of the generated java class. */ public void setDbcpSuperclass(String sc) { if (sc!=null) { this.dbcpSuperclass=sc; } } /** * Set the DBSP superclass of the generated java class. */ public void setDbspSuperclass(String sc) { if (sc!=null) { this.dbspSuperclass=sc; } } private static synchronized void initTypes() { if(types==null) { javaTypes=new HashMap<String, String>(); javaResultTypes=new HashMap<String, String>(); String jstypes="java.sql.Types."; int jstypeslen=jstypes.length(); // Map the jdbc types to useful java types String typ="java.sql.Types.BIT"; javaTypes.put(typ, "Boolean"); javaResultTypes.put(typ, "boolean"); typ="java.sql.Types.DATE"; javaTypes.put(typ, "Date"); javaResultTypes.put(typ, "java.sql.Date"); typ="java.sql.Types.DOUBLE"; javaTypes.put(typ, "Double"); javaResultTypes.put(typ, "double"); typ="java.sql.Types.FLOAT"; javaTypes.put(typ, "Float"); javaResultTypes.put(typ, "float"); typ="java.sql.Types.INTEGER"; javaTypes.put(typ, "Int"); javaResultTypes.put(typ, "int"); typ="java.sql.Types.BIGINT"; javaTypes.put(typ, "BigDecimal"); javaResultTypes.put(typ, "java.math.BigDecimal"); typ="java.sql.Types.NUMERIC"; javaTypes.put(typ, "BigDecimal"); javaResultTypes.put(typ, "java.math.BigDecimal"); typ="java.sql.Types.DECIMAL"; javaTypes.put(typ, "BigDecimal"); javaResultTypes.put(typ, "java.math.BigDecimal"); typ="java.sql.Types.SMALLINT"; javaTypes.put(typ, "Int"); javaResultTypes.put(typ, "int"); typ="java.sql.Types.TINYINT"; javaTypes.put(typ, "Int"); javaResultTypes.put(typ, "int"); typ="java.sql.Types.OTHER"; javaTypes.put(typ, "Object"); javaResultTypes.put(typ, "Object"); typ="java.sql.Types.VARCHAR"; javaTypes.put(typ, "String"); javaResultTypes.put(typ, "String"); typ="java.sql.Types.CLOB"; javaTypes.put(typ, "String"); javaResultTypes.put(typ, "String"); typ="java.sql.Types.TIME"; javaTypes.put(typ, "Time"); javaResultTypes.put(typ, "java.sql.Time"); typ="java.sql.Types.TIMESTAMP"; javaTypes.put(typ, "Timestamp"); javaResultTypes.put(typ, "java.sql.Timestamp"); // Same as above, without the java.sql. part Map<String, String> tmp=new HashMap<String, String>(); for(Map.Entry<String, String> me : javaTypes.entrySet()) { String k=me.getKey(); if(k.startsWith(jstypes)) { tmp.put(k.substring(jstypeslen), me.getValue()); } } javaTypes.putAll(tmp); tmp.clear(); for(Map.Entry<String, String> me : javaResultTypes.entrySet()) { if(me.getKey().startsWith(jstypes)) { tmp.put(me.getKey().substring(jstypeslen), me.getValue()); } } javaResultTypes.putAll(tmp); Field[] fields=java.sql.Types.class.getDeclaredFields(); types=new HashSet<String>(); for(int i=0; i<fields.length; i++) { types.add(fields[i].getName()); } } } /** * Return true if this is a valid JDBC type. * * @param name the name of the field to test * @return true if the field is valid */ public static boolean isValidJDBCType(String name) { return(types.contains(name)); } /** * Perform the actual generation. * * @throws Exception if there's a problem parsing or writing */ public void generate() throws Exception { parse(); write(); } // Make a pretty string out of the cache time for the documentation. private String formatCacheTime() { long nowT=System.currentTimeMillis(); long thenT=nowT-(cachetime * 1000); Date now=new Date(nowT); Date then=new Date(thenT); TimeSpan ts=new TimeSpan(now, then); return(ts.toString()); } // Create a methodable name (i.e. blah returns Blah so you can make // getBlah. private String methodify(String word) { StringBuilder sb=new StringBuilder(word.length()); StringTokenizer st=new StringTokenizer(word, "_"); while(st.hasMoreTokens()) { String part=st.nextToken(); StringBuilder mntmp=new StringBuilder(part); char c=Character.toUpperCase(mntmp.charAt(0)); mntmp.setCharAt(0, c); sb.append(mntmp.toString()); } return(sb.toString()); } // Create a specific set method for a given parameter. private String createSetMethod(Parameter p) throws Exception { String rv=null; String[] atypes=null; // Get the type map entry for this parameter try { ResourceBundle typeMap= ResourceBundle.getBundle("net.spy.db.typemap"); String typeString=typeMap.getString(p.getType()); atypes=SpyUtil.split(" ", typeString); } catch(MissingResourceException e) { getLogger().warn("Can't set all types for " + p, e); String[] typesTmp={"java.lang.Object"}; atypes=typesTmp; } String methodName=methodify(p.getName()); rv=""; for(int i=0; i<atypes.length; i++) { String type=atypes[i]; // Too verbose, need some way to configure this kind of stuff getLogger().debug("Generating " + p + " for " + type); rv+="\t/**\n" + "\t * Set the ``" + p.getName() + "'' parameter.\n" + "\t * " + p.getDescription() + "\n" + "\t *\n" + "\t * @param to the value to which to set the parameter\n" + "\t */\n" + "\tpublic void set" + methodName + "(" + type + " to)\n" + "\t\tthrows SQLException"; if(isInterface) { rv+=";\n"; } else { rv+=" {\n\n" + "\t\tsetArg(\"" + p.getName() + "\", to, " + p.getType() +");\n\t}\n"; } } return(rv); } private void write() throws Exception { if(verbose) { System.out.println("Writing out " + pkg + "." + classname); } // Copyright info out.println( "// Copyright (c) 2001 SPY internetworking <dustin@spy.net>\n" + "// Written by Dustin's SQL generator\n" + "//\n" + "// $" + "Id" + "$\n"); out.flush(); // Package info out.println("package " + pkg + ";\n"); // Imports imports.add("java.sql.SQLException"); if(!isInterface) { imports.add("java.sql.Connection"); imports.add("java.util.Map"); imports.add("java.util.HashMap"); imports.add("net.spy.util.SpyConfig"); } if(wantsResultSet && results.size() > 0) { imports.add("java.sql.ResultSet"); } // output imports for (String tmpimp : imports) { out.print("import "); out.print(tmpimp); out.println(";"); } out.println("\n"); // Generate the documentation. out.println("/**\n" + " * \n" + " * " + description + "\n" + " *\n" + " * <p>\n" + " *\n" + " * Generated by SPGen on " + new java.util.Date() + ".\n" + " *\n" + " * </p>\n" + " * <p>\n" + " *"); // cursor requested mode. if(wantsCursor) { out.println(" * <b>This query requests a cursor.</b>\n" + " *\n" + " * </p>\n" + " *\n" + " * <p>\n" + " *"); } // Different stuff for different classes if(typeDbsp) { out.println(" * <b>Procedure Name</b>\n" + " *\n" + " * <ul>\n" + " * <li>" + procname + "</li>\n" + " * </ul>"); } else if (typeDbcp) { out.println(" * <b>Callable Name</b>\n" + " *\n" + " * <ul>\n" + " * <li>" + procname + "</li>\n" + " * </ul>"); } else { // If it's not a stored procedure or callable, and it's not an // interface, show the query. if(!isInterface) { out.println(" * <b>SQL Query</b>\n" + " *\n" + " * <ul>\n" + " " + getDocQuery() + "\n" + " * </ul>"); } } // Required parameters out.println(" *\n" + " * <b>Required Parameters</b>\n" + " * <ul>"); if(getRequiredArgs(false).size()==0) { out.println(" * <li><i>none</i></li>"); } else { for(Parameter p : getRequiredArgs(false)) { out.print(" * <li>" + p.getName() + " - " + "{@link java.sql.Types#" + p.getShortType() + " " + p.getType() + "}\n * " + " - " + p.getDescription()); Default d=p.getDefaultValue(); if(d!=null) { out.print(" (default: <i>" + d.getPrintValue() + "</i>)"); } out.println("</li>"); } } out.println(" * </ul>\n" + " *\n" + " * </p>\n" + " * <p>\n" + " *"); // Optional parameters out.println(" *\n" + " * <b>Optional Parameters</b>\n" + " * <ul>"); if(getOptionalArgs().size()==0) { out.println(" * <li><i>none</i></li>"); } else { for(Parameter p : getOptionalArgs()) { out.println(" * <li>" + p.getName() + " - " + "{@link java.sql.Types#" + p.getShortType() + " " + p.getType() + "}\n * " + " - " + p.getDescription() + "</li>"); } } out.println(" * </ul>\n" + " *\n" + " * </p>\n" + " * <p>\n" + " *"); if (typeDbcp) { // Output parameters out.println(" *\n" + " * <b>Output Parameters</b>\n" + " * <ul>"); if(getOutputParameters().size()==0) { out.println(" * <li><i>none</i></li>"); } else { for(Parameter p : getOutputParameters()) { out.println(" * <li>" + p.getName() + " - " + "{@link java.sql.Types#" + p.getShortType() + " " + p.getType() + "}\n * " + " - " + p.getDescription() + "</li>"); } } out.println(" * </ul>\n" + " *\n" + " * </p>\n" + " * <p>\n" + " *"); } // Results if(results.size() > 0) { out.println(" * <b>Results</b>\n" + " * <ul>"); for(Result r : results) { out.print(" * <li>" + r.getName() + " - "); if(isValidJDBCType(r.getType())) { out.print("{@link java.sql.Types#" + r.getShortType() + " " + r.getType() + "}\n * "); } else { out.print(r.getType()); } out.println(" - " + r.getDescription() + "</li>"); } out.println(" * </ul>\n" + " *"); } // Document the cache time out.println(" * <b>Cache Time</b>\n" + " * <ul>"); if(cachetime > 0) { NumberFormat nf=NumberFormat.getNumberInstance(); out.println(" * <li>The results of this call will be cached for " + formatCacheTime() + " (" + nf.format(cachetime) + " seconds) by default.</li>"); } else { out.println(" * <li>The results of this call will not " + "be cached by default.</li>"); } out.println(" * </ul>"); // end the class documentation comment out.println(" * </p>\n" + " */"); // Actual code generation out.print("public " + (isInterface?"interface ":"class ") + classname + " extends " + (isInterface?superinterface:superclass)); if(interfaces.size() > 0) { // If this is an interface, extra interfaces are just appended if(isInterface) { out.print(", "); } else { out.print("\n\timplements "); } out.print(SpyUtil.join(interfaces, ", ")); } out.println(" {\n"); // The map (staticially initialized) if(!isInterface) { if(!(typeDbsp || typeDbcp)) { out.println("\tprivate static final Map<String, String> " + "queries=getQueries();\n"); } // Constructor documentation out.println("\t/**\n" + "\t * Construct a DBSP which will get its connections from\n" + "\t * SpyDB using the given config.\n" + "\t * @param conf the configuration to use\n" + "\t * @exception SQLException if there's a failure to " + "construct\n" + "\t */"); // SpyConfig constructor out.println("\tpublic " + classname + "(SpyConfig conf) " + "throws SQLException {\n" + "\t\t// Super constructor\n" + "\t\tsuper(conf);\n" + "\t\tspinit();\n" + "\t}\n"); // Constructor documentation out.println("\t/**\n" + "\t * Construct a DBSP which use the existing Connection\n" + "\t * for database operations.\n" + "\t * @param conn the connection to use\n" + "\t * @exception SQLException if there's a failure to " + "construct\n" + "\t */"); // Connection constructor out.println("\tpublic " + classname + "(Connection conn) " + "throws SQLException {\n" + "\t\t// Super constructor\n" + "\t\tsuper(conn);\n" + "\t\tspinit();\n" + "\t}\n"); // Initializer out.println("\tprivate void spinit() throws SQLException {"); // If a cursor was requested, build one if(wantsCursor) { out.println("\t\t// Generate a cursor for this query\n" + "\t\tgenerateCursorName();\n"); } // set the timeout variable out.println("\t\tsetQueryTimeout("+timeout+");\n"); // Figure out whether we're a DBSP or a DBSQL if(typeDbsp || typeDbcp) { out.println("\t\t// Set the stored procedure name\n" + "\t\tsetSPName(\"" + procname + "\");"); } else { out.println("\t\t// Register the SQL queries"); out.println("\t\tsetRegisteredQueryMap(queries);"); } // parameters if (args.size()>0) { out.println("\n\t\t// Set the parameters."); for (Parameter p : args) { if (p.isRequired()) { if (p.isOutput()) { out.println("\t\tsetOutput(\"" + p.getName() + "\", " + p.getType() + ");"); } else { out.println("\t\tsetRequired(\"" + p.getName() + "\", " + p.getType() + ");"); } } else { out.println("\t\tsetOptional(\"" + p.getName() + "\", " + p.getType() + ");"); } Default d=p.getDefaultValue(); if(d!=null) { out.println("\t\t// Default for " + d.getName()); out.println("\t\tset(\"" + d.getName() + "\", " + d.getPrintValue() + ");"); } } } // Set the cachetime, if there is one if(cachetime>0) { out.println("\n\t\t// Set the default cache time."); out.println("\t\tsetCacheTime(" + cachetime + ");"); } // End of spinit out.println("\t}\n"); // Create the static initializers if(!(typeDbsp || typeDbcp)) { out.println("\t// Static initializer for query map."); out.println( "\tprivate static Map<String, String> getQueries() {"); out.println("\t\t" + getJavaQueries()); out.println("\n\t\treturn(rv);"); out.println("\t}\n"); } } // Create set methods for all the individual parameters int count=1; for(Parameter p : args) { if (p.isOutput()) { // output param out.println(createGetOutputMethod(p, count)); } else { out.println(createSetMethod(p)); } count++; } // If we want result sets, add them. if(wantsResultSet) { if(results.size() > 0) { out.println(createExecuteMethods()); out.println(createResultClass()); } } out.println("}"); } private String createExecuteMethods() { String rv="\t/**\n" + "\t * Execute this query and get a Result object.\n" + "\t */\n" + "\tpublic Result getResult() throws SQLException {\n" + "\t\treturn(new Result(executeQuery()));\n" + "\t}\n"; return(rv); } private String createGetMethod(Result r) { String rv="\t\t/**\n" + "\t\t * Get the " + r.getName() + " value.\n" + "\t\t */\n" + "\t\tpublic " + r.getJavaResultType() + " get" + methodify(r.getName()) + "() throws SQLException {\n" + "\t\t\treturn(get" + r.getJavaType() + "(\"" + r.getName() + "\"));\n" + "\t\t}\n\n"; return(rv); } private String createGetOutputMethod(Parameter p, int index) { String rv="\tpublic Object get"+methodify(p.getName()) +"() throws SQLException {\n" +"\t\treturn(getCallableStatement().getObject("+index +"));\n" +"\t}\n\n"; return(rv); } private String createResultClass() { String rv=""; // Class header rv+="\t/**\n" + "\t * ResultSet object representing the results of this query.\n" + "\t */\n" + "\tpublic class Result extends net.spy.db.DBSPResult {\n" + "\n\t\tprivate Result(ResultSet rs) {\n" + "\t\t\tsuper(rs);\n" + "\t\t}\n\n"; for(Result r : results) { rv+=createGetMethod(r); } // End of class rv+="\n\t}\n"; return(rv); } // Fix > and < characters, and & characters if there are any private String docifySQL(String sql) { StringBuilder sb=new StringBuilder(sql.length()); char[] acters=sql.toCharArray(); for(int i=0; i<acters.length; i++) { switch(acters[i]) { case '>': sb.append(">"); break; case '<': sb.append("<"); break; case '&': sb.append("&"); break; default: sb.append(acters[i]); } } return (sb.toString()); } private String getDocQuery() { StringBuilder sb=new StringBuilder(1024); for(Map.Entry<String, List<String>> me : queries.entrySet()) { List<String> sqlquery=me.getValue(); sb.append(" * <li>\n"); sb.append(" * <b>\n"); sb.append(" * "); sb.append(me.getKey()); sb.append("\n"); sb.append(" * </b>\n"); sb.append(" * <pre>\n"); for(String part : sqlquery) { sb.append(" * "); sb.append(docifySQL(part)); sb.append("\n"); } sb.append(" * </pre>\n * </li>\n"); } return(sb.toString().trim()); } private String getJavaQueries() { StringBuilder sb=new StringBuilder(1024); sb.append("StringBuilder query=null;\n"); sb.append("\t\tMap<String, String> rv" + "=new HashMap<String, String>();\n"); for(Map.Entry<String, List<String>> me : queries.entrySet()) { List<String> sqlquery=me.getValue(); sb.append("\n\t\tquery=new StringBuilder(1024);"); for(String part : sqlquery) { sb.append("\n\t\tquery.append(\""); for(StringTokenizer st=new StringTokenizer(part, "\"", true); st.hasMoreTokens();) { String tmp=st.nextToken(); if(tmp.equals("\"")) { tmp="\\\""; } sb.append(tmp); } sb.append("\\n\");"); } sb.append("\n\t\trv.put(\""); sb.append(me.getKey()); sb.append("\", "); sb.append("query.toString());\n"); } return(sb.toString().trim()); } private void parse() throws Exception { // this is for when a user overrides the superclass StringBuilder userSuperclass=null; if(verbose) { System.out.println("Parsing " + classname + ".spt"); } String tmp=in.readLine(); while(tmp!=null) { // Don't do anything if the line is empty if(tmp.length() > 0) { if(tmp.charAt(0) == '@') { // lower case and trim before we begin...RAP WITH ME!! section=tmp.substring(1).trim().toLowerCase(); // System.out.println("Working on section " + section); // Handlers for things that occur when a section is begun if (section.equals("genresults")) { wantsResultSet=true; } else if (section.equals("cursor")) { wantsCursor=true; } else if (section.startsWith("loosetyp")) { looseTypes=true; } else if (section.startsWith("sql.")) { currentQuery=section.substring(4); section="sql"; } else if (section.equals("sql")) { currentQuery=QuerySelector.DEFAULT_QUERY; } } else if(tmp.charAt(0) == '#') { // Comment, ignore } else { if(section.equals("description")) { description+=tmp; } else if(section.equals("sql")) { isInterface=false; if (superclass==null) { superclass="net.spy.db.DBSQL"; } List<String> sqlquery=queries.get(currentQuery); if(sqlquery == null) { sqlquery=new ArrayList<String>(); queries.put(currentQuery, sqlquery); } sqlquery.add(tmp); } else if(section.equals("procname")) { isInterface=false; procname+=tmp; if (dbspSuperclass==null) { superclass="net.spy.db.DBSP"; } else { superclass=dbspSuperclass; } typeDbsp=true; } else if(section.equals("callable")) { isInterface=false; procname+=tmp; if (dbcpSuperclass==null) { superclass="net.spy.db.DBCP"; } else { superclass=dbcpSuperclass; } typeDbcp=true; } else if(section.equals("defaults")) { Default d=new Default(tmp); registerDefault(d); } else if(section.equals("params")) { Parameter param=new Parameter(tmp); args.add(param); } else if(section.equals("results")) { try { results.add(new Result(tmp)); } catch(IllegalArgumentException e) { System.err.println("Warning in " + classname + ": " + e.getMessage()); } } else if(section.equals("package")) { pkg+=tmp; } else if(section.equals("cachetime")) { cachetime=Long.parseLong(tmp); } else if(section.equals("timeout")) { timeout=Integer.parseInt(tmp); } else if(section.equals("superclass")) { userSuperclass=new StringBuilder(96); userSuperclass.append(tmp); } else if(section.equals("import")) { imports.add(tmp); } else if(section.equals("implements")) { interfaces.add(tmp); } else { throw new Exception("Unknown section: ``"+section+"''"); } } } tmp=in.readLine(); } // Make sure a superinterface got defined if(superinterface == null) { superinterface="net.spy.db.DBSPLike"; } // if the user over-rode (like your mom) the superclass, use it!! if (userSuperclass!=null) { if(isInterface) { superinterface=userSuperclass.toString(); } else { superclass=userSuperclass.toString(); } } } private void registerDefault(Default d) { boolean done=false; for(Parameter p : args) { if(p.getName().equals(d.getName())) { p.setDefaultValue(d); done=true; break; } } if(!done) { throw new IllegalArgumentException("Didn't find parameter " + d.getName() + " when registering"); } } // get all of the required arguments // If evenOutput is true, then we even get the output parameters private Collection<Parameter> getRequiredArgs(boolean evenOutput) { Collection<Parameter> rv=new ArrayList<Parameter>(args.size()); for(Parameter p : args) { if(p.isRequired()) { // Deal with output parameters if(p.isOutput()) { if(evenOutput) { rv.add(p); } } else { rv.add(p); } } } return(rv); } // get all of the required arguments private Collection<Parameter> getOptionalArgs() { Collection<Parameter> rv=new ArrayList<Parameter>(args.size()); for(Parameter p : args) { if(!p.isRequired()) { rv.add(p); } } return(rv); } // Get the output parameters private Collection<Parameter> getOutputParameters() { Collection<Parameter> rv=new ArrayList<Parameter>(args.size()); for(Parameter p : args) { if(p.isOutput()) { rv.add(p); } } return(rv); } // Private class for results static class Result extends Object { private String name=null; private String type=null; private String description=null; public Result(String line) { super(); StringTokenizer st=new StringTokenizer(line, " \t"); try { name=st.nextToken(); } catch(NoSuchElementException e) { throw new IllegalArgumentException("No name given for result"); } try { type=st.nextToken(); if(!isValidJDBCType(type)) { throw new IllegalArgumentException("Invalid JDBC type: " + type); } } catch(NoSuchElementException e) { throw new IllegalArgumentException( "No type given for result ``" + name + "''"); } try { description=st.nextToken("\n"); } catch(NoSuchElementException e) { throw new IllegalArgumentException( "No description given for result ``" + name + "''"); } } public String getName() { return(name); } public String getType() { return(type); } public String getShortType() { String rv=type; int i=type.lastIndexOf('.'); if(i>0) { rv=type.substring(i+1); } return(rv); } public String getJavaType() { String rv=javaTypes.get(type); if(rv==null) { throw new RuntimeException("Whoops! " + type + " must have been overlooked"); } return(rv); } public String getJavaResultType() { String rv=javaResultTypes.get(type); if(rv==null) { throw new RuntimeException("Whoops! " + type + " must have been overlooked"); } return(rv); } public String getDescription() { return(description); } } // Private class for parameters class Parameter extends Object { private String name=null; private boolean required=false; private String type=null; private String paramDescr=null; private boolean output=false; private Default defaultValue=null; public Parameter(String line) { super(); StringTokenizer st=new StringTokenizer(line, " \t"); try { name=st.nextToken(); } catch (NoSuchElementException ex) { // ASSERT: this theoretically should never happen, // otherwise how did we end up here in the first place? throw new IllegalArgumentException("Missing parameter name! " +ex.toString()); } String tmp=null; try { tmp=st.nextToken(); } catch (NoSuchElementException ex) { // at this point you have forgotten to add in the parameter // of whether or not the parameter is required/optional. I // guess a default could be applied here, but I'm just // gonna throw an Exception. throw new IllegalArgumentException( "Missing parameter requirement! " +ex.toString()); } if(tmp.equals("required")) { required=true; output=false; } else if(tmp.equals("optional")) { required=false; output=false; } else if(tmp.equals("output")) { required=true; output=true; } else { throw new IllegalArgumentException( "Parameter must be required or optional, not " + tmp + " like in " + line); } try { type=st.nextToken(); if(isValidJDBCType(type)) { type="java.sql.Types." + getParamTypeAlias(type); } else { if (!looseTypes) { throw new IllegalArgumentException("Invalid JDBC type: " + type); } } } catch (NoSuchElementException ex) { // now the variable type is missing That's no good, you // need a speficic type ya know. throw new IllegalArgumentException("Missing parameter type! " +ex.toString()); } try { // This character pretty much can't be in the line. paramDescr=st.nextToken("\n"); } catch (NoSuchElementException ex) { // I don't think we cre if it's documented or not! But // honestly I don't think this should ever happen cause you // need a newline. Well, I guess if you ended the file odd // enough, and without a EOL before the EOF...very odd case paramDescr=""; } } // Need to alias NUMERIC to DECIMAL, since I can't otherwise tell them // apart. private String getParamTypeAlias(String pt) { String rv=pt; if(pt.equals("NUMERIC")) { rv="DECIMAL"; } return(rv); } @Override public String toString() { return("{Parameter " + name + "}"); } /** * Get the hash code of the name of this parameter. */ @Override public int hashCode() { return(name.hashCode()); } /** * True if the given objet is an instance of Parameter with the * same name. */ @Override public boolean equals(Object o) { boolean rv=false; if(o instanceof Parameter) { Parameter p=(Parameter)o; rv=name.equals(p.name); } return(rv); } public String getName() { return(name); } public String getType() { return(type); } public String getShortType() { String rv=type; int i=type.lastIndexOf('.'); if(i>0) { rv=type.substring(i+1); } return(rv); } public String getDescription() { return(paramDescr); } public boolean isRequired() { return(required); } public boolean isOutput() { return(output); } public Default getDefaultValue() { return(defaultValue); } public void setDefaultValue(Default d) { defaultValue=d; } } // Default values for parameters class Default extends Object { private String name=null; // Class of this parameter private String type=null; private Object value=null; public Default(String line) { super(); StringTokenizer st=new StringTokenizer(line, " \t"); try { name=st.nextToken(); } catch (NoSuchElementException ex) { // ASSERT: this theoretically should never happen, // otherwise how did we end up here in the first place? throw new IllegalArgumentException("Missing parameter name! " +ex.toString()); } // Figure out the type of this parameter type=findType(); // Get the rest of the line String rest=st.nextToken("\n"); parse(rest); } public String getName() { return(name); } // Get the value to be used for printing in the source public String getPrintValue() { String rv=null; boolean needsCast=false; // Figure out if we need a cast if(value == null) { // If the value was null, cast it needsCast=true; } if(needsCast) { rv="(" + javaResultTypes.get(type) + ")" + value; } else { rv=value.toString(); } return(rv); } private void parse(String input) { if(input==null) { throw new NullPointerException("Can't parse a null string"); } // Get rid of whitespace from the ends input=input.trim(); // make sure there's something left if(input.length() == 0) { throw new IllegalArgumentException( "Can't parse nothin'"); } if(input.equals("NULL")) { value=null; } else { if(type.equals("java.sql.Types.INTEGER")) { value=new Integer(input); } else if(type.equals("java.sql.Types.SMALLINT")) { value=new Short(input); } else if(type.equals("java.sql.Types.TINYINT")) { value=new Short(input); } else if(type.equals("java.sql.Types.BIGINT")) { value=new BigDecimal(input); } else if(type.equals("java.sql.Types.Decimal")) { value=new BigDecimal(input); } else if(type.equals("java.sql.Types.BIT")) { value=SpyUtil.getBoolean(input); } else if(type.equals("java.sql.Types.DOUBLE")) { value=new Double(input); } else if(type.equals("java.sql.Types.FLOAT")) { value=new Float(input); } else if(type.equals("java.sql.Types.VARCHAR")) { value=input; } else { throw new IllegalArgumentException( "I don't know how to parse a default for " + type); } } } // Lookup the type for this spt. private String findType() { String rv=null; // Look for the parameter for(Parameter p : args) { if(p.getName().equals(name)) { rv=p.getType(); break; } } // Make sure we got some if(rv==null) { throw new IllegalArgumentException( "No parameter for this default: " + name); } return(rv); } // String me @Override public String toString() { String rv="{Default " + name + " (" + type + ") " + value + "}"; return(rv); } } /** * Usage: SPGen filename */ public static void main(String argv[]) throws Exception { String infile=argv[0]; // Get rid of the .spt int lastslash=infile.lastIndexOf(File.separatorChar); String basename=infile.substring(0, infile.indexOf(".spt")); // If it matches, start at the next character, if it didn't, it's // -1 and start at 0 String cname=basename.substring(lastslash+1); String outfile=basename + ".java"; BufferedReader ireader=new BufferedReader(new FileReader(infile)); PrintWriter owriter=new PrintWriter(new FileWriter(outfile)); SPGen spg=new SPGen(cname, ireader, owriter); spg.generate(); CloseUtil.close(ireader); CloseUtil.close(owriter); } }