All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.aguacate.configuration.compiler.ConfigurationCompilerImpl Maven / Gradle / Ivy

There is a newer version: 0.10.9
Show newest version
package net.sf.aguacate.configuration.compiler;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.sf.aguacate.configuration.Configuration;
import net.sf.aguacate.configuration.field.Field;
import net.sf.aguacate.configuration.field.FieldArray;
import net.sf.aguacate.configuration.field.FieldBoolean;
import net.sf.aguacate.configuration.field.FieldDate;
import net.sf.aguacate.configuration.field.FieldDynamicDate;
import net.sf.aguacate.configuration.field.FieldFloat;
import net.sf.aguacate.configuration.field.FieldInteger;
import net.sf.aguacate.configuration.field.FieldString;
import net.sf.aguacate.configuration.field.FieldStructure;
import net.sf.aguacate.configuration.field.FieldStructureArray;
import net.sf.aguacate.configuration.field.FieldTimeWithZone;
import net.sf.aguacate.configuration.field.format.FieldFormat;
import net.sf.aguacate.configuration.field.format.FieldFormatDate;
import net.sf.aguacate.configuration.field.format.FieldFormatDefault;
import net.sf.aguacate.configuration.field.format.FieldFormatReflective;
import net.sf.aguacate.configuration.impl.ConfigurationImpl;
import net.sf.aguacate.context.impl.ContextValidatorSql;
import net.sf.aguacate.context.spi.sql.ContextProcessorSql;
import net.sf.aguacate.context.spi.sql.impl.AbstractSentenceSql;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlBeginTransaction;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlDelete;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlEndTransaction;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlInsert;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlInsertWithId;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlSelectMultipleRow;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlSelectSingle;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlSelectSingleRow;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticCountNotZero;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticCountZero;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticDelete;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticInsert;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticInsertWithId;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticSelectList;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticSelectMultipleRow;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticSelectSingle;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticSelectSingleRow;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlStaticUpdate;
import net.sf.aguacate.context.spi.sql.impl.SentenceSqlUpdate;
import net.sf.aguacate.function.Function;
import net.sf.aguacate.function.spi.impl.FunctionArrayIterator;
import net.sf.aguacate.function.spi.impl.FunctionBase64Decode;
import net.sf.aguacate.function.spi.impl.FunctionBase64Encode;
import net.sf.aguacate.function.spi.impl.FunctionConditional;
import net.sf.aguacate.function.spi.impl.FunctionConnectorCall;
import net.sf.aguacate.function.spi.impl.FunctionCopy;
import net.sf.aguacate.function.spi.impl.FunctionCreateContext;
import net.sf.aguacate.function.spi.impl.FunctionEquals;
import net.sf.aguacate.function.spi.impl.FunctionGreaterEquals;
import net.sf.aguacate.function.spi.impl.FunctionGreaterThan;
import net.sf.aguacate.function.spi.impl.FunctionJsonDecode;
import net.sf.aguacate.function.spi.impl.FunctionJsonEncode;
import net.sf.aguacate.function.spi.impl.FunctionLessEquals;
import net.sf.aguacate.function.spi.impl.FunctionLessThan;
import net.sf.aguacate.function.spi.impl.FunctionLiteralInteger;
import net.sf.aguacate.function.spi.impl.FunctionLiteralString;
import net.sf.aguacate.function.spi.impl.FunctionNotEquals;
import net.sf.aguacate.function.spi.impl.FunctionRename;
import net.sf.aguacate.function.spi.impl.FunctionResponseJson;
import net.sf.aguacate.function.spi.impl.FunctionScript;
import net.sf.aguacate.function.spi.impl.FunctionSha256;
import net.sf.aguacate.function.spi.impl.FunctionStringSubstitutor;
import net.sf.aguacate.function.spi.impl.FunctionZero;
import net.sf.aguacate.util.config.database.DatabaseBridge;
import net.sf.aguacate.util.config.database.DatabaseCoupling;
import net.sf.aguacate.util.parameter.Prm;
import net.sf.aguacate.util.type.Bool;
import net.sf.aguacate.util.type.Str;

public class ConfigurationCompilerImpl implements ConfigurationCompiler {

	private static final Logger LOGGER = LogManager.getLogger(ConfigurationCompilerImpl.class);

	private static final String[] METHODS = new String[] { "GET", "POST", "PUT", "DELETE", "GET0", "PATCH" };

	private static final FastDateFormat FMT_TIME;

	private static final FastDateFormat FMT_DATE;

	private static final FastDateFormat FMT_DYN_DATE;

	private static final FastDateFormat FMT_DATETIME;

