ee.ioc.cs.vsle.synthesize.SpecParser.java Source code

Java tutorial

Introduction

Here is the source code for ee.ioc.cs.vsle.synthesize.SpecParser.java

Source

package ee.ioc.cs.vsle.synthesize;

/*-
 * #%L
 * CoCoViLa
 * %%
 * Copyright (C) 2003 - 2017 Institute of Cybernetics at Tallinn University of Technology
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import static ee.ioc.cs.vsle.util.TypeUtil.TYPE_ANY;
import static ee.ioc.cs.vsle.util.TypeUtil.TYPE_DOUBLE;
import static ee.ioc.cs.vsle.util.TypeUtil.TYPE_INT;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ee.ioc.cs.vsle.editor.RuntimeProperties;
import ee.ioc.cs.vsle.equations.EquationSolver;
import ee.ioc.cs.vsle.equations.EquationSolver.Relation;
import ee.ioc.cs.vsle.parser.JavaFileSourceProvider;
import ee.ioc.cs.vsle.parser.SpecificationSourceProvider;
import ee.ioc.cs.vsle.table.Table;
import ee.ioc.cs.vsle.util.FileFuncs;
import ee.ioc.cs.vsle.util.TypeToken;
import ee.ioc.cs.vsle.util.TypeUtil;
import ee.ioc.cs.vsle.vclass.Alias;
import ee.ioc.cs.vsle.vclass.AliasLength;
import ee.ioc.cs.vsle.vclass.ClassField;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class takes care of parsing the specification and translating it into a
 * graph on which planning can be run.
 * 
 * @author Ando Saabas, Pavel Grigorenko
 */
public class SpecParser {

    private static final Logger logger = LoggerFactory.getLogger(SpecParser.class);

    private static final Pattern PATTERN_SPEC = Pattern.compile(
            ".*/\\*@.*specification [a-zA-Z_0-9-.]+ ?(super ([ a-zA-Z_0-9-,]+ ))? ?\\{ ?(.+) ?\\} ?@\\*/ ?");
    private static final Pattern PATTERN_WHITESPACE = Pattern.compile("[ \r\t\n]+");
    private static final Pattern PATTERN_DECLARATION = Pattern.compile(
            "^ *(static)? *([a-zA-Z_][0-9a-zA-Z_]*(([\\.][a-zA-Z_][0-9a-zA-Z_]*)*)[0-9a-zA-Z_$]*(\\[\\])*) (([a-zA-Z_$][0-9a-zA-Z_$]* ?, ?)* ?[a-zA-Z_$][0-9a-zA-Z_$]* ?$)");
    private static final Pattern PATTERN_AXIOM_SPEC = Pattern.compile("(.*) *-> *([ -_a-zA-Z0-9.,]+) *$");
    private static final Pattern PATTERN_AXIOM_SUBTASK = Pattern
            .compile("\\[ *(([a-zA-Z_$][0-9a-zA-Z_$]*) *\\|-)? *(.*) *-> ?(.*)\\]");
    private static final Pattern PATTERN_AXIOM_SUBTASKS = Pattern.compile("\\[([^\\]\\[]*) *-> *([^\\]\\[]*)\\]");
    private static final Pattern PATTERN_AXIOM_FULL = Pattern.compile("(.*) *-> *(.+) *\\{(.+)\\}");
    private static final Pattern PATTERN_EQUATION = Pattern
            .compile(" *([^=]+) *= *([-_0-9a-zA-Z.()\\+\\*/^ ]+) *$");
    private static final Pattern PATTERN_ASSIGNMENT = Pattern
            .compile(" *([^= ]+) *= *((\".*\")|(new .*\\(.*\\))|(\\{.*\\})|(true)|(false)) *$");
    private static final Pattern PATTERN_CONSTANT = Pattern.compile(
            " *([a-zA-Z_$][0-9a-zA-Z_$]*[\\[\\]]*) +([a-zA-Z_$][0-9a-zA-Z_$]*) *= *([a-zA-Z0-9.{}\"]+|new [a-zA-Z0-9.{}\\[\\]]+) *");
    private static final Pattern PATTERN_SUPERCLASSES = Pattern.compile("super#([^ .]+)");
    private static final Pattern PATTERN_ALIAS_VARS = Pattern.compile(" *([^= ]+) *= *\\[(.*)\\] *$");
    private static final Pattern PATTERN_ALIAS_DECLARATION = Pattern
            .compile("alias *(\\(( *[^\\(\\) ]+ *)\\))* *([^= ]+) *");
    private static final Pattern PATTERN_ALIAS_FULL = Pattern
            .compile("alias *(\\(( *[^\\(\\) ]+ *)\\))* *([^= ]+) *= *\\((.*)\\) *");

    private final SpecificationSourceProvider specSourceProvider;

    private String rootClassName;

    public SpecParser(String packagePath) {
        this(new JavaFileSourceProvider(packagePath));
    }

    public SpecParser(SpecificationSourceProvider specSourceProvider) {
        this.specSourceProvider = specSourceProvider;
    }

