de.interactive_instruments.ShapeChange.SBVR.SbvrRuleLoader.java Source code

Java tutorial

Introduction

Here is the source code for de.interactive_instruments.ShapeChange.SBVR.SbvrRuleLoader.java

Source

/**
 * ShapeChange - processing application schemas for geographic information
 *
 * This file is part of ShapeChange. ShapeChange takes a ISO 19109 
 * Application Schema from a UML model and translates it into a 
 * GML Application Schema or other implementation representations.
 *
 * Additional information about the software can be found at
 * http://shapechange.net/
 *
 * (c) 2002-2015 interactive instruments GmbH, Bonn, Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:
 * interactive instruments GmbH
 * Trierer Strasse 70-72
 * 53115 Bonn
 * Germany
 */
package de.interactive_instruments.ShapeChange.SBVR;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import de.interactive_instruments.ShapeChange.MessageSource;
import de.interactive_instruments.ShapeChange.Options;
import de.interactive_instruments.ShapeChange.ShapeChangeResult;
import de.interactive_instruments.ShapeChange.Model.ClassInfo;
import de.interactive_instruments.ShapeChange.Model.Constraint;
import de.interactive_instruments.ShapeChange.Model.Model;
import de.interactive_instruments.ShapeChange.Model.PackageInfo;
import de.interactive_instruments.ShapeChange.Model.Generic.GenericFolConstraint;
import de.interactive_instruments.antlr.ShapeChangeAntlr.SbvrParserHelper;
import de.interactive_instruments.antlr.sbvr.SBVRLexer;
import de.interactive_instruments.antlr.sbvr.SBVRParser;

/**
 * Loads SBVR rule information from an Excel file so that the information can be
 * added as First Order Logic constraint - with type 'SBVR' - to classes in the
 * model.
 * 
 * @author Johannes Echterhoff
 *
 */
public class SbvrRuleLoader implements MessageSource {

    private Options options;
    private ShapeChangeResult result;
    private Model model;

    private SbvrParserHelper helper;

    /**
     * Map with SBVR rule information obtained from an external source.
     * 
     * <ul>
     * <li>key: class name</li>
     * <li>value: mapping of schema package name to SBVR rule info
     * <ul>
     * <li>key: schema package name ({@value #UNSPECIFIED_SCHEMA_PACKAGE_NAME}
     * if no schema package name has been provided)</li>
     * <li>value: list of SBVR rules that apply to classes in that schema (the
     * list is sorted according to lexical order on a) the class name and b) the
     * rule text)</li>
     * </ul>
     * </ul>
     */
    private TreeMap<String, TreeMap<String, List<SbvrRuleInfo>>> sbvrRules;

    public static final String UNSPECIFIED_SCHEMA_PACKAGE_NAME = "UNSPECIFIED";

    public SbvrRuleLoader(String sbvrFileLocation, Options options, ShapeChangeResult result, Model model) {

        this.options = options;
        this.result = result;
        this.model = model;

        if (sbvrFileLocation != null) {

            /*
             * Before loading the rules, load model verbs and nouns because we might
             * need to parse the main class name for a rule from the rule text.
             */
            helper = SbvrUtil.createParserHelper(model);

            java.io.File sbvrFile = new java.io.File(sbvrFileLocation);
            boolean ex = true;
            if (!sbvrFile.exists()) {
                ex = false;
                if (!sbvrFileLocation.toLowerCase().endsWith(".xlsx")) {
                    sbvrFileLocation += ".xlsx";
                    sbvrFile = new java.io.File(sbvrFileLocation);
                    ex = sbvrFile.exists();
                }
            }
            if (!ex) {

                result.addError(null, 36, sbvrFileLocation);

            } else {

                try {

                    Workbook sbvrXls = WorkbookFactory.create(sbvrFile);
                    sbvrRules = parseSBVRRuleInfos(sbvrXls);

                } catch (InvalidFormatException e) {

                    result.addError(this, 1, e.getMessage());

                } catch (IOException e) {

                    result.addError(this, 2, e.getMessage());
                }
            }
        }
    }