	private static final FieldFormat DEFAULT;

	private static final Map FORMATS;

	static {
		FMT_TIME = FastDateFormat.getInstance("HH:mmZZ");

		final String date = "yyyy-MM-dd";
		FMT_DATE = FastDateFormat.getInstance(date);

		final String dynamic = date + "ZZ";
		FMT_DYN_DATE = FastDateFormat.getInstance(dynamic);

		final String datetime = date + "'T'HH:mm:ss.SSSZZ";
		FMT_DATETIME = FastDateFormat.getInstance(datetime);

		DEFAULT = new FieldFormatDefault();
		Map temp = new HashMap<>();
		FieldFormat reflective = new FieldFormatReflective();
		temp.put(Field.DATE, new FieldFormatDate(FMT_DATE));
		temp.put(Field.DYNAMIC_DATE, new FieldFormatDate(FMT_DYN_DATE));
		temp.put(Field.DATETIME, new FieldFormatDate(FMT_DATETIME));
		temp.put(Field.TIME, new FieldFormatDate(FMT_TIME));
		temp.put(Field.BOOLEAN, reflective);
		temp.put(Field.INTEGER, reflective);
		temp.put(Field.FLOAT, reflective);
		// TODO: Avoid use of reflection, implement a specific version for this type
		temp.put(Field.ARRAY, reflective);
		// TODO: Avoid use of reflection, implement a specific version for this type
		temp.put(Field.STRUCTURE_ARRAY, reflective);
		// TODO: Avoid use of reflection, implement a specific version for this type
		temp.put(Field.STRUCTURE, reflective);
		FORMATS = temp;
	}

	@Override
	public Configuration compile(Map data) {
		return processFields(data, processValidations(data), processSentences(data));
	}

	@SuppressWarnings("unchecked")
	Configuration processFields(Map data, List functions, List sentences) {
		Map fields = (Map) data.get("fields");
		if (fields == null) {
			return null;
		} else {
			Map> inputFields = new HashMap<>();
			Map> outputFields = new HashMap<>();
			for (Map.Entry entry : fields.entrySet()) {
				String name = entry.getKey();
				Map meta = (Map) entry.getValue();
				parseInputField(name, meta, inputFields);
				parseOutputField(name, meta, outputFields);
			}
			String primary = (String) data.get("primary");
			DatabaseBridge databaseBridge = DatabaseCoupling.getDatabaseBridge((String) data.get("datasource"));
			return new ConfigurationImpl(primary, inputFields, outputFields,
					new ContextValidatorSql(databaseBridge, functions),
					new ContextProcessorSql(databaseBridge, sentences));
		}
	}

	@SuppressWarnings("unchecked")
	void parseInputField(String name, Map meta, Map> methods) {
		Map input = (Map) meta.get("input");
		for (String method : METHODS) {
			Map meta2 = (Map) input.get(method);
			if (meta2 != null) {
				Boolean mandatory = (Boolean) meta2.get("mandatory");
				if (mandatory != null) {
					Field field = toField(name, meta, !mandatory.booleanValue());
					if (field != null) {
						Map fields = methods.get(method);
						if (fields == null) {
							methods.put(method, fields = new HashMap<>());
						}
						fields.put(name, field);
					}
				}
			}
		}
	}

	Map toField(Map meta) {
		Map fields = new HashMap<>();
		for (Map.Entry entry : meta.entrySet()) {
			String name = entry.getKey();
			@SuppressWarnings("unchecked")
			Map meta2 = (Map) entry.getValue();
			// TODO: avoid optional with a hardcoded value (false)
			fields.put(name, toField(name, meta2, true));
		}
		return fields;
	}

