// /////////////////////////////
// Makumba, Makumba tag library
// Copyright (C) 2000-2003 http://www.makumba.org
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// -------------
// $Id: RecordParser.java 2079 2007-11-20 22:43:24Z cristian_bogdan $
// $Name$
/////////////////////////////////////
package org.makumba.providers.datadefinition.makumba;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.makumba.DataDefinition;
import org.makumba.DataDefinitionParseError;
import org.makumba.FieldDefinition;
import org.makumba.MakumbaError;
import org.makumba.ValidationDefinitionParseError;
import org.makumba.ValidationRule;
import org.makumba.commons.OrderedProperties;
import org.makumba.commons.RegExpUtils;
import org.makumba.commons.ReservedKeywords;
import org.makumba.commons.StringUtils;
import org.makumba.providers.DataDefinitionProvider;
import org.makumba.providers.datadefinition.makumba.validation.BasicValidationRule;
import org.makumba.providers.datadefinition.makumba.validation.ComparisonValidationRule;
import org.makumba.providers.datadefinition.makumba.validation.NumberRangeValidationRule;
import org.makumba.providers.datadefinition.makumba.validation.RangeValidationRule;
import org.makumba.providers.datadefinition.makumba.validation.RegExpValidationRule;
import org.makumba.providers.datadefinition.makumba.validation.StringLengthValidationRule;
public class RecordParser {
public static final String VALIDATION_INDICATOR = "%";
// regular expressions for multi-field unique keys //
public static final String multiUniqueRegExpElement = RegExpUtils.LineWhitespaces + "(" + RegExpUtils.fieldName
+ ")" + RegExpUtils.LineWhitespaces;
public static final String multiUniqueRegExpElementRepeatment = "(?:" + RegExpUtils.LineWhitespaces + "," + "(?:"
+ multiUniqueRegExpElement + "))*";
public static final String multiUniqueRegExp = RegExpUtils.LineWhitespaces + "(?:" + multiUniqueRegExpElement + ")"
+ multiUniqueRegExpElementRepeatment + RegExpUtils.LineWhitespaces;
public static final Pattern multiUniquePattern = Pattern.compile(multiUniqueRegExp);
// regular expressions for validation definitions //
public static final String validationDefinitionRegExp = RegExpUtils.LineWhitespaces + "(" + RegExpUtils.fieldName
+ ")" + RegExpUtils.LineWhitespaces + VALIDATION_INDICATOR + "(matches|length|range|compare|unique)"
+ RegExpUtils.LineWhitespaces + "=" + RegExpUtils.LineWhitespaces + "(.+)" + RegExpUtils.LineWhitespaces
+ ":" + RegExpUtils.LineWhitespaces + ".+";
public static final Pattern validationDefinitionPattern = Pattern.compile(validationDefinitionRegExp);
// regular expressions for function definitions //
/**
* defines all possible types. <br>
* FIXME: maybe this shall be move to {@link FieldDefinition}?
*/
public static final String funcDefParamTypeRegExp = "(char|char\\[\\]|int|real|date|intEnum|charEnum|text|binary|ptr|set|setIntEnum|setCharEnum)";
/** defines "int a" or "int 5". */
public static final String funcDefParamRegExp = funcDefParamTypeRegExp + RegExpUtils.minOneLineWhitespace
+ "(\\d+|" + RegExpUtils.fieldName + ")";
/** treats (int a, char b, ...) */
public static final String funcDefParamRepeatRegExp = "\\((?:" + "(?:" + funcDefParamRegExp + ")" + "(?:"
+ RegExpUtils.LineWhitespaces + "," + RegExpUtils.LineWhitespaces + funcDefParamRegExp + ")*"
+ RegExpUtils.LineWhitespaces + ")?\\)";
/**
* treats function(params) = queryFragment : errorMessage.<br>
* FIXME: this regexp always produces at least 4 groups for the parameters, which get the value of null if there are
* no params --> would be good to remove this.
*/
public static final String funcDefRegExp = "(" + RegExpUtils.fieldName + ")" + funcDefParamRepeatRegExp
+ RegExpUtils.LineWhitespaces + "=" + RegExpUtils.LineWhitespaces + "(.[^:]+)"
+ RegExpUtils.LineWhitespaces + "(?::" + RegExpUtils.LineWhitespaces + "(.*))?";
public static final Pattern funcDefPattern = Pattern.compile(funcDefRegExp);
OrderedProperties text;
OrderedProperties fields = new OrderedProperties();
OrderedProperties subfields = new OrderedProperties();
DataDefinitionParseError mpe;
Properties definedTypes;
DataDefinition dd;
// moved from ptrOneParser
HashMap<String, RecordParser> ptrOne_RecordParsers = new HashMap<String, RecordParser>();
// moved from setParser
HashMap<String, DataDefinition> setParser_settbls = new HashMap<String, DataDefinition>();
// moved from subtableParser
HashMap<String, DataDefinition> subtableParser_subtables = new HashMap<String, DataDefinition>();
HashMap<String, DataDefinition> subtableParser_here = new HashMap<String, DataDefinition>();
private ArrayList<String> unparsedValidationDefinitions = new ArrayList<String>();
public static boolean isValidationRule(String s) {
return validationDefinitionPattern.matcher(s).matches();
}
public static boolean isFunction(String s) {
return funcDefPattern.matcher(s).matches();
}
RecordParser() {
definedTypes = new Properties();
}
// for parsing of subtalbes
RecordParser(RecordInfo dd, RecordParser rp) {
this.dd = dd;
text = new OrderedProperties();
definedTypes = rp.definedTypes;
mpe = rp.mpe;
}
void parse(RecordInfo dd) {
this.dd = dd;
text = new OrderedProperties();
mpe = new DataDefinitionParseError();
try {
read(text, dd.origin);
} catch (IOException e) {
throw fail(e);
}
try {
// make the default pointers resulted from the table name
dd.addStandardFields(dd.name.substring(dd.name.lastIndexOf('.') + 1));
parse();
} catch (RuntimeException e) {
throw new MakumbaError(e, "Internal error in parser while parsing " + dd.getName());
}
if (!mpe.isSingle() && !(dd.getParentField() != null))
throw mpe;
}
DataDefinition parse(java.net.URL u, String path) {
dd = new RecordInfo(u, path);
parse((RecordInfo) dd);
return dd;
}
RecordInfo parse(String txt) {
dd = new RecordInfo();
text = new OrderedProperties();
mpe = new DataDefinitionParseError();
try {
read(text, txt);
} catch (IOException e) {
throw fail(e);
}
try {
// make the default pointers resulted from the table name
((RecordInfo) dd).addStandardFields(dd.getName().substring(dd.getName().lastIndexOf('.') + 1));
parse();
} catch (RuntimeException e) {
throw new MakumbaError(e, "Internal error in parser while parsing " + dd.getName());
}
if (!mpe.isSingle() && dd.getParentField() == null)
throw mpe;
return (RecordInfo) dd;
}
void parse() {
// include all the files and add them to the text, delete the
// !include command
solveIncludes();
// put fields in the fields table, subfields in subfields
separateFields();
// determine the title field, delete !title
setTitle();
// read predefined types, delete all !type.*
readTypes();
// commands should be finished at this point
if (text.size() != 0)
mpe.add(fail("unrecognized commands", text.toString()));
// make a FieldParser for each field, let it parse and substitute
// itself
treatMyFields();
// send info from the subfield table to the subfields
configSubfields();
// call solveAll() on all subfields
treatSubfields();
// parse validation definition
parseValidationDefinition();
// after all fields are processed, process the multi field indices & check for field existance
checkMultipleUniqueFields();
}
/** Check whether all fields used in multiple uniqueness checks are defined in the data definition. */
private void checkMultipleUniqueFields() {
for (int i = 0; i < dd.getMultiFieldUniqueKeys().length; i++) {
DataDefinition.MultipleUniqueKeyDefinition multiUniqueKeyDefinition = (DataDefinition.MultipleUniqueKeyDefinition) dd.getMultiFieldUniqueKeys()[i];
for (int j = 0; j < multiUniqueKeyDefinition.getFields().length; j++) {
String fieldName = multiUniqueKeyDefinition.getFields()[j];
// check for potential sub-fields
DataDefinition checkedDataDef = dd;
int indexOf = -1;
while ((indexOf = fieldName.indexOf(".")) != -1) {
// we have a sub-record-field
String subFieldName = fieldName.substring(0, indexOf);
fieldName = fieldName.substring(indexOf + 1);
checkedDataDef = checkedDataDef.getFieldDefinition(subFieldName).getPointedType();
}
if (checkedDataDef.getFieldDefinition(fieldName) == null) {
mpe.add(new DataDefinitionParseError(dd.getName(), "Unique index contains an unknown field: "
+ fieldName, multiUniqueKeyDefinition.getLine()));
} else if (checkedDataDef != dd) {
multiUniqueKeyDefinition.setKeyOverSubfield(true);
}
}
}
}
void separateFields() {
for (Enumeration e = text.keys(); e.hasMoreElements();) {
String k = (String) e.nextElement();
if (k.indexOf('!') == 0)
continue;
if (k.indexOf("->") == -1)
fields.putLast(k, text.getOriginal(k), text.remove(k));
else
subfields.putLast(k, text.getOriginal(k), text.remove(k));
}
}
void setTitle() {
String origCmd = (String) text.getOriginal("!title");
String ttl = (String) text.remove("!title");
String ttlt = null;
if (ttl != null) {
if (fields.get(ttlt = ttl.trim()) == null) {
mpe.add(fail("no such field for title", makeLine(origCmd, ttl)));
return;
}
} else if (fields.get("name") != null)
ttlt = "name";
else
// if there are any relations, we skip their fields as
// titles...
if (fields.size() > 0)
ttlt = fields.keyAt(0);
((RecordInfo) dd).title = ttlt;
}
static java.net.URL getResource(String s) {
return org.makumba.commons.ClassResource.get(s);
}
static public java.net.URL findDataDefinition(String s, String ext) {
// must specify a filename, not a directory (or package), see bug 173
java.net.URL u = findDataDefinitionOrDirectory(s, ext);
if (u != null && (s.endsWith("/") || getResource(s+'/')!=null))
return null;
return u;
}
static public java.net.URL findDataDefinitionOrDirectory(String s, String ext) {
java.net.URL u = null;
if (s.startsWith("/"))
s = s.substring(1);
if (s.endsWith(".") || s.endsWith("//"))
return null;
u = getResource(s.replace('.', '/') + "." + ext);
if (u == null) {
u = getResource("dataDefinitions/" + s.replace('.', '/') + "." + ext);
if (u == null) {
u = getResource("dataDefinitions/" + s.replace('.', '/'));
if (u == null) {
u = getResource(s.replace('.', '/'));
}
}
}
return u;
}
void solveIncludes() {
int line = 0;
OrderedProperties inclText;
Vector<String> overridenFields = new Vector<String>();
for (Enumeration e = text.keys(); e.hasMoreElements(); line++) {
String st = (String) e.nextElement();
if (st.startsWith("!include")) {
String ok = text.getOriginal(st);
String incl = (String) text.remove(st);
line--;
String s = incl.trim();
java.net.URL u = findDataDefinition(s, "idd");
// String n = "." + dd.getName();
// if(u==null && s.indexOf('.')==-1)
// u=findTable(n.substring(1, n.lastIndexOf('.')+1));
if (u == null) {
mpe.add(fail("could not find include file " + s, ok + "=" + incl));
return;
}
try {
inclText = new OrderedProperties();
read(inclText, u);
} catch (IOException ioe) {
mpe.add(fail("could not find include file " + s + " " + ioe, ok + "=" + incl));
;
return;
}
for (Enumeration k = inclText.keys(); k.hasMoreElements();) {
String key = (String) k.nextElement();
String val = text.getProperty(key);
if (val == null) // new field, not overriden in main mdd
text.putAt(++line, key, inclText.getOriginal(key), inclText.getProperty(key));
else
// field is overriden in main mdd, ignore it
overridenFields.add(key);
}
}
}
// now we remove all overriden empty fields
// keep the non-overriden ones with empty definiton to report a mdd
// error
for (Enumeration k = overridenFields.elements(); k.hasMoreElements();) {
String key = (String) k.nextElement();
if (((String) text.get(key)).trim().length() == 0)
text.remove(key);
}
}
void readTypes() {
for (Enumeration e = text.keys(); e.hasMoreElements();) {
String s = (String) e.nextElement();
if (s.startsWith("!type.")) {
String nm = s.substring(6);
definedTypes.put(nm, text.remove(s));
}
}
}
FieldCursor currentRowCursor;
FieldInfo getFieldInfo(String fieldName) {
return (FieldInfo) dd.getFieldDefinition(fieldName);
}
void treatMyFields() {
FieldInfo fi;
String nm;
int line = 0;
for (Enumeration e = fields.keys(); e.hasMoreElements(); line++) {
nm = (String) e.nextElement();
// check name for validity:
for (int i = 0; i < nm.length(); i++) {
if (i == 0 && !Character.isJavaIdentifierStart(nm.charAt(i)) || i > 0
&& !Character.isJavaIdentifierPart(nm.charAt(i)))
mpe.add(fail("Invalid character \"" + nm.charAt(i) + "\" in field name \"" + nm + "\"", nm));
}
if (ReservedKeywords.isReservedKeyword(nm)) {
mpe.add(fail("Error: field name cannot be one of the reserved keywords "
+ ReservedKeywords.getKeywordsAsString(), nm));
}
fi = new FieldInfo((RecordInfo) dd, nm);
((RecordInfo) dd).addField1(fi);
try {
parse(nm, new FieldCursor(this, makeLine(fields, nm)));
} catch (DataDefinitionParseError pe) {
((RecordInfo) dd).fields.remove(nm);
((RecordInfo) dd).fieldOrder.remove(nm);
mpe.add(pe);
continue;
}
}
}
void configSubfields() {
String nm;
int p;
for (Enumeration e = subfields.keys(); e.hasMoreElements();) {
nm = (String) e.nextElement();
p = nm.indexOf("->");
FieldInfo fieldInfo = getFieldInfo(nm.substring(0, p));
if (fieldInfo == null) { // we did not find the field info, i.e. accessed unknownField->field.
mpe.add(fail("Could not find subfield '" + nm.substring(0, p) + "'", makeLine(subfields, nm)));
continue;
}
String type = (String) fieldInfo.type;
if (type == null) {
mpe.add(fail("no such field in subfield definition", makeLine(subfields, nm)));
continue;
}
String s;
if ((s = addText(nm.substring(0, p), nm.substring(p + 2), subfields.getOriginal(nm),
subfields.getProperty(nm))) != null)
mpe.add(fail(s, makeLine(subfields, nm)));
}
}
void treatSubfields() {
for (Enumeration e = dd.getFieldNames().elements(); e.hasMoreElements();) {
String fieldName = (String) e.nextElement();
parseSubfields(fieldName);
}
}
static String makeLine(String origKey, String value) {
return origKey + "=" + value;
}
static String makeLine(OrderedProperties p, String k) {
return p.getOriginal(k) + "=" + p.getProperty(k);
}
DataDefinitionParseError fail(String why, String where) {
return new DataDefinitionParseError(dd.getName(), why, where, where.length());
}
DataDefinitionParseError fail(IOException ioe) {
return new DataDefinitionParseError(dd.getName(), ioe);
}
void read(OrderedProperties op, String txt) throws IOException {
read(op, new BufferedReader(new StringReader(txt)));
}
void read(OrderedProperties op, java.net.URL u) throws IOException {
read(op, new BufferedReader(new InputStreamReader((InputStream) u.getContent())));
}
void read(OrderedProperties op, BufferedReader rd) throws IOException {
while (true) {
String s = null;
s = rd.readLine();
if (s == null)
break;
String st = s.trim();
if (st.length() == 0 || st.charAt(0) == '#')
continue;
String lineWithoutComment = null;
if (st.indexOf(";") == -1) {
lineWithoutComment = st;
} else {
lineWithoutComment = st.substring(0, st.indexOf(";"));
}
// check if the line is a validation definition
Matcher matcher = validationDefinitionPattern.matcher(lineWithoutComment);
if (matcher.matches()) {
// we parse them later
unparsedValidationDefinitions.add(lineWithoutComment);
continue;
}
// check if the line is a function definition
matcher = funcDefPattern.matcher(lineWithoutComment);
if (matcher.matches()) {
String name = matcher.group(1);
if (dd.getFunction(name) != null) {
mpe.add(new DataDefinitionParseError(dd.getName(), "Duplicate function name: " + name, st));
}
String queryFragment = matcher.group(matcher.groupCount() - 1);
String errorMessage = matcher.group(matcher.groupCount());
DataDefinition params = new RecordInfo(dd.getName() + "." + matcher.group(0));
for (int i = 2; i < matcher.groupCount() - 2; i += 2) {
String type = matcher.group(i);
// if we provide < 2 params, we still get 2 * 2 empty groups matched ==> need to check tht here
// see the comment in the field init.
if (type != null) {
if (type.equals("char[]")) { // we substitute char[] with the max char length
type = ("char[255]");
}
params.addField(new FieldInfo(matcher.group(i + 1), type));
}
}
DataDefinition.QueryFragmentFunction function = new DataDefinition.QueryFragmentFunction(name,
queryFragment, params, errorMessage);
dd.addFunction(name, function);
continue;
}
int l = s.indexOf('=');
if (l == -1) {
mpe.add(fail("non-empty, non-comment line without =", s));
continue;
}
String k = s.substring(0, l);
String kt = k.trim();
if (kt.length() == 0) {
mpe.add(fail("zero length key", s));
continue;
}
if (kt.charAt(0) == '!' && kt.length() == 1) {
mpe.add(fail("zero length command", s));
continue;
}
if (kt.startsWith("!include")) {
if (kt.length() > 8) {
mpe.add(fail("unknown command: " + kt, s));
continue;
}
while (op.get(kt) != null)
kt = kt + "_";
}
String val = s.substring(l + 1);
if (op.putLast(kt, k, val) != null)
mpe.add(fail("ambiguous key " + kt, s));
}
rd.close();
}
// moved from FieldParser
public void parseSubfields(String fieldName) {
switch (getFieldInfo(fieldName).getIntegerType()) {
case FieldDefinition._setComplex:
case FieldDefinition._ptrOne:
parse_ptrOne_Subfields(fieldName);
break;
case FieldDefinition._set:
parse_set_Subfields(fieldName);
break;
default:
;
}
}
// moved from ptrOneParser
public void parse_ptrOne_Subfields(String fieldName) {
ptrOne_RecordParsers.get(fieldName).parse();
getFieldInfo(fieldName).extra2 = ((RecordParser) ptrOne_RecordParsers.get(fieldName)).dd.getTitleFieldName();
}
// moved from setParser
public void parse_set_Subfields(String fieldName) {
if (getFieldInfo(fieldName).extra2 == null) {
getFieldInfo(fieldName).extra2 = ((RecordInfo) subtableParser_subtables.get(fieldName)).title = ((DataDefinition) setParser_settbls.get(fieldName)).getTitleFieldName();
}
}
// moved from FieldParser
String acceptTitle(String fieldName, String nm, String origNm, String val, Object o) {
val = val.trim();
if (nm.equals("!title")) {
DataDefinition ri = (DataDefinition) o;
if (ri.getFieldDefinition(val) == null)
return ri.getName() + " has no field called " + val;
getFieldInfo(fieldName).extra2 = val;
return null;
}
return addText(fieldName, nm, origNm, val);
}
// moved from FieldParser
String addText(String fieldName, String nm, String origNm, String val) {
switch (getFieldInfo(fieldName).getIntegerType()) {
case FieldDefinition._ptr:
case FieldDefinition._ptrRel:
return add_ptr_Text(fieldName, nm, origNm, val);
case FieldDefinition._ptrOne:
case FieldDefinition._setComplex:
return add_ptrOne_Text(fieldName, nm, origNm, val);
case FieldDefinition._set:
return add_set_Text(fieldName, nm, origNm, val);
default:
return base_addText(fieldName, nm, origNm, val);
}
}
// original from FieldParser
String base_addText(String fieldName, String nm, String origNm, String val) {
return "subfield not allowed";
}
// moved from ptrParser
String add_ptr_Text(String fieldName, String nm, String origNm, String val) {
return acceptTitle(fieldName, nm, origNm, val, getFieldInfo(fieldName).extra1);
}
// moved from ptrOneParser
String add_ptrOne_Text(String fieldName, String nm, String origNm, String val) {
if (ptrOne_RecordParsers.get(fieldName).text.putLast(nm, origNm, val) != null)
return "field already exists";
return null;
}
// moved from setParser
String add_set_Text(String fieldName, String nm, String origNm, String val) {
String s = acceptTitle(fieldName, nm, origNm, val, (DataDefinition) setParser_settbls.get(fieldName));
if (s == null)
((RecordInfo) subtableParser_subtables.get(fieldName)).title = val.trim();
return s;
}
// moved from FieldParser
void parse(String fieldName, FieldCursor fc) throws org.makumba.DataDefinitionParseError {
while (true) {
if (fc.lookup("not")) {
if (getFieldInfo(fieldName).notNull)
throw fc.fail("too many not null");
fc.expect("null");
fc.expectWhitespace();
getFieldInfo(fieldName).notNull = true;
continue;
}
if (fc.lookup("fixed")) {
fc.expectWhitespace();
if (getFieldInfo(fieldName).fixed)
throw fc.fail("too many fixed");
getFieldInfo(fieldName).fixed = true;
continue;
}
if (fc.lookup("unique")) {
fc.expectWhitespace();
if (getFieldInfo(fieldName).unique)
throw fc.fail("already unique");
getFieldInfo(fieldName).unique = true;
continue;
}
break;
}
if (setType(fieldName, fc.expectTypeLiteral(), fc) == null) {
String s = definedTypes.getProperty(getFieldInfo(fieldName).type);
if (s == null)
throw fc.fail("unknown type: " + getFieldInfo(fieldName).type);
fc.substitute(getFieldInfo(fieldName).type.length(), s);
if (setType(fieldName, fc.expectTypeLiteral(), fc) == null)
throw fc.fail("unknown type: " + getFieldInfo(fieldName).type);
}
getFieldInfo(fieldName).description = getFieldInfo(fieldName).description == null ? getFieldInfo(fieldName).name
: getFieldInfo(fieldName).description;
}
// moved from FieldParser
String setType(String fieldName, String type, FieldCursor fc) throws org.makumba.DataDefinitionParseError {
String initialType = type;
getFieldInfo(fieldName).type = type;
while (true) {
if (FieldInfo.integerTypeMap.get(getFieldInfo(fieldName).type) == null) {
// getFieldInfo(fieldName).type = null;
return null;
}
parse1(fieldName, fc);
if (getFieldInfo(fieldName).type.equals(initialType))
return initialType;
initialType = getFieldInfo(fieldName).type;
}
}
/**
* switch for the existing parse methods. set the field type to another value if we want to change it
*/
void parse1(String fieldName, FieldCursor fc) {
switch (getFieldInfo(fieldName).getIntegerType()) {
case FieldDefinition._charEnum:
charEnum_parse1(fieldName, fc);
return;
case FieldDefinition._char:
char_parse1(fieldName, fc);
return;
case FieldDefinition._intEnum:
intEnum_parse1(fieldName, fc);
return;
case FieldDefinition._int:
int_parse1(fieldName, fc);
return;
case FieldDefinition._ptrOne:
ptrOne_parse1(fieldName, fc);
return;
case FieldDefinition._ptrRel:
case FieldDefinition._ptr:
ptr_parse1(fieldName, fc);
return;
case FieldDefinition._setCharEnum:
setCharEnum_parse1(fieldName, fc);
return;
case FieldDefinition._setComplex:
setComplex_parse1(fieldName, fc);
return;
case FieldDefinition._setIntEnum:
setIntEnum_parse1(fieldName, fc);
return;
case FieldDefinition._set:
set_parse1(fieldName, fc);
return;
case FieldDefinition._text:
text_parse1(fieldName, fc);
return;
case FieldDefinition._date:
case FieldDefinition._real:
case FieldDefinition._ptrIndex:
case FieldDefinition._dateCreate:
case FieldDefinition._dateModify:
simple_parse1(fieldName, fc);
return;
default:
;
}
}
// moved from intParser
public void int_parse1(String fieldName, FieldCursor fc) {
if (!fc.lookup("{")) {
getFieldInfo(fieldName).description = fc.lookupDescription();
return;
}
getFieldInfo(fieldName).type = "intEnum";
}
// moved from intEnumParser
public void intEnum_parse1(String fieldName, FieldCursor fc) {
fc.expectIntEnum(getFieldInfo(fieldName));
getFieldInfo(fieldName).description = fc.lookupDescription();
return;
}
// moved from charParser
public void char_parse1(String fieldName, FieldCursor fc) {
if (!fc.lookup("{")) {
fc.expect("[");
Integer size = fc.expectInteger();
if (size.intValue() > 255 || size.intValue() < 0)
throw fc.fail("char size must be between 0 and 255, not " + size.toString());
getFieldInfo(fieldName).extra2 = size;
fc.expect("]");
getFieldInfo(fieldName).description = fc.lookupDescription();
return;
}
getFieldInfo(fieldName).type = "charEnum";
}
// moved from charEnumParser
public void charEnum_parse1(String fieldName, FieldCursor fc) {
fc.expectCharEnum(getFieldInfo(fieldName));
getFieldInfo(fieldName).description = fc.lookupDescription();
return;
}
// moved from simpleParser
public void simple_parse1(String fieldName, FieldCursor fc) {
getFieldInfo(fieldName).description = fc.lookupDescription();
return;
}
// moved from textParser
public void text_parse1(String fieldName, FieldCursor fc) throws org.makumba.DataDefinitionParseError {
if (getFieldInfo(fieldName).isUnique())
throw fc.fail("text fields can't be declared unique");
return;
}
// moved from setComplexParser
public void setComplex_parse1(String fieldName, FieldCursor fc) {
ptrOne_parse1(fieldName, fc);
((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
return;
}
// moved from ptrOneParser
public void ptrOne_parse1(String fieldName, FieldCursor fc) {
makeSubtable(fieldName, fc);
ptrOne_RecordParsers.put(fieldName, new RecordParser(((RecordInfo) subtableParser_subtables.get(fieldName)),
this));
return;
}
// moved from setEnumParser, setChatEnumParser
public void setCharEnum_parse1(String fieldName, FieldCursor fc) {
FieldInfo _enum = new FieldInfo(((RecordInfo) subtableParser_subtables.get(fieldName)), "enum");
makeSubtable(fieldName, fc);
((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
((RecordInfo) subtableParser_subtables.get(fieldName)).addField1(_enum);
((RecordInfo) subtableParser_subtables.get(fieldName)).title = _enum.name;
((RecordInfo) subtableParser_subtables.get(fieldName)).setField = _enum.name;
_enum.type = "charEnum";
fc.expectCharEnum(_enum);
getFieldInfo(fieldName).description = fc.lookupDescription();
_enum.description = getFieldInfo(fieldName).getDescription() == null ? _enum.name
: getFieldInfo(fieldName).getDescription();
return;
}
// moved from setEnumParser, setIntEnumParser
public void setIntEnum_parse1(String fieldName, FieldCursor fc) {
FieldInfo _enum = new FieldInfo(((RecordInfo) subtableParser_subtables.get(fieldName)), "enum");
makeSubtable(fieldName, fc);
((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
((RecordInfo) subtableParser_subtables.get(fieldName)).addField1(_enum);
((RecordInfo) subtableParser_subtables.get(fieldName)).title = _enum.name;
((RecordInfo) subtableParser_subtables.get(fieldName)).setField = _enum.name;
_enum.type = "intEnum";
fc.expectIntEnum(_enum);
getFieldInfo(fieldName).description = fc.lookupDescription();
_enum.description = getFieldInfo(fieldName).getDescription() == null ? _enum.name
: getFieldInfo(fieldName).getDescription();
return;
}
// moved from ptrParser
public void ptr_parse1(String fieldName, FieldCursor fc) {
Object o = fc.lookupTableSpecifier();
if (o != null)
getFieldInfo(fieldName).extra1 = o;
try {
getFieldInfo(fieldName).description = fc.lookupDescription();
} catch (org.makumba.DataDefinitionParseError e) {
throw fc.fail("table specifier or nothing expected");
}
if (o != null)
return;
// getFieldInfo(fieldName).unique = true;
getFieldInfo(fieldName).type = "ptrOne";
}
// moved from setParser
public void set_parse1(String fieldName, FieldCursor fc) {
if (getFieldInfo(fieldName).isUnique())
throw fc.fail("sets can't be declared unique");
DataDefinition ori = fc.lookupTableSpecifier();
if (ori == null) {
String word = fc.lookupTypeLiteral();
if (word == null) {
try {
getFieldInfo(fieldName).description = fc.lookupDescription();
} catch (org.makumba.DataDefinitionParseError pe) {
throw fc.fail("table specifier, enumeration type, or nothing expected");
}
getFieldInfo(fieldName).type = "setComplex";
return;
}
String newName = enumSet(fieldName, fc, word);
if (newName != null) {
getFieldInfo(fieldName).type = newName;
return;
}
String s = fc.rp.definedTypes.getProperty(word);
if (s == null)
throw fc.fail("table, char{}, int{} or macro type expected after set");
fc.substitute(word.length(), s);
newName = enumSet(fieldName, fc, fc.expectTypeLiteral());
if (newName != null) {
getFieldInfo(fieldName).type = newName;
return;
}
throw fc.fail("int{} or char{} macro expected after set");
}
makeSubtable(fieldName, fc);
((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
setParser_settbls.put(fieldName, ori);
((RecordInfo) subtableParser_subtables.get(fieldName)).setField = addPtr(fieldName,
((RecordInfo) setParser_settbls.get(fieldName)).getBaseName(), ori);
return;
}
// moved from setParser
String enumSet(String fieldName, FieldCursor fc, String word) {
String newName;
if (fc.lookup("{")) {
newName = "set" + word + "Enum";
getFieldInfo(fieldName).type = newName;
if (newName != null)
return newName;
fc.fail("int{} or char{} expected after set");
}
return null;
}
// moved from subtableParser
void makeSubtable(String fieldName, FieldCursor fc) {
subtableParser_here.put(fieldName, dd);
subtableParser_subtables.put(fieldName,
((RecordInfo) subtableParser_here.get(fieldName)).makeSubtable(getFieldInfo(fieldName).name));
((RecordInfo) subtableParser_subtables.get(fieldName)).addStandardFields(((RecordInfo) subtableParser_subtables.get(fieldName)).subfield);
getFieldInfo(fieldName).extra1 = ((RecordInfo) subtableParser_subtables.get(fieldName));
}
// moved from subtableParser
String addPtr(String fieldName, String name, DataDefinition o) {
int n = name.lastIndexOf('.');
if (n != -1)
name = name.substring(n + 1);
while (((RecordInfo) subtableParser_subtables.get(fieldName)).fields.get(name) != null)
name = name + "_";
FieldInfo ptr = new FieldInfo(((RecordInfo) subtableParser_subtables.get(fieldName)), name);
((RecordInfo) subtableParser_subtables.get(fieldName)).addField1(ptr);
ptr.fixed = true;
ptr.notNull = true;
ptr.type = "ptrRel";
ptr.extra1 = o;
ptr.description = "relational pointer";
return name;
}
// moved from subtableParser
String addPtrHere(String fieldName) {
// System.err.println(here.canonicalName()+"
// "+subtable.canonicalName());
((RecordInfo) subtableParser_subtables.get(fieldName)).relations = 1;
if (((RecordInfo) subtableParser_here.get(fieldName)).getParentField() != null)
return addPtr(fieldName, ((RecordInfo) subtableParser_here.get(fieldName)).subfield,
((RecordInfo) subtableParser_here.get(fieldName)));
else
return addPtr(fieldName, ((RecordInfo) subtableParser_here.get(fieldName)).name,
((RecordInfo) subtableParser_here.get(fieldName)));
}
public void parseValidationDefinition() throws ValidationDefinitionParseError {
ValidationDefinitionParseError mpe = new ValidationDefinitionParseError();
for (int i = 0; i < unparsedValidationDefinitions.size(); i++) {
String line = unparsedValidationDefinitions.get(i);
try {
line = line.trim();
if (line.indexOf(";") != -1) { // cut off end-of-line comments
line = line.substring(0, line.indexOf(";")).trim();
}
// check if the line is a validation definition
Matcher singleValidationMatcher = validationDefinitionPattern.matcher(line);
if (!singleValidationMatcher.matches()) {
throw new ValidationDefinitionParseError(dd.getName(), "Illegal rule definition!", line);
}
String[] definitionParts = line.split(":");
if (definitionParts.length < 2) {
throw new ValidationDefinitionParseError(dd.getName(),
"Rule does not consist of the two parts <rule>:<message>!", line);
}
String fieldName = singleValidationMatcher.group(1).trim();
String operation = singleValidationMatcher.group(2).trim();
String ruleDef = singleValidationMatcher.group(3).trim();
String errorMessage = definitionParts[1].trim();
String ruleName = line;
ValidationRule rule = null;
Matcher matcher;
// check all possible validation types
if (StringUtils.equals(operation, RegExpValidationRule.getOperator())) {
// regexp validation
FieldDefinition fd = DataDefinitionProvider.getFieldDefinition(dd, fieldName, line);
rule = new RegExpValidationRule(fd, fieldName, ruleName, errorMessage, ruleDef);
} else if (StringUtils.equals(operation, NumberRangeValidationRule.getOperator())) {
// number (int or real) validation
FieldDefinition fd = DataDefinitionProvider.getFieldDefinition(dd, fieldName, line);
matcher = RangeValidationRule.getMatcher(ruleDef);
if (!matcher.matches()) {
throw new ValidationDefinitionParseError("", "Illegal range definition", line);
}
rule = new NumberRangeValidationRule(fd, fieldName, ruleName, errorMessage,
matcher.group(1).trim(), matcher.group(2).trim());
} else if (StringUtils.equals(operation, StringLengthValidationRule.getOperator())) {
// string lenght (char or text) validation
FieldDefinition fd = DataDefinitionProvider.getFieldDefinition(dd, fieldName, line);
matcher = RangeValidationRule.getMatcher(ruleDef);
if (!matcher.matches()) {
throw new ValidationDefinitionParseError("", "Illegal range definition", line);
}
rule = new StringLengthValidationRule(fd, fieldName, ruleName, errorMessage,
matcher.group(1).trim(), matcher.group(2).trim());
} else if (StringUtils.equals(operation, ComparisonValidationRule.getOperator())) {
// comparison validation, compares two fields or a field with a constant
// fieldName = matcher.group(1);
matcher = ComparisonValidationRule.getMatcher(ruleDef);
if (!matcher.matches()) {
throw new ValidationDefinitionParseError("", "Illegal comparison definition", line);
}
if (dd.getFieldDefinition(fieldName) == null) { // let's see if the first part is a field name
fieldName = matcher.group(1);
}
String functionName = null;
if (BasicValidationRule.isValidFunctionCall(fieldName)) {
functionName = BasicValidationRule.extractFunctionNameFromStatement(fieldName);
fieldName = BasicValidationRule.extractFunctionArgument(fieldName);
}
FieldDefinition fd = DataDefinitionProvider.getFieldDefinition(dd, fieldName, line);
String operator = matcher.group(2).trim();
String compareTo = matcher.group(3).trim();
if (fd.getIntegerType() == FieldDefinition._date
&& ComparisonValidationRule.matchesDateExpression(compareTo)) {
// we have a comparison to a date constant / expression
rule = new ComparisonValidationRule(fd, fieldName, compareTo, ruleName, errorMessage, operator);
} else {
FieldDefinition otherFd = DataDefinitionProvider.getFieldDefinition(dd, compareTo, line);
rule = new ComparisonValidationRule(fd, fieldName, functionName, otherFd, compareTo, ruleName,
errorMessage, operator);
}
} else if (StringUtils.equals(operation, "unique")) {
// check if the line defines a multi-field unique key
matcher = multiUniquePattern.matcher(ruleDef);
if (!matcher.matches()) {
throw new ValidationDefinitionParseError("", "Illegal multi-field unique definition", line);
}
ArrayList<String> groupList = new ArrayList<String>();
for (int j = 1; j <= matcher.groupCount(); j++) {
if (matcher.group(j) != null) {
// checking if the fields exist will be done later
groupList.add(matcher.group(j).trim());
}
}
String[] groups = (String[]) groupList.toArray(new String[groupList.size()]);
dd.addMultiUniqueKey(new DataDefinition.MultipleUniqueKeyDefinition(groups, line));
java.util.logging.Logger.getLogger("org.makumba." + "datadefinition.makumba").finer(
"added multi-field unique key: " + new DataDefinition.MultipleUniqueKeyDefinition(groups, line));
continue;
} else {
// no recognised rule
throw new ValidationDefinitionParseError("", "Rule type not recognised!", line);
}
rule.getFieldDefinition().addValidationRule(rule);
// validationRules.put(fieldName, rule);
((RecordInfo) dd).addValidationRule(rule);
java.util.logging.Logger.getLogger("org.makumba." + "datadefinition.makumba").finer(
"added rule: " + rule);
} catch (ValidationDefinitionParseError e) {
mpe.add(e);
}
if (!mpe.isSingle()) {
throw mpe;
}
}
if (!mpe.isSingle()) {
throw mpe;
}
// System.out.println("Finished parsing validation definition '" + name + "'.");
}
public static void main(String[] args) {
// test some function definition
RegExpUtils.evaluate(RecordParser.funcDefPattern, new String[] { " someFunc() = abc : errorMessage",
" someFunc(char[] a, int 5) =abc:errorMessages", "someFunction(int a, char[] b) = yeah:errorMessage3",
"someOtherFunction(int age, char[] b) = this.age > age : You are too young!" });
// test some mdd reading
RecordInfo.getRecordInfo("test.Person");
}
}
|