Load.java
/***************************************************************************
Copyright 2012 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.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.metanotion.scripting.Struct;
import net.metanotion.scripting.StructManager;
import net.metanotion.sqlc.setters.RSGetter;
import net.metanotion.sqlc.setters.SQLSetter;
import net.metanotion.sqlc.parser.ParseException;
import net.metanotion.sqlc.parser.SQLParser;
import net.metanotion.sqlc.parser.syntaxtree.*;
/** This class parses a SQL class source file into the abstract syntax tree used by the SQLC framework.
The AST is represented by the {@link net.metanotion.sqlc.SQLClass} object.
*/
final class Load {
private static final Logger logger = LoggerFactory.getLogger(Load.class);
private static String pName(QCName q) {
final StringBuilder out = new StringBuilder();
out.append(q.f0.toString());
for(Node n: q.f1.nodes) {
out.append(".");
out.append(((NodeSequence) n).nodes.get(1).toString());
}
return out.toString();
}
private static String pName(QVName q) {
final StringBuilder out = new StringBuilder();
out.append(q.f0.toString());
for(Node n: q.f1.nodes) {
out.append(".");
out.append(((NodeSequence) n).nodes.get(1).toString());
}
return out.toString();
}
private static String pName(QName q) {
final StringBuilder out = new StringBuilder();
out.append(q.f0.toString());
for(Node n: q.f1.nodes) {
out.append(".");
out.append(((NodeSequence) n).nodes.get(1).toString());
}
return out.toString();
}
private static List<String> parseTypeExpr(TypeExpr te, Map<String,String> typeTranslation) {
final List<String> list = new ArrayList<String>();
while(te!=null) {
final String t = typeTranslation.get(pName(te.f0));
list.add((t != null) ? t : pName(te.f0));
if(te.f1.present()) {
te = (TypeExpr) ((NodeSequence) te.f1.node).nodes.get(1);
} else {
te = null;
}
}
return list;
}
private static void parseParam(Param p, List<String> pList, Map<String,String> typeMap, Map<String,String> typeTranslation) {
final String name = p.f1.toString();
String type = pName(p.f0);
pList.add(name);
final String alias = typeTranslation.get(type);
if(alias != null) { type = alias; }
typeMap.put(name, type);
}
private static void parseImport(Import imp, Map<String,String> typeTranslation, Set<String> implicits) {
final String fullName = pName(imp.f1);
final String[] pieces = fullName.split("\\.");
String shortName = pieces[pieces.length - 1];
if(imp.f2.present()) {
final NodeSequence ns = (NodeSequence) imp.f2.node;
shortName = ns.nodes.get(1).toString();
}
if(imp.f3.present()) { implicits.add(fullName); }
final String check = typeTranslation.get(shortName);
if(check != null) {
throw new RuntimeException("'" + shortName + "' for '" + fullName + "' is already used by another import statement.");
}
typeTranslation.put(shortName, fullName);
}
private static QueryExpr doReturn(final Iterator<String> types,
final List<QueryExpr> body,
final RSGetter[] getters,
final boolean isSelect,
final StructManager sm,
final String[] finalType) {
switch(types.next()) {
case "TX":
return new TXWrap(doReturn(types, body, getters, isSelect, sm, finalType));
case "Value":
finalType[0] = types.next();
return new ValueWrap(new DoWrap(body), typeLib.getter(finalType[0], sm, getters));
case "List":
finalType[0] = (types.hasNext()) ? types.next() : "java.util.Map<String,Object>";
finalType[1] = "java.util.List";
return new ListWrap(new DoWrap(body), typeLib.getter(finalType[0], sm, getters));
case "int":
finalType[0] = "int";
return new DoWrap(body);
case "void":
finalType[0] = "void";
return new VoidWrap(new DoWrap(body));
default:
throw new RuntimeException("Invalid Return type");
}
}
private static final SQLTypes typeLib = new SQLTypes();
/** Create an instance of a SQLClass from a source file represented by the input stream using a struct manager
provided to cache information about the container classes referred to by the SQL class.
@param in The input stream representing the source file.
@param sm The struct manager to cache/load/store information about the value classes imported.
@return An instance of a SQLClass representing the queries contained in the input stream.
*/
public static SQLClass load(InputStream in, StructManager sm, Set<String> implicits) throws ParseException {
final SQLParser fParser = new SQLParser(in);
final Functions klazz = fParser.Functions();
final String packName = (klazz.f0.present()) ? pName(((PkgDecl) klazz.f0.node).f1) : "";
final boolean isPublic = (klazz.f2.present()) ? true : false;
final String clsName = klazz.f4.toString();
final String fullName = (("".equals(packName)) ? "" : (packName + ".")) + clsName;
logger.debug("Package name: " + packName);
logger.debug("Class Name: " + clsName);
logger.debug("Full Name: " + fullName);
final HashMap<String,Map<String,String>> implicitStructs = new HashMap<String,Map<String,String>>();
final HashMap<String,SQLMethod> methods = new HashMap<String,SQLMethod>();
final HashMap<String,String> typeTranslation = new HashMap<String,String>();
typeTranslation.put("Date", "java.util.Date");
typeTranslation.put("BigDecimal", "java.math.BigDecimal");
typeTranslation.put("Instant", "org.joda.time.ReadableInstant");
typeTranslation.put("Json", "net.metanotion.json.Json");
typeTranslation.put("JsonArray", "net.metanotion.json.JsonArray");
typeTranslation.put("JsonObject", "net.metanotion.json.JsonObject");
for(final Node n: klazz.f1.nodes) {
parseImport((Import) n, typeTranslation, implicits);
}
for(final Node n: klazz.f6.nodes) {
final Function f = (Function) n;
final String fName = f.f1.toString();
logger.debug("Parsing Function " + fName);
final List<String> pList = new ArrayList<String>();
final Map<String,String> typeMap = new HashMap<String,String>();
final List<String> retType = parseTypeExpr(f.f0, typeTranslation);
if(f.f2.f1.present()) {
final Params ps = (Params) f.f2.f1.node;
parseParam(ps.f0, pList, typeMap, typeTranslation);
for(final Node np: ps.f1.nodes) {
final Param p = (Param) ((NodeSequence) np).nodes.get(1);
parseParam(p, pList, typeMap, typeTranslation);
}
}
final List<QueryExpr> body = new ArrayList<QueryExpr>();
int counter = 1;
boolean isSelect = false;
boolean isStart = true;
boolean isMacro = false;
List<SQLSetter> setters = new ArrayList<SQLSetter>();
List<SQLElement> macro = new ArrayList<SQLElement>();
List<RSGetter> getters = new ArrayList<RSGetter>();
StringBuilder statement = new StringBuilder();
for(final Node ns: f.f3.f1.nodes) {
final SQL s = (SQL) ns;
if(s.f0.choice instanceof PassThru) {
final PassThru pt = (PassThru) s.f0.choice;
final String sql = pt.f0.choice.toString();
if((sql.trim().length() > 0) || !isStart) {
if(isStart) { getters = new ArrayList<RSGetter>(); }
if(";".equals(sql)) {
//logger.debug("Statement terminator");
// build queryexpr
if(isMacro) {
if(statement.length() > 0) { macro.add(new SEConstant(statement.toString())); }
if(isSelect) {
body.add(new QueryMacro(setters, macro));
} else {
body.add(new UpdateMacro(setters, macro));
}
} else {
logger.debug("Simple statement: '" + statement.toString() + "'");
if(isSelect) {
body.add(new Query(statement.toString(), setters));
} else {
body.add(new Update(statement.toString(), setters));
}
}
// reset
counter = 1;
isSelect = false;
isStart = true;
isMacro = false;
statement = new StringBuilder();
macro = new ArrayList<SQLElement>();
setters = new ArrayList<SQLSetter>();
} else if(("select".equalsIgnoreCase(sql) || "with".equalsIgnoreCase(sql)) && isStart) {
//logger.debug("Encountered select");
statement.append(sql);
isSelect = true;
isStart = false;
} else {
// TO DO handle escapes.
isStart = false;
statement.append(sql);
}
}
} else {
final VarStmt vs = (VarStmt) s.f0.choice;
if(isStart) { getters = new ArrayList<RSGetter>(); }
isStart = false;
if(vs.f1.choice instanceof MacroVar) {
isMacro = true;
macro.add(new SEConstant(statement.toString()));
final MacroVar mv = (MacroVar) vs.f1.choice;
final String vname = mv.f1.toString();
//logger.debug("MacroVar: " + vname);
macro.add(new SEMacro(vname));
statement = new StringBuilder();
} else if(vs.f1.choice instanceof InVar) {
final InVar iv = (InVar) vs.f1.choice;
final String vname = iv.f0.toString();
//logger.debug("InVar: " + vname + " type " + typeMap.get(vname));
statement.append("?");
setters.add(typeLib.getSetter(counter, vname, typeMap.get(vname)));
counter++;
} else if(vs.f1.choice instanceof OutVar) {
final OutVar ov = (OutVar) vs.f1.choice;
final String vname = ov.f1.toString();
statement.append(vname);
//logger.debug("OutVar: " + vname);
String type = "Object";
if(ov.f2.present()) {
type = pName((QVName) ov.f2.node);
final String alias = typeTranslation.get(type);
if(alias != null) { type = alias; }
}
//logger.debug(getters.size());
getters.add(typeLib.getGetter(vname, type));
}
}
}
// build queryexpr
if((!isStart) && ((statement.toString().trim().length() > 0) || isMacro)) {
//logger.debug("Last statement '" + statement.toString() + "'");
if(isMacro) {
if(statement.toString().trim().length() > 0) { macro.add(new SEConstant(statement.toString())); }
if(isSelect) {
body.add(new QueryMacro(setters, macro));
} else {
body.add(new UpdateMacro(setters, macro));
}
} else {
if(isSelect) {
body.add(new Query(statement.toString(), setters));
} else {
body.add(new Update(statement.toString(), setters));
}
}
}
// Now look at return type
final QueryExpr[] qs = body.toArray(new QueryExpr[body.size()]);
final RSGetter[] rsg = getters.toArray(new RSGetter[getters.size()]);
QueryExpr wrapped;
final QueryExpr last = qs[qs.length - 1];
isSelect = (last instanceof Query) || (last instanceof QueryMacro);
final String[] finalType = new String[2];
try {
wrapped = doReturn(retType.iterator(), body, rsg, isSelect, sm, finalType);
} catch(RuntimeException re) { throw new RuntimeException("Error in function " + fName, re); }
logger.debug("Final type is " + finalType[0]);
if((finalType[0] != null) && (!typeLib.isPrimitive(finalType[0]))) {
final Map<String,String> base = implicitStructs.get(finalType[0]);
final Map<String,String> current = new HashMap<String,String>();
//logger.debug("Adding struct with " + rsg.length);
for(final RSGetter g: rsg) {
logger.debug("Adding " + g.name + " to " + finalType[0]);
current.put(g.name, g.type);
}
if(base == null) {
implicitStructs.put(finalType[0], current);
} else {
logger.debug("**** TYPE CHECKING: {} ****", finalType[0]);
int ct = 0;
for(final String field: base.keySet()) {
ct++;
if(current.get(field) == null) {
throw new RuntimeException("Struct Type Error in '" + fName + "': field is " + field
+ " not assigned a value.");
}
}
if(ct != current.size()) {
throw new RuntimeException("Struct Type Error in '" + fName + "': too many fields");
}
}
}
methods.put(fName,
new SQLMethod( fName,
pList.toArray(new String[pList.size()]),
wrapped,
typeMap,
finalType[1],
finalType[0]));
}
for(Map.Entry<String,Map<String,String>> s: implicitStructs.entrySet()) {
logger.debug("Checking Implicit Struct " + s.getKey());
if(sm.getStruct(s.getKey()) == null) {
logger.debug(" - Struct not found, creating dynamic version.");
sm.setStruct(s.getKey(), new Struct(s.getKey(), s.getValue()));
}
}
return new SQLClass(fullName, isPublic, methods);
}
}