    /**
     * @param sbvrXls
     * @return mapping of schema package name to SBVR rules that apply to
     *         classes in this schema *
     *         <ul>
     *         <li>key: class name</li>
     *         <li>value: mapping of schema package name to SBVR rule info
     *         <ul>
     *         <li>key: schema package name (
     *         {@value #UNSPECIFIED_SCHEMA_PACKAGE_NAME} if no schema package
     *         name has been provided)</li>
     *         <li>value: list of SBVR rules that apply to classes in that
     *         schema (the list is sorted according to lexical order on a) the
     *         class name and b) the rule text)</li>
     *         </ul>
     *         </ul>
     */
    private TreeMap<String, TreeMap<String, List<SbvrRuleInfo>>> parseSBVRRuleInfos(Workbook sbvrXls) {

        TreeMap<String, TreeMap<String, List<SbvrRuleInfo>>> rules = new TreeMap<String, TreeMap<String, List<SbvrRuleInfo>>>();

        if (sbvrXls == null)
            return null;

        Sheet rulesSheet = null;

        for (int i = 0; i < sbvrXls.getNumberOfSheets(); i++) {

            String sheetName = sbvrXls.getSheetName(i);

            if (sheetName.equalsIgnoreCase("Constraints")) {
                rulesSheet = sbvrXls.getSheetAt(i);
                break;
            }
        }

        if (rulesSheet == null) {

            result.addError(this, 3);
            return null;
        }

        // read header row to determine which columns contain relevant
        // information
        Map<String, Integer> fieldIndexes = new HashMap<String, Integer>();

        Row header = rulesSheet.getRow(rulesSheet.getFirstRowNum());

        if (header == null) {
            result.addError(this, 4);
            return null;
        }

        boolean classNameFound = false;
        boolean commentsFound = false;
        boolean ruleNameFound = false;
        boolean ruleTextFound = false;
        boolean schemaPackageFound = false;

        for (short i = header.getFirstCellNum(); i < header.getLastCellNum(); i++) {

            Cell c = header.getCell(i, Row.RETURN_BLANK_AS_NULL);

            if (c == null) {
                // this is allowed
            } else {

                String value = c.getStringCellValue();

                if (value.equalsIgnoreCase(SbvrRuleInfo.CLASS_COLUMN_NAME)) {

                    fieldIndexes.put(SbvrRuleInfo.CLASS_COLUMN_NAME, (int) i);
                    classNameFound = true;

                } else if (value.equalsIgnoreCase(SbvrRuleInfo.COMMENT_COLUMN_NAME)) {

                    fieldIndexes.put(SbvrRuleInfo.COMMENT_COLUMN_NAME, (int) i);
                    commentsFound = true;

                } else if (value.equalsIgnoreCase(SbvrRuleInfo.SCHEMA_PACKAGE_COLUMN_NAME)) {

                    fieldIndexes.put(SbvrRuleInfo.SCHEMA_PACKAGE_COLUMN_NAME, (int) i);
                    schemaPackageFound = true;

                } else if (value.equalsIgnoreCase(SbvrRuleInfo.RULE_TEXT_COLUMN_NAME)) {

                    fieldIndexes.put(SbvrRuleInfo.RULE_TEXT_COLUMN_NAME, (int) i);
                    ruleTextFound = true;

                } else if (value.equalsIgnoreCase(SbvrRuleInfo.RULE_NAME_COLUMN_NAME)) {

                    fieldIndexes.put(SbvrRuleInfo.RULE_NAME_COLUMN_NAME, (int) i);
                    ruleNameFound = true;
                }
            }
        }

        // if (fieldIndexes.size() != 5) {
        if (!ruleNameFound && !ruleTextFound) {
            // log message that required fields were not found
            result.addError(this, 5);
            return null;
        }

        /*
         * Read rule content
         */
        for (int i = rulesSheet.getFirstRowNum() + 1; i <= rulesSheet.getLastRowNum(); i++) {

            Row r = rulesSheet.getRow(i);
            int rowNumber = i + 1;

            if (r == null) {
                // ignore empty rows
                continue;
            }

            SbvrRuleInfo sri = new SbvrRuleInfo();

            // get rule name (required)
            Cell c = r.getCell(fieldIndexes.get(SbvrRuleInfo.RULE_NAME_COLUMN_NAME), Row.RETURN_BLANK_AS_NULL);
            if (c == null) {
                // log message
                result.addWarning(this, 6, "" + rowNumber);
                continue;
            } else {
                String cellValue = c.getStringCellValue();
                if (cellValue != null) {
                    if (cellValue.contains(":")) {
                        sri.setName(cellValue.substring(cellValue.lastIndexOf(":") + 1));
                    } else {
                        sri.setName(cellValue);
                    }
                }
            }

            // get rule text (required)
            c = r.getCell(fieldIndexes.get(SbvrRuleInfo.RULE_TEXT_COLUMN_NAME), Row.RETURN_BLANK_AS_NULL);
            if (c == null) {
                // log message
                result.addWarning(this, 7, "" + rowNumber);
                continue;
            } else {
                sri.setText(c.getStringCellValue());
            }

            // get comment (optional)
            if (commentsFound) {
                c = r.getCell(fieldIndexes.get(SbvrRuleInfo.COMMENT_COLUMN_NAME), Row.RETURN_BLANK_AS_NULL);
                if (c != null) {
                    sri.setComment(c.getStringCellValue());
                }
            }

            // get schema package (optional)
            if (schemaPackageFound) {
                c = r.getCell(fieldIndexes.get(SbvrRuleInfo.SCHEMA_PACKAGE_COLUMN_NAME), Row.RETURN_BLANK_AS_NULL);
                if (c == null) {
                    sri.setSchemaPackageName(UNSPECIFIED_SCHEMA_PACKAGE_NAME);
                } else {
                    sri.setSchemaPackageName(c.getStringCellValue());
                }
            }

            /*
             * get class name (optional when loading from excel because later we
             * can still try parsing it from the rule text)
             */
            if (classNameFound) {
                c = r.getCell(fieldIndexes.get(SbvrRuleInfo.CLASS_COLUMN_NAME), Row.RETURN_BLANK_AS_NULL);
                if (c == null) {
                    /*
                     * then after this we'll try to parse the class name from
                     * the rule text
                     */
                } else {
                    sri.setClassName(c.getStringCellValue());
                }
            }

            if (sri.getClassName() == null) {

                /*
                 * try parsing the main class name from the rule text
                 */
                result.addInfo(this, 10, sri.getName());

                String mainClassName = parseClassNameFromRuleText(sri.getText());

                if (mainClassName == null) {
                    result.addWarning(this, 8, sri.getName());
                    continue;
                } else {
                    sri.setClassName(mainClassName);
                }
            }

            List<SbvrRuleInfo> rulesList;
            TreeMap<String, List<SbvrRuleInfo>> rulesBySchemaPackageName;

            if (rules.containsKey(sri.getClassName())) {

                rulesBySchemaPackageName = rules.get(sri.getClassName());

                if (rulesBySchemaPackageName.containsKey(sri.getSchemaPackageName())) {
                    rulesList = rulesBySchemaPackageName.get(sri.getSchemaPackageName());
                } else {
                    rulesList = new ArrayList<SbvrRuleInfo>();
                    rulesBySchemaPackageName.put(sri.getSchemaPackageName(), rulesList);
                }

            } else {

                rulesBySchemaPackageName = new TreeMap<String, List<SbvrRuleInfo>>();
                rules.put(sri.getClassName(), rulesBySchemaPackageName);

                rulesList = new ArrayList<SbvrRuleInfo>();
                rulesBySchemaPackageName.put(sri.getSchemaPackageName(), rulesList);
            }

            rulesList.add(sri);
        }

        // now sort all lists contained in the map
        for (TreeMap<String, List<SbvrRuleInfo>> rulesBySchemaPackageName : rules.values()) {
            for (List<SbvrRuleInfo> rulesList : rulesBySchemaPackageName.values()) {

                Collections.sort(rulesList, new Comparator<SbvrRuleInfo>() {

                    @Override
                    public int compare(SbvrRuleInfo o1, SbvrRuleInfo o2) {

                        int classNameComparison = o1.getClassName().compareTo(o2.getClassName());

                        if (classNameComparison != 0) {
                            return classNameComparison;
                        } else {
                            return o1.getText().compareTo(o2.getText());
                        }
                    }
                });
            }
        }

        return rules;
    }