    public static String getClassName(String spec) {
        Pattern pattern = Pattern.compile("class[ \t\n]+([a-zA-Z_0-9-]+)[ \t\n]+");
        Matcher matcher = pattern.matcher(spec);

        if (matcher.find()) {
            return matcher.group(1);
        }

        return "";
    }

    /**
     * @return ArrayList of lines in specification
     * @param text Secification text as String
     * @throws SpecParseException 
     */
    static ArrayList<String> getSpec(String text, boolean isRefinedSpec) {
        if (!isRefinedSpec) {
            text = refineSpec(text);
        }
        String[] s = text.trim().split(";", 0);
        ArrayList<String> a = new ArrayList<String>();

        for (int i = 0; i < s.length; i++) {
            a.add(s[i].trim());
        }
        return a;

    }

    /**
     * Reads a line from an arraylist of specification lines, removes it from
     * the arraylist and returns the line together with its type information
     * 
     * @return a specification line with its type information
     * @param a arraylist of specification lines
     */
    static LineType getLine(ArrayList<String> a) throws SpecParseException {
        Matcher matcher;

        while ((a.get(0)).equals("") || a.get(0).trim().startsWith("//")) {
            a.remove(0);
            if (a.isEmpty()) {
                return null;
            }
        }
        final String line = a.get(0);

        a.remove(0);
        if (line.startsWith("alias ")) {
            matcher = PATTERN_ALIAS_FULL.matcher(line);
            if (matcher.find()) {
                if (matcher.group(3).indexOf(".") > -1) {
                    throw new SpecParseException(
                            "Alias " + matcher.group(3) + " cannot be declared with compound name");
                }

                LineType.Alias st = new LineType.Alias();
                st.setName(matcher.group(3));
                String vars = matcher.group(4).trim();
                st.setComponents(vars.length() == 0 ? new String[0] : vars.split(" *, *", -1));
                st.setComponentType((matcher.group(2) == null || matcher.group(2).equals("null") ? ""
                        : matcher.group(2).trim()));

                return new LineType(LineType.TYPE_ALIAS, st, line);
            }
            // allow empty alias declaration e.g. "alias x;"
            matcher = PATTERN_ALIAS_DECLARATION.matcher(line);
            if (matcher.find()) {
                if (matcher.group(3).indexOf(".") > -1) {
                    throw new SpecParseException(
                            "Alias " + matcher.group(3) + " cannot be declared with compound name");
                }

                LineType.Alias st = new LineType.Alias();
                st.setName(matcher.group(3));
                st.setDeclaration(true);
                st.setComponentType((matcher.group(2) == null || matcher.group(2).equals("null") ? ""
                        : matcher.group(2).trim()));
                return new LineType(LineType.TYPE_ALIAS, st, line);
            }

            return new LineType(LineType.TYPE_ERROR, null, line);

        } else if (line.indexOf("super") >= 0 && (matcher = PATTERN_SUPERCLASSES.matcher(line)).find()) {
            LineType.Superclasses st = new LineType.Superclasses();
            st.setClassNames(matcher.group(1).split("#", -1));
            return new LineType(LineType.TYPE_SUPERCLASSES, st, line);

        } else if (line.trim().startsWith("const")) {
            matcher = PATTERN_CONSTANT.matcher(line);
            if (matcher.find()) {
                LineType.Constant st = new LineType.Constant();
                st.setName(matcher.group(2).trim());
                st.setType(matcher.group(1).trim());
                st.setValue(matcher.group(3).trim());
                return new LineType(LineType.TYPE_CONST, st, line);
            }
            return new LineType(LineType.TYPE_ERROR, null, line);

        } else if (line.indexOf("=") >= 0) { // Extract on solve
                                             // equations
                                             // lets check if it's an alias, e.g. x = [a,b,c];
            matcher = PATTERN_ALIAS_VARS.matcher(line);
            if (matcher.find()) {
                LineType.Alias st = new LineType.Alias();
                st.setName(matcher.group(1));
                String vars = matcher.group(2).trim();
                st.setComponents(vars.length() == 0 ? new String[0] : vars.split(" *, *", -1));
                st.setAssignment(true);

                return new LineType(LineType.TYPE_ALIAS, st, line);
            }

            matcher = PATTERN_ASSIGNMENT.matcher(line);
            if (matcher.find()) {
                LineType.Assignment st = new LineType.Assignment();
                st.setName(matcher.group(1));
                st.setValue(matcher.group(2));
                return new LineType(LineType.TYPE_ASSIGNMENT, st, line);
            }
            matcher = PATTERN_EQUATION.matcher(line);
            if (matcher.find()) {
                LineType.Equation st = new LineType.Equation();
                st.setEq(line);
                return new LineType(LineType.TYPE_EQUATION, st, line);
            }
            return new LineType(LineType.TYPE_ERROR, null, line);

        } else if (line.indexOf("->") >= 0) {
            //axiom
            matcher = PATTERN_AXIOM_FULL.matcher(line);
            if (matcher.find()) {
                LineType.Axiom st = new LineType.Axiom();

                //check for subtasks
                //FIXME - this pattern allows subtasks to be anywhere in the axiom, but they need to come before anything else
                Matcher subMatcher = PATTERN_AXIOM_SUBTASKS.matcher(line);

                while (subMatcher.find()) {
                    String subtaskString = subMatcher.group(0);

                    Matcher singleSubtaskMatcher = PATTERN_AXIOM_SUBTASK.matcher(subtaskString);

                    if (singleSubtaskMatcher.find()) {
                        String context = singleSubtaskMatcher.group(2);
                        String[] subInputs = singleSubtaskMatcher.group(3).trim().split(" *, *", -1);
                        String[] subOutputs = singleSubtaskMatcher.group(4).trim().split(" *, *", -1);

                        st.getSubtasks().put(subtaskString,
                                new String[][] { new String[] { context }, subInputs, subOutputs });
                    } else {
                        return new LineType(LineType.TYPE_ERROR, null, line);
                    }
                }

                String newLine = line.replaceAll("\\[([^\\]\\[]*) *-> *([^\\]\\[]*)\\]", "#");

                matcher = PATTERN_AXIOM_FULL.matcher(newLine);

                if (matcher.find()) {
                    String in = matcher.group(1).trim();
                    st.setInputs(in.length() == 0 ? new String[0] : in.split(" *, *", -1));
                    String out = matcher.group(2).trim();
                    st.setOutputs(out.length() == 0 ? new String[0] : out.split(" *, *", -1));
                    st.setMethod(matcher.group(3).trim());
                } else {
                    return new LineType(LineType.TYPE_ERROR, null, line);
                }

                return new LineType(LineType.TYPE_AXIOM, st, line);
            }
            //specaxiom
            matcher = PATTERN_AXIOM_SPEC.matcher(line);

            if (matcher.find()) {
                LineType.Axiom st = new LineType.Axiom();
                st.setSpecAxiom(true);
                String in = matcher.group(1).trim();
                st.setInputs(in.length() == 0 ? new String[0] : in.split(" *, *", -1));
                String out = matcher.group(2).trim();
                st.setOutputs(out.length() == 0 ? new String[0] : out.split(" *, *", -1));
                return new LineType(LineType.TYPE_SPECAXIOM, st, line);

            }
            return new LineType(LineType.TYPE_ERROR, null, line);
        } else {
            matcher = PATTERN_DECLARATION.matcher(line);
            if (matcher.find()) {
                LineType.Declaration st = new LineType.Declaration();
                st.setStatic((matcher.group(1) != null));
                st.setType(matcher.group(2).trim());
                st.setNames(matcher.group(6).trim().split(" *, *", -1));
                return new LineType(LineType.TYPE_DECLARATION, st, line);
            }
            return new LineType(LineType.TYPE_ERROR, null, line);
        }
    }

