MZTabColumn.java

/* 
 * Copyright 2018 Leibniz-Institut für Analytische Wissenschaften – ISAS – e.V..
 *
 * 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.
 */
package uk.ac.ebi.pride.jmztab2.model;

import de.isas.mztab2.io.serialization.Serializers;
import de.isas.mztab2.model.IndexedElement;

/**
 * Define a column header which used in {@link uk.ac.ebi.pride.jmztab2.model.Section#Protein_Header}, {@link uk.ac.ebi.pride.jmztab2.model.Section#Peptide_Header},
 * {@link uk.ac.ebi.pride.jmztab2.model.Section#PSM_Header}, or {@link uk.ac.ebi.pride.jmztab2.model.Section#Small_Molecule_Header}. There are two kinds of columns: stable column
 * and optional column. Stable column has stable position and header name, while optional column not.
 * {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory} used to create and maintain these column objects.
 *
 * @see MZTabColumnFactory
 * @see SmallMoleculeColumn
 * @see SmallMoleculeFeatureColumn
 * @see SmallMoleculeEvidenceColumn
 * @see OptionColumn
 * @see ParameterOptionColumn
 * @see AbundanceColumn
 * @author qingwei
 * @author nilshoffmann
 * @since 23/05/13
 * 
 */
public class MZTabColumn implements IMZTabColumn {
    private final String name;
    private String order;
    private Integer id;
    private String header;
    private String logicPosition;
    private Class dataType;
    private boolean optional;

    private Object element;

    /**
     * Create a column header object. Default, the column header keep the same value with name, and logical position keep
     * the same value with order.
     *
     * @param name define a stable name for column. For optional column, only set stable part for name.
     * @param dataType define the data type for column.
     * @param optional if false the column is stable type, otherwise is optional column.
     * @param order internal order. Every non {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} has stable order. Column order is used to maintain the
     *              logical position in {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory}
     */
    public MZTabColumn(String name, Class dataType, boolean optional, String order) {
        this(name, dataType, optional, order, null);
    }

    /**
     * Create a column header object. Default, the column header keep the same value with name, and logical position keep
     * the same value with order.
     *
     * @param name define a stable name for column. For optional column, only set stable part for name.
     * @param dataType define the data type for column.
     * @param optional if false the column is stable type, otherwise is optional column.
     * @param order internal order. Every non {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} has stable order. Column order is used to maintain the
     *              logical position in {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory}
     * @param id incremental index used for some optional columns like best_search_engine_score[1], best_search_engine_score[2]
     */
    public MZTabColumn(String name, Class dataType, boolean optional, String order, Integer id) {
        if (MZTabStringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Column name should not empty.");
        }
        this.name = name;

        if (dataType == null) {
            throw new NullPointerException("Column data type should not set null!");
        }
        this.dataType = dataType;
        this.optional = optional;
        this.order = order;
        this.id = id;

        this.header = generateHeader(name);
        this.logicPosition = generateLogicalPosition();
    }

    private String generateHeader(String name) {
        StringBuilder sb = new StringBuilder();

        sb.append(name);
        if (id != null) {
            sb.append("[").append(id).append("]");
        }

        return sb.toString();
    }

    private String generateLogicalPosition() {
        StringBuilder sb = new StringBuilder();

        sb.append(order);
        if (id != null) {
            // generate id string which length is 2. Eg. 12, return 12; 1, return 01
            sb.append(String.format("%02d", id));
        } else {
            sb.append("00");
        }

        if (element != null) {
            sb.append(String.format("%02d", IndexedElement.of(element).getId()));
        } else {
            sb.append("00");
        }

        return sb.toString();
    }


    /**
     * {@inheritDoc}
     *
     * Get the column name. For stable column, name and header are same. But for optional column, name is part
     * of its header. For example, optional column which header is search_engine_score_ms_run[1-n], and its name
     * is search_engine_score. Besides this, ms_run[1-n] is kind of {@link #element}
     *
     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     * @see #getHeader()
     * @see #setElement(IndexedElement)
     * @see #getHeader()
     * @see #setElement(IndexedElement)
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     *
     * Get the column internal order. For stable column, order and logical position are same. But for optional column,
     * the logical position need to be calculated by concatenating order and index element id. For example, optional column
     * search_engine_score_ms_run[2] in Protein section, its order is 09, and the logical position is 092. Because the
     * element ms_run[2] 's index is 2.
     *
     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     * @see #getLogicPosition()
     */
    @Override
    public String getOrder() {
        return order;
    }

    /*
     * Allows to reassign the order in case the file doesn't follow the recommended order
     */
    /** {@inheritDoc} */
    @Override
    public void setOrder(String order) {
        //As the order change, the logic position need to be regenerated.
        this.order = order;
        this.logicPosition = generateLogicalPosition();

    }

    /**
     * {@inheritDoc}
     *
     * Get the column name. For stable column, name and header are same. While for optional column, name is part
     * of its header. For example, optional column which header is search_engine_score_ms_run[1-n], and its name
     * is search_engine_score.  Besides this, ms_run[1-n] is kind of {@link #element}
     *
     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     * @see #getName()
     * @see #setElement(IndexedElement)
     * @see #getName()
     * @see #setElement(IndexedElement)
     */
    @Override
    public String getHeader() {
        return header;
    }

