lu.list.itis.dkd.aig.resolution.Template.java Source code

Java tutorial

Introduction

Here is the source code for lu.list.itis.dkd.aig.resolution.Template.java

Source

/**
 * Copyright (c) 2016-2017  Luxembourg Institute of Science and Technology (LIST).
 * 
 * This software is 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.
 * 
 * for more information about the software, please contact info@list.lu
 */
package lu.list.itis.dkd.aig.resolution;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.jena.ext.com.google.common.collect.Lists;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;

import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;

import lu.list.itis.dkd.aig.Variable;
import lu.list.itis.dkd.aig.VariableBuilder;
import lu.list.itis.dkd.aig.process.InitializationProcess;
import lu.list.itis.dkd.aig.process.OptimizationProcess;
import lu.list.itis.dkd.aig.process.ResolutionProcess;
import lu.list.itis.dkd.aig.process.ResolutionProcessBuilder;
import lu.list.itis.dkd.aig.util.DocumentConverter;
import lu.list.itis.dkd.aig.util.Externalization;
import lu.list.itis.dkd.aig.util.PropertiesFetcher;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;

/**
 * Class containing all the information extracted from an underlying XML template.
 *
 * @author Eric Tobias [eric.tobias@list.lu]
 * @since 0.7
 * @version 0.8.0
 */
@NonNullByDefault
public class Template {
    private final Set<URI> variableKeys = new HashSet<>();
    private final SetMultimap<URI, Variable> variables = HashMultimap.create();
    private final Map<URI, InitializationProcess> initializationProcesses = new LinkedHashMap<>();
    private final Map<URI, OptimizationProcess> optimizationProcesses = new LinkedHashMap<>();
    private final TreeSet<Dependency> dependencies = new TreeSet<>();
    private final HashMap<URI, Dependency> dependencyByHead = new HashMap<>();

    private URI identifier;
    private InteractionType interactionType;
    private CorrectResponseAttributionType correctResponseAttributionType;
    private DistractorAttributionType distractorAttributionType;

    private String itemLayer;
    private final HashMap<String, String> templateMetadata = new HashMap<>();

    // private TaskModel taskModel; //TODO To be implemented in a latter version.

    private static final Logger logger = Logger.getLogger(Template.class.getSimpleName());

    static {
        Template.logger.setLevel(Level.parse(PropertiesFetcher.getProperties()
                .getProperty(Externalization.LOGGER_LEVEL_PROEPRTY, Level.INFO.toString())));
    }

    /**
     * Constructor initializing all fields.
     *
     * @param document
     *        The document containing all the information to build the template instance from.
     * @pre null != document
     * @throws TemplateParseException
     *         Exception thrown when parsing the document failed or if any of the preconditions
     *         associated with the document failed to hold, e.g. if any of the mandatory fields are
     *         not provided or if values are given outside of their accepted inputs domain.
     */
    public Template(final @Nullable Document document) throws TemplateParseException {
        if (null == document) {
            throw new TemplateParseException("The template document must be provided and cannot be null!"); //$NON-NLS-1$
        }
        // TODO validate xml document

        populateTemplateMetadataValues(document);
        extractItem(document);

        try {
            identifier = new URI(templateMetadata.get(Externalization.IDENTIFIER_ELEMENT));
            interactionType = InteractionType
                    .fromString((templateMetadata.get(Externalization.INTERACTION_TYPE_NODE)).toUpperCase());
        } catch (final URISyntaxException e) {
            Template.logger.log(Level.SEVERE,
                    "No unique identifier could be extracted from the template. Check for correct nesting and spelling!", //$NON-NLS-1$
                    e);
            throw new TemplateParseException(
                    "No unique identifier could be extracted from the template. Check for correct nesting and spelling!", //$NON-NLS-1$
                    e);
        } catch (final IllegalArgumentException | NullPointerException e) {
            Template.logger.log(Level.SEVERE,
                    "No construct type could be extracted from the template. Check your node \"metadata\" and the nested \"constructType\" nodes for correct nesting and spelling!", //$NON-NLS-1$
                    e);
            throw new TemplateParseException(
                    "No construct type could be extracted from the template. Check your node \"metadata\" and the nested \"constructType\" nodes for correct nesting and spelling! ", //$NON-NLS-1$
                    e);
        } catch (final ResolutionException e) {
            Template.logger.log(Level.SEVERE,
                    "No interaction type could be extracted from the template. Check your node \"metadata\" and the nested \"interaction\" nodes for correct nesting and spelling!", //$NON-NLS-1$
                    e);
            throw new TemplateParseException(
                    "No interaction type could be extracted from the template. Check your node \"metadata\" and the nested \"interaction\" nodes for correct nesting and spelling! ", //$NON-NLS-1$
                    e);
        }

        extractVariableKeys(document);
        extractDependencies(document);
        extractProcesses(document);

        // TODO Sanity checks!
    }

    /**
     * Method used to extract and build all processes from the template.
     *
     * @throws TemplateParseException
     *         Thrown when either the extraction or the instantiation of a process failed.
     *
     */
    @SuppressWarnings("null")
    private void extractProcesses(final Document document) throws TemplateParseException {
        List<Element> processElements;

        try {
            final XPathExpression<Element> xpath = XPathFactory.instance().compile(Externalization.PROCESS_XPATH,
                    Filters.element());
            processElements = xpath.evaluate(document);
            if ((null == processElements) || (processElements.isEmpty())) {
                // throw new TemplateParseException("No processes could be extracted from the
                // template. Check your node \"processes\" and the subsequent \"process\" nodes for
                // correct nesting and spelling!"); //$NON-NLS-1$
                Template.logger.log(Level.WARNING, "No processes could be extracted from the template."); //$NON-NLS-1$
            }
        } catch (final NullPointerException | IllegalArgumentException | IllegalStateException e) {
            throw new TemplateParseException(
                    "No processes could be extracted from the template. Check your node \"processes\" and the subsequent \"process\" nodes for correct nesting and spelling!", //$NON-NLS-1$
                    e);
        }

        for (final Element processElement : processElements) {
            final ResolutionProcess process = ResolutionProcessBuilder.buildProcessFrom(processElement);

            if (process instanceof OptimizationProcess) {
                optimizationProcesses.put(process.getIdentifier(), (OptimizationProcess) process);
            } else { // Both are exclusive.
                initializationProcesses.put(process.getIdentifier(), (InitializationProcess) process);
            }
        }
    }

    /**
     * Method used to extract all dependency statements from the template document.
     *
     * @throws TemplateParseException
     *         Thrown whenever the extraction of dependencies failed or when the retrieved
     *         dependency element did not contain all the correct building blocks to instantiate a
     *         new instance of a dependency.
     */
    @SuppressWarnings("null")
    private void extractDependencies(final Document document) throws TemplateParseException {
        List<Element> dependencyElements;
        try {
            final XPathExpression<Element> xpath = XPathFactory.instance().compile(Externalization.DEPENDENCY_XPATH,
                    Filters.element());
            // The new list is required to be able to remove elements.
            dependencyElements = Lists.newArrayList(xpath.evaluate(document));

            if ((null == dependencyElements) || (dependencyElements.isEmpty())) {
                Template.logger.log(Level.WARNING, "No dependencies could be extracted from the template."); //$NON-NLS-1$
            }

            dependencyElements.removeIf(Objects::isNull);
        } catch (final NullPointerException | IllegalArgumentException | IllegalStateException e) {
            throw new TemplateParseException(
                    "No dependencies could be extracted from the template. Check your node \"variableDependencies\" and the subsequent \"depencency\" nodes for correct nesting and spelling!", //$NON-NLS-1$
                    e);
        }

        for (final Element dependencyElement : dependencyElements) {
            final Dependency dependency = new Dependency(dependencyElement);
            dependencies.add(dependency);
            dependencyByHead.put(dependency.getHead(), dependency);
        }
    }

    /**
     * Helper method used to extract all variable's keys from the template document and register the
     * blueprint to build the variable on demand by its key with the {@link VariableBuilder}.
     *
     * @throws TemplateParseException
     *         Thrown when the provided variable identifier was not a valid URI or if no variables
     *         could be extracted.
     */
    @SuppressWarnings("null")
    private void extractVariableKeys(final Document document) throws TemplateParseException {
        List<Element> variableElements;
        try {
            final XPathExpression<Element> xpath = XPathFactory.instance().compile(Externalization.VARIABLE_XPATH,
                    Filters.element());
            variableElements = xpath.evaluate(document);
            if ((null == variableElements) || (variableElements.isEmpty())) {
                // throw new TemplateParseException("No variables could be extracted from the
                // template. Check your node \"variableDefinitions\" and the subsequent \"variable\"
                // nodes for correct nesting and spelling!"); //$NON-NLS-1$
                Template.logger.log(Level.WARNING, "No variables could be extracted from the template."); //$NON-NLS-1$
            }
        } catch (final NullPointerException | IllegalArgumentException | IllegalStateException e) {
            throw new TemplateParseException(
                    "No variables could be extracted from the template. Check your node \"variableDefinitions\" and the subsequent \"variable\" nodes for correct nesting and spelling!"); //$NON-NLS-1$
        }

        for (final Element variableElement : variableElements) {
            variableKeys.add(VariableBuilder.registerBlueprint(variableElement));
        }
    }

    /**
     * Method used to extract the metadata node from the provided document template.
     *
     * @param document
     *        The document to extract the metadata from.
     * @throws TemplateParseException
     *         Thrown when the metadata could not be extracted from the document.
     */
    private void populateTemplateMetadataValues(final Document document) throws TemplateParseException {
        Element metadataRoot;
        try {
            final XPathExpression<Element> xpath = XPathFactory.instance()
                    .compile(Externalization.TEMPLATE_METADATA_XPATH, Filters.element());
            metadataRoot = xpath.evaluateFirst(document);
        } catch (final NullPointerException | IllegalArgumentException | IllegalStateException e) {
            throw new TemplateParseException(
                    "Compiling the XPath expression to resolve the template metadata root node failed!", e); //$NON-NLS-1$
        }

        for (final Element metadataElement : metadataRoot.getChildren()) {
            templateMetadata.put(metadataElement.getName(), metadataElement.getText());
        }
    }

    /**
     * Method used for extracting the QTI item husk from an item template.
     *
     * @throws TemplateParseException
     *         TODO
     */
    private void extractItem(final Document document) throws TemplateParseException {
        Element qtiRoot;

        try {
            final XPathExpression<Element> xpath = XPathFactory.instance().compile(Externalization.QTI_XPATH,
                    Filters.element());
            qtiRoot = xpath.evaluateFirst(document);
        } catch (final NullPointerException | IllegalArgumentException | IllegalStateException e) {
            throw new TemplateParseException("Compiling the Xpath expression to resolve the QTI root node failed!", //$NON-NLS-1$
                    e);
        }

        itemLayer = DocumentConverter.convertDocumentToString(new Document(qtiRoot.detach()));
        itemLayer = itemLayer.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", ""); //$NON-NLS-1$ //$NON-NLS-2$
    }

    /**
     * Simple getter method for variables.
     *
     * @return The value of variables.
     */
    @SuppressWarnings("null")
    public SetMultimap<URI, Variable> getVariables() {
        return variables;
    }

    /**
     * Simple getter method for initializationProcesses.
     *
     * @return The value of initializationProcesses.
     */
    @SuppressWarnings("null")
    public Map<URI, InitializationProcess> getInitializationProcesses() {
        return initializationProcesses;
    }

    /**
     * Simple getter method for optimizationProcesses.
     *
     * @return The value of optimizationProcesses.
     */
    @SuppressWarnings("null")
    public Map<URI, OptimizationProcess> getOptimizationProcesses() {
        return optimizationProcesses;
    }

    /**
     * Simple getter method for dependencies.
     *
     * @return The value of dependencies.
     */
    @SuppressWarnings("null")
    public TreeSet<Dependency> getDependencies() {
        return dependencies;
    }

    /**
     * Simple getter method for identifier.
     *
     * @return The value of identifier.
     */
    @SuppressWarnings("null")
    public URI getIdentifier() {
        return identifier;
    }

    /**
     * Simple getter method for interactionType.
     *
     * @return The value of interactionType.
     */
    @SuppressWarnings("null")
    public InteractionType getInteractionType() {
        return interactionType;
    }

    /**
     * Simple getter method for itemLayer.
     *
     * @return The value of itemLayer.
     */
    @SuppressWarnings("null")
    public String getItemLayer() {
        return Strings.nullToEmpty(itemLayer);
    }

    /**
     * Simple getter method for templateMetadata.
     *
     * @return The value of templateMetadata.
     */
    @SuppressWarnings("null")
    public HashMap<String, String> getTemplateMetadata() {
        return templateMetadata;
    }

    /**
     * Simple getter method for templateMetadata.
     *
     * @param key
     *        The key to retrieve metadata for.
     * @return The value associated to the key or <code>null</code> if no value was associated to
     *         the key.
     */
    public @Nullable String getTemplateMetadata(final String key) {
        return templateMetadata.get(key);
    }

    /**
     * Method used to retrieve the key variable as given by the template metadata entry. The method
     * will return all instances of the key variable as a list.
     *
     * @return A list of variables that represent all instances of the designated key variable. Note
     *         that the list may be empty if the variables have not been initialized.
     * @throws TemplateConsistencyException
     *         Thrown when the retrieval of the key variable was unsuccessful due to either the
     *         variable not being defined properly in the template, either in the metadata or
     *         variable definition section, or the key variable not being a valid URI.
     */
    @SuppressWarnings("null")
    public Set<Variable> getKeyVariables() throws TemplateConsistencyException {
        try {
            return variables.get(new URI(templateMetadata.get(Externalization.KEY_VARIABLE)));
        } catch (final URISyntaxException e) {
            throw new TemplateConsistencyException(
                    "The key variable as given by the template metadata entry \"keyVariable\" is required to be a valid URI and mapped to a variable defined in the template as well!", //$NON-NLS-1$
                    e);
        }
    }

    /**
     * Method used to retrieve the correct response variable as given by the template metadata
     * entry. The method will return all instances of the correct response variable as a list.
     *
     * @return A list of variables that represent all instances of the designated correct response.
     *         Note that the list may be empty if the variables have not been initialized.
     * @throws TemplateConsistencyException
     *         Thrown when the retrieval of the correct response was unsuccessful due to either the
     *         variable not being defined properly in the template, either in the metadata or
     *         variable definition section, or the correct response not being a valid URI.
     */
    @SuppressWarnings("null")
    public Set<Variable> getCorrectResponseVariables() throws TemplateConsistencyException {
        return variables.get(getCorrectResponseURI());
    }

    /**
     * Method used to retrieve the URI that is the identifier of all correct responses for this
     * template.
     *
     * @return The URI that is the identifier of all correct responses.
     * @throws TemplateConsistencyException
     *         Thrown when the identifier was not a valid URI.
     */
    public URI getCorrectResponseURI() throws TemplateConsistencyException {
        try {
            return new URI(templateMetadata.get(Externalization.CORRECT_RESPONSE_VARIABLE));
        } catch (final URISyntaxException e) {
            throw new TemplateConsistencyException(
                    "The correct response variable as given by the template metadata entry \"correctResponseVariable\" is required to be a valid URI!", //$NON-NLS-1$
                    e);
        }
    }

    /**
     * Method used to retrieve the distractor variable as given by the template metadata entry.The
     * method will return all instances of the distractor variable as a list.
     *
     * @return A list of variables that represent all instances of the designated distractor
     *         variable. Note that the list may be empty if the variables have not been initialized.
     * @throws TemplateConsistencyException
     *         Thrown when the retrieval of the distractor variable was unsuccessful due to either
     *         the variable not being defined properly in the template, either in the metadata or
     *         variable definition section, or the key variable not being a valid URI.
     */
    @SuppressWarnings("null")
    public Set<Variable> getDistractorVariables() throws TemplateConsistencyException {
        return variables.get(getDistractorURI());
    }

    /**
     * Method used to retrieve the URI of the distractor variable for this template.
     *
     * @return The URI that is the identifier of the distractor.
     * @throws TemplateConsistencyException
     *         Thrown when the identifier was not a valid URI.
     */
    public URI getDistractorURI() throws TemplateConsistencyException {
        try {
            return new URI(templateMetadata.get(Externalization.DISTRACTOR_VARIABLE));
        } catch (final URISyntaxException e) {
            throw new TemplateConsistencyException(
                    "The distractor variable as given by the template metadata entry \"distractorVariable\" is required to be a valid URI!", //$NON-NLS-1$
                    e);
        }
    }

    /**
     * Method used to retrieve a dependency by the URI of its head.
     *
     * @param head
     *        The URI the head of the dependency to retrieve.
     * @return The dependency for the given head or <code>null</code> if no such dependency was
     *         mapped.
     */
    public @Nullable Dependency getDependencyForHead(final URI head) {
        return dependencyByHead.get(head);
    }
}