    /**
     * Extracts the specification from the java file, also removing unnecessary
     * whitespaces
     * 
     * @return specification text
     * @param fileString a (Java) file containing the specification
     * @throws SpecParseException 
     */
    private static String refineSpec(String fileString) {
        Matcher matcher;

        // remove comments before removing line brake \n
        String[] s = fileString.split("\n");
        StringBuilder tmpBuf = new StringBuilder(fileString.length() / 2);
        for (int i = 0; i < s.length; i++) {
            if (!s[i].trim().startsWith("//")) {
                tmpBuf.append(s[i]);
            }
        }

        // remove unneeded whitespace
        matcher = PATTERN_WHITESPACE.matcher(tmpBuf);
        // This is broken as spaces should not be replaced in,
        // e.g. string literals. Keeping it now for compatibility.
        tmpBuf.replace(0, tmpBuf.length(), matcher.replaceAll(" "));

        // find spec
        matcher = PATTERN_SPEC.matcher(tmpBuf);
        if (matcher.find()) {
            StringBuilder sc = new StringBuilder();
            if (matcher.group(2) != null) {
                sc.append("super");
                String[] superclasses = matcher.group(2).split(",");
                for (int i = 0; i < superclasses.length; i++) {
                    String t = superclasses[i].trim();
                    if (t.length() > 0) {
                        sc.append("#");
                        sc.append(t);
                    }
                }
                sc.append(";\n");
            }
            return sc.append(matcher.group(3)).toString();
        }

        throw new SpecParseException("Specification parsing error");
    }

    public ClassList parseSpecification(String fullSpec, String mainClassName, Set<String> schemeObjects) {

        rootClassName = mainClassName;

        long start = System.currentTimeMillis();

        ClassList classes = parseSpecificationImpl(refineSpec(fullSpec), mainClassName, schemeObjects,
                new LinkedHashSet<String>());

        logger.info("Specification parsed in: " + (System.currentTimeMillis() - start) + "ms.");

        /* ****** SPEC_OBJECT_NAME for scheme spec ? ****** */
        // AnnotatedClass _this = classes.getType( TYPE_THIS );
        //       
        // String meth = AnnotatedClass.SPEC_OBJECT_NAME + " = " + "\"" +
        // mainClassName + "\"";
        //       
        // ClassRelation classRelation = new ClassRelation(
        // RelType.TYPE_EQUATION, meth );
        //
        // classRelation.getOutputs().add( _this.getFieldByName(
        // AnnotatedClass.SPEC_OBJECT_NAME ) );
        // classRelation.setMethod( meth );
        // _this.addClassRelation( classRelation );
        /* ****** SPEC_OBJECT_NAME ****** */

        return classes;
    }