	Field toField(String name, Map meta, boolean optional) {
		String string = (String) meta.get("type");
		switch (string) {
		case Field.TIME: {
			return new FieldTimeWithZone(name, Field.TIME, optional, FMT_TIME, (String) meta.get("minval"),
					(String) meta.get("maxval"));
		}
		case Field.DYNAMIC_DATE:
			return new FieldDynamicDate(name, optional, FMT_DYN_DATE, (String) meta.get("minval"),
					(String) meta.get("maxval"));
		case Field.DATE: {
			return new FieldDate(name, optional, FMT_DATE, (String) meta.get("minval"), (String) meta.get("maxval"));
		}
		case Field.DATETIME: {
			return new FieldTimeWithZone(name, Field.DATETIME, optional, FMT_DATETIME, (String) meta.get("minval"),
					(String) meta.get("maxval"));
		}
		case Field.FLOAT: {
			return new FieldFloat(name, optional, (String) meta.get("minval"), (String) meta.get("maxval"));
		}
		case Field.INTEGER: {
			return new FieldInteger(name, optional, (String) meta.get("minval"), (String) meta.get("maxval"));
		}
		case Field.STRING: {
			int minLenth = Integer.parseInt((String) meta.get("minlen"));
			int maxLength = Integer.parseInt((String) meta.get("maxlen"));
			String regex = (String) meta.get("regex");
			LOGGER.trace("{} regex: {}", name, regex);
			return new FieldString(name, optional, minLenth, maxLength, regex);
		}
		case Field.BOOLEAN: {
			return new FieldBoolean(name, optional);
		}
		case Field.STRUCTURE_ARRAY: {
			@SuppressWarnings("unchecked")
			Map sub = (Map) meta.get("data");
			return new FieldStructureArray(name, optional, toField(sub));
		}
		case Field.STRUCTURE: {
			@SuppressWarnings("unchecked")
			Map sub = (Map) meta.get("data");
			return new FieldStructure(name, optional, toField(sub));
		}
		case Field.ARRAY: {
			@SuppressWarnings("unchecked")
			Map sub = (Map) meta.get("data");
			return new FieldArray(name, optional, (String) sub.get("name"), toField("inner", sub, false),
					Bool.valueOf(meta.get("unique")));
		}
		default:
			throw new IllegalArgumentException(string);
		}
	}

	@SuppressWarnings("unchecked")
	List processValidations(Map data) {
		List> validations = (List>) data.get("validations");
		if (validations == null) {
			return Collections.emptyList();
		} else {
			List functions = new ArrayList<>();
			int size = validations.size();
			for (int i = 0; i < size; i++) {
				Map validation = validations.get(i);
				String name = (String) validation.get("name");
				String message = (String) validation.get("message");
				String type = (String) validation.get("type");
				List parameters = (List) validation.get("parameters");
				List methods = (List) validation.get("methods");
				functions.add(toFunction(validation, methods, name, message, type, parameters));
			}
			return functions;
		}
	}

