001package uk.ac.ebi.pride.jmztab2.utils.errors;
002
003import de.isas.mztab2.model.ValidationMessage;
004import java.io.Serializable;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.regex.Matcher;
008import java.util.regex.Pattern;
009import static uk.ac.ebi.pride.jmztab2.model.MZTabConstants.NEW_LINE;
010
011/**
012 * mzTab files can be validated to ensure that they comply with the latest
013 * version of the format specification. The process includes two steps: first of
014 * all the basic model architecture is created, including the metadata section
015 * and the generation of the table column headers. The second step is the
016 * validation of the column rows, which take most of the processing time. The
017 * class MZTabFileParser is used to parse and validate the mzTab files. If the
018 * validation is successful, an MZTabFile model will be then generated. A series
019 * of messages are then reported, which can help to diagnose different types of
020 * format-related (reporting format problems) and/or logical (reporting errors
021 * related to the logical relationships among the different sections in a file)
022 * errors. At the moment of writing, there are about sixty types of error
023 * messages (http://mztab.googlecode.com/wiki/jmzTab_message). The validation
024 * messages have a unique identifier and are classified in three levels: Info,
025 * Warn and Error, according to the requirements included in the specification
026 * document.
027 *
028 * @author qingwei
029 * @since 06/02/13
030 * 
031 */
032public class MZTabError implements Serializable {
033
034    private int lineNumber;
035    private MZTabErrorType type;
036    private String message;
037
038    /**
039     * System will fill a couple of values one by one, and generate a concrete
040     * error message during parse {@link #lineNumber} line in mzTab file.
041     *
042     * @param type SHOULD NOT null.
043     * @param lineNumber SHOULD be positive integer. Except "-1", which means
044     * the line number unknown.
045     * @param values May be null, if no variable in error's original pattern.
046     */
047    public MZTabError(MZTabErrorType type, int lineNumber, String... values) {
048        if (type == null) {
049            throw new NullPointerException("MZTabErrorType should not set null");
050        }
051        this.type = type;
052
053        this.lineNumber = lineNumber;
054
055        List<String> valueList = new ArrayList<String>();
056        for (String value : values) {
057            valueList.add(value == null ? "" : value);
058        }
059
060        this.message = fill(0, valueList, type.getOriginal());
061    }
062
063    /**
064     * fill "{id}" parameter list one by one.
065     */
066    private String fill(int count, List<String> values, String message) {
067        String regexp = "\\{\\w\\}";
068        Pattern pattern = Pattern.compile(regexp);
069        Matcher matcher = pattern.matcher(message);
070
071        String value;
072        if (matcher.find()) {
073            if (count >= values.size()) {
074                throw new ArrayIndexOutOfBoundsException(
075                    "Tried to replace placeholder " + (count + 1) + " but only " + values.
076                        size() + " values are available for " + getClass().
077                        getSimpleName() + " " + type.toString());
078            }
079            value = values.get(count);
080            message = matcher.replaceFirst(Matcher.quoteReplacement(value));
081            return fill(count + 1, values, message);
082        } else {
083            return message;
084        }
085    }
086
087    /**
088     * <p>Getter for the field <code>type</code>.</p>
089     *
090     * @return {@link uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorType}
091     * @see FormatErrorType
092     * @see LogicalErrorType
093     */
094    public MZTabErrorType getType() {
095        return type;
096    }
097
098    /**
099     * <p>Getter for the field <code>message</code>.</p>
100     *
101     * @return a concrete error/warn message.
102     */
103    public String getMessage() {
104        return message;
105    }
106
107    /**
108     * <p>Getter for the field <code>lineNumber</code>.</p>
109     *
110     * @return the line number.
111     */
112    public int getLineNumber() {
113        return lineNumber;
114    }
115
116    /**
117     * {@inheritDoc}
118     *
119     * Code: Unique number for error/warn Category: Currently, there are three
120     * types of messages: Format, Logical Original: Message expression pattern.
121     * "{?}" is a couple of parameters which can be filled during validate
122     * processing. Cause: A readable text to describe the reason why raise this
123     * error/warn. Currently, these cause message coming from mztab
124     * specification mainly.
125     */
126    @Override
127    public String toString() {
128        StringBuilder sb = new StringBuilder();
129
130        sb.append("[").
131            append(type.getLevel()).
132            append("-").
133            append(type.getCode()).
134            append("] ");
135        sb.append("line ").
136            append(lineNumber).
137            append(": ");
138        sb.append(message).
139            append(NEW_LINE);
140
141        return sb.toString();
142    }
143    
144    public ValidationMessage toValidationMessage() throws IllegalStateException {
145        ValidationMessage.MessageTypeEnum level = ValidationMessage.MessageTypeEnum.INFO;
146        switch (getType().
147            getLevel()) {
148            case Error:
149                level = ValidationMessage.MessageTypeEnum.ERROR;
150                break;
151            case Info:
152                level = ValidationMessage.MessageTypeEnum.INFO;
153                break;
154            case Warn:
155                level = ValidationMessage.MessageTypeEnum.WARN;
156                break;
157            default:
158                throw new IllegalStateException("State " + 
159                    getType().
160                    getLevel() + " is not handled in switch/case statement!");
161        }
162        ValidationMessage.CategoryEnum category = ValidationMessage.CategoryEnum.FORMAT;
163        switch(getType().getCategory()) {
164            case Format:
165                category = ValidationMessage.CategoryEnum.FORMAT;
166                break;
167            case Logical:
168                category = ValidationMessage.CategoryEnum.LOGICAL;
169                break;
170            case CrossCheck:
171                category = ValidationMessage.CategoryEnum.CROSS_CHECK;
172                break;
173            default:
174                throw new IllegalStateException("Category " + 
175                    getType().
176                    getCategory()+ " is not handled in switch/case statement!");
177        }
178        ValidationMessage vr = new ValidationMessage().lineNumber(
179            Long.valueOf(getLineNumber())).
180            category(category).
181            messageType(level).
182            message(getMessage()).
183            code(toString());
184        return vr;
185    }
186}