    /**
     * A recrusve method that does the actual parsing. It creates a list of
     * annotated classes that carry infomation about the fields and relations in
     * a class specification.
     * 
     * @param spec a specfication to be parsed. If it includes a declaration of
     *                an annotated class, it will be recursively parsed.
     * @param className the name of the class being parsed
     * @param checkedClasses the list of classes that parser has started to
     *                check. Needed to prevent infinite loop in case of mutual
     *                declarations.
     */
    private ClassList parseSpecificationImpl(String spec, String className, Set<String> schemeObjects,
            Set<String> checkedClasses) {

        AnnotatedClass annClass = new AnnotatedClass(className);

        /* ****** SPEC_OBJECT_NAME ****** */
        ClassField specObjectName = new ClassField(CodeGenerator.SPEC_OBJECT_NAME, "String");
        annClass.addField(specObjectName);
        /* ****** SPEC_OBJECT_NAME ****** */

        ClassList classList = new ClassList();

        ArrayList<String> specLines = getSpec(spec, true);

        LineType lt = null;

        try {

            while (!specLines.isEmpty()) {

                if ((lt = getLine(specLines)) != null) {

                    if (RuntimeProperties.isLogDebugEnabled())
                        logger.info("Parsing: Class " + className + " " + lt);

                    if (lt.getType() == LineType.TYPE_SUPERCLASSES) {
                        LineType.Superclasses statement = (LineType.Superclasses) lt.getStatement();

                        for (int i = 0; i < statement.getClassNames().length; i++) {
                            String name = statement.getClassNames()[i];

                            if (checkSpecClass(className, checkedClasses, classList, name)) {

                                AnnotatedClass superClass = classList.getType(name);

                                annClass.addSuperClass(superClass);
                            } else {
                                throw new SpecParseException(
                                        "Unable to parse superclass " + name + " of " + className);
                            }

                        }
                    } else if (lt.getType() == LineType.TYPE_ASSIGNMENT) {
                        ClassRelation classRelation = new ClassRelation(RelType.TYPE_EQUATION,
                                lt.getOrigSpecLine());
                        LineType.Assignment statement = (LineType.Assignment) lt.getStatement();
                        classRelation.addOutput(statement.getName(), annClass.getFields());
                        classRelation.setMethod(statement.getName() + " = " + statement.getValue());
                        checkAnyType(getVar(statement.getName(), annClass.getFields()), statement.getValue(),
                                annClass, classList);
                        annClass.addClassRelation(classRelation);
                        logger.debug(classRelation.toString());

                    } else if (lt.getType() == LineType.TYPE_CONST) {
                        LineType.Constant statement = (LineType.Constant) lt.getStatement();

                        if (containsVar(annClass.getFields(), statement.getName())) {
                            throw new SpecParseException("Variable " + statement.getName()
                                    + " declared more than once in class " + className);
                        }

                        if (isSpecClass(statement.getType())) {
                            throw new SpecParseException("Constant " + statement.getName() + " cannot be of type "
                                    + statement.getType());
                        }
                        logger.debug("---===!!! " + statement.getType() + " " + statement.getName() + " = "
                                + statement.getValue());

                        ClassField var = new ClassField(statement.getName(), statement.getType(),
                                statement.getValue(), true);

                        annClass.addField(var);

                    } else if (lt.getType() == LineType.TYPE_DECLARATION) {
                        LineType.Declaration statement = (LineType.Declaration) lt.getStatement();

                        boolean isStatic = statement.isStatic();

                        boolean specClass = checkSpecClass(className, checkedClasses, classList,
                                statement.getType());

                        String[] vars = statement.getNames();

                        for (int i = 0; i < vars.length; i++) {
                            if (containsVar(annClass.getFields(), vars[i])) {
                                throw new SpecParseException(
                                        "Variable " + vars[i] + " declared more than once in class " + className);
                            }
                            ClassField var = new ClassField(vars[i], statement.getType(), specClass);
                            var.setStatic(isStatic);

                            /* ****** SPEC_OBJECT_NAME ****** */
                            // add the following relation only if the object
                            // exists on a given scheme
                            if (schemeObjects != null && specClass && (className.equals(rootClassName))
                                    && schemeObjects.contains(vars[i])) {
                                var.setSchemeObject(true);
                                String s = vars[i] + "." + CodeGenerator.SPEC_OBJECT_NAME;
                                String meth = s + " = " + "\"" + vars[i] + "\"";

                                ClassRelation classRelation = new ClassRelation(RelType.TYPE_EQUATION, meth);

                                classRelation.addOutput(s, annClass.getFields());
                                classRelation.setMethod(meth);
                                annClass.addClassRelation(classRelation);
                            }
                            /* ****** SPEC_OBJECT_NAME ****** */

                            annClass.addField(var);
                        }

                    } else if (lt.getType() == LineType.TYPE_ALIAS) {
                        LineType.Alias statement = (LineType.Alias) lt.getStatement();

                        Alias alias = null;

                        String name = statement.getName();

                        if (statement.isDeclaration() && !containsVar(annClass.getFields(), name)) {
                            alias = new Alias(name, statement.getComponentType());
                            annClass.addField(alias);
                            continue;
                        }

                        ClassField var = getVar(name, annClass.getFields());

                        if (var != null && !var.isAlias()) {
                            throw new SpecParseException("Variable " + name + " declared more than once in class "
                                    + className + ", line: " + lt.getOrigSpecLine());
                        } else if (var != null && var.isAlias()) {
                            alias = (Alias) var;
                            if (alias.isInitialized()) {
                                throw new SpecParseException("Alias " + name
                                        + " has already been initialized and cannot be overriden, class "
                                        + className + ", line: " + lt.getOrigSpecLine());
                            }
                        } else if (statement.isAssignment()) {
                            // if its an assignment, check if alias has already
                            // been declared
                            try {
                                if ((name.indexOf(".") == -1) && !containsVar(annClass.getFields(), name)) {
                                    throw new UnknownVariableException("Alias " + name + " not declared",
                                            lt.getOrigSpecLine());

                                } else if (name.indexOf(".") > -1) {
                                    // here we have to dig deeply
                                    int ind = name.indexOf(".");

                                    String parent = name.substring(0, ind);
                                    String leftFromName = name.substring(ind + 1, name.length());

                                    ClassField parentVar = getVar(parent, annClass.getFields());
                                    String parentType = parentVar.getType();

                                    AnnotatedClass parentClass = classList.getType(parentType);

                                    while (leftFromName.indexOf(".") > -1) {

                                        ind = leftFromName.indexOf(".");
                                        parent = leftFromName.substring(0, ind);
                                        leftFromName = leftFromName.substring(ind + 1, leftFromName.length());

                                        parentVar = parentClass.getFieldByName(parent);

                                        parentType = parentVar.getType();
                                        parentClass = classList.getType(parentType);
                                    }

                                    if (!parentClass.hasField(leftFromName)) {
                                        throw new UnknownVariableException("Variable " + leftFromName
                                                + " is not declared in class " + parentClass, lt.getOrigSpecLine());
                                    }

                                    Alias aliasDeclaration = (Alias) parentClass.getFieldByName(leftFromName);

                                    if (aliasDeclaration.isInitialized()) {
                                        throw new SpecParseException("Alias " + aliasDeclaration.getName()
                                                + " has already been initialized and cannot be overriden, class "
                                                + className + ", line: " + lt.getOrigSpecLine());
                                    }

                                    // if everything is ok, create alias
                                    alias = new Alias(name, aliasDeclaration.getVarType());

                                }
                            } catch (Exception e) {
                                throw new SpecParseException("Alias " + name + " is not declared, class "
                                        + className + (e.getMessage() != null ? "\n" + e.getMessage() : ""));
                            }
                        } else {
                            //empty declaration is not allowed, e.g. "alias x = ();".
                            // note, however, it is possible to declare empty alias using two lines, e.g. "alias x; x = [];"
                            if (statement.getComponents() == null || statement.getComponents().length == 0) {
                                throw new SpecParseException("Alias " + name
                                        + " does not bind any variables, line: " + lt.getOrigSpecLine());
                            }
                            alias = new Alias(name, statement.getComponentType());
                        }

                        String[] vars = statement.getComponents();

                        alias.addAll(vars, annClass.getFields(), classList);
                        if (!containsVar(annClass.getFields(), name)) {
                            annClass.addField(alias);
                        }
                        ClassRelation classRelation = new ClassRelation(RelType.TYPE_ALIAS, lt.getOrigSpecLine());

                        classRelation.addInputs(vars, annClass.getFields());
                        classRelation.setMethod(TypeUtil.TYPE_ALIAS);
                        classRelation.addOutput(name, annClass.getFields());
                        annClass.addClassRelation(classRelation);

                        logger.debug(classRelation.toString());

                        if (!alias.isWildcard()) {
                            classRelation = new ClassRelation(RelType.TYPE_ALIAS, lt.getOrigSpecLine());
                            classRelation.addOutputs(vars, annClass.getFields());
                            classRelation.setMethod(TypeUtil.TYPE_ALIAS);
                            classRelation.addInput(name, annClass.getFields());
                            annClass.addClassRelation(classRelation);
                            logger.debug(classRelation.toString());
                        }

                        alias.setInitialized(true);

                    } else if (lt.getType() == LineType.TYPE_EQUATION) {
                        LineType.Equation statement = (LineType.Equation) lt.getStatement();
                        EquationSolver solver = new EquationSolver();
                        solver.solve(statement.getEq());
                        next: for (Relation rel : solver.getRelations()) {
                            logger.debug("equation: " + rel);
                            String[] pieces = rel.getRel().split(":");
                            String method = rel.getExp();
                            String out = pieces[2].trim();

                            // cannot assign new values for constants
                            ClassField tmp = getVar(checkAliasLength(out, annClass, className),
                                    annClass.getFields());
                            if (tmp != null && (tmp.isConstant() || tmp.isAliasLength())) {
                                logger.info("Ignoring constant as equation output: " + tmp);
                                continue;
                            }
                            // if one variable is used on both sides of "=", we
                            // cannot use such relation.
                            String[] inputs = pieces[1].trim().split(" ");
                            for (int j = 0; j < inputs.length; j++) {
                                if (inputs[j].equals(out)) {
                                    logger.debug(" - unable use this equation because variable " + out
                                            + " appears on both sides of =");
                                    continue next;
                                }
                            }

                            ClassRelation classRelation = new ClassRelation(RelType.TYPE_EQUATION,
                                    lt.getOrigSpecLine());

                            ClassField output = getVarWithType(out, annClass, classList);
                            classRelation.addOutput(output);

                            // checkAliasLength( inputs, annClass.getFields(), className );
                            for (int i = 0; i < inputs.length; i++) {
                                String initial = inputs[i];
                                inputs[i] = checkAliasLength(inputs[i], annClass, className);
                                String name = inputs[i];
                                if (name.startsWith("*")) {
                                    name = inputs[i].substring(1);
                                }
                                method = method.replaceAll("\\$" + initial + "\\$", name);
                            }
                            method = method.replaceAll("\\$" + out + "\\$", out);

                            checkAnyType(output, inputs, annClass, classList);

                            if (!inputs[0].equals("")) {
                                classRelation.addInputs(inputs, annClass.getFields());
                            }
                            classRelation.setMethod(method);
                            annClass.addClassRelation(classRelation);
                            logger.debug("Equation: " + classRelation);

                        }
                    } else if (lt.getType() == LineType.TYPE_AXIOM) {
                        LineType.Axiom statement = (LineType.Axiom) lt.getStatement();

                        if (statement.getOutputs().length > 0) {
                            if (statement.getOutputs()[0].indexOf("*") >= 0) {
                                getWildCards(classList, statement.getOutputs()[0]);
                            }

                        }
                        ClassRelation classRelation = new ClassRelation(RelType.TYPE_JAVAMETHOD,
                                lt.getOrigSpecLine());

                        if (statement.getOutputs().length == 0) {
                            throw new SpecParseException("Error in line \n" + lt.getOrigSpecLine() + "\nin class "
                                    + className + ".\nAn axiom can not have an empty output.");
                        }

                        classRelation.addOutputs(statement.getOutputs(), annClass.getFields());

                        classRelation.setMethod(statement.getMethod());

                        if (Table.TABLE_KEYWORD.equals(classRelation.getMethod())) {
                            classRelation.getExceptions().clear();
                            classRelation.getExceptions().add(new ClassField("java.lang.Exception", "exception"));
                        }

                        checkAliasLength(statement.getInputs(), annClass, className);

                        classRelation.addInputs(statement.getInputs(), annClass.getFields());

                        if (statement.getSubtasks().size() != 0) {

                            for (String subtaskString : statement.getSubtasks().keySet()) {

                                String[][] stuff = statement.getSubtasks().get(subtaskString);

                                Collection<ClassField> varsForSubtask = annClass.getFields();
                                SubtaskClassRelation subtask;

                                String context = stuff[0][0];
                                // this denotes independant subtask,
                                // have to make sure that this class has
                                // already been parsed
                                if (context != null) {
                                    if (!checkSpecClass(className, checkedClasses, classList, context)) {
                                        throw new SpecParseException(
                                                "Unable to parse independent subtask's context specification "
                                                        + subtaskString);
                                    }
                                    varsForSubtask = classList.getType(context).getFields();

                                    ClassField contextCF = new ClassField("_" + context.toLowerCase(), context,
                                            true);

                                    subtask = SubtaskClassRelation.createIndependentSubtask(subtaskString,
                                            contextCF);
                                } else {
                                    subtask = SubtaskClassRelation.createDependentSubtask(subtaskString);
                                }

                                for (int j = 0; j < stuff[2].length; j++) {
                                    subtask.addOutput(stuff[2][j], varsForSubtask);
                                }

                                if (!stuff[1][0].equals("")) {
                                    subtask.addInputs(stuff[1], varsForSubtask);
                                }
                                classRelation.addSubtask(subtask);
                            }
                            classRelation.setType(RelType.TYPE_METHOD_WITH_SUBTASK);
                        }

                        logger.debug(classRelation.toString());

                        annClass.addClassRelation(classRelation);

                    } else if (lt.getType() == LineType.TYPE_SPECAXIOM) {
                        LineType.Axiom statement = (LineType.Axiom) lt.getStatement();

                        ClassRelation classRelation = new ClassRelation(RelType.TYPE_UNIMPLEMENTED,
                                lt.getOrigSpecLine());

                        classRelation.addOutputs(statement.getOutputs(), annClass.getFields());

                        classRelation.addInputs(statement.getInputs(), annClass.getFields());

                        annClass.addClassRelation(classRelation);

                        logger.debug(classRelation.toString());

                    } else if (lt.getType() == LineType.TYPE_ERROR) {
                        throw new LineErrorException(lt.getOrigSpecLine());
                    }
                }
            }
        } catch (UnknownVariableException uve) {

            String line = uve.getLine() != null ? uve.getLine() : lt != null ? lt.getOrigSpecLine() : null;
            throw new UnknownVariableException(className + "." + uve.getMessage(), line);

        }
        classList.add(annClass);
        return classList;
    }