    private String parseClassNameFromRuleText(String text) {

        ANTLRInputStream input = new ANTLRInputStream(text);

        // create a lexer that feeds off of input CharStream
        SBVRLexer lexer = new SBVRLexer(input);

        // create a buffer of tokens pulled from the lexer
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        // create a parser that feeds off the tokens buffer
        SBVRParser parser = new SBVRParser(tokens);
        parser.helper = helper;

        /*
         * remove ConsoleErrorListener and add our own
         */
        parser.removeErrorListeners();
        SbvrErrorListener parsingErrorListener = new SbvrErrorListener();
        parser.addErrorListener(parsingErrorListener);

        // execute parsing, starting with rule 'sentence'
        ParseTree tree = parser.sentence();

        // if there were parsing errors, we cannot identify the main class name
        if (parsingErrorListener.hasErrors()) {

            SbvrUtil.printErrors(parsingErrorListener.getErrors(), text, result);

            return null;

        } else {

            SbvrClassNameDetectionListener nameDetectionListener = new SbvrClassNameDetectionListener();

            ParseTreeWalker walker = new ParseTreeWalker();
            walker.walk(nameDetectionListener, tree);

            return nameDetectionListener.getMainClassName();
        }
    }

    /**
     * Enriches the model by adding SBVR constraints to the classes in the given
     * schema.
     * 
     * @param schemaPackage
     *            a schema package (NOT any of the child packages that are in
     *            the same target namespace)
     */
    public void loadSBVRRulesAsConstraints(PackageInfo schemaPackage) {

        // check that there are rules
        if (sbvrRules == null || sbvrRules.isEmpty())
            return;

        // load information for all classes in the schema
        Set<ClassInfo> classesInSchema = model.classes(schemaPackage);

        for (ClassInfo ci : classesInSchema) {

            if (options.isAIXM() && ci.category() == Options.AIXMEXTENSION) {
                // do not add constraints to AIXM <<extension>> types
                continue;
            }

            /*
             * create SBVR constraints based upon the rules defined for that
             * class in its schema but also in case of an unspecified schema
             * (i.e. apply those rules as well where no schema package info has
             * been provided)
             */
            List<SbvrRuleInfo> rulesForClass = new ArrayList<SbvrRuleInfo>();

            if (sbvrRules.containsKey(ci.name())) {

                TreeMap<String, List<SbvrRuleInfo>> rulesBySchemaPackageName = sbvrRules.get(ci.name());

                if (rulesBySchemaPackageName.containsKey(schemaPackage.name())) {
                    rulesForClass.addAll(rulesBySchemaPackageName.get(schemaPackage.name()));
                } else {
                    // fine, no sbvr rules specific to the given schema defined
                    // for this class
                }

                if (rulesBySchemaPackageName.containsKey(UNSPECIFIED_SCHEMA_PACKAGE_NAME)) {
                    rulesForClass.addAll(rulesBySchemaPackageName.get(UNSPECIFIED_SCHEMA_PACKAGE_NAME));
                } else {
                    // fine, no sbvr rules without specific schema defined for
                    // this class
                }

            } else {
                // no SBVR rules defined for this class in the external source
            }

            /*
             * Now create SBVR constraints for the rule info contained in the
             * combined list.
             * 
             * NOTE: parsing of the constraints is done during model
             * postprocessing. See ModelImpl.postprocessFolConstraints()
             */
            List<Constraint> constraints = ci.constraints();

            if (constraints == null) {

                result.addWarning(this, 9, ci.name());

            } else {

                for (SbvrRuleInfo rule : rulesForClass) {

                    GenericFolConstraint genCon = new GenericFolConstraint(ci, rule.getName(), "",
                            SbvrConstants.FOL_SOURCE_TYPE, rule.getText());

                    constraints.add(genCon);
                }
            }
        }
    }

    @Override
    public String message(int mnr) {

        switch (mnr) {

        case 1:
            return "Invalid format for excel file containing SBVR constraints. Message is: $1$";
        case 2:
            return "Could not read excel file containing SBVR rules. Message is: $1$";
        case 3:
            return "No constraints sheet found in excel file containing SBVR rules.";
        case 4:
            return "Header not found in SBVR rules sheet.";
        case 5:
            return "Did not find required columns in SBVR rules sheet.";
        case 6:
            return "No name found for rule declared in line $1$ of SBVR rules sheet. This kind of message can occur for rows the are seemingly empty in the excel sheet.";
        case 7:
            return "No text found for rule declared in line $1$ of SBVR rules sheet.";
        case 8:
            return "Parsing main class name for rule '$1$' was not successful. This rule will not be added to the model.";
        case 9:
            return "Cannot load SBVR rules from external source for class '$1$' because the constraint vector of the class is null.";
        case 10:
            return "No main class name provided for rule '$1$'. Parsing the name from the rule text.";

        default:
            return "(Unknown message)";
        }
    }
}