0001: // /////////////////////////////
0002: // Makumba, Makumba tag library
0003: // Copyright (C) 2000-2003 http://www.makumba.org
0004: //
0005: // This library is free software; you can redistribute it and/or
0006: // modify it under the terms of the GNU Lesser General Public
0007: // License as published by the Free Software Foundation; either
0008: // version 2.1 of the License, or (at your option) any later version.
0009: //
0010: // This library is distributed in the hope that it will be useful,
0011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0013: // Lesser General Public License for more details.
0014: //
0015: // You should have received a copy of the GNU Lesser General Public
0016: // License along with this library; if not, write to the Free Software
0017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018: //
0019: // -------------
0020: // $Id: RecordParser.java 2079 2007-11-20 22:43:24Z cristian_bogdan $
0021: // $Name$
0022: /////////////////////////////////////
0023:
0024: package org.makumba.providers.datadefinition.makumba;
0025:
0026: import java.io.BufferedReader;
0027: import java.io.File;
0028: import java.io.IOException;
0029: import java.io.InputStream;
0030: import java.io.InputStreamReader;
0031: import java.io.StringReader;
0032: import java.util.ArrayList;
0033: import java.util.Enumeration;
0034: import java.util.HashMap;
0035: import java.util.Properties;
0036: import java.util.Vector;
0037: import java.util.jar.JarEntry;
0038: import java.util.regex.Matcher;
0039: import java.util.regex.Pattern;
0040:
0041: import org.makumba.DataDefinition;
0042: import org.makumba.DataDefinitionParseError;
0043: import org.makumba.FieldDefinition;
0044: import org.makumba.MakumbaError;
0045: import org.makumba.ValidationDefinitionParseError;
0046: import org.makumba.ValidationRule;
0047: import org.makumba.commons.OrderedProperties;
0048: import org.makumba.commons.RegExpUtils;
0049: import org.makumba.commons.ReservedKeywords;
0050: import org.makumba.commons.StringUtils;
0051: import org.makumba.providers.DataDefinitionProvider;
0052: import org.makumba.providers.datadefinition.makumba.validation.BasicValidationRule;
0053: import org.makumba.providers.datadefinition.makumba.validation.ComparisonValidationRule;
0054: import org.makumba.providers.datadefinition.makumba.validation.NumberRangeValidationRule;
0055: import org.makumba.providers.datadefinition.makumba.validation.RangeValidationRule;
0056: import org.makumba.providers.datadefinition.makumba.validation.RegExpValidationRule;
0057: import org.makumba.providers.datadefinition.makumba.validation.StringLengthValidationRule;
0058:
0059: public class RecordParser {
0060: public static final String VALIDATION_INDICATOR = "%";
0061:
0062: // regular expressions for multi-field unique keys //
0063: public static final String multiUniqueRegExpElement = RegExpUtils.LineWhitespaces
0064: + "("
0065: + RegExpUtils.fieldName
0066: + ")"
0067: + RegExpUtils.LineWhitespaces;
0068:
0069: public static final String multiUniqueRegExpElementRepeatment = "(?:"
0070: + RegExpUtils.LineWhitespaces
0071: + ","
0072: + "(?:"
0073: + multiUniqueRegExpElement + "))*";
0074:
0075: public static final String multiUniqueRegExp = RegExpUtils.LineWhitespaces
0076: + "(?:"
0077: + multiUniqueRegExpElement
0078: + ")"
0079: + multiUniqueRegExpElementRepeatment
0080: + RegExpUtils.LineWhitespaces;
0081:
0082: public static final Pattern multiUniquePattern = Pattern
0083: .compile(multiUniqueRegExp);
0084:
0085: // regular expressions for validation definitions //
0086: public static final String validationDefinitionRegExp = RegExpUtils.LineWhitespaces
0087: + "("
0088: + RegExpUtils.fieldName
0089: + ")"
0090: + RegExpUtils.LineWhitespaces
0091: + VALIDATION_INDICATOR
0092: + "(matches|length|range|compare|unique)"
0093: + RegExpUtils.LineWhitespaces
0094: + "="
0095: + RegExpUtils.LineWhitespaces
0096: + "(.+)"
0097: + RegExpUtils.LineWhitespaces
0098: + ":"
0099: + RegExpUtils.LineWhitespaces + ".+";
0100:
0101: public static final Pattern validationDefinitionPattern = Pattern
0102: .compile(validationDefinitionRegExp);
0103:
0104: // regular expressions for function definitions //
0105: /**
0106: * defines all possible types. <br>
0107: * FIXME: maybe this shall be move to {@link FieldDefinition}?
0108: */
0109: public static final String funcDefParamTypeRegExp = "(char|char\\[\\]|int|real|date|intEnum|charEnum|text|binary|ptr|set|setIntEnum|setCharEnum)";
0110:
0111: /** defines "int a" or "int 5". */
0112: public static final String funcDefParamRegExp = funcDefParamTypeRegExp
0113: + RegExpUtils.minOneLineWhitespace
0114: + "(\\d+|"
0115: + RegExpUtils.fieldName + ")";
0116:
0117: /** treats (int a, char b, ...) */
0118: public static final String funcDefParamRepeatRegExp = "\\((?:"
0119: + "(?:" + funcDefParamRegExp + ")" + "(?:"
0120: + RegExpUtils.LineWhitespaces + ","
0121: + RegExpUtils.LineWhitespaces + funcDefParamRegExp + ")*"
0122: + RegExpUtils.LineWhitespaces + ")?\\)";
0123:
0124: /**
0125: * treats function(params) = queryFragment : errorMessage.<br>
0126: * FIXME: this regexp always produces at least 4 groups for the parameters, which get the value of null if there are
0127: * no params --> would be good to remove this.
0128: */
0129: public static final String funcDefRegExp = "("
0130: + RegExpUtils.fieldName + ")" + funcDefParamRepeatRegExp
0131: + RegExpUtils.LineWhitespaces + "="
0132: + RegExpUtils.LineWhitespaces + "(.[^:]+)"
0133: + RegExpUtils.LineWhitespaces + "(?::"
0134: + RegExpUtils.LineWhitespaces + "(.*))?";
0135:
0136: public static final Pattern funcDefPattern = Pattern
0137: .compile(funcDefRegExp);
0138:
0139: OrderedProperties text;
0140:
0141: OrderedProperties fields = new OrderedProperties();
0142:
0143: OrderedProperties subfields = new OrderedProperties();
0144:
0145: DataDefinitionParseError mpe;
0146:
0147: Properties definedTypes;
0148:
0149: DataDefinition dd;
0150:
0151: // moved from ptrOneParser
0152: HashMap<String, RecordParser> ptrOne_RecordParsers = new HashMap<String, RecordParser>();
0153:
0154: // moved from setParser
0155: HashMap<String, DataDefinition> setParser_settbls = new HashMap<String, DataDefinition>();
0156:
0157: // moved from subtableParser
0158: HashMap<String, DataDefinition> subtableParser_subtables = new HashMap<String, DataDefinition>();
0159:
0160: HashMap<String, DataDefinition> subtableParser_here = new HashMap<String, DataDefinition>();
0161:
0162: private ArrayList<String> unparsedValidationDefinitions = new ArrayList<String>();
0163:
0164: public static boolean isValidationRule(String s) {
0165: return validationDefinitionPattern.matcher(s).matches();
0166: }
0167:
0168: public static boolean isFunction(String s) {
0169: return funcDefPattern.matcher(s).matches();
0170: }
0171:
0172: RecordParser() {
0173: definedTypes = new Properties();
0174: }
0175:
0176: // for parsing of subtalbes
0177: RecordParser(RecordInfo dd, RecordParser rp) {
0178: this .dd = dd;
0179: text = new OrderedProperties();
0180: definedTypes = rp.definedTypes;
0181: mpe = rp.mpe;
0182: }
0183:
0184: void parse(RecordInfo dd) {
0185: this .dd = dd;
0186: text = new OrderedProperties();
0187: mpe = new DataDefinitionParseError();
0188:
0189: try {
0190: read(text, dd.origin);
0191: } catch (IOException e) {
0192: throw fail(e);
0193: }
0194: try {
0195: // make the default pointers resulted from the table name
0196: dd.addStandardFields(dd.name.substring(dd.name
0197: .lastIndexOf('.') + 1));
0198: parse();
0199: } catch (RuntimeException e) {
0200: throw new MakumbaError(e,
0201: "Internal error in parser while parsing "
0202: + dd.getName());
0203: }
0204: if (!mpe.isSingle() && !(dd.getParentField() != null))
0205: throw mpe;
0206: }
0207:
0208: DataDefinition parse(java.net.URL u, String path) {
0209: dd = new RecordInfo(u, path);
0210: parse((RecordInfo) dd);
0211: return dd;
0212: }
0213:
0214: RecordInfo parse(String txt) {
0215: dd = new RecordInfo();
0216: text = new OrderedProperties();
0217: mpe = new DataDefinitionParseError();
0218:
0219: try {
0220: read(text, txt);
0221: } catch (IOException e) {
0222: throw fail(e);
0223: }
0224: try {
0225: // make the default pointers resulted from the table name
0226: ((RecordInfo) dd).addStandardFields(dd.getName().substring(
0227: dd.getName().lastIndexOf('.') + 1));
0228: parse();
0229: } catch (RuntimeException e) {
0230: throw new MakumbaError(e,
0231: "Internal error in parser while parsing "
0232: + dd.getName());
0233: }
0234: if (!mpe.isSingle() && dd.getParentField() == null)
0235: throw mpe;
0236:
0237: return (RecordInfo) dd;
0238: }
0239:
0240: void parse() {
0241: // include all the files and add them to the text, delete the
0242: // !include command
0243: solveIncludes();
0244:
0245: // put fields in the fields table, subfields in subfields
0246: separateFields();
0247:
0248: // determine the title field, delete !title
0249: setTitle();
0250:
0251: // read predefined types, delete all !type.*
0252: readTypes();
0253:
0254: // commands should be finished at this point
0255: if (text.size() != 0)
0256: mpe.add(fail("unrecognized commands", text.toString()));
0257:
0258: // make a FieldParser for each field, let it parse and substitute
0259: // itself
0260: treatMyFields();
0261:
0262: // send info from the subfield table to the subfields
0263: configSubfields();
0264:
0265: // call solveAll() on all subfields
0266: treatSubfields();
0267:
0268: // parse validation definition
0269: parseValidationDefinition();
0270:
0271: // after all fields are processed, process the multi field indices & check for field existance
0272: checkMultipleUniqueFields();
0273:
0274: }
0275:
0276: /** Check whether all fields used in multiple uniqueness checks are defined in the data definition. */
0277: private void checkMultipleUniqueFields() {
0278: for (int i = 0; i < dd.getMultiFieldUniqueKeys().length; i++) {
0279: DataDefinition.MultipleUniqueKeyDefinition multiUniqueKeyDefinition = (DataDefinition.MultipleUniqueKeyDefinition) dd
0280: .getMultiFieldUniqueKeys()[i];
0281: for (int j = 0; j < multiUniqueKeyDefinition.getFields().length; j++) {
0282: String fieldName = multiUniqueKeyDefinition.getFields()[j];
0283:
0284: // check for potential sub-fields
0285: DataDefinition checkedDataDef = dd;
0286: int indexOf = -1;
0287: while ((indexOf = fieldName.indexOf(".")) != -1) {
0288: // we have a sub-record-field
0289: String subFieldName = fieldName.substring(0,
0290: indexOf);
0291: fieldName = fieldName.substring(indexOf + 1);
0292: checkedDataDef = checkedDataDef.getFieldDefinition(
0293: subFieldName).getPointedType();
0294: }
0295: if (checkedDataDef.getFieldDefinition(fieldName) == null) {
0296: mpe.add(new DataDefinitionParseError(dd.getName(),
0297: "Unique index contains an unknown field: "
0298: + fieldName,
0299: multiUniqueKeyDefinition.getLine()));
0300: } else if (checkedDataDef != dd) {
0301: multiUniqueKeyDefinition.setKeyOverSubfield(true);
0302: }
0303: }
0304: }
0305: }
0306:
0307: void separateFields() {
0308: for (Enumeration e = text.keys(); e.hasMoreElements();) {
0309: String k = (String) e.nextElement();
0310: if (k.indexOf('!') == 0)
0311: continue;
0312:
0313: if (k.indexOf("->") == -1)
0314: fields.putLast(k, text.getOriginal(k), text.remove(k));
0315: else
0316: subfields.putLast(k, text.getOriginal(k), text
0317: .remove(k));
0318: }
0319: }
0320:
0321: void setTitle() {
0322: String origCmd = (String) text.getOriginal("!title");
0323: String ttl = (String) text.remove("!title");
0324: String ttlt = null;
0325: if (ttl != null) {
0326: if (fields.get(ttlt = ttl.trim()) == null) {
0327: mpe.add(fail("no such field for title", makeLine(
0328: origCmd, ttl)));
0329: return;
0330: }
0331: } else if (fields.get("name") != null)
0332: ttlt = "name";
0333: else
0334: // if there are any relations, we skip their fields as
0335: // titles...
0336: if (fields.size() > 0)
0337: ttlt = fields.keyAt(0);
0338: ((RecordInfo) dd).title = ttlt;
0339: }
0340:
0341: static java.net.URL getResource(String s) {
0342: return org.makumba.commons.ClassResource.get(s);
0343: }
0344:
0345: static public java.net.URL findDataDefinition(String s, String ext) {
0346: // must specify a filename, not a directory (or package), see bug 173
0347: java.net.URL u = findDataDefinitionOrDirectory(s, ext);
0348: if (u != null
0349: && (s.endsWith("/") || getResource(s + '/') != null))
0350: return null;
0351: return u;
0352: }
0353:
0354: static public java.net.URL findDataDefinitionOrDirectory(String s,
0355: String ext) {
0356: java.net.URL u = null;
0357: if (s.startsWith("/"))
0358: s = s.substring(1);
0359: if (s.endsWith(".") || s.endsWith("//"))
0360: return null;
0361: u = getResource(s.replace('.', '/') + "." + ext);
0362: if (u == null) {
0363: u = getResource("dataDefinitions/" + s.replace('.', '/')
0364: + "." + ext);
0365: if (u == null) {
0366: u = getResource("dataDefinitions/"
0367: + s.replace('.', '/'));
0368: if (u == null) {
0369: u = getResource(s.replace('.', '/'));
0370: }
0371: }
0372: }
0373: return u;
0374: }
0375:
0376: void solveIncludes() {
0377: int line = 0;
0378: OrderedProperties inclText;
0379: Vector<String> overridenFields = new Vector<String>();
0380:
0381: for (Enumeration e = text.keys(); e.hasMoreElements(); line++) {
0382: String st = (String) e.nextElement();
0383:
0384: if (st.startsWith("!include")) {
0385: String ok = text.getOriginal(st);
0386: String incl = (String) text.remove(st);
0387: line--;
0388: String s = incl.trim();
0389: java.net.URL u = findDataDefinition(s, "idd");
0390: // String n = "." + dd.getName();
0391: // if(u==null && s.indexOf('.')==-1)
0392: // u=findTable(n.substring(1, n.lastIndexOf('.')+1));
0393:
0394: if (u == null) {
0395: mpe.add(fail("could not find include file " + s, ok
0396: + "=" + incl));
0397: return;
0398: }
0399: try {
0400: inclText = new OrderedProperties();
0401: read(inclText, u);
0402: } catch (IOException ioe) {
0403: mpe.add(fail("could not find include file " + s
0404: + " " + ioe, ok + "=" + incl));
0405: ;
0406: return;
0407: }
0408:
0409: for (Enumeration k = inclText.keys(); k
0410: .hasMoreElements();) {
0411: String key = (String) k.nextElement();
0412: String val = text.getProperty(key);
0413: if (val == null) // new field, not overriden in main mdd
0414: text.putAt(++line, key, inclText
0415: .getOriginal(key), inclText
0416: .getProperty(key));
0417: else
0418: // field is overriden in main mdd, ignore it
0419: overridenFields.add(key);
0420: }
0421: }
0422: }
0423:
0424: // now we remove all overriden empty fields
0425: // keep the non-overriden ones with empty definiton to report a mdd
0426: // error
0427: for (Enumeration k = overridenFields.elements(); k
0428: .hasMoreElements();) {
0429: String key = (String) k.nextElement();
0430: if (((String) text.get(key)).trim().length() == 0)
0431: text.remove(key);
0432: }
0433: }
0434:
0435: void readTypes() {
0436: for (Enumeration e = text.keys(); e.hasMoreElements();) {
0437: String s = (String) e.nextElement();
0438: if (s.startsWith("!type.")) {
0439: String nm = s.substring(6);
0440: definedTypes.put(nm, text.remove(s));
0441: }
0442: }
0443: }
0444:
0445: FieldCursor currentRowCursor;
0446:
0447: FieldInfo getFieldInfo(String fieldName) {
0448: return (FieldInfo) dd.getFieldDefinition(fieldName);
0449: }
0450:
0451: void treatMyFields() {
0452: FieldInfo fi;
0453: String nm;
0454:
0455: int line = 0;
0456: for (Enumeration e = fields.keys(); e.hasMoreElements(); line++) {
0457: nm = (String) e.nextElement();
0458: // check name for validity:
0459: for (int i = 0; i < nm.length(); i++) {
0460: if (i == 0
0461: && !Character.isJavaIdentifierStart(nm
0462: .charAt(i))
0463: || i > 0
0464: && !Character
0465: .isJavaIdentifierPart(nm.charAt(i)))
0466: mpe.add(fail("Invalid character \"" + nm.charAt(i)
0467: + "\" in field name \"" + nm + "\"", nm));
0468: }
0469:
0470: if (ReservedKeywords.isReservedKeyword(nm)) {
0471: mpe.add(fail(
0472: "Error: field name cannot be one of the reserved keywords "
0473: + ReservedKeywords
0474: .getKeywordsAsString(), nm));
0475: }
0476:
0477: fi = new FieldInfo((RecordInfo) dd, nm);
0478: ((RecordInfo) dd).addField1(fi);
0479: try {
0480: parse(nm, new FieldCursor(this , makeLine(fields, nm)));
0481: } catch (DataDefinitionParseError pe) {
0482: ((RecordInfo) dd).fields.remove(nm);
0483: ((RecordInfo) dd).fieldOrder.remove(nm);
0484: mpe.add(pe);
0485: continue;
0486: }
0487: }
0488: }
0489:
0490: void configSubfields() {
0491: String nm;
0492: int p;
0493: for (Enumeration e = subfields.keys(); e.hasMoreElements();) {
0494: nm = (String) e.nextElement();
0495:
0496: p = nm.indexOf("->");
0497: FieldInfo fieldInfo = getFieldInfo(nm.substring(0, p));
0498: if (fieldInfo == null) { // we did not find the field info, i.e. accessed unknownField->field.
0499: mpe.add(fail("Could not find subfield '"
0500: + nm.substring(0, p) + "'", makeLine(subfields,
0501: nm)));
0502: continue;
0503: }
0504: String type = (String) fieldInfo.type;
0505: if (type == null) {
0506: mpe.add(fail("no such field in subfield definition",
0507: makeLine(subfields, nm)));
0508: continue;
0509: }
0510:
0511: String s;
0512: if ((s = addText(nm.substring(0, p), nm.substring(p + 2),
0513: subfields.getOriginal(nm), subfields
0514: .getProperty(nm))) != null)
0515: mpe.add(fail(s, makeLine(subfields, nm)));
0516: }
0517: }
0518:
0519: void treatSubfields() {
0520: for (Enumeration e = dd.getFieldNames().elements(); e
0521: .hasMoreElements();) {
0522: String fieldName = (String) e.nextElement();
0523: parseSubfields(fieldName);
0524: }
0525: }
0526:
0527: static String makeLine(String origKey, String value) {
0528: return origKey + "=" + value;
0529: }
0530:
0531: static String makeLine(OrderedProperties p, String k) {
0532: return p.getOriginal(k) + "=" + p.getProperty(k);
0533: }
0534:
0535: DataDefinitionParseError fail(String why, String where) {
0536: return new DataDefinitionParseError(dd.getName(), why, where,
0537: where.length());
0538: }
0539:
0540: DataDefinitionParseError fail(IOException ioe) {
0541: return new DataDefinitionParseError(dd.getName(), ioe);
0542: }
0543:
0544: void read(OrderedProperties op, String txt) throws IOException {
0545: read(op, new BufferedReader(new StringReader(txt)));
0546: }
0547:
0548: void read(OrderedProperties op, java.net.URL u) throws IOException {
0549: read(op, new BufferedReader(new InputStreamReader(
0550: (InputStream) u.getContent())));
0551: }
0552:
0553: void read(OrderedProperties op, BufferedReader rd)
0554: throws IOException {
0555: while (true) {
0556: String s = null;
0557: s = rd.readLine();
0558: if (s == null)
0559: break;
0560:
0561: String st = s.trim();
0562: if (st.length() == 0 || st.charAt(0) == '#')
0563: continue;
0564:
0565: String lineWithoutComment = null;
0566: if (st.indexOf(";") == -1) {
0567: lineWithoutComment = st;
0568: } else {
0569: lineWithoutComment = st.substring(0, st.indexOf(";"));
0570: }
0571:
0572: // check if the line is a validation definition
0573: Matcher matcher = validationDefinitionPattern
0574: .matcher(lineWithoutComment);
0575: if (matcher.matches()) {
0576: // we parse them later
0577: unparsedValidationDefinitions.add(lineWithoutComment);
0578: continue;
0579: }
0580:
0581: // check if the line is a function definition
0582: matcher = funcDefPattern.matcher(lineWithoutComment);
0583: if (matcher.matches()) {
0584: String name = matcher.group(1);
0585: if (dd.getFunction(name) != null) {
0586: mpe.add(new DataDefinitionParseError(dd.getName(),
0587: "Duplicate function name: " + name, st));
0588: }
0589: String queryFragment = matcher.group(matcher
0590: .groupCount() - 1);
0591: String errorMessage = matcher.group(matcher
0592: .groupCount());
0593: DataDefinition params = new RecordInfo(dd.getName()
0594: + "." + matcher.group(0));
0595: for (int i = 2; i < matcher.groupCount() - 2; i += 2) {
0596: String type = matcher.group(i);
0597: // if we provide < 2 params, we still get 2 * 2 empty groups matched ==> need to check tht here
0598: // see the comment in the field init.
0599: if (type != null) {
0600: if (type.equals("char[]")) { // we substitute char[] with the max char length
0601: type = ("char[255]");
0602: }
0603: params.addField(new FieldInfo(matcher
0604: .group(i + 1), type));
0605: }
0606: }
0607: DataDefinition.QueryFragmentFunction function = new DataDefinition.QueryFragmentFunction(
0608: name, queryFragment, params, errorMessage);
0609: dd.addFunction(name, function);
0610: continue;
0611: }
0612:
0613: int l = s.indexOf('=');
0614: if (l == -1) {
0615: mpe
0616: .add(fail(
0617: "non-empty, non-comment line without =",
0618: s));
0619: continue;
0620: }
0621: String k = s.substring(0, l);
0622: String kt = k.trim();
0623: if (kt.length() == 0) {
0624: mpe.add(fail("zero length key", s));
0625: continue;
0626: }
0627: if (kt.charAt(0) == '!' && kt.length() == 1) {
0628: mpe.add(fail("zero length command", s));
0629: continue;
0630: }
0631:
0632: if (kt.startsWith("!include")) {
0633: if (kt.length() > 8) {
0634: mpe.add(fail("unknown command: " + kt, s));
0635: continue;
0636: }
0637: while (op.get(kt) != null)
0638: kt = kt + "_";
0639: }
0640: String val = s.substring(l + 1);
0641: if (op.putLast(kt, k, val) != null)
0642: mpe.add(fail("ambiguous key " + kt, s));
0643: }
0644: rd.close();
0645: }
0646:
0647: // moved from FieldParser
0648: public void parseSubfields(String fieldName) {
0649: switch (getFieldInfo(fieldName).getIntegerType()) {
0650: case FieldDefinition._setComplex:
0651: case FieldDefinition._ptrOne:
0652: parse_ptrOne_Subfields(fieldName);
0653: break;
0654: case FieldDefinition._set:
0655: parse_set_Subfields(fieldName);
0656: break;
0657: default:
0658: ;
0659: }
0660: }
0661:
0662: // moved from ptrOneParser
0663: public void parse_ptrOne_Subfields(String fieldName) {
0664: ptrOne_RecordParsers.get(fieldName).parse();
0665: getFieldInfo(fieldName).extra2 = ((RecordParser) ptrOne_RecordParsers
0666: .get(fieldName)).dd.getTitleFieldName();
0667: }
0668:
0669: // moved from setParser
0670: public void parse_set_Subfields(String fieldName) {
0671: if (getFieldInfo(fieldName).extra2 == null) {
0672: getFieldInfo(fieldName).extra2 = ((RecordInfo) subtableParser_subtables
0673: .get(fieldName)).title = ((DataDefinition) setParser_settbls
0674: .get(fieldName)).getTitleFieldName();
0675: }
0676: }
0677:
0678: // moved from FieldParser
0679: String acceptTitle(String fieldName, String nm, String origNm,
0680: String val, Object o) {
0681: val = val.trim();
0682: if (nm.equals("!title")) {
0683: DataDefinition ri = (DataDefinition) o;
0684: if (ri.getFieldDefinition(val) == null)
0685: return ri.getName() + " has no field called " + val;
0686: getFieldInfo(fieldName).extra2 = val;
0687: return null;
0688: }
0689: return addText(fieldName, nm, origNm, val);
0690: }
0691:
0692: // moved from FieldParser
0693: String addText(String fieldName, String nm, String origNm,
0694: String val) {
0695: switch (getFieldInfo(fieldName).getIntegerType()) {
0696: case FieldDefinition._ptr:
0697: case FieldDefinition._ptrRel:
0698: return add_ptr_Text(fieldName, nm, origNm, val);
0699: case FieldDefinition._ptrOne:
0700: case FieldDefinition._setComplex:
0701: return add_ptrOne_Text(fieldName, nm, origNm, val);
0702: case FieldDefinition._set:
0703: return add_set_Text(fieldName, nm, origNm, val);
0704: default:
0705: return base_addText(fieldName, nm, origNm, val);
0706: }
0707: }
0708:
0709: // original from FieldParser
0710: String base_addText(String fieldName, String nm, String origNm,
0711: String val) {
0712: return "subfield not allowed";
0713: }
0714:
0715: // moved from ptrParser
0716: String add_ptr_Text(String fieldName, String nm, String origNm,
0717: String val) {
0718: return acceptTitle(fieldName, nm, origNm, val,
0719: getFieldInfo(fieldName).extra1);
0720: }
0721:
0722: // moved from ptrOneParser
0723: String add_ptrOne_Text(String fieldName, String nm, String origNm,
0724: String val) {
0725: if (ptrOne_RecordParsers.get(fieldName).text.putLast(nm,
0726: origNm, val) != null)
0727: return "field already exists";
0728: return null;
0729: }
0730:
0731: // moved from setParser
0732: String add_set_Text(String fieldName, String nm, String origNm,
0733: String val) {
0734: String s = acceptTitle(fieldName, nm, origNm, val,
0735: (DataDefinition) setParser_settbls.get(fieldName));
0736: if (s == null)
0737: ((RecordInfo) subtableParser_subtables.get(fieldName)).title = val
0738: .trim();
0739: return s;
0740: }
0741:
0742: // moved from FieldParser
0743: void parse(String fieldName, FieldCursor fc)
0744: throws org.makumba.DataDefinitionParseError {
0745: while (true) {
0746: if (fc.lookup("not")) {
0747: if (getFieldInfo(fieldName).notNull)
0748: throw fc.fail("too many not null");
0749: fc.expect("null");
0750: fc.expectWhitespace();
0751: getFieldInfo(fieldName).notNull = true;
0752: continue;
0753: }
0754:
0755: if (fc.lookup("fixed")) {
0756: fc.expectWhitespace();
0757: if (getFieldInfo(fieldName).fixed)
0758: throw fc.fail("too many fixed");
0759: getFieldInfo(fieldName).fixed = true;
0760: continue;
0761: }
0762:
0763: if (fc.lookup("unique")) {
0764: fc.expectWhitespace();
0765: if (getFieldInfo(fieldName).unique)
0766: throw fc.fail("already unique");
0767: getFieldInfo(fieldName).unique = true;
0768: continue;
0769: }
0770:
0771: break;
0772: }
0773:
0774: if (setType(fieldName, fc.expectTypeLiteral(), fc) == null) {
0775: String s = definedTypes
0776: .getProperty(getFieldInfo(fieldName).type);
0777: if (s == null)
0778: throw fc.fail("unknown type: "
0779: + getFieldInfo(fieldName).type);
0780:
0781: fc.substitute(getFieldInfo(fieldName).type.length(), s);
0782:
0783: if (setType(fieldName, fc.expectTypeLiteral(), fc) == null)
0784: throw fc.fail("unknown type: "
0785: + getFieldInfo(fieldName).type);
0786: }
0787: getFieldInfo(fieldName).description = getFieldInfo(fieldName).description == null ? getFieldInfo(fieldName).name
0788: : getFieldInfo(fieldName).description;
0789:
0790: }
0791:
0792: // moved from FieldParser
0793: String setType(String fieldName, String type, FieldCursor fc)
0794: throws org.makumba.DataDefinitionParseError {
0795: String initialType = type;
0796: getFieldInfo(fieldName).type = type;
0797: while (true) {
0798: if (FieldInfo.integerTypeMap
0799: .get(getFieldInfo(fieldName).type) == null) {
0800: // getFieldInfo(fieldName).type = null;
0801: return null;
0802: }
0803: parse1(fieldName, fc);
0804: if (getFieldInfo(fieldName).type.equals(initialType))
0805: return initialType;
0806: initialType = getFieldInfo(fieldName).type;
0807: }
0808: }
0809:
0810: /**
0811: * switch for the existing parse methods. set the field type to another value if we want to change it
0812: */
0813: void parse1(String fieldName, FieldCursor fc) {
0814: switch (getFieldInfo(fieldName).getIntegerType()) {
0815: case FieldDefinition._charEnum:
0816: charEnum_parse1(fieldName, fc);
0817: return;
0818: case FieldDefinition._char:
0819: char_parse1(fieldName, fc);
0820: return;
0821: case FieldDefinition._intEnum:
0822: intEnum_parse1(fieldName, fc);
0823: return;
0824: case FieldDefinition._int:
0825: int_parse1(fieldName, fc);
0826: return;
0827: case FieldDefinition._ptrOne:
0828: ptrOne_parse1(fieldName, fc);
0829: return;
0830: case FieldDefinition._ptrRel:
0831: case FieldDefinition._ptr:
0832: ptr_parse1(fieldName, fc);
0833: return;
0834: case FieldDefinition._setCharEnum:
0835: setCharEnum_parse1(fieldName, fc);
0836: return;
0837: case FieldDefinition._setComplex:
0838: setComplex_parse1(fieldName, fc);
0839: return;
0840: case FieldDefinition._setIntEnum:
0841: setIntEnum_parse1(fieldName, fc);
0842: return;
0843: case FieldDefinition._set:
0844: set_parse1(fieldName, fc);
0845: return;
0846: case FieldDefinition._text:
0847: text_parse1(fieldName, fc);
0848: return;
0849: case FieldDefinition._date:
0850: case FieldDefinition._real:
0851: case FieldDefinition._ptrIndex:
0852: case FieldDefinition._dateCreate:
0853: case FieldDefinition._dateModify:
0854: simple_parse1(fieldName, fc);
0855: return;
0856: default:
0857: ;
0858: }
0859: }
0860:
0861: // moved from intParser
0862: public void int_parse1(String fieldName, FieldCursor fc) {
0863: if (!fc.lookup("{")) {
0864: getFieldInfo(fieldName).description = fc
0865: .lookupDescription();
0866: return;
0867: }
0868: getFieldInfo(fieldName).type = "intEnum";
0869: }
0870:
0871: // moved from intEnumParser
0872: public void intEnum_parse1(String fieldName, FieldCursor fc) {
0873: fc.expectIntEnum(getFieldInfo(fieldName));
0874: getFieldInfo(fieldName).description = fc.lookupDescription();
0875: return;
0876: }
0877:
0878: // moved from charParser
0879: public void char_parse1(String fieldName, FieldCursor fc) {
0880: if (!fc.lookup("{")) {
0881: fc.expect("[");
0882: Integer size = fc.expectInteger();
0883: if (size.intValue() > 255 || size.intValue() < 0)
0884: throw fc
0885: .fail("char size must be between 0 and 255, not "
0886: + size.toString());
0887: getFieldInfo(fieldName).extra2 = size;
0888: fc.expect("]");
0889: getFieldInfo(fieldName).description = fc
0890: .lookupDescription();
0891: return;
0892: }
0893:
0894: getFieldInfo(fieldName).type = "charEnum";
0895: }
0896:
0897: // moved from charEnumParser
0898: public void charEnum_parse1(String fieldName, FieldCursor fc) {
0899: fc.expectCharEnum(getFieldInfo(fieldName));
0900: getFieldInfo(fieldName).description = fc.lookupDescription();
0901: return;
0902: }
0903:
0904: // moved from simpleParser
0905: public void simple_parse1(String fieldName, FieldCursor fc) {
0906: getFieldInfo(fieldName).description = fc.lookupDescription();
0907: return;
0908: }
0909:
0910: // moved from textParser
0911: public void text_parse1(String fieldName, FieldCursor fc)
0912: throws org.makumba.DataDefinitionParseError {
0913: if (getFieldInfo(fieldName).isUnique())
0914: throw fc.fail("text fields can't be declared unique");
0915: return;
0916: }
0917:
0918: // moved from setComplexParser
0919: public void setComplex_parse1(String fieldName, FieldCursor fc) {
0920: ptrOne_parse1(fieldName, fc);
0921: ((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
0922: return;
0923: }
0924:
0925: // moved from ptrOneParser
0926: public void ptrOne_parse1(String fieldName, FieldCursor fc) {
0927: makeSubtable(fieldName, fc);
0928: ptrOne_RecordParsers.put(fieldName, new RecordParser(
0929: ((RecordInfo) subtableParser_subtables.get(fieldName)),
0930: this ));
0931: return;
0932: }
0933:
0934: // moved from setEnumParser, setChatEnumParser
0935: public void setCharEnum_parse1(String fieldName, FieldCursor fc) {
0936: FieldInfo _enum = new FieldInfo(
0937: ((RecordInfo) subtableParser_subtables.get(fieldName)),
0938: "enum");
0939: makeSubtable(fieldName, fc);
0940: ((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
0941: ((RecordInfo) subtableParser_subtables.get(fieldName))
0942: .addField1(_enum);
0943: ((RecordInfo) subtableParser_subtables.get(fieldName)).title = _enum.name;
0944: ((RecordInfo) subtableParser_subtables.get(fieldName)).setField = _enum.name;
0945: _enum.type = "charEnum";
0946: fc.expectCharEnum(_enum);
0947: getFieldInfo(fieldName).description = fc.lookupDescription();
0948: _enum.description = getFieldInfo(fieldName).getDescription() == null ? _enum.name
0949: : getFieldInfo(fieldName).getDescription();
0950: return;
0951: }
0952:
0953: // moved from setEnumParser, setIntEnumParser
0954: public void setIntEnum_parse1(String fieldName, FieldCursor fc) {
0955: FieldInfo _enum = new FieldInfo(
0956: ((RecordInfo) subtableParser_subtables.get(fieldName)),
0957: "enum");
0958: makeSubtable(fieldName, fc);
0959: ((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
0960: ((RecordInfo) subtableParser_subtables.get(fieldName))
0961: .addField1(_enum);
0962: ((RecordInfo) subtableParser_subtables.get(fieldName)).title = _enum.name;
0963: ((RecordInfo) subtableParser_subtables.get(fieldName)).setField = _enum.name;
0964: _enum.type = "intEnum";
0965: fc.expectIntEnum(_enum);
0966: getFieldInfo(fieldName).description = fc.lookupDescription();
0967: _enum.description = getFieldInfo(fieldName).getDescription() == null ? _enum.name
0968: : getFieldInfo(fieldName).getDescription();
0969: return;
0970: }
0971:
0972: // moved from ptrParser
0973: public void ptr_parse1(String fieldName, FieldCursor fc) {
0974: Object o = fc.lookupTableSpecifier();
0975:
0976: if (o != null)
0977: getFieldInfo(fieldName).extra1 = o;
0978: try {
0979: getFieldInfo(fieldName).description = fc
0980: .lookupDescription();
0981: } catch (org.makumba.DataDefinitionParseError e) {
0982: throw fc.fail("table specifier or nothing expected");
0983: }
0984:
0985: if (o != null)
0986: return;
0987:
0988: // getFieldInfo(fieldName).unique = true;
0989: getFieldInfo(fieldName).type = "ptrOne";
0990: }
0991:
0992: // moved from setParser
0993: public void set_parse1(String fieldName, FieldCursor fc) {
0994: if (getFieldInfo(fieldName).isUnique())
0995: throw fc.fail("sets can't be declared unique");
0996:
0997: DataDefinition ori = fc.lookupTableSpecifier();
0998: if (ori == null) {
0999: String word = fc.lookupTypeLiteral();
1000: if (word == null) {
1001: try {
1002: getFieldInfo(fieldName).description = fc
1003: .lookupDescription();
1004: } catch (org.makumba.DataDefinitionParseError pe) {
1005: throw fc
1006: .fail("table specifier, enumeration type, or nothing expected");
1007: }
1008: getFieldInfo(fieldName).type = "setComplex";
1009: return;
1010: }
1011: String newName = enumSet(fieldName, fc, word);
1012: if (newName != null) {
1013: getFieldInfo(fieldName).type = newName;
1014: return;
1015: }
1016: String s = fc.rp.definedTypes.getProperty(word);
1017: if (s == null)
1018: throw fc
1019: .fail("table, char{}, int{} or macro type expected after set");
1020:
1021: fc.substitute(word.length(), s);
1022:
1023: newName = enumSet(fieldName, fc, fc.expectTypeLiteral());
1024:
1025: if (newName != null) {
1026: getFieldInfo(fieldName).type = newName;
1027: return;
1028: }
1029:
1030: throw fc.fail("int{} or char{} macro expected after set");
1031: }
1032:
1033: makeSubtable(fieldName, fc);
1034: ((RecordInfo) subtableParser_subtables.get(fieldName)).mainPtr = addPtrHere(fieldName);
1035:
1036: setParser_settbls.put(fieldName, ori);
1037: ((RecordInfo) subtableParser_subtables.get(fieldName)).setField = addPtr(
1038: fieldName, ((RecordInfo) setParser_settbls
1039: .get(fieldName)).getBaseName(), ori);
1040: return;
1041: }
1042:
1043: // moved from setParser
1044: String enumSet(String fieldName, FieldCursor fc, String word) {
1045: String newName;
1046: if (fc.lookup("{")) {
1047: newName = "set" + word + "Enum";
1048: getFieldInfo(fieldName).type = newName;
1049: if (newName != null)
1050: return newName;
1051: fc.fail("int{} or char{} expected after set");
1052: }
1053: return null;
1054: }
1055:
1056: // moved from subtableParser
1057: void makeSubtable(String fieldName, FieldCursor fc) {
1058: subtableParser_here.put(fieldName, dd);
1059:
1060: subtableParser_subtables.put(fieldName,
1061: ((RecordInfo) subtableParser_here.get(fieldName))
1062: .makeSubtable(getFieldInfo(fieldName).name));
1063: ((RecordInfo) subtableParser_subtables.get(fieldName))
1064: .addStandardFields(((RecordInfo) subtableParser_subtables
1065: .get(fieldName)).subfield);
1066: getFieldInfo(fieldName).extra1 = ((RecordInfo) subtableParser_subtables
1067: .get(fieldName));
1068: }
1069:
1070: // moved from subtableParser
1071: String addPtr(String fieldName, String name, DataDefinition o) {
1072: int n = name.lastIndexOf('.');
1073: if (n != -1)
1074: name = name.substring(n + 1);
1075: while (((RecordInfo) subtableParser_subtables.get(fieldName)).fields
1076: .get(name) != null)
1077: name = name + "_";
1078:
1079: FieldInfo ptr = new FieldInfo(
1080: ((RecordInfo) subtableParser_subtables.get(fieldName)),
1081: name);
1082: ((RecordInfo) subtableParser_subtables.get(fieldName))
1083: .addField1(ptr);
1084: ptr.fixed = true;
1085: ptr.notNull = true;
1086: ptr.type = "ptrRel";
1087: ptr.extra1 = o;
1088: ptr.description = "relational pointer";
1089: return name;
1090: }
1091:
1092: // moved from subtableParser
1093: String addPtrHere(String fieldName) {
1094: // System.err.println(here.canonicalName()+"
1095: // "+subtable.canonicalName());
1096: ((RecordInfo) subtableParser_subtables.get(fieldName)).relations = 1;
1097: if (((RecordInfo) subtableParser_here.get(fieldName))
1098: .getParentField() != null)
1099: return addPtr(fieldName, ((RecordInfo) subtableParser_here
1100: .get(fieldName)).subfield,
1101: ((RecordInfo) subtableParser_here.get(fieldName)));
1102: else
1103: return addPtr(fieldName, ((RecordInfo) subtableParser_here
1104: .get(fieldName)).name,
1105: ((RecordInfo) subtableParser_here.get(fieldName)));
1106: }
1107:
1108: public void parseValidationDefinition()
1109: throws ValidationDefinitionParseError {
1110: ValidationDefinitionParseError mpe = new ValidationDefinitionParseError();
1111: for (int i = 0; i < unparsedValidationDefinitions.size(); i++) {
1112: String line = unparsedValidationDefinitions.get(i);
1113: try {
1114: line = line.trim();
1115: if (line.indexOf(";") != -1) { // cut off end-of-line comments
1116: line = line.substring(0, line.indexOf(";")).trim();
1117: }
1118:
1119: // check if the line is a validation definition
1120: Matcher singleValidationMatcher = validationDefinitionPattern
1121: .matcher(line);
1122: if (!singleValidationMatcher.matches()) {
1123: throw new ValidationDefinitionParseError(dd
1124: .getName(), "Illegal rule definition!",
1125: line);
1126: }
1127: String[] definitionParts = line.split(":");
1128: if (definitionParts.length < 2) {
1129: throw new ValidationDefinitionParseError(
1130: dd.getName(),
1131: "Rule does not consist of the two parts <rule>:<message>!",
1132: line);
1133: }
1134: String fieldName = singleValidationMatcher.group(1)
1135: .trim();
1136: String operation = singleValidationMatcher.group(2)
1137: .trim();
1138: String ruleDef = singleValidationMatcher.group(3)
1139: .trim();
1140: String errorMessage = definitionParts[1].trim();
1141: String ruleName = line;
1142: ValidationRule rule = null;
1143: Matcher matcher;
1144:
1145: // check all possible validation types
1146: if (StringUtils.equals(operation, RegExpValidationRule
1147: .getOperator())) {
1148: // regexp validation
1149: FieldDefinition fd = DataDefinitionProvider
1150: .getFieldDefinition(dd, fieldName, line);
1151: rule = new RegExpValidationRule(fd, fieldName,
1152: ruleName, errorMessage, ruleDef);
1153:
1154: } else if (StringUtils.equals(operation,
1155: NumberRangeValidationRule.getOperator())) {
1156: // number (int or real) validation
1157: FieldDefinition fd = DataDefinitionProvider
1158: .getFieldDefinition(dd, fieldName, line);
1159: matcher = RangeValidationRule.getMatcher(ruleDef);
1160: if (!matcher.matches()) {
1161: throw new ValidationDefinitionParseError("",
1162: "Illegal range definition", line);
1163: }
1164: rule = new NumberRangeValidationRule(fd, fieldName,
1165: ruleName, errorMessage, matcher.group(1)
1166: .trim(), matcher.group(2).trim());
1167:
1168: } else if (StringUtils.equals(operation,
1169: StringLengthValidationRule.getOperator())) {
1170: // string lenght (char or text) validation
1171: FieldDefinition fd = DataDefinitionProvider
1172: .getFieldDefinition(dd, fieldName, line);
1173: matcher = RangeValidationRule.getMatcher(ruleDef);
1174: if (!matcher.matches()) {
1175: throw new ValidationDefinitionParseError("",
1176: "Illegal range definition", line);
1177: }
1178: rule = new StringLengthValidationRule(fd,
1179: fieldName, ruleName, errorMessage, matcher
1180: .group(1).trim(), matcher.group(2)
1181: .trim());
1182:
1183: } else if (StringUtils.equals(operation,
1184: ComparisonValidationRule.getOperator())) {
1185: // comparison validation, compares two fields or a field with a constant
1186: // fieldName = matcher.group(1);
1187: matcher = ComparisonValidationRule
1188: .getMatcher(ruleDef);
1189: if (!matcher.matches()) {
1190: throw new ValidationDefinitionParseError("",
1191: "Illegal comparison definition", line);
1192: }
1193: if (dd.getFieldDefinition(fieldName) == null) { // let's see if the first part is a field name
1194: fieldName = matcher.group(1);
1195: }
1196: String functionName = null;
1197: if (BasicValidationRule
1198: .isValidFunctionCall(fieldName)) {
1199: functionName = BasicValidationRule
1200: .extractFunctionNameFromStatement(fieldName);
1201: fieldName = BasicValidationRule
1202: .extractFunctionArgument(fieldName);
1203: }
1204: FieldDefinition fd = DataDefinitionProvider
1205: .getFieldDefinition(dd, fieldName, line);
1206: String operator = matcher.group(2).trim();
1207: String compareTo = matcher.group(3).trim();
1208: if (fd.getIntegerType() == FieldDefinition._date
1209: && ComparisonValidationRule
1210: .matchesDateExpression(compareTo)) {
1211: // we have a comparison to a date constant / expression
1212: rule = new ComparisonValidationRule(fd,
1213: fieldName, compareTo, ruleName,
1214: errorMessage, operator);
1215: } else {
1216: FieldDefinition otherFd = DataDefinitionProvider
1217: .getFieldDefinition(dd, compareTo, line);
1218: rule = new ComparisonValidationRule(fd,
1219: fieldName, functionName, otherFd,
1220: compareTo, ruleName, errorMessage,
1221: operator);
1222: }
1223: } else if (StringUtils.equals(operation, "unique")) {
1224: // check if the line defines a multi-field unique key
1225: matcher = multiUniquePattern.matcher(ruleDef);
1226: if (!matcher.matches()) {
1227: throw new ValidationDefinitionParseError(
1228: "",
1229: "Illegal multi-field unique definition",
1230: line);
1231: }
1232: ArrayList<String> groupList = new ArrayList<String>();
1233: for (int j = 1; j <= matcher.groupCount(); j++) {
1234: if (matcher.group(j) != null) {
1235: // checking if the fields exist will be done later
1236: groupList.add(matcher.group(j).trim());
1237: }
1238: }
1239: String[] groups = (String[]) groupList
1240: .toArray(new String[groupList.size()]);
1241: dd
1242: .addMultiUniqueKey(new DataDefinition.MultipleUniqueKeyDefinition(
1243: groups, line));
1244: java.util.logging.Logger
1245: .getLogger(
1246: "org.makumba."
1247: + "datadefinition.makumba")
1248: .finer(
1249: "added multi-field unique key: "
1250: + new DataDefinition.MultipleUniqueKeyDefinition(
1251: groups, line));
1252: continue;
1253: } else {
1254: // no recognised rule
1255: throw new ValidationDefinitionParseError("",
1256: "Rule type not recognised!", line);
1257: }
1258: rule.getFieldDefinition().addValidationRule(rule);
1259: // validationRules.put(fieldName, rule);
1260: ((RecordInfo) dd).addValidationRule(rule);
1261: java.util.logging.Logger.getLogger(
1262: "org.makumba." + "datadefinition.makumba")
1263: .finer("added rule: " + rule);
1264: } catch (ValidationDefinitionParseError e) {
1265: mpe.add(e);
1266: }
1267:
1268: if (!mpe.isSingle()) {
1269: throw mpe;
1270: }
1271: }
1272:
1273: if (!mpe.isSingle()) {
1274: throw mpe;
1275: }
1276: // System.out.println("Finished parsing validation definition '" + name + "'.");
1277: }
1278:
1279: public static void main(String[] args) {
1280: // test some function definition
1281: RegExpUtils
1282: .evaluate(
1283: RecordParser.funcDefPattern,
1284: new String[] {
1285: " someFunc() = abc : errorMessage",
1286: " someFunc(char[] a, int 5) =abc:errorMessages",
1287: "someFunction(int a, char[] b) = yeah:errorMessage3",
1288: "someOtherFunction(int age, char[] b) = this.age > age : You are too young!" });
1289:
1290: // test some mdd reading
1291: RecordInfo.getRecordInfo("test.Person");
1292: }
1293:
1294: }
|