    /**
     * Parses a given specification class and fills a list of classes
     * 
     * @param className
     * @param path
     * @param classList
     * @throws IOException
     * @throws SpecParseException
     */
    public static void parseSpecClass(String className, String path, ClassList classList)
            throws SpecParseException {

        new SpecParser(path).checkSpecClass(null, null, classList, className);
    }

    /**
     * @param parentClassName
     * @param checkedClasses
     * @param classList
     * @param type
     * @return
     * @throws IOException
     * @throws SpecParseException 
     * @throws SpecParseException
     */
    private boolean checkSpecClass(String parentClassName, Set<String> checkedClasses, ClassList classList,
            String type) {

        if (checkedClasses == null)
            checkedClasses = new LinkedHashSet<String>();

        if (checkedClasses.contains(type)) {
            if (RuntimeProperties.isRecursiveSpecsAllowed()) {
                return true;
            }
            throw new MutualDeclarationException(parentClassName + " <-> " + type);
        } else if (classList.getType(type) != null) {
            // do not need to parse already parsed class again
            return true;
        }

        boolean specClass = false;
        // if a file by this name exists in the package directory and it
        // includes a specification, we're gonna check it
        if (isSpecClass(type)) {
            specClass = true;
            if (!classList.containsType(type)) {
                checkedClasses.add(type);
                String s = specSourceProvider.getSource(type);

                try {
                    classList.addAll(parseSpecificationImpl(refineSpec(s), type, null, checkedClasses));
                } catch (SpecParseException e) {
                    throw new SpecParseException("Class \"" + type + "\": " + e.toString(), e);
                }
                checkedClasses.remove(type);
            }
        }
        return specClass;
    }