	Function toFunction(Map validation, Collection methods, String name, String message,
			String type, List parameters) {
		Function function;
		switch (type) {
		case Function.LT:
			if (parameters.size() == 2) {
				function = new FunctionLessThan<>(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.LE:
			if (parameters.size() == 2) {
				function = new FunctionLessEquals<>(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.GT:
			if (parameters.size() == 2) {
				function = new FunctionGreaterThan<>(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.GE:
			if (parameters.size() == 2) {
				function = new FunctionGreaterEquals<>(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.EQ:
			if (parameters.size() == 2) {
				function = new FunctionEquals(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.NE:
			if (parameters.size() == 2) {
				function = new FunctionNotEquals(methods, name, message, Prm.toParameter(parameters.get(0)),
						Prm.toParameter(parameters.get(1)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 2");
			}
			break;
		case Function.ZERO:
			if (parameters.size() == 1) {
				function = new FunctionZero(methods, name, message, Prm.toParameter(parameters.get(0)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 1");
			}
			break;
		case Function.NOT_ZERO:
			if (parameters.size() == 1) {
				function = new FunctionZero(methods, name, message, Prm.toParameter(parameters.get(0)));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 1");
			}
			break;
		case Function.REN:
			if (parameters.size() == 1) {
				function = new FunctionRename(methods, name, message, Prm.toParameter(parameters.get(0)),
						Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 1");
			}
			break;
		case Function.COPY:
			if (parameters.size() == 1) {
				function = new FunctionCopy(methods, name, message, Prm.toParameter(parameters.get(0)),
						Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 1");
			}
			break;
		case Function.SCRIPT:
			function = new FunctionScript(methods, name, message, Prm.toList(parameters),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"),
					(String) validation.get("scriptName"), (String) validation.get("functionName"));
			break;
		case Function.STRUCTURE_ARRAY_ITERATOR: {
			List subvalidations = processValidations(validation);
			int size = subvalidations.size();
			List newmethods = new ArrayList<>(size);
			for (int i = 0; i < size; i++) {
				Function current = subvalidations.get(i);
				for (String method : METHODS) {
					if (current.validFor(method) && !newmethods.contains(method)) {
						newmethods.add(method);
					}
				}
			}
			function = new FunctionArrayIterator(newmethods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"), subvalidations);
			break;
		}
		case Function.CONDITIONAL: {
			List subvalidations = processValidations(validation);
			int size = subvalidations.size();
			List newmethods = new ArrayList<>(size);
			for (int i = 0; i < size; i++) {
				Function current = subvalidations.get(i);
				for (String method : METHODS) {
					if (current.validFor(method) && !newmethods.contains(method)) {
						newmethods.add(method);
					}
				}
			}
			function = new FunctionConditional(newmethods, name, message, Prm.toParameter(parameters.get(0)),
					subvalidations, (String) validation.get("test"));
			break;
		}
		case Function.LITERAL_STRING:
			function = new FunctionLiteralString(methods, name, message, Str.toList(validation.get("outputContext")),
					(String) validation.get("outputName"), validation.get("value"));
			break;
		case Function.LITERAL_INTEGER:
			function = new FunctionLiteralInteger(methods, name, message, Str.toList(validation.get("outputContext")),
					(String) validation.get("outputName"), validation.get("value"));
			break;
		case Function.SQL_SELECT_SINGLE: {
			function = new SentenceSqlStaticSelectSingle(name, message, (String) validation.get("sql"), methods,
					Prm.toList(parameters), Prm.toList(Collections.emptyList()),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		}
		case Function.SQL_COUNT_NOT_ZERO:
			function = new SentenceSqlStaticCountNotZero(methods, name, message, (String) validation.get("sql"),
					Prm.toList(parameters));
			break;
		case Function.SQL_COUNT_ZERO:
			function = new SentenceSqlStaticCountZero(methods, name, message, (String) validation.get("sql"),
					Prm.toList(parameters));
			break;
		case Function.BASE64_DECODE:
			function = new FunctionBase64Decode(methods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		case Function.BASE64_ENCODE:
			function = new FunctionBase64Encode(methods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		case Function.SHA256:
			function = new FunctionSha256(methods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		case Function.JSON_ENCODE:
			function = new FunctionJsonEncode(methods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		case Function.JSON_DECODE:
			function = new FunctionJsonDecode(methods, name, message, Prm.toParameter(parameters.get(0)),
					Str.toList(validation.get("outputContext")), (String) validation.get("outputName"));
			break;
		case Function.STRING_SUBSTITUTOR:
			function = new FunctionStringSubstitutor(methods, name, message, Prm.toParameter(parameters.get(0)),
					Prm.toList(parameters, 1), Str.toList(validation.get("outputContext")),
					(String) validation.get("outputName"));
			break;
		case Function.CONNECTOR_CALL:
			function = new FunctionConnectorCall(methods, name, (String) validation.get("connectorName"),
					Prm.toList(parameters), Str.toList(validation.get("outputContext")),
					(String) validation.get("outputName"));
			break;
		default:
			throw new IllegalArgumentException(type);
		}
		return function;
	}

	@SuppressWarnings("unchecked")
	List processSentences(Map data) {
		List> sentences = (List>) data.get("sentences");
		if (sentences == null) {
			return Collections.emptyList();
		} else {
			List result = new ArrayList<>();
			int size = sentences.size();
			for (int i = 0; i < size; i++) {
				result.add(toSentence(sentences.get(i)));
			}
			return result;
		}
	}

	@SuppressWarnings("unchecked")
	Function toSentence(Map data) {
		String type = (String) data.get("type");
		String name = (String) data.get("name");
		String message = (String) data.get("message");
		String table = (String) data.get("table");
		List required = (List) data.get("required");
		List optional = (List) data.get("optional");
		List methods = (List) data.get("methods");
		Function function;
		switch (type) {
		case AbstractSentenceSql.DELETE:
			function = new SentenceSqlDelete(name, message, table, methods, Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.INSERT:
			function = new SentenceSqlInsert(name, message, table, methods, Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.INSERT_RECOVER_ID:
			function = new SentenceSqlInsertWithId(name, message, table, methods, Prm.toList(required),
					Prm.toList(optional), Str.toList(data.get("outputContext")), (String) data.get("outputName"),
					(String) data.get("column"));
			break;
		case AbstractSentenceSql.UPDATE:
			function = new SentenceSqlUpdate(name, message, table, methods, Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.SELECT_SINGLE_ROW:
			function = new SentenceSqlSelectSingleRow(name, message, table, methods, Prm.toList(required),
					Prm.toList(optional), Str.toList(data.get("outputContext")), (String) data.get("outputName"));
			break;
		case AbstractSentenceSql.SELECT_SINGLE:
			function = new SentenceSqlSelectSingle(name, message, table, methods, Prm.toList(required),
					Prm.toList(optional), Str.toList(data.get("outputContext")), (String) data.get("outputName"));
			break;
		case AbstractSentenceSql.SELECT_MULTIPLE_ROW:
			function = new SentenceSqlSelectMultipleRow(name, message, table, methods, Prm.toList(required),
					Prm.toList(optional), Str.toList(data.get("outputContext")), (String) data.get("outputName"));
			break;
		case AbstractSentenceSql.JSON_RESPONSE:
			function = new FunctionResponseJson(methods, name, Prm.toParameter(required.get(0)));
			break;
		case AbstractSentenceSql.STRUCTURE_ARRAY_ITERATOR: {
			List subsentences = processSentences(data);
			List newmethods = new ArrayList<>();
			int size = subsentences.size();
			for (int i = 0; i < size; i++) {
				Function current = subsentences.get(i);
				for (String method : METHODS) {
					if (current.validFor(method) && !newmethods.contains(method)) {
						newmethods.add(method);
					}
				}
			}
			function = new FunctionArrayIterator(newmethods, name, message, Prm.toParameter(required.get(0)),
					Str.toList(data.get("outputContext")), (String) data.get("outputName"), subsentences);
			break;
		}
		case AbstractSentenceSql.CONDITIONAL: {
			List subsentences = processSentences(data);
			List newmethods = new ArrayList<>();
			int size = subsentences.size();
			for (int i = 0; i < size; i++) {
				Function current = subsentences.get(i);
				for (String method : METHODS) {
					if (current.validFor(method) && !newmethods.contains(method)) {
						newmethods.add(method);
					}
				}
			}
			function = new FunctionConditional(newmethods, name, message, Prm.toParameter(required.get(0)),
					subsentences, (String) data.get("test"));
			break;
		}
		case AbstractSentenceSql.SQL_INSERT:
			function = new SentenceSqlStaticInsert(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.SQL_INSERT_RECOVER_ID:
			function = new SentenceSqlStaticInsertWithId(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional), (String) data.get("column"));
			break;
		case AbstractSentenceSql.SQL_UPDATE:
			function = new SentenceSqlStaticUpdate(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.SQL_DELETE:
			function = new SentenceSqlStaticDelete(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional));
			break;
		case AbstractSentenceSql.SQL_SELECT_SINGLE:
			function = new SentenceSqlStaticSelectSingle(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional), Str.toList(data.get("outputContext")),
					(String) data.get("outputName"));
			break;
		case AbstractSentenceSql.SQL_SELECT_LIST:
			function = new SentenceSqlStaticSelectList(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional), Str.toList(data.get("outputContext")),
					(String) data.get("outputName"));
			break;
		case AbstractSentenceSql.SQL_SELECT_MULTIPLE_ROW:
			function = new SentenceSqlStaticSelectMultipleRow(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional), Str.toList(data.get("outputContext")),
					(String) data.get("outputName"));
			break;
		case AbstractSentenceSql.SQL_SELECT_SINGLE_ROW:
			function = new SentenceSqlStaticSelectSingleRow(name, message, (String) data.get("sql"), methods,
					Prm.toList(required), Prm.toList(optional), Str.toList(data.get("outputContext")),
					(String) data.get("outputName"));
			break;
		case AbstractSentenceSql.LITERAL_STRING:
			function = new FunctionLiteralString(methods, name, message, Str.toList(data.get("outputContext")),
					(String) data.get("outputName"), data.get("value"));
			break;
		case AbstractSentenceSql.BEGIN_TRANSACTION:
			function = new SentenceSqlBeginTransaction(name, message, methods);
			break;
		case AbstractSentenceSql.END_TRANSACTION:
			function = new SentenceSqlEndTransaction(name, message, methods);
			break;
		case AbstractSentenceSql.CONTEXT: {
			function = new FunctionCreateContext(methods, name, Prm.toList(required),
					Str.toList(data.get("outputContext")), (String) data.get("outputName"));
			break;
		}
		case AbstractSentenceSql.REN:
			if (required.size() == 1) {
				function = new FunctionRename(methods, name, message, Prm.toParameter(required.get(0)),
						Str.toList(data.get("outputContext")), (String) data.get("outputName"));
			} else {
				throw new IllegalArgumentException(
						"The number or parameters for function type '" + type + "'(" + name + ") must be 1");
			}
			break;
		default:
			throw new IllegalArgumentException(type);
		}
		return function;
	}

	@SuppressWarnings("unchecked")
	void parseOutputField(String name, Map meta, Map> outputFields) {
		String string = (String) meta.get("type");
		List output = (List) meta.get("output");
		int size = output.size();
		for (int i = 0; i < size; i++) {
			String method = output.get(i);
			Map fields = outputFields.get(method);
			if (fields == null) {
				fields = new HashMap<>();
				outputFields.put(method, fields);
			}
			FieldFormat format = FORMATS.get(string);
			if (format == null) {
				format = DEFAULT;
			}
			fields.put(name, format);
		}
	}

}