    /** {@inheritDoc} */
    @Override
    public void setHeader(String header) {
        this.header = header;
    }

    /**
     * {@inheritDoc}
     *
     * Get the column logical position. For stable column, order and logical position are same. But for optional column,
     * the logical position need to calculate by concatenate order and index element id. For example, optional column
     * search_engine_score_ms_run[2] in Protein section, its order is 09, and the logical position is 092. Because the
     * element ms_run[2] 's index is 2.
     *
     *<p>Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.</p>
     *
     *<p>Notice: in {@link MZTabColumnFactory}, we use logical position to maintain the logical consistence within the {@link de.isas.mztab2.model.MzTab} file.
     * During the process of parsing mzTab file, we create a mapping between physical position and internal logical position.</p>
     * @see #getOrder()
     */
    @Override
    public String getLogicPosition() {
        generateLogicalPosition();
        return logicPosition;
    }

    /** {@inheritDoc} */
    @Override
    public void setLogicPosition(String logicPosition) {
        this.logicPosition = logicPosition;
    }

    /**
     * {@inheritDoc}
     *
     * Get the column data type Class.
     */
    @Override
    public Class getDataType() {
        return dataType;
    }

    /**
     * {@inheritDoc}
     *
     * Judge this column belong to stable column or optional column.
     */
    @Override
    public boolean isOptional() {
        return optional;
    }

    /**
     * {@inheritDoc}
     *
     * Indexed element used in optional column header and logical position definition.
     * In stable column, the return is null.
     *
     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     * @see #getHeader()
     * @see #getLogicPosition()
     * @see #getHeader()
     * @see #getLogicPosition()
     */
    @Override
    public Object getElement() {
        return element;
    }

    /**
     * {@inheritDoc}
     *
     * Indexed element used in optional column header and logical position definition.
     * In stable column, the return is null.
     *
     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     * @see #getHeader()
     * @see #getLogicPosition()
     * @see #getHeader()
     * @see #getLogicPosition()
     */
    public void setElement(Object element) {
        if (element == null) {
            throw new NullPointerException("Can not set null indexed element for optional column!");
        }
        this.element = element;

        this.logicPosition = generateLogicalPosition();
        StringBuilder sb = new StringBuilder();
        if(this instanceof AbundanceColumn) {
            sb.append(this.header).append("[").
            append(IndexedElement.of(element).getId()).
            append("]");
        } else {
            sb.append(this.header).append("_").append(Serializers.getReference(element, IndexedElement.of(element).getId()));
        }
        this.header = sb.toString();
    }

    /**
     * Create a optional column for {@link Section#Protein_Header}, {@link Section#Peptide_Header},
     * {@link Section#PSM_Header}, {@link Section#Small_Molecule_Header}, {@link Section#Small_Molecule_Feature_Header},
     * or {@link Section#Small_Molecule_Evidence_Header}.
     *
     * Notice: this function only used to create stable order optional column, such as  num_psms_ms_run[1-n] and so on.
     * Not used to create {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
     *
     * @see MZTabColumnFactory#addOptionalColumn(MZTabColumn, MsRun)
     */
    static IMZTabColumn createOptionalColumn(Section section, IMZTabColumn column, Integer id, IndexedElement element) {
        if (! column.isOptional()) {
            throw new IllegalArgumentException(column + " is not optional column!");
        }

        IMZTabColumn optionColumn = null;
        switch (section) {
//            case Protein_Header:
//                optionColumn = new ProteinColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
//                break;
//            case Peptide_Header:
//                optionColumn = new PeptideColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
//                break;
//            case PSM_Header:
//                optionColumn = new PSMColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
//                break;
            case Small_Molecule_Header:
                optionColumn = new SmallMoleculeColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
                break;
            case Small_Molecule_Feature_Header:
                optionColumn = new SmallMoleculeFeatureColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
                break;
            case Small_Molecule_Evidence_Header:
                optionColumn = new SmallMoleculeEvidenceColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
                break;
        }

        if (optionColumn != null && element != null) {
            optionColumn.setElement(element);
        }

        return optionColumn;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return "MZTabColumn{" +
                "header='" + header + '\'' +
                ", logicPosition='" + logicPosition + '\'' +
                ", dataType=" + dataType +
                ", optional=" + optional +
                '}';
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MZTabColumn column = (MZTabColumn) o;

        if (optional != column.optional) return false;
        if (dataType != null ? !dataType.equals(column.dataType) : column.dataType != null) return false;
        return (header != null ? header.equals(column.header) : column.header == null) && (logicPosition != null ? logicPosition.equals(column.logicPosition) : column.logicPosition == null);
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        int result = header != null ? header.hashCode() : 0;
        result = 31 * result + (logicPosition != null ? logicPosition.hashCode() : 0);
        result = 31 * result + (dataType != null ? dataType.hashCode() : 0);
        result = 31 * result + (optional ? 1 : 0);
        return result;
    }
}