    private static void checkAnyType(ClassField output, String input, AnnotatedClass parentClass, ClassList classes)
            throws UnknownVariableException {
        checkAnyType(output, new String[] { input }, parentClass, classes);
    }

    // TODO - implement _any_!!!
    private static void checkAnyType(ClassField out, String[] inputs, AnnotatedClass parentClass, ClassList classes)
            throws UnknownVariableException {

        if (out == null || (!out.isAny() && !TYPE_ANY.equals(getVarType(out.getName(), parentClass, classes)))) {
            return;
        }

        Collection<ClassField> vars = parentClass.getFields();

        String newType = TYPE_ANY;

        for (int i = 0; i < inputs.length; i++) {
            ClassField in = getVarWithType(inputs[i], parentClass, classes);

            if (in == null) {
                try {
                    Integer.parseInt(inputs[i]);
                    newType = TYPE_INT;
                    continue;
                } catch (NumberFormatException ex) {
                }

                try {
                    Double.parseDouble(inputs[i]);
                    newType = TYPE_DOUBLE;
                    continue;
                } catch (NumberFormatException ex) {
                }

                if (inputs[i] != null && inputs[i].trim().equals("")) {
                    newType = TYPE_DOUBLE;// TODO - tmp
                    continue;
                }

                throw new UnknownVariableException(inputs[i]);
            }
            if (in.isAny()) {
                newType = in.getAnySpecificType();
                continue;
            } else if (i == 0) {
                newType = in.getType();
                continue;
            }
            TypeToken token = TypeToken.getTypeToken(newType);

            TypeToken tokenIn = TypeToken.getTypeToken(in.getType());

            if (token != null && tokenIn != null && token.compareTo(tokenIn) < 0) {
                newType = in.getType();
            }
        }

        if (!TYPE_ANY.equals(newType))
            out.setAnySpecificType(newType);
    }

    private static String getVarType(String var, AnnotatedClass parentClass, ClassList classes) {
        return getVarWithType(var, parentClass, classes).getType();
    }

    private static ClassField getVarWithType(String var, AnnotatedClass parentClass, ClassList classes) {
        String[] split = var.split("\\.");
        if (split.length > 1) {
            String type = "";
            for (int i = 0; i < split.length; i++) {
                ClassField cf = parentClass.getFieldByName(split[i]);
                if (cf.isAlias()) {
                    //if it's alias element access, stop the search, ProblemCreater will handle it
                    break;
                }
                type = cf.getType();
                parentClass = classes.getType(type);
            }
            return new ClassField(var, type);
        }
        return parentClass.getFieldByName(var);
    }

    private static void checkAliasLength(String inputs[], AnnotatedClass thisClass, String className)
            throws UnknownVariableException {
        for (int i = 0; i < inputs.length; i++) {
            inputs[i] = checkAliasLength(inputs[i], thisClass, className);
        }
    }

    public static String checkAliasLength(String input, AnnotatedClass thisClass, String className)
            throws UnknownVariableException {
        // check if inputs contain <alias>.lenth variable
        if (input.endsWith(".length")) {
            int index = input.lastIndexOf(".length");
            String aliasName = input.substring(0, index);
            ClassField field = getVar(aliasName, thisClass.getFields());
            if (field != null && field.isAlias()) {
                Alias alias = (Alias) field;
                String aliasLengthName = aliasName + "_LENGTH";
                if (containsVar(thisClass.getFields(), aliasLengthName)) {
                    return aliasLengthName;
                }
                int length = alias.getVars().size();
                AliasLength var = new AliasLength(alias, thisClass.getName());
                thisClass.addField(var);
                //if value cannot be determined here, it will be defined in ProgramCreator
                if (!alias.isWildcard() && alias.isInitialized()) {
                    String meth = aliasLengthName + " = " + length;
                    ClassRelation cr = new ClassRelation(RelType.TYPE_EQUATION, meth);
                    cr.addOutput(aliasLengthName, thisClass.getFields());
                    cr.setMethod(meth);
                    thisClass.addClassRelation(cr);
                }
                return aliasLengthName;

            }
            throw new UnknownVariableException("Alias " + aliasName + " not found in " + className);
        }
        return input;
    }

    private static void getWildCards(ClassList classList, String output) {
        String list[] = output.split("\\.");
        for (int i = 0; i < list.length; i++) {
            logger.debug(list[i]);
        }
    }

    /**
     * @return list of fields declared in a specification.
     * @throws SpecParseException 
     */
    public static Collection<ClassField> getFields(String path, String fileName, String ext)
            throws IOException, SpecParseException {
        Map<String, ClassField> fields = new LinkedHashMap<String, ClassField>();
        String s = FileFuncs.getFileContents(new File(path, fileName + ext));
        ArrayList<String> specLines = getSpec(s, false);

        while (!specLines.isEmpty()) {
            LineType lt = null;
            try {
                lt = getLine(specLines);
            } catch (SpecParseException e) {
                e.printStackTrace();
            }

            if (lt != null) {
                if (lt.getType() == LineType.TYPE_ASSIGNMENT) {
                    LineType.Assignment statement = (LineType.Assignment) lt.getStatement();
                    ClassField field;
                    if ((field = fields.get(statement.getName())) != null) {
                        field.setValue(statement.getValue());
                    }
                } else if (lt.getType() == LineType.TYPE_DECLARATION) {
                    LineType.Declaration statement = (LineType.Declaration) lt.getStatement();

                    for (int i = 0; i < statement.getNames().length; i++) {
                        ClassField var = new ClassField(statement.getNames()[i], statement.getType());

                        fields.put(var.getName(), var);
                    }
                } else if (lt.getType() == LineType.TYPE_ALIAS) {
                    LineType.Alias statement = (LineType.Alias) lt.getStatement();
                    Alias alias = new Alias(statement.getName(), statement.getComponentType());
                    if (statement.getComponents() != null && statement.getComponents().length > 0) {
                        try {
                            //TODO - probably some time it will be needed to fill the class list
                            //and this does not work for aliases with wildcards
                            alias.addAll(statement.getComponents(), fields.values(), new ClassList());
                            //alternative approach is to do next - 
                            //for( String var : list ) {
                            //    ClassField aliasCF = fields.get( var );
                            //    if( aliasCF != null )
                            //        alias.addVar( aliasCF );
                            //}
                        } catch (UnknownVariableException e) {
                            logger.info("Line: " + e.getLine() + ", " + e.toString());
                        } catch (AliasException e) {
                        }
                    }
                    fields.put(statement.getName(), alias);
                } else if (lt.getType() == LineType.TYPE_SUPERCLASSES) {
                    LineType.Superclasses statement = (LineType.Superclasses) lt.getStatement();

                    for (String name : statement.getClassNames()) {
                        for (ClassField var : getFields(path, name, ext)) {
                            fields.put(var.getName(), var);
                        }
                    }
                }
            }
        }
        return fields.values();
    }

    private boolean isSpecClass(String type) {

        String source = specSourceProvider.getSource(type);
        return source != null && source.matches("(?s).*specification +" + type + ".*");
    }

    private static boolean containsVar(Collection<ClassField> vars, String varName) {

        return getVar(varName, vars) != null;
    }

    /**
     * @param varName String
     * @param varList ArrayList
     * @return ClassField
     */
    public static ClassField getVar(String varName, Collection<ClassField> varList) {

        for (ClassField var : varList) {
            if (var.getName().equals(varName)) {
                return var;
            }
        }
        return null;
    } // getVar

}