net.metanotion.sqlc.SQLC.java Source code

Java tutorial

Introduction

Here is the source code for net.metanotion.sqlc.SQLC.java

Source

/***************************************************************************
   Copyright 2008 Emily Estes
    
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
    
   http://www.apache.org/licenses/LICENSE-2.0
    
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
***************************************************************************/
package net.metanotion.sqlc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.sql.PreparedStatement;

import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.metanotion.scripting.Struct;
import net.metanotion.scripting.StructManager;
import net.metanotion.util.Pair;
import net.metanotion.util.reflect.GetInitializer;

import net.metanotion.sqlc.setters.*;
import net.metanotion.sqlc.parser.ParseException;
import net.metanotion.sqlc.parser.SQLParser;

/** SQL Compiler. */
public final class SQLC {
    private static final Logger logger = LoggerFactory.getLogger(SQLC.class);

    private static File getFilePath(final String outPath, final String[] packageName) {
        String outFolder = outPath;
        for (final String pe : packageName) {
            outFolder += File.separator + pe;
        }
        logger.debug("::" + outFolder + " - " + packageName.length);
        return new File(outFolder);
    }

    private static String mkPath(final String outPath, final String[] packageName, final String name) {
        final File f = getFilePath(outPath, packageName);
        f.mkdirs();
        return (f.toString() + File.separator + name + ".java");
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final DoWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        for (final QueryExpr<PreparedStatement> e : qe.exprs) {
            makeMethod(writer, m, e, level + 1, gensym, braces, retValue);
        }
        if (level == 0) {
            writer.println("\t\t\treturn _" + (gensym[0] - 1) + ";");
            while (braces[0] > 0) {
                writer.println("}");
                braces[0]--;
            }
        }
        return gensym[0] - 1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final ListWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        makeMethod(writer, m, qe.e, level + 1, gensym, braces, retValue);
        final int stmt = gensym[0] - 1;

        final int rsVar = gensym[0];
        gensym[0]++;

        writer.println("\t\t\ttry (final java.sql.ResultSet _" + rsVar + " = _" + stmt + ".getResultSet()) {");
        braces[0] = braces[0] + 1;

        // convert result set to list.
        final int list = gensym[0];
        gensym[0]++;

        if (qe.g instanceof StructGetter) {
            final StructGetter sg = (StructGetter) qe.g;
            writer.println("\t\t\tfinal java.util.List<" + sg.struct + "> _" + list + " = new java.util.ArrayList<"
                    + sg.struct + ">();");

            final int gi = gensym[0];
            final int init = gensym[0] + 1;
            gensym[0] += 2;
            writer.println("\t\tfinal net.metanotion.util.reflect.GetInitializer<" + sg.struct + "> _" + gi
                    + " = net.metanotion.util.reflect.ReflectiveFieldInitializer.getInitializer(" + sg.struct
                    + ".class, this.types);");
            writer.println("\t\twhile(_" + rsVar + ".next()) {");
            writer.println("\t\t\t\t\tfinal net.metanotion.util.reflect.Initializer<" + sg.struct + "> _" + init
                    + " = _" + gi + ".initializer();");
            for (final RSGetter rg : sg.fields) {
                writer.println(
                        "\t\t\t\t\t_" + init + ".put(\"" + rg.name + "\", " + rg.getStatic("_" + rsVar) + ");");
            }
            writer.println("\t\t\t\t\t_" + list + ".add(_" + init + ".instance());");
        } else if (qe.g instanceof ValueGetter) {
            final RSGetter rg = ((ValueGetter) qe.g).field;
            writer.println("\t\t\tfinal java.util.List<" + rg.type + "> _" + list + " = new java.util.ArrayList<"
                    + rg.type + ">();");

            writer.println("\t\twhile(_" + rsVar + ".next()) {");
            writer.println("\t\t\t\t\t_" + list + ".add(" + rg.getStatic("_" + rsVar, 1) + ");");
        }
        writer.println("\t\t}");
        if (retValue) {
            writer.println("\t\t\treturn _" + list + ";");
            while (braces[0] > 0) {
                writer.println("}");
                braces[0]--;
            }
        }
        return list;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final Statement qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        final int stmt = gensym[0];
        gensym[0]++;
        writer.println("\t\t\ttry (final java.sql.PreparedStatement _" + stmt + " = _0.prepareStatement(\""
                + StringEscapeUtils.escapeJava(qe.sql.trim()) + "\")) {");
        braces[0] = braces[0] + 1;
        for (final SQLSetter s : qe.setters) {
            int i = 0;
            for (int j = 0; j < m.pList.length; j++) {
                if (s.name.equals(m.pList[j])) {
                    i = j + 1;
                    break;
                }
            }
            writer.println("\t\t\t" + s.setStatic("_" + stmt, "_" + i) + ";");
        }
        writer.println("\t\t\t_" + stmt + ".execute();");
        return -1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final StatementMacro qe,
            final int level, final int[] gensym, final int[] braces, final boolean retValue) {
        final int sb = gensym[0];
        gensym[0]++;
        final int stmt = gensym[0];
        gensym[0]++;
        writer.println("\t\t\tfinal StringBuilder _" + sb + " = new StringBuilder();");
        for (final SQLElement e : qe.statement) {
            if (e instanceof SEConstant) {
                writer.println("\t\t\t_" + sb + ".append(\"" + StringEscapeUtils.escapeJava(((SEConstant) e).sql)
                        + "\");");
            } else {
                final String vName = ((SEMacro) e).name;
                for (int i = 0; i < m.pList.length; i++) {
                    if (m.pList[i].equals(vName)) {
                        writer.println("\t\t\t_" + sb + ".append(_" + (i + 1) + ".toString());");
                    }
                }
            }
        }

        writer.println("\t\t\ttry (final java.sql.PreparedStatement _" + stmt + " = _0.prepareStatement(_" + sb
                + ".toString())) {");
        braces[0] = braces[0] + 1;
        for (final SQLSetter s : qe.setters) {
            int i = 0;
            for (int j = 0; j < m.pList.length; j++) {
                if (s.name.equals(m.pList[j])) {
                    i = j + 1;
                    break;
                }
            }
            writer.println("\t\t\t" + s.setStatic("_" + stmt, "_" + i) + ";");
        }
        writer.println("\t\t\t_" + stmt + ".execute();");
        return -1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final TXWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        writer.println("\t\ttry {");
        writer.println("\t\t\t_0.setAutoCommit(false);");
        makeMethod(writer, m, qe.expr, level + 1, gensym, braces, retValue);
        if (!(qe.expr instanceof VoidWrap)) {
            writer.println("\t\t\treturn _" + (gensym[0] - 1) + ";");
        }
        while (braces[0] > 0) {
            writer.println("}");
            braces[0]--;
        }
        writer.println("\t\t} catch (final java.sql.SQLException sqle) {");
        writer.println("\t\t\t_0.rollback();");
        writer.println("\t\t\tthrow sqle;");
        writer.println("\t\t} finally {");
        writer.println("\t\t\t_0.setAutoCommit(true);");
        writer.println("\t\t}");
        return -1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final ValueWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        makeMethod(writer, m, qe.e, level + 1, gensym, braces, retValue);
        final int stmt = gensym[0] - 1;

        final int rsVar = gensym[0];
        gensym[0]++;

        writer.println("\t\t\ttry (final java.sql.ResultSet _" + rsVar + " = _" + stmt + ".getResultSet()) {");
        braces[0] = braces[0] + 1;

        writer.println("\t\t_" + rsVar + ".next();");
        if (qe.g instanceof StructGetter) {
            final StructGetter sg = (StructGetter) qe.g;
            final int gi = gensym[0];
            final int init = gensym[0] + 1;
            gensym[0] += 2;
            writer.println("\t\tfinal net.metanotion.util.reflect.GetInitializer<" + sg.struct + "> _" + gi
                    + " = net.metanotion.util.reflect.ReflectiveFieldInitializer.getInitializer(" + sg.struct
                    + ".class, this.types);");
            writer.println("\t\tfinal net.metanotion.util.reflect.Initializer<" + sg.struct + "> _" + init + " = _"
                    + gi + ".initializer();");
            for (final RSGetter rg : sg.fields) {
                writer.println("\t\t_" + init + ".put(\"" + rg.name + "\", " + rg.getStatic("_" + rsVar) + ");");
            }
            if (retValue) {
                writer.println("\t\treturn _" + init + ".instance();");
            } else {
                gensym[0]++;
                writer.println(
                        "\t\t\tfinal " + sg.struct + " _" + (gensym[0] - 1) + " = _" + init + ".instance();");
            }
        } else if (qe.g instanceof ValueGetter) {
            final RSGetter rg = ((ValueGetter) qe.g).field;
            if (retValue) {
                writer.println("\t\t\treturn " + rg.getStatic("_" + rsVar, 1) + ";");
            } else {
                gensym[0]++;
                writer.println("\t\t\tfinal " + rg.type + " _" + (gensym[0] - 1) + " =  "
                        + rg.getStatic("_" + rsVar, 1) + ";");
            }
        }
        if (retValue) {
            while (braces[0] > 0) {
                writer.println("}");
                braces[0]--;
            }
        }
        return gensym[0] - 1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final OptionalWrap qe,
            final int level, final int[] gensym, final int[] braces, final boolean retValue) {
        makeMethod(writer, m, qe.e, level + 1, gensym, braces, retValue);
        final int stmt = gensym[0] - 1;

        final int rsVar = gensym[0];
        gensym[0]++;

        writer.println("\t\t\ttry (final java.sql.ResultSet _" + rsVar + " = _" + stmt + ".getResultSet()) {");
        braces[0] = braces[0] + 1;

        final int result = gensym[0];
        gensym[0]++;
        if (!retValue) {
            writer.println("\t\t\t" + ((qe.g instanceof StructGetter) ? (((StructGetter) qe.g).struct)
                    : (((RSGetter) (((ValueGetter) qe.g).field)).type)) + " _" + result + ";");
        }

        writer.println("\t\t\tif (_" + rsVar + ".next()) {");
        if (qe.g instanceof StructGetter) {
            final StructGetter sg = (StructGetter) qe.g;
            final int gi = gensym[0];
            final int init = gensym[0] + 1;
            gensym[0] += 2;
            writer.println("\t\t\tfinal net.metanotion.util.reflect.GetInitializer<" + sg.struct + "> _" + gi
                    + " = net.metanotion.util.reflect.ReflectiveFieldInitializer.getInitializer(" + sg.struct
                    + ".class, this.types);");
            writer.println("\t\t\tfinal net.metanotion.util.reflect.Initializer<" + sg.struct + "> _" + init
                    + " = _" + gi + ".initializer();");
            for (final RSGetter rg : sg.fields) {
                writer.println("\t\t\t_" + init + ".put(\"" + rg.name + "\", " + rg.getStatic("_" + rsVar) + ");");
            }
            if (retValue) {
                writer.println("\t\t\treturn _" + init + ".instance();");
            } else {
                writer.println("\t\t\t\t_" + result + " = _" + init + ".instance();");
            }
        } else if (qe.g instanceof ValueGetter) {
            final RSGetter rg = ((ValueGetter) qe.g).field;
            if (retValue) {
                writer.println("\t\t\t\treturn " + rg.getStatic("_" + rsVar, 1) + ";");
            } else {
                writer.println("\t\t\t\t_" + result + " = _" + rg.getStatic("_" + rsVar, 1) + ";");
            }
        }
        if (retValue) {
            writer.println("\t\t\t\t} else { return null; }");
        } else {
            writer.println("\t\t\t\t} else { _" + result + " = null; }");
        }

        if (retValue) {
            while (braces[0] > 0) {
                writer.println("}");
                braces[0]--;
            }
        }
        return result;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final AssignmentWrap qe,
            final int level, final int[] gensym, final int[] braces, final boolean retValue) {
        final int gi = gensym[0];
        final int init = gensym[0] + 1;
        gensym[0] += 2;
        writer.println("\t\t\tfinal net.metanotion.util.reflect.GetInitializer<" + qe.result + "> _" + gi
                + " = net.metanotion.util.reflect.ReflectiveFieldInitializer.getInitializer(" + qe.result
                + ".class, this.types);");
        writer.println("\t\t\tfinal net.metanotion.util.reflect.Initializer<" + qe.result + "> _" + init + " = _"
                + gi + ".initializer();");

        final Iterator<String> fields = qe.fields.iterator();
        final Iterator<QueryExpr> qs = qe.exprs.iterator();
        while (qs.hasNext()) {
            final QueryExpr q = qs.next();
            final String field = fields.next();
            int returnSymbol = makeMethod(writer, m, q, level, gensym, braces, false);
            writer.println("\t\t\t_" + init + ".put(\"" + field + "\", _" + (returnSymbol) + ");");
        }
        writer.println("\t\t\treturn _" + init + ".instance();");
        while (braces[0] > 0) {
            writer.println("}");
            braces[0]--;
        }
        return -1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final CountWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        makeMethod(writer, m, qe.expr, level + 1, gensym, braces, retValue);
        final int stmt = gensym[0] - 1;
        if (retValue) {
            writer.println("\t\t\treturn _" + stmt + ".getUpdateCount();");
            while (braces[0] > 0) {
                writer.println("}");
                braces[0]--;
            }
        } else {
            gensym[0]++;
            writer.println("\t\t\tfinal int _" + (gensym[0] - 1) + " = _" + stmt + ".getUpdateCount();");
        }
        return gensym[0] - 1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final VoidWrap qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        makeMethod(writer, m, qe.e, level + 1, gensym, braces, retValue);
        while (braces[0] > 0) {
            writer.println("}");
            braces[0]--;
        }
        return -1;
    }

    public static int makeMethod(final PrintWriter writer, final SQLMethod m, final QueryExpr qe, final int level,
            final int[] gensym, final int[] braces, final boolean retValue) {
        if (qe instanceof AssignmentWrap) {
            return makeMethod(writer, m, (AssignmentWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof CountWrap) {
            return makeMethod(writer, m, (CountWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof DoWrap) {
            return makeMethod(writer, m, (DoWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof ListWrap) {
            return makeMethod(writer, m, (ListWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof Statement) {
            return makeMethod(writer, m, (Statement) qe, level, gensym, braces, retValue);
        } else if (qe instanceof StatementMacro) {
            return makeMethod(writer, m, (StatementMacro) qe, level, gensym, braces, retValue);
        } else if (qe instanceof TXWrap) {
            return makeMethod(writer, m, (TXWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof OptionalWrap) {
            return makeMethod(writer, m, (OptionalWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof ValueWrap) {
            return makeMethod(writer, m, (ValueWrap) qe, level, gensym, braces, retValue);
        } else if (qe instanceof VoidWrap) {
            return makeMethod(writer, m, (VoidWrap) qe, level, gensym, braces, retValue);
        } else {
            throw new RuntimeException("Invalid QueryExpr type");
        }
    }

    private static boolean needToGenerateStruct(final String clsName, final GetInitializer gi,
            final Iterable<String> srcPathes) {
        logger.debug("Checking {}", clsName);
        if (!(gi instanceof Struct)) {
            return false;
        }
        final String[] name = clsName.split("\\.");
        final String[] pkg = new String[name.length - 1];
        for (int i = 0; i < pkg.length; i++) {
            pkg[i] = name[i];
        }
        for (final String path : srcPathes) {
            try {
                final File javaSrc = new File(
                        getFilePath(path, pkg).toString() + File.separator + name[pkg.length] + ".java");
                logger.debug("  Trying {} with {}", path, javaSrc);
                if (javaSrc.exists()) {
                    logger.debug("   Exists!");
                    return false;
                }
            } catch (Exception e) {
                logger.debug("fail", e);
            }
        }
        return true;
    }

    public static void compile(final String outputFolder, final SQLClass q, final StructManager sm,
            final Iterable<String> srcPathes) throws IOException {
        final String visibility = q.isPublic() ? "public " : " ";
        System.out.println("COMPILING " + visibility + " " + q.getName());
        final String finality = q.isOpen() ? "" : "final ";
        final String[] name = q.getName().split("\\.");
        final String[] pkg = new String[name.length - 1];
        for (int i = 0; i < pkg.length; i++) {
            pkg[i] = name[i];
        }
        logger.debug("package name:" + q.getName() + " - " + name.length);

        try (final FileOutputStream fos = new FileOutputStream(mkPath(outputFolder, pkg, name[pkg.length]))) {
            try (final PrintWriter writer = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"))) {

                writer.println(q.packageDoc);
                if (pkg.length > 0) {
                    writer.print("package ");
                    String sep = "";
                    for (final String pe : pkg) {
                        writer.print(sep + pe);
                        sep = ".";
                    }
                    writer.println(";");
                    writer.println("");
                }

                writer.print("/**");
                writer.println(q.docString.substring(3, q.docString.length() - 2));
                writer.println("<i>This is a SQL query class generated by the SQLC compiler.</i> */");
                writer.println("@javax.annotation.Generated(\"net.metanotion.sqlc.SQLC\") " + visibility + finality
                        + " class " + name[pkg.length]
                        + ((q.implement != null) ? (" implements " + q.implement) : "") + " {");
                writer.println(
                        "\tprivate final net.metanotion.util.Dictionary<Class,net.metanotion.util.types.Parser> types;");
                writer.println("\tpublic " + name[pkg.length]
                        + "() { this.types = new net.metanotion.util.types.TypeDictionary(); }");
                writer.println("\tpublic " + name[pkg.length]
                        + "(net.metanotion.util.Dictionary<Class,net.metanotion.util.types.Parser> types) { this.types = types; }");

                for (final SQLMethod m : q.getMethods()) {
                    final int[] gensym = new int[] { 1 };
                    final String type = (m.modifier == null) ? m.finalType : (m.modifier + "<" + m.finalType + ">");
                    writer.println(m.docString);
                    writer.print("\tpublic " + type + " " + m.name + "(final java.sql.Connection _0");
                    for (final String p : m.pList) {
                        writer.print(", final @javax.inject.Named(\"" + p + "\") " + m.parameterTypes.get(p) + " _"
                                + gensym[0]);
                        gensym[0]++;
                    }
                    writer.println(") throws");
                    for (final ExceptionBlock ex : m.exceptions) {
                        writer.println("\t\t" + ex.name + ",");
                    }
                    writer.println("\t\tException {");
                    if (m.exceptions.size() > 0) {
                        writer.println("\t\ttry {");
                    }
                    makeMethod(writer, m, m.body, 0, gensym, new int[] { 0 }, true);
                    if (m.exceptions.size() > 0) {
                        final int exVar = gensym[0];
                        gensym[0]++;
                        writer.println("\t\t} catch (final java.sql.SQLException _" + exVar + ") {");
                        for (final ExceptionBlock ex : m.exceptions) {
                            writer.println("\t\t\tif (");
                            String sep = "";
                            for (final ExceptionBlock.Pattern pattern : ex.patterns) {
                                writer.print("\t\t\t\t\t" + sep);
                                if (pattern instanceof ExceptionBlock.EqualityCheck) {
                                    final ExceptionBlock.EqualityCheck ec = (ExceptionBlock.EqualityCheck) pattern;
                                    final Object value = ((ExceptionBlock.Literal) ec.value).value;
                                    writer.print("(" + value + ".equals(");
                                    if ("SQLState".equals(ec.variable)) {
                                        writer.print("_" + exVar + ".getSQLState())");
                                    } else if ("message".equals(ec.variable)) {
                                        writer.print("_" + exVar + ".getMessage())");
                                    }
                                    writer.println(")");
                                } else if (pattern instanceof ExceptionBlock.ValueContains) {
                                    final ExceptionBlock.ValueContains vc = (ExceptionBlock.ValueContains) pattern;
                                    if (!"message".equals(vc.variable)) {
                                        throw new RuntimeException(
                                                "message only supported exception variable for contains clause.");
                                    }
                                    writer.print("(");
                                    String sep2 = "";
                                    for (final ExceptionBlock.Param value : vc.values) {
                                        final Object v = ((ExceptionBlock.Literal) value).value;
                                        writer.println(sep2 + "(_" + exVar + ".getMessage().contains(" + v + "))");
                                        sep2 = "\t\t\t\t\t\t&&";
                                    }
                                    writer.print("\t\t\t\t\t)");
                                } else {
                                    throw new RuntimeException("invalid exception throw pattern.");
                                }
                                sep = "&&";
                            }
                            writer.println("\t\t\t) {");
                            writer.println("\t\t\t\tthrow new " + ex.name + "(");
                            sep = "";
                            for (final ExceptionBlock.Param param : ex.params) {
                                writer.print("\t\t\t\t\t" + sep);
                                if (param instanceof ExceptionBlock.Literal) {
                                    writer.println(((ExceptionBlock.Literal) param).value.toString());
                                } else {
                                    throw new RuntimeException(
                                            "variables in custom exception parameters not supported.");
                                }
                                sep = ", ";
                            }
                            writer.println("\t\t\t\t);");
                            writer.println("\t\t\t} else");
                        }
                        writer.println("\t\t\t{ throw _" + exVar + "; }");
                        writer.println("\t\t}");
                    }
                    writer.println("\t}");
                }
                writer.println("}");
            }
        }
    }

    private static void generateExceptions(final String outputFolder, final Map<String, ExceptionInfo> exceptions,
            final Map<String, String> inheritanceMap, final Set<String> implicits,
            final Map<String, String> implicitDocs, final Map<String, Boolean> implicitVisibility)
            throws IOException {
        for (final Map.Entry<String, ExceptionInfo> ex : exceptions.entrySet()) {
            if (!implicits.contains(ex.getKey())) {
                continue;
            }
            final String visibility = implicitVisibility.get(ex.getKey()).booleanValue() ? "public " : " ";
            final String docString = implicitDocs.get(ex.getKey());
            final String[] name = ex.getKey().split("\\.");
            final String[] pkg = new String[name.length - 1];
            for (int i = 0; i < pkg.length; i++) {
                pkg[i] = name[i];
            }
            final FileOutputStream fos = new FileOutputStream(mkPath(outputFolder, pkg, name[pkg.length]));
            final PrintWriter writer = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"));
            if (pkg.length > 0) {
                writer.print("package ");
                String sep = "";
                for (String pe : pkg) {
                    writer.print(sep + pe);
                    sep = ".";
                }
                writer.println(";");
                writer.println("");
            }
            writer.print("/** ");
            if (docString != null) {
                writer.println(docString.substring(3, docString.length() - 2));
            }
            writer.println("<i>This is an exception class generated by the SQLC compiler.</i> */");
            writer.println("@javax.annotation.Generated(\"net.metanotion.sqlc.SQLC\") " + visibility + "class "
                    + name[pkg.length] + " extends ");
            final String parent = inheritanceMap.get(ex.getKey());
            writer.println((parent == null) ? "java.lang.Exception" : parent);
            writer.println(" {");
            int curriedParamCount = 0;
            for (final Pair<String, Object> param : ex.getValue().params) {
                writer.println("\tpublic final " + valueType(param.getValue()) + " " + param.getKey() + ";");
                if (param.getValue() != null) {
                    curriedParamCount++;
                }
            }
            while (curriedParamCount > 0) {
                writer.println("\t" + name[pkg.length] + "(");
                String sep = "\t\t";
                int curryCount = curriedParamCount;
                for (final Pair<String, Object> param : ex.getValue().params) {
                    if ((param.getValue() != null) && (curryCount > 0)) {
                        curryCount--;
                        continue;
                    }
                    writer.println(sep + "final " + valueType(param.getValue()) + " " + param.getKey());
                    sep = "\t\t, ";
                }
                writer.println("\t) { this(");
                curryCount = curriedParamCount;
                sep = "\t\t";
                for (final Pair<String, Object> param : ex.getValue().params) {
                    if ((param.getValue() != null) && (curryCount > 0)) {
                        writer.println(sep + param.getValue().toString());
                        curryCount--;
                    } else {
                        writer.println(sep + param.getKey());
                    }
                    sep = "\t\t, ";
                }
                writer.println("\t); }");
                curriedParamCount--;
            }
            writer.println("\t" + name[pkg.length] + "(");
            String sep = "\t\t";
            for (final Pair<String, Object> param : ex.getValue().params) {
                writer.println(sep + "final " + valueType(param.getValue()) + " " + param.getKey());
                sep = "\t\t, ";
            }
            writer.println("\t) {");
            writer.println("\t\tsuper();");
            for (final Pair<String, Object> param : ex.getValue().params) {
                writer.println("\t\tthis." + param.getKey() + " = " + param.getKey() + ";");
            }
            writer.println("\t}");
            for (final Pair<String, Object> param : ex.getValue().params) {
                final String key = param.getKey();
                writer.println("\tpublic " + valueType(param.getValue()) + " get"
                        + key.substring(0, 1).toUpperCase() + key.substring(1) + "() {");
                writer.println("\t\treturn this." + key + ";");
                writer.println("\t}");
            }
            writer.println("}");
            writer.close();
            fos.close();
        }
    }

    private static String valueType(final Object value) {
        if (value instanceof Integer) {
            return "int";
        }
        if (value instanceof Boolean) {
            return "boolean";
        }
        if (value instanceof String) {
            return "String";
        }
        return "Object";
    }

    private static void generateStructs(final String outputFolder, final StructManager sm,
            final Set<String> implicits, final Map<String, String> implicitDocs,
            final Map<String, Boolean> implicitVisibility, final Iterable<String> srcPathes) throws IOException {
        for (final Map.Entry<String, GetInitializer> e : sm) {
            if (!implicits.contains(e.getKey())) {
                continue;
            }
            final String visibility = implicitVisibility.get(e.getKey()).booleanValue() ? "public " : " ";
            final String docString = implicitDocs.get(e.getKey());
            final GetInitializer gi = e.getValue();
            if (needToGenerateStruct(e.getKey(), gi, srcPathes)) {
                final Struct s = (Struct) gi;
                final String[] name = e.getKey().split("\\.");
                final String[] pkg = new String[name.length - 1];
                for (int i = 0; i < pkg.length; i++) {
                    pkg[i] = name[i];
                }
                final FileOutputStream fos = new FileOutputStream(mkPath(outputFolder, pkg, name[pkg.length]));
                final PrintWriter writer = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"));
                if (pkg.length > 0) {
                    writer.print("package ");
                    String sep = "";
                    for (String pe : pkg) {
                        writer.print(sep + pe);
                        sep = ".";
                    }
                    writer.println(";");
                    writer.println("");
                }

                System.out.println("GENERATING: " + visibility + " " + name[pkg.length]);
                writer.print("/** ");
                if (docString != null) {
                    writer.println(docString.substring(3, docString.length() - 2));
                }
                writer.println("<i>This is a data/struct/value class generated by the SQLC compiler.</i> */");
                writer.println("@javax.annotation.Generated(\"net.metanotion.sqlc.SQLC\") " + visibility
                        + "final class " + name[pkg.length] + " {");
                for (final String p : s.listProperties()) {
                    writer.print("\tpublic ");
                    writer.print(s.getType(p));
                    writer.println(" " + p + ";");
                }
                writer.println("}");
                writer.close();
                fos.close();
            }
        }
    }

    public static void processFile(final ClassInfo ir, final String sqlFileName, final Iterable<String> srcPathes)
            throws Exception {
        ir.classes.add(new SQLParser(new FileInputStream(sqlFileName)).Functions(ir.implicits, ir.sm,
                ir.implicitDocs, ir.implicitVisibility, ir.exceptions, ir.inheritanceMap));
    }

    public static final class ClassInfo {
        public final StructManager sm = new StructManager();
        public final HashMap<String, Boolean> implicitVisibility = new HashMap<>();
        public final HashMap<String, String> implicitDocs = new HashMap<>();
        public final HashSet<String> implicits = new HashSet<>();
        public final HashMap<String, ExceptionInfo> exceptions = new HashMap<>();
        public final HashMap<String, String> inheritanceMap = new HashMap<>();
        public final List<SQLClass> classes = new ArrayList<>();
    }

    public static void treewalk(final ClassInfo ir, final File folder, final Iterable<String> srcPathes)
            throws Exception {
        for (final File file : folder.listFiles()) {
            if (file.isFile()) {
                final String sqlFileName = file.toString();
                final int len = sqlFileName.length();
                if ((len > 4) && (".sql".equals(sqlFileName.substring(len - 4, len).toLowerCase()))) {
                    try {
                        processFile(ir, sqlFileName, srcPathes);
                    } catch (ParseException pe) {
                        logger.debug("Error in file {}", sqlFileName);
                        throw pe;
                    }
                }
            } else if (file.isDirectory()) {
                treewalk(ir, file, srcPathes);
            }
        }
    }

    public static void main(final String[] args) throws Exception {
        final String outputFolder = args[0];
        final String sqlFileName = args[1];

        Iterable<String> srcPathes = java.util.Collections.emptyList();
        if (args.length > 3) {
            srcPathes = java.util.Arrays.asList(args[3].split(File.pathSeparator));
        }

        final ClassInfo ir = new ClassInfo();
        final File f = new File(sqlFileName);
        if (f.isDirectory()) {
            treewalk(ir, f, srcPathes);
        } else {
            processFile(ir, sqlFileName, srcPathes);
        }
        for (final SQLClass q : ir.classes) {
            SQLC.compile(outputFolder, q, ir.sm, srcPathes);
        }
        if (ir.implicits.size() > 0) {
            SQLC.generateStructs(outputFolder, ir.sm, ir.implicits, ir.implicitDocs, ir.implicitVisibility,
                    srcPathes);
            SQLC.generateExceptions(outputFolder, ir.exceptions, ir.inheritanceMap, ir.implicits, ir.implicitDocs,
                    ir.implicitVisibility);
        }
    }
}