org.biopax.validator.api.beans.Validation.java Source code

Java tutorial

Introduction

Here is the source code for org.biopax.validator.api.beans.Validation.java

Source

package org.biopax.validator.api.beans;

/*
 * #%L
 * Object Model Validator Core
 * %%
 * Copyright (C) 2008 - 2013 University of Toronto (baderlab.org) and Memorial Sloan-Kettering Cancer Center (cbio.mskcc.org)
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.annotation.*;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.biopax.validator.api.Rule;

@XmlType(name = "Validation")
@XmlAccessorType(XmlAccessType.FIELD)
public class Validation implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final Log log = LogFactory.getLog(Validation.class);

    @XmlTransient
    private Object model;
    // "forcedly" associated objects, i.e., parser, model(s), and dangling elements 
    @XmlTransient
    private final Set<Object> objects;
    // extra/optional settings
    @XmlTransient
    private final Properties properties;
    // getting object's ID strategy (for error reporting)
    @XmlTransient
    private final Identifier idCalc;

    @XmlElement(required = false)
    private String modelData; //cannot store more than ~1Gb data.
    @XmlElement
    private final Set<ErrorType> error;
    @XmlAttribute(required = false)
    private String description;
    @XmlElement
    private final Set<String> comment;
    @XmlAttribute
    private int notFixedProblems = 0;
    @XmlAttribute
    private int notFixedErrors = 0;
    @XmlAttribute
    private int totalProblemsFound = 0;
    @XmlAttribute
    private boolean fix = false;
    @XmlAttribute(required = false)
    private Behavior threshold;
    // limit not fixed error cases (1 means "fall-fast" mode, i.e., stop after the first serious and not fixed error)
    @XmlAttribute(required = false)
    private int maxErrors;
    @XmlAttribute(required = false)
    private String profile;
    @XmlAttribute
    private String summary;

    /** 
     * Default Constructor (this is mainly for OXM)
     * @param idCalculator a strategy object to get a domain-specific identifier (for reporting)
     */
    public Validation(Identifier idCalculator) {
        this.error = new TreeSet<ErrorType>();
        this.objects = Collections.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
        this.description = "unknown";
        this.comment = new HashSet<String>();
        this.fix = false;
        this.threshold = Behavior.WARNING;
        this.maxErrors = Integer.MAX_VALUE;
        this.profile = null;
        this.properties = new Properties();
        this.idCalc = (idCalculator != null) ? idCalculator : new Identifier() {
            // fall-back Identifier strategy implementation that uses toString
            public String identify(Object obj) {
                return String.valueOf(obj);
            }
        };
    }

    /** 
     * Default Constructor (this is mainly for OXM)
     */
    public Validation() {
        this(null);
    }

    /**
     * Non-default settings Constructor. 
     * Default values will be used if null or zero values were provided.
     * @param idCalculator a strategy object to get a domain-specific identifier (for reporting)
     * @param description default is "unknown"
     * @param autoFix default is false
     * @param errorLevel default is WARNING
     * @param maxErrors default is 0 (means unlimited, actually it's {@link Integer#MAX_VALUE, but the effect is same})
     * @param profile validation profile name (if null, the default is used)
     * 
     * @throws IllegalArgumentException when maxErrors < 0
     */
    public Validation(Identifier idCalculator, String description, boolean autoFix, Behavior errorLevel,
            int maxErrors, String profile) {
        this(idCalculator);

        setDescription(description);

        this.fix = autoFix;

        if (errorLevel != null)
            this.threshold = errorLevel;

        if (maxErrors < 0)
            throw new IllegalArgumentException("Illegal value for maxErrors: " + maxErrors);
        this.maxErrors = maxErrors;

        if (profile != null && !profile.isEmpty())
            this.profile = profile;
    }

    /**
     * Gets the current Model.
     * 
     * @return
     */
    @XmlTransient
    public Object getModel() {
        return model;
    }

    /**
     * Sets Model (to check or report about)
     * 
     * Note: use with care, because 
     * 'objects' may still contain elements from 
     * the old model, which is up to developer to clean them
     * or continue working with; different models can share the same 
     * objects, etc.
     * 
     * @param model
     */
    public void setModel(Object model) {
        this.model = model;
    }

    /**
     * List of error types; each item has unique code.
     * 
     * (Note: the property name is 'Error', and not 'Errors',
     * simply for the simpler object-XML binding.)
     * 
     * @return
     */
    public Collection<ErrorType> getError() {
        return error;
    }

    /**
     * This method should never be used
     * (this is for OXM frameworks only)!
     * 
     * @param errors
     */
    public synchronized void setError(Collection<ErrorType> errors) {
        error.clear();
        error.addAll(errors);
    }

    /**
     * This method should never be used
     * outside this framework;
     * this is for the biopax-validator Web
     * server and client applications only.
     * 
     * @return BioPAX RDF/XML
     */
    public String getModelData() {
        return modelData;
    }

    /**
     * This method should never be used
     * outside this framework;
     * this is for the biopax-validator Web 
     * server and client applications only.
     *
     * @param modelData
     */
    public void setModelData(String modelData) {
        this.modelData = modelData;
    }

    /**
     * Returns the data as HTML-escaped string 
     * (to show on a web page).
     * 
     * @return
     */
    @XmlTransient
    public String getModelDataHtmlEscaped() {
        return (modelData != null) ? StringEscapeUtils.escapeHtml(modelData).replaceAll(
                System.getProperty("line.separator"), System.getProperty("line.separator") + "<br/>") : null;
    }

    /**
     * Adds or updates the error (with cases) to the errors collection.
     * This is the primary method to save or update a just discovered or 
     * fixed validation problem!
     * 
     * If the same 'error' already exists there 
     * (i.e. type:code where type is either 'warning' or 'error'), 
     * the new error cases will be copied to it;
     * otherwise, the new one is simply added to the set.
     * 
     * It also updates the notfixed errors counter, 
     * taking into account current validation threshold (level)
     * and error's own type {@link Behavior} set by a validation rule
     * that created it!
     * 
     * @see #setThreshold(Behavior)
     * @see ErrorType#hashCode()
     * @see ErrorType#equals(Object)
     * 
     * @param e Error type
     */
    public synchronized void addError(ErrorType e) {

        int numNotFixedErrors = countErrors(null, null, null, null, true, true);

        if (isMaxErrorsSet() && numNotFixedErrors >= getMaxErrors()) {
            log.info("Won't save the case: max. errors " + "limit exceeded for " + getDescription());
            return;
        }

        switch (threshold) {
        case IGNORE:
            break; // do nothing
        case ERROR:
            if (e.getType() == Behavior.WARNING) {
                break; // do not add (only errors pass)
            }
        default: // add error with all cases
            synchronized (this) {
                if (error.contains(e)) {
                    for (ErrorType et : error) {
                        if (et.equals(e)) {
                            et.addCases(e.getErrorCase());
                            break;
                        }
                    }
                } else { // adding a new error type (code)
                    error.add(e);
                }
            }
            break;
        }
    }

    /**
     * Removes the error type and all cases.
     * @param e
     */
    public synchronized void removeError(ErrorType e) {
        error.remove(e);
    }

    /**
     * Sets the information about this validation task. 
     * 
     * @param description
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Information about this validation task. 
     * 
     * @return
     */
    public String getDescription() {
        return description;
    }

    /**
     * This method should never be used
     * (this is for OXM frameworks only)
     *
     * @param comments
     */
    public void setComment(Collection<String> comments) {
        this.comment.clear();
        this.comment.addAll(comments);
    }

    /**
     * Adds a comment, e.g., counts, regarding this validation task.
     * @param comment
     */
    public void addComment(String comment) {
        this.comment.add(comment);
    }

    /**
     * Comments (e.g., counts) about this validation task.
     * @return
     */
    public Collection<String> getComment() {
        return comment;
    }

    /**
     * Validation results summary.
     * 
     * @return
     */
    public synchronized String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    /**
     * String representation of this validation settings and results:
     * {@link #getDescription()} plus {@link #getSummary()}
     * 
     */
    @Override
    public String toString() {
        return super.toString() + " (" + getDescription() + "; " + getSummary() + ")";
    }

    /**
     * Searches for existing (registered) error type.
     * This is mostly for testing.
     * 
     * @param code
     * @param type
     * @return
     */
    public ErrorType findErrorType(String code, Behavior type) {
        return findErrorType(new ErrorType(code, type));
    }

    /**
     * Finds the same error type among
     * existing (already happened to have error cases)
     * 
     * @see ErrorType#equals(Object)
     * @see ErrorType#hashCode()
     * 
     * @param errorType
     * @return
     */
    private ErrorType findErrorType(ErrorType errorType) {
        if (getError().contains(errorType)) {
            for (ErrorType et : getError()) {
                if (et.equals(errorType)) {
                    return et;
                }
            }
        }
        return null;
    }

    /**
     * Finds the same error case in the registry.
     * 
     * @see ErrorType#equals(Object)
     * @see ErrorType#hashCode()
     * @see ErrorCaseType#equals(Object)
     * @see ErrorCaseType#hashCode()
     *  
     * @param errorType
     * @return
     */
    private ErrorCaseType findErrorCase(ErrorType errorType, ErrorCaseType errCase) {
        ErrorType etype = findErrorType(errorType);
        if (etype != null) {
            ErrorCaseType ecase = etype.findErrorCase(errCase);
            return ecase; //bug fixed ('return' was missing)
        }
        return null;
    }

    /**
     * Counts the number of errors/warnings.
     * Extra parameters are used to exclude 
     * some of the cases.
     * 
     * @param forObject when 'null', counts all
     * @param reportedBy when 'null', counts all
     * @param code when 'null', counts all
     * @param category when 'null', counts all
     * @param ignoreWarnings do not count WARNINGs
     * @param ignoreFixed do not count fixed
     * @return
     */
    public int countErrors(String forObject, String reportedBy, String code, Category category,
            boolean ignoreWarnings, boolean ignoreFixed) {
        int count = 0;

        for (ErrorType et : getError()) {
            // skip warnings?
            if (ignoreWarnings == true && et.getType() == Behavior.WARNING) {
                continue;
            }

            // skip other codes?
            if (code != null && !code.equalsIgnoreCase(et.getCode())) {
                continue;
            }

            // skip other categories?
            if (category != null && !(category == et.getCategory())) {
                continue;
            }

            count += et.countErrors(forObject, reportedBy, ignoreFixed);
        }

        return count;
    }

    /**
     * Objects associated with this validation by the Validator,
     * usually - indirectly, during I/O operations. 
     * Any object O can be potentially linked to the Validation object 
     * if it makes sense, and if there is a {@link Rule} or some method
     * (such as in AOP Aspects) which is about to register errors about O.
     * 
     * @return
     */
    @XmlTransient
    public Set<Object> getObjects() {
        return objects;
    }

    /**
     * @return true if auto-fix is enabled; otherwise - false
     */
    public boolean isFix() {
        return fix;
    }

    /**
     * @param fix true when auto-fix is enabled
     */
    protected void setFix(boolean fix) {
        this.fix = fix;
    }

    /**
     * Gets the error reporting level/threshold, i.e., 
     * this is to ignore issues reported with a level
     * less than desired; e.g., to hide warnings (similar to logging).
     * 
     * @return
     */
    public Behavior getThreshold() {
        return threshold;
    }

    /**
     * Sets the errors reporting level.
     * 
     * @see #getThreshold()
     * 
     * @param threshold
     */
    protected void setThreshold(Behavior threshold) {
        this.threshold = threshold;
    }

    /**
     * Total number of {@link Behavior#ERROR} and 
     * {@link Behavior#WARNING} cases (either fixed or not).
     * 
     * @return
     */
    public int getTotalProblemsFound() {
        return totalProblemsFound;
    }

    public void setTotalProblemsFound(int n) {
        this.totalProblemsFound = n;
    }

    /**
     * Total number of {@link Behavior#ERROR} and 
     * {@link Behavior#WARNING} cases, NOT fixed.
     * 
     * @return
     */
    public synchronized int getNotFixedProblems() {
        return notFixedProblems;
    }

    public void setNotFixedProblems(int n) {
        notFixedProblems = n;
    }

    /** 
     * Total number of NOT fixed {@link Behavior#ERROR} cases.
     * 
     * @return
     */
    public synchronized int getNotFixedErrors() {
        return notFixedErrors;
    }

    public void setNotFixedErrors(int n) {
        notFixedErrors = n;
    }

    /** 
     * Finds a previously reported
     * by the rule error case and marks it as fixed.
     * 
     * @param objectId model element identifier (associated with the error)
     * @param rule a validation rule name (that reports the error code)
     * @param errCode specific error code
     * @param newMsg a message, if not null/empty, to replace the original one
     */
    public void setFixed(String objectId, String rule, String errCode, String newMsg) {
        Behavior type = Behavior.WARNING;
        ErrorCaseType ect = findErrorCase(new ErrorType(errCode, type), new ErrorCaseType(rule, objectId, null)); // msg is ignored when comparing errors anyway

        if (ect == null) {
            type = Behavior.ERROR;
            ect = findErrorCase(new ErrorType(errCode, type), new ErrorCaseType(rule, objectId, null));
        }

        if (ect != null && !ect.isFixed()) {
            ect.setFixed(true);
            if (newMsg != null && !"".equals(newMsg.trim()))
                ect.setMessage(newMsg);
        }
    }

    /**
     * Errors limit. After this value is reached, the validator stops registering new error cases
     * with this validation object (though rules continue to check and report). 
     * If this method returns 0 that actually means "not set", which also means 'unlimited'
     * (this is to avoid generating unnecessary XML attribute value, such as {@link Integer#MAX_VALUE}).
     * 
     * @return the max no. errors to be collected, or 0 if {@link #isMaxErrorsSet()} returns 'false'
     */
    public int getMaxErrors() {
        return (isMaxErrorsSet()) ? maxErrors : 0;
    }

    /**
     * Sets the errors threshold (max no. errors to collect/report),
     * an integer value between 0 and {@link Integer#MAX_VALUE}, otherwise
     * it has no effect ({@link #isMaxErrorsSet()} will return 'false').
     * 
     * @param maxErrors
     */
    protected void setMaxErrors(int maxErrors) {
        this.maxErrors = maxErrors;
    }

    /**
     * @return true iif {@link #maxErrors} is greater than 0 and less than {@link Integer#MAX_VALUE}
     */
    @XmlTransient
    public boolean isMaxErrorsSet() {
        return this.maxErrors > 0 && this.maxErrors < Integer.MAX_VALUE;
    }

    /**
     * Gets normalizer settings.
     * @return
     */
    @XmlTransient
    public Properties getProperties() {
        return properties;
    }

    /**
     * Validation profile to used when checking/reporting issues.
     * 
     * @return the profile
     */
    public String getProfile() {
        return profile;
    }

    /**
     * Sets the validation profile to use when checking/reporting issues.
     * 
     * @param profile the profile name
     */
    protected void setProfile(String profile) {
        this.profile = profile;
    }

    /**
     * Gets a domain-specific identifier 
     * for a model element, stream, parser, etc.
     * objects related to this validation.
     * (for error reporting)
     * 
     * @param obj
     * @return
     */
    public String identify(Object obj) {
        return idCalc.identify(obj);
    }

    /**
     * This is a generic functional object (strategy)
     * interface that contain the only method, which
     * is called by the validator framework to get an
     * identifier or name of a model element to report 
     * an error about. 
     * 
     * Not BioPAX specific.
     * 
     * @author rodche
     *
     */
    public static interface Identifier {

        /**
         * Gets the id or name of a model element or 
         * other object that matters during a validation
         * and worth mentioning in a error message.
         * 
         * This is called by the Validator framework to get a
         * domain specific identifier or name of a model element 
         * to report an error about.
         * 
         * This interface is required, because a domain specific 'get id'
         * method, if any, is not known to the core validation framework
         * in advance, and toString method may not be satisfactory.
         * 
         * @param obj
         * @return
         */
        String identify(Object obj);

    }
}