// $Id: Module.java,v 1.19 2004/06/14 20:55:21 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" 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 2 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.

package com.meyling.principia.module;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.meyling.principia.argument.AbstractDynamicArgumentList;
import com.meyling.principia.argument.Argument;
import com.meyling.principia.argument.ArgumentException;
import com.meyling.principia.argument.Text;
import com.meyling.principia.io.ParsingException;
import com.meyling.principia.io.Utility;
import com.meyling.principia.log.Trace;
import com.meyling.principia.logic.basic.Formula;
import com.meyling.principia.logic.paragraph.Abbreviation;
import com.meyling.principia.logic.paragraph.Axiom;
import com.meyling.principia.logic.paragraph.Paragraph;
import com.meyling.principia.logic.paragraph.ParagraphCheck;
import com.meyling.principia.logic.paragraph.Proposition;
import com.meyling.principia.logic.paragraph.RuleDeclaration;
import com.meyling.principia.logic.paragraph.Sentence;
import com.meyling.principia.logic.rule.Rule;


/**
 * Main data object of this project. A <code>Module</code> is like an
 * chapter of a mathematical textbook. It is organized in
 * {@link Paragraph}s that could contain {@link Abbreviation}s,
 * {@link Axiom}s or {@link Proposition}s. It could reference to
 * other <code>Module</code>s.
 *
 * @version $Revision: 1.19 $
 * @author  Michael Meyling
 */
public class Module extends AbstractDynamicArgumentList implements Argument {

    /** URL where this module is located */
    private ModuleAddress moduleAddress = null;

    /** collects all direct or indirect referenced {@link Module} objects */
    private final Map referencedModules = new HashMap();

    /** maps labels to {@link Module} objects */
    private final Map labels = new HashMap();

    /** maps abbreviation labels to abbreviations */
    private final Map abbreviations = new HashMap();

    /** maps axiom labels to axioms */
    private final Map axioms = new HashMap();

    /** maps sentence labels to sentences */
    private final Map sentences = new HashMap();

    /** maps rule labels to rules */
    private final Map rules = new HashMap();

    /** maps rules to declarations */
    private final Map ruleDeclarations = new HashMap();

    /** maps rules to labels */
    private final Map rule2Label = new HashMap();

    /** maps rules to declarations */
    private final List rulesList = new ArrayList();

    /** header of this module */
    private Header header;

    /** import list of this module */
    private ImportList imports;

    /** usedby list of this module */
    private UsedbyList usedby;

    /** paragraph list of this module */
    private ParagraphList paragraphs;


    /**
     * Constructs a module. This is the default constructor, that
     * is not used if one wants a proof verified module. To construct
     * a checked module see the next constructor.
     *
     * @param   arguments   see <code>ArgumentException<code> below
     * @throws  ArgumentException if the arguments are not of the
     *          form {@link Header}, {@link ImportList}?,
     *          {@link UsedbyList}? and {@ParagraphList} (in this
     *          order, the question marks mark arguments as optional).
     *
     */
    public Module(Argument[] arguments)
            throws ArgumentException {
        super(arguments);
        final String convention
            = ModuleConstants.CONVENTION
            + ModuleCreator.getName(Header.class) + " "
            + ModuleCreator.getName(ImportList.class) + "? "
            + ModuleCreator.getName(ParagraphList.class);
        if (arguments.length < 2 || arguments.length > 4) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                ModuleCreator.getName(this.getClass())
                + ModuleConstants.OPERATOR_WITH_TWO_TO_FOUR_ARGUMENTS);
        }
        if (!(arguments[0] instanceof Header)) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + ModuleConstants.FIRST_ARGUMENT_TYPE
                + ModuleCreator.getName(Header.class),
                arguments[0]);
        }
        this.header = (Header) arguments[0];
        int position = 1;
        if ((arguments[position] instanceof ImportList)) {
            this.imports = (ImportList) arguments[position];
            final int lines = imports.getArgumentSize();
            // step through all imports and get them
            for (int n = 0; n < lines; n++) {
                final Import imp = ((Import) imports.getArgument(n));
                // don't know what to do
            }
            position++;
        } else {
            this.imports = null;
        }
        if ((arguments[position] instanceof UsedbyList)) {
            this.usedby = (UsedbyList) arguments[position];
            position++;
        } else {
            this.usedby = null;
        }
        if (!(arguments[position] instanceof ParagraphList)) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + convention,
                arguments[position]);
        }
        this.paragraphs  = (ParagraphList) arguments[position];
    }


    /**
     * Constructs a module and verifies it.
     *
     * @param   arguments   see <code>ArgumentException<code> below
     * @param   moduleAddress     the URL where the module was loaded from
     * @throws  ArgumentException if the arguments are not of the
     *          form {@link Header}, {@link ImportList}?,
     *          {@link UsedbyList}? and {@ParagraphList} (in this
     *          order, the question marks mark arguments as optional).
     *          Also thrown if the module has any mathematical
     *          verification problem.
     * @throws  ParsingException if {@link ModuleContext.loadModule()}
     *          failed
     */
    public Module(final Argument[] arguments,
            final ModuleAddress moduleAddress)
            throws ArgumentException, ParsingException {
        super(arguments);
        setModuleAddress(moduleAddress);
        internalCheck();
    }

    
    /**
     * Verifies this module. The module must have an address.
     *
     * @throws  ArgumentException   the module has any mathematical
     *           verification problem.
     * @throws  ParsingException    {@link ModuleContext.loadModule()}
     *           failed
     */
    private void internalCheck() throws ArgumentException, ParsingException {
        ModuleContext.getInstance().moduleCheckStarted(this.moduleAddress);
        check();
    }
    

    /**
     * Verifies this module. The module must have an address.
     *
     * @throws  ArgumentException   the module has any mathematical
     *           verification problem.
     * @throws  ParsingException    {@link ModuleContext.loadModule()}
     *           failed
     */
    public void check() throws ArgumentException, ParsingException {
        if (this.moduleAddress == null) {
            throw new ArgumentException(
                ModuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                ModuleCreator.getName(this.getClass())
                + " " + ModuleConstants.NO_MODULE_ADDRESS);
        }
        this.labels.clear();
        this.abbreviations.clear();
        this.axioms.clear();
        this.sentences.clear();
        this.rulesList.clear();
        this.rules.clear();
        this.ruleDeclarations.clear();
        this.rule2Label.clear();
        this.referencedModules.clear();

        final String convention
            = ModuleConstants.CONVENTION
            + ModuleCreator.getName(Header.class) + " "
            + ModuleCreator.getName(ImportList.class) + "? "
            + ModuleCreator.getName(ParagraphList.class);
        if (getArgumentSize() < 2 || getArgumentSize() > 4) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_NUMBER,
                ModuleCreator.getName(this.getClass())
                + ModuleConstants.OPERATOR_WITH_TWO_TO_FOUR_ARGUMENTS);
        }

        if (!(getArgument(0) instanceof Header)) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + ModuleConstants.FIRST_ARGUMENT_TYPE
                + ModuleCreator.getName(Header.class),
                getArgument(0));
        }
        this.header = (Header) getArgument(0);
        int position = 1;
        if ((getArgument(position) instanceof ImportList)) {
            this.imports = (ImportList) getArgument(position);
            final int lines = imports.getArgumentSize();
            // step through all imports and get them
            for (int n = 0; n < lines; n++) {
                final Import imp = ((Import) imports.getArgument(n));
                final Module module;
                try {
                    module = ModuleContext.getInstance().loadModule(
                        this, (Specification) imp.getArgument(0));
                } catch (IllegalArgumentException e) {
                    throw new ArgumentException(
                        ModuleConstants.CODE_CONSTRUCTOR_ARGUMENT_CHECK_FAILED,
                        ModuleConstants.MODULE_IMPORT_FAILED
                        + e.getMessage(),
                        imp.getArgument(0));
                }
                try {
                    final String alias;
                    if (imp.getArgumentSize() > 1) {
                        alias = ((Text) imp.getArgument(1).getArgument(0))
                            .getText() + ".";
                    } else {
                        alias = "";
                    }
                    // Q & D
                    for (Iterator it = module.abbreviations.entrySet().iterator();
                            it.hasNext(); ) {
                        Map.Entry entry = (Map.Entry) it.next();
                        addAbbreviation(alias + (String) entry.getKey(),
                            (Abbreviation) entry.getValue(),
                            module.getLabelModule((String) entry.getKey()));
                    }
                    // Q & D
                    for (Iterator it = module.axioms.entrySet().iterator();
                            it.hasNext(); ) {
                        Map.Entry entry = (Map.Entry) it.next();
                        addAxiom(alias + (String) entry.getKey(),
                            (Axiom) entry.getValue(),
                            module.getLabelModule((String) entry.getKey()));
                    }
                    // Q & D
                    for (Iterator it = module.sentences.entrySet().iterator();
                            it.hasNext(); ) {
                        Map.Entry entry = (Map.Entry) it.next();
                        addSentence(alias + (String) entry.getKey(),
                            (Sentence) entry.getValue(),
                            module.getLabelModule((String) entry.getKey()));
                    }
                    // Q & D
                    {
                        final int len = module.rulesList.size();
                        for (int i = 0; i < len; i++) {
                            final String label = alias
                                + (String) module.rulesList.get(i);
                            final RuleDeclaration declaration
                                = (RuleDeclaration) module.rules.get(label);
                            addRuleDeclaration(label, declaration,
                                module.getLabelModule((String) module
                                .rulesList.get(i)));
                        }
                    }
                    // Q & D
                    {
                        addReferencedModule(module);
                        for (Iterator it = module.referencedModules.entrySet().iterator();
                                it.hasNext(); ) {
                            Map.Entry entry = (Map.Entry) it.next();
                            addReferencedModule((Module) entry.getValue());
                        }
                    }
                } catch (IllegalArgumentException e) {
                    throw new ArgumentException(
                        ModuleConstants.CODE_CONSTRUCTOR_ARGUMENT_CHECK_FAILED,
                        ModuleConstants.MODULE_IMPORT_FAILED
                        + e.getMessage(),
                        imp.getArgument(0));
                }

            }
            position++;
        } else {
            this.imports = null;
        }
        if ((getArgument(position) instanceof UsedbyList)) {
            this.usedby = (UsedbyList) getArgument(position);
            position++;
        } else {
            this.usedby = null;
        }
        if (!(getArgument(position) instanceof ParagraphList)) {
            throw new ArgumentException(
                ModuleConstants.CODE_CONSTRUCTOR_WRONG_ARGUMENT_TYPE,
                ModuleCreator.getName(this.getClass())
                + convention,
                getArgument(position));
        }
        this.paragraphs = (ParagraphList) getArgument(position);
        final int lines = paragraphs.getArgumentSize();
        // step through all paragraphs and check them
        for (int n = 0; n < lines; n++) {
            ((Paragraph) paragraphs.getArgument(n)).check(this);
        }
    }


    /**
     * Get the abbreviation with label <code>abbreviationLabel</code>.
     *
     * @param   abbreviationLabel  reference to abbreviation
     * @return  abbreviation
     * @throws  IllegalArgumentException if such an abbreviation
     *          doesn't exist
     */
    public final Abbreviation getAbbreviation (
            final String abbreviationLabel)
            throws IllegalArgumentException {
        final Abbreviation abbreviation
            = (Abbreviation) abbreviations.get(abbreviationLabel);
        if (abbreviation == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_ABBREVIATION_DOES_NOT_EXIST
                + " " + Utility.quote(abbreviationLabel));
        } else {
            return abbreviation;
        }
    }


    /**
     * Add referenced module.
     *
     * @param   module       module
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void addReferencedModule(final Module module)
            throws IllegalArgumentException {
        System.out.println("adding reference: " + module.getModuleAddress().getAddress());
        if (this.referencedModules.containsKey(module.getModuleAddress())) {
            return; // already referenced
        }
        this.referencedModules.put(module.getModuleAddress(), module);
    }


    /**
     * Add abbreviation with label <code>abbreviationLabel</code>.
     *
     * @param   abbreviationLabel  reference to abbreviation
     * @param   abbreviation       abbreviation
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void addAbbreviation(final String abbreviationLabel,
            final Abbreviation abbreviation, final Module module)
            throws IllegalArgumentException {
        final Abbreviation old;
        try {
            old = getAbbreviation(abbreviationLabel);
            if (old == abbreviation) {
                return;
            }
        } catch (IllegalArgumentException e) {  // not used jet
        }
        checkLabel(abbreviationLabel);
        labels.put(abbreviationLabel, module);
        abbreviations.put(abbreviationLabel, abbreviation);
    }


    /**
     * Get the axiom with label <code>axiomLabel</code>.
     *
     * @param   axiomLabel  reference to axiom
     * @return  axiom
     * @throws  IllegalArgumentException if such an axiom
     *          doesn't exist
     */
    public final Axiom getAxiom(final String axiomLabel)
            throws IllegalArgumentException {
        final Axiom axiom = (Axiom) axioms.get(axiomLabel);
        if (axiom == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_AXIOM_DOES_NOT_EXIST
                + " " + Utility.quote(axiomLabel));
        } else {
            return axiom;
        }
    }


    /**
     * Add axiom with label <code>axiomLabel</code>.
     *
     * @param   axiomLabel  reference to axiom
     * @param   axiom       axiom
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void addAxiom(final String axiomLabel, final Axiom axiom,
            final Module module)
            throws IllegalArgumentException {
        final Axiom old;
        try {
            old = getAxiom(axiomLabel);
            if (old == axiom) {
                return;                         // already there
            }
        } catch (IllegalArgumentException e) {  // not used jet
        }
        labels.put(axiomLabel, module);
        axioms.put(axiomLabel, axiom);
    }


    /**
     * Get the sentence with label <code>sentenceLabel</code>.
     *
     * @param   sentenceLabel  reference to sentence
     * @return  sentence
     * @throws  IllegalArgumentException if such an sentence
     *          doesn't exist
     */
    public final Sentence getSentence(final String sentenceLabel)
            throws IllegalArgumentException {
        final Sentence sentence = (Sentence) sentences.get(sentenceLabel);
        if (sentence == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_SENTENCE_DOES_NOT_EXIST
                + " " + Utility.quote(sentenceLabel));
        } else {
            return sentence;
        }
    }


    /**
     * Add sentence with label <code>sentenceLabel</code>.
     *
     * @param   sentenceLabel  reference to sentence
     * @param   sentence       sentence
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void addSentence(final String sentenceLabel,
            final Sentence sentence, final Module module)
            throws IllegalArgumentException {
        final Sentence old;
        try {
            old = getSentence(sentenceLabel);
            if (old == sentence) {
                return;                         // already there
            }
        } catch (IllegalArgumentException e) {  // not used jet
        }
        labels.put(sentenceLabel, module);
        sentences.put(sentenceLabel, sentence);
    }


    /**
     * Get the declaration for a rule.
     *
     * @param   rule  look for this rule class
     * @return  declaration for this rule
     * @throws  IllegalArgumentException if such a declaration
     *          doesn't exist
     */
    public final RuleDeclaration getRuleDeclaration(final Class rule)
            throws IllegalArgumentException {
        final RuleDeclaration declaration
            = (RuleDeclaration) ruleDeclarations.get(rule);

        if (declaration == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_RULE_IS_NOT_DECLARED
                + ": " + ModuleCreator.getName(rule));
        } else {
            return declaration;
        }
    }


    /**
     * Get the declaration for a rule.
     *
     * @param   ruleLabel  look for this rule
     * @return  declaration for this rule
     * @throws  IllegalArgumentException if such a declaration
     *          doesn't exist
     */
    public final RuleDeclaration getRuleDeclaration(final String ruleLabel)
            throws IllegalArgumentException {
        final RuleDeclaration declaration
            = (RuleDeclaration) rules.get(ruleLabel);

        if (declaration == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_RULE_DOES_NOT_EXIST
                + " " + Utility.quote(ruleLabel));
        } else {
            return declaration;
        }
    }


    /**
     * Get the declaration for a rule.
     *
     * @param   rule  look for this rule class
     * @return  label for this rule
     * @throws  IllegalArgumentException if such a declaration
     *          doesn't exist
     */
    public final String getRuleDeclarationLabel(final Class rule)
            throws IllegalArgumentException {
        final String label
            = (String) rule2Label.get(rule);

        if (label == null) {
            throw new IllegalArgumentException(
                ModuleConstants.REFERENCED_RULE_IS_NOT_DECLARED
                + ": " + ModuleCreator.getName(rule));
        } else {
            return label;
        }
    }


    /**
     * Is a certain rule declared?
     *
     * @param   rule  look for this rule class
     * @return  is this rule already declared?
     */
    public final boolean isRuleDeclared(final Class rule) {
        return null != (RuleDeclaration) ruleDeclarations.get(rule);

    }


    /**
     * Declare a new rule.
     *
     * @param   ruleLabel   label of this rule
     * @param   declaration declaration of a rule
     * @param   module      in this module the rule is declared
     * @throws  ArgumentException if the check failed
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void addRuleDeclaration(final String ruleLabel,
            final RuleDeclaration declaration, final Module module)
            throws IllegalArgumentException, ArgumentException {
        final String METHOD = "addRuleDeclaration"; 
        final RuleDeclaration old;
        try {
            old = getRuleDeclaration(ruleLabel);
            if (old == declaration) {
                return;                         // already there
            }
        } catch (IllegalArgumentException e) {  // not used jet
        }
        checkLabel(ruleLabel);
        labels.put(ruleLabel, module);
        rules.put(ruleLabel, declaration);
        final String ruleName = ((Text) declaration.getArgument(0))
                .getText();
        final Class ruleClass = ModuleCreator.getClass(ruleName);
        if (!Rule.class.isAssignableFrom(ruleClass)) {
            throw new IllegalArgumentException(
                ModuleConstants.THIS_NO_RULE
                + ": " + ModuleCreator.getName(ruleClass));
        }
        if (ruleDeclarations.containsKey(ruleClass)) {
            throw new IllegalArgumentException(
                ModuleConstants.RULE_ALREADY_DECLARED
                + ": " + ModuleCreator.getName(ruleClass));
        }

        try {
            final Method method = ruleClass.getMethod("checkDeclaration",
                new Class[] {Module.class, RuleDeclaration.class});
            if (method == null) {
                throw new IllegalArgumentException(ruleClass.getName()
                + " has no method check(DeclarationRule)");
            }
            method.invoke(null, new Object[] {
                    this, declaration
                });
        } catch (ClassCastException e) {
            throw new IllegalArgumentException("Programming error "
                + e.toString());
        } catch (InvocationTargetException e) {
            Trace.trace(this, METHOD, "should not occur!", e.getTargetException());
            if (e.getTargetException() instanceof ArgumentException) {
                throw (ArgumentException) e.getTargetException().fillInStackTrace();
            } else {
                throw new IllegalArgumentException(e.getTargetException().getMessage());
            }
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Programming error "
                + e.toString());
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Programming error "
                + e.toString());
        }
        ruleDeclarations.put(ruleClass, declaration);
        rulesList.add(ruleLabel);
        rule2Label.put(ruleClass, ruleLabel);
    }


    /**
     * Get the sentence or axiom formula with label <code>label</code>.
     *
     * @param   label  reference to an axiom or a proposition
     * @return  axiom or sentence formula
     * @throws  IllegalArgumentException if such an reference
     *          doesn't exist
     */
    public final Formula getLabeledFormula(final String label)
            throws IllegalArgumentException {
        final Sentence sentence = (Sentence) sentences.get(label);
        if (sentence == null) {
            final Axiom axiom = (Axiom) axioms.get(label);
            if (axiom == null) {
                throw new IllegalArgumentException(
                    ModuleConstants.REFERENCED_FORMULA_DOES_NOT_EXIST
                    + " " + Utility.quote(label));
            } else {
                return (Formula) axiom.getArgument(0);
            }
        } else {
            return (Formula) sentence.getArgument(0);
        }
    }


    /**
     * Get the sentence or axiom with label <code>label</code>.
     *
     * @param   label  reference to an axiom or a proposition
     * @return  axiom or sentence
     * @throws  IllegalArgumentException if such an reference
     *          doesn't exist
     */
    public final Argument getLabeledArgument(
            final String label)
            throws IllegalArgumentException {
        final Sentence sentence = (Sentence) sentences.get(label);
        if (sentence == null) {
            final Axiom axiom = (Axiom) axioms.get(label);
            if (axiom == null) {
                throw new IllegalArgumentException(
                    ModuleConstants.REFERENCED_FORMULA_DOES_NOT_EXIST
                    + " " + Utility.quote(label));
            } else {
                return axiom;
            }
        } else {
            return sentence;
        }
    }


    /**
     * Test if label is already known.
     *
     * @return  is this label undeclared
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final void checkLabel(final String label)
            throws IllegalArgumentException {

        if (labels.containsKey(label)) {
            if (this != getLabelModule(label)) {
                throw new IllegalArgumentException(
                    ModuleConstants.LABEL_ALREADY_EXISTS_IN
                    + getModuleFileName()
                    + ": " + Utility.quote(label)
                    + ", it was first used in "
                    + getLabelModule(label).getModuleFileName());
            } else {
                throw new IllegalArgumentException(
                    ModuleConstants.LABEL_ALREADY_EXISTS
                    + Utility.quote(label));
            }
        }
    }



    /**
     * Get module the label was defined in.
     *
     * @param   label  reference name to look for
     * @throws  IllegalArgumentException if such an label
     *          already exists
     */
    public final Module getLabelModule(final String label)
            throws IllegalArgumentException {

        if (!labels.containsKey(label)) {
            throw new IllegalArgumentException(
                "this label doesn't exist: "
                + ": " + Utility.quote(label));
        }
        return (Module) labels.get(label);
    }


    /**
     * Set the URL where this module is from.
     *
     * @param   address     URL of this module
     * @throws  ArgumentException if URL doesn't end how
     *              the specification says
     */
    public final void setModuleAddress(
        final ModuleAddress moduleAddress) throws ArgumentException {

        // check if address ends like specification says
        final Argument spec = getArgument(0).getArgument(0);
        if (!((Name) spec.getArgument(0))
                .equals(moduleAddress.getName()) ||
                !((Version) spec.getArgument(1))
                .equals(moduleAddress.getModuleVersion()) ||
                !((Version) spec.getArgument(2))
                .equals(moduleAddress.getRuleVersion())) {
            throw new ArgumentException(
                ModuleConstants.CODE_METHOD_ARGUMENT_CHECK_FAILED,
                moduleAddress
                + " " + ModuleConstants.MODULE_SPECIFICATION_DIFFERS_FROM_NAME
                + " " + getModuleFileName() + ", " + moduleAddress,
                spec);
        }
        this.moduleAddress = moduleAddress;
    }


    /**
     * Get the full module name, with version and rule version,
     * but without path.
     *
     * @return  name of this module
     */
    public final String getModuleFileName() {
        final Argument spec = getArgument(0).getArgument(0);
        return ModuleAddress.getModuleFileName((Specification) spec);
      }

    /**
     * Get the {@link ModuleAddress} where this module is from.
     *
     * @return  address of this module
     */
    public final ModuleAddress getModuleAddress() {
        return this.moduleAddress;
    }


    /**
     * Get a map of all known labels.
     *
     * @return  known labels of this module
     */
    public final Map getLabels() {
        return new HashMap(labels);
    }


    /**
     * Get all imports of this module.
     *
     * @return  all imports of this module
     */
    public final ImportList getImports() {
        return this.imports;
    }


    /**
     * Get all usedby references of this module.
     *
     * @return  all usedby references of this module
     */
    public final UsedbyList getUsedby() {
        return this.usedby;
    }


    /**
     * Get all paragraphs of this module.
     *
     * @return  all paragraphs of this module
     */
    public final ParagraphList getParagraphs() {
        return this.paragraphs;
    }


    /**
     * Get all propositions of this module.
     *
     * @return  all propositions of this module
     */
    public final Proposition[] getPropositions() {
        final ArrayList list = new ArrayList();
        for (int i = 0; i < paragraphs.getArgumentSize(); i++) {
            final ParagraphCheck paragraphCheck = ((Paragraph) paragraphs
                .getArgument(i)).getParagraphCheck();
            if (paragraphCheck instanceof Proposition) {
                list.add(paragraphCheck);
            }
        }
        return (Proposition[]) list.toArray(
            new Proposition[list.size()]);
    }


    /**
     * Get the rule version this module could use maximal.
     *
     * @return  maximal rule version
     */
    public final Version getRuleVersion() {
        return (Version) this.header.getArgument(0).getArgument(2);
    }


    /**
     * Set the rule version this module could use maximal.
     * After this method the module address is unknown.
     *
     * @param   version  maximal rule version
     * @throws  ArgumentException   if setting failed
     */
    public final void setRuleVersion(final Version version)
            throws ArgumentException {
        this.replace(0,
            new Header(
                new Argument[] {
                    new Specification(
                        new Argument[] {
                            this.header.getArgument(0).getArgument(0),
                            this.header.getArgument(0).getArgument(1),
                            version,
                            this.header.getArgument(0).getArgument(3),
                        }
                    ),
                    this.header.getArgument(1),
                    this.header.getArgument(2),
                    this.header.getArgument(3),
                    this.header.getArgument(4),
                }
            )
        );
        this.moduleAddress = null;
    }


    /**
     * Get a map of all referenced modules.
     *
     * @return  map with all referenced modules. Key is the ModuleAddress,
     *          value is the Module itself.
     */
    public final Map getReferencedModules() {
        return this.referencedModules;
    }




    /* @inherited */
    public final Argument create(final Argument[] arguments)
            throws ArgumentException {
        return new Module(arguments);
    }


    /* @inherited */
    public final String toString() {
        final StringBuffer buf = new StringBuffer("Module ");
        for (int i = 0; i < getArgumentSize(); i++) {
            buf.append(getArgument(i));
        }
        return buf.toString();
    }

}
