001/* 
002 * Copyright 2018 Leibniz-Institut für Analytische Wissenschaften – ISAS – e.V..
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package uk.ac.ebi.pride.jmztab2.model;
017
018import de.isas.mztab2.io.serialization.Serializers;
019import de.isas.mztab2.model.IndexedElement;
020
021/**
022 * 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},
023 * {@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
024 * and optional column. Stable column has stable position and header name, while optional column not.
025 * {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory} used to create and maintain these column objects.
026 *
027 * @see MZTabColumnFactory
028 * @see SmallMoleculeColumn
029 * @see SmallMoleculeFeatureColumn
030 * @see SmallMoleculeEvidenceColumn
031 * @see OptionColumn
032 * @see ParameterOptionColumn
033 * @see AbundanceColumn
034 * @author qingwei
035 * @author nilshoffmann
036 * @since 23/05/13
037 * 
038 */
039public class MZTabColumn implements IMZTabColumn {
040    private final String name;
041    private String order;
042    private Integer id;
043    private String header;
044    private String logicPosition;
045    private Class dataType;
046    private boolean optional;
047
048    private IndexedElement element;
049
050    /**
051     * Create a column header object. Default, the column header keep the same value with name, and logical position keep
052     * the same value with order.
053     *
054     * @param name define a stable name for column. For optional column, only set stable part for name.
055     * @param dataType define the data type for column.
056     * @param optional if false the column is stable type, otherwise is optional column.
057     * @param order internal order. Every non {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} has stable order. Column order is used to maintain the
058     *              logical position in {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory}
059     */
060    public MZTabColumn(String name, Class dataType, boolean optional, String order) {
061        this(name, dataType, optional, order, null);
062    }
063
064    /**
065     * Create a column header object. Default, the column header keep the same value with name, and logical position keep
066     * the same value with order.
067     *
068     * @param name define a stable name for column. For optional column, only set stable part for name.
069     * @param dataType define the data type for column.
070     * @param optional if false the column is stable type, otherwise is optional column.
071     * @param order internal order. Every non {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} has stable order. Column order is used to maintain the
072     *              logical position in {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory}
073     * @param id incremental index used for some optional columns like best_search_engine_score[1], best_search_engine_score[2]
074     */
075    public MZTabColumn(String name, Class dataType, boolean optional, String order, Integer id) {
076        if (MZTabStringUtils.isEmpty(name)) {
077            throw new IllegalArgumentException("Column name should not empty.");
078        }
079        this.name = name;
080
081        if (dataType == null) {
082            throw new NullPointerException("Column data type should not set null!");
083        }
084        this.dataType = dataType;
085        this.optional = optional;
086        this.order = order;
087        this.id = id;
088
089        this.header = generateHeader(name);
090        this.logicPosition = generateLogicalPosition();
091    }
092
093    private String generateHeader(String name) {
094        StringBuilder sb = new StringBuilder();
095
096        sb.append(name);
097        if (id != null) {
098            sb.append("[").append(id).append("]");
099        }
100
101        return sb.toString();
102    }
103
104    private String generateLogicalPosition() {
105        StringBuilder sb = new StringBuilder();
106
107        sb.append(order);
108        if (id != null) {
109            // generate id string which length is 2. Eg. 12, return 12; 1, return 01
110            sb.append(String.format("%02d", id));
111        } else {
112            sb.append("00");
113        }
114
115        if (element != null) {
116            sb.append(String.format("%02d", element.getId()));
117        } else {
118            sb.append("00");
119        }
120
121        return sb.toString();
122    }
123
124
125    /**
126     * {@inheritDoc}
127     *
128     * Get the column name. For stable column, name and header are same. But for optional column, name is part
129     * of its header. For example, optional column which header is search_engine_score_ms_run[1-n], and its name
130     * is search_engine_score. Besides this, ms_run[1-n] is kind of {@link #element}
131     *
132     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
133     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
134     * @see #getHeader()
135     * @see #setElement(IndexedElement)
136     * @see #getHeader()
137     * @see #setElement(IndexedElement)
138     */
139    @Override
140    public String getName() {
141        return name;
142    }
143
144    /**
145     * {@inheritDoc}
146     *
147     * Get the column internal order. For stable column, order and logical position are same. But for optional column,
148     * the logical position need to be calculated by concatenating order and index element id. For example, optional column
149     * search_engine_score_ms_run[2] in Protein section, its order is 09, and the logical position is 092. Because the
150     * element ms_run[2] 's index is 2.
151     *
152     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
153     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
154     * @see #getLogicPosition()
155     */
156    @Override
157    public String getOrder() {
158        return order;
159    }
160
161    /*
162     * Allows to reassign the order in case the file doesn't follow the recommended order
163     */
164    /** {@inheritDoc} */
165    @Override
166    public void setOrder(String order) {
167        //As the order change, the logic position need to be regenerated.
168        this.order = order;
169        this.logicPosition = generateLogicalPosition();
170
171    }
172
173    /**
174     * {@inheritDoc}
175     *
176     * Get the column name. For stable column, name and header are same. While for optional column, name is part
177     * of its header. For example, optional column which header is search_engine_score_ms_run[1-n], and its name
178     * is search_engine_score.  Besides this, ms_run[1-n] is kind of {@link #element}
179     *
180     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
181     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
182     * @see #getName()
183     * @see #setElement(IndexedElement)
184     * @see #getName()
185     * @see #setElement(IndexedElement)
186     */
187    @Override
188    public String getHeader() {
189        return header;
190    }
191
192    /** {@inheritDoc} */
193    @Override
194    public void setHeader(String header) {
195        this.header = header;
196    }
197
198    /**
199     * {@inheritDoc}
200     *
201     * Get the column logical position. For stable column, order and logical position are same. But for optional column,
202     * the logical position need to calculate by concatenate order and index element id. For example, optional column
203     * search_engine_score_ms_run[2] in Protein section, its order is 09, and the logical position is 092. Because the
204     * element ms_run[2] 's index is 2.
205     *
206     *<p>Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
207     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.</p>
208     *
209     *<p>Notice: in {@link MZTabColumnFactory}, we use logical position to maintain the logical consistence within the {@link de.isas.mztab2.model.MzTab} file.
210     * During the process of parsing mzTab file, we create a mapping between physical position and internal logical position.</p>
211     * @see #getOrder()
212     */
213    @Override
214    public String getLogicPosition() {
215        generateLogicalPosition();
216        return logicPosition;
217    }
218
219    /** {@inheritDoc} */
220    @Override
221    public void setLogicPosition(String logicPosition) {
222        this.logicPosition = logicPosition;
223    }
224
225    /**
226     * {@inheritDoc}
227     *
228     * Get the column data type Class.
229     */
230    @Override
231    public Class getDataType() {
232        return dataType;
233    }
234
235    /**
236     * {@inheritDoc}
237     *
238     * Judge this column belong to stable column or optional column.
239     */
240    @Override
241    public boolean isOptional() {
242        return optional;
243    }
244
245    /**
246     * {@inheritDoc}
247     *
248     * Indexed element used in optional column header and logical position definition.
249     * In stable column, the return is null.
250     *
251     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
252     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
253     * @see #getHeader()
254     * @see #getLogicPosition()
255     * @see #getHeader()
256     * @see #getLogicPosition()
257     */
258    @Override
259    public IndexedElement getElement() {
260        return element;
261    }
262
263    /**
264     * {@inheritDoc}
265     *
266     * Indexed element used in optional column header and logical position definition.
267     * In stable column, the return is null.
268     *
269     * Notice: this design pattern not fit for {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
270     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
271     * @see #getHeader()
272     * @see #getLogicPosition()
273     * @see #getHeader()
274     * @see #getLogicPosition()
275     */
276    public void setElement(IndexedElement element) {
277        if (element == null) {
278            throw new NullPointerException("Can not set null indexed element for optional column!");
279        }
280        this.element = element;
281
282        this.logicPosition = generateLogicalPosition();
283        StringBuilder sb = new StringBuilder();
284        if(this instanceof AbundanceColumn) {
285            sb.append(this.header).append("[").
286            append(element.getId()).
287            append("]");
288        } else {
289            sb.append(this.header).append("_").append(Serializers.getReference(element, element.getId()));
290        }
291        this.header = sb.toString();
292    }
293
294    /**
295     * Create a optional column for {@link Section#Protein_Header}, {@link Section#Peptide_Header},
296     * {@link Section#PSM_Header}, {@link Section#Small_Molecule_Header}, {@link Section#Small_Molecule_Feature_Header},
297     * or {@link Section#Small_Molecule_Evidence_Header}.
298     *
299     * Notice: this function only used to create stable order optional column, such as  num_psms_ms_run[1-n] and so on.
300     * Not used to create {@link AbundanceColumn}, {@link OptionColumn} and {@link ParameterOptionColumn}.
301     * These optional columns need be generated by calling {@link MZTabColumnFactory} 's methods.
302     *
303     * @see MZTabColumnFactory#addOptionalColumn(MZTabColumn, MsRun)
304     */
305    static IMZTabColumn createOptionalColumn(Section section, IMZTabColumn column, Integer id, IndexedElement element) {
306        if (! column.isOptional()) {
307            throw new IllegalArgumentException(column + " is not optional column!");
308        }
309
310        IMZTabColumn optionColumn = null;
311        switch (section) {
312//            case Protein_Header:
313//                optionColumn = new ProteinColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
314//                break;
315//            case Peptide_Header:
316//                optionColumn = new PeptideColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
317//                break;
318//            case PSM_Header:
319//                optionColumn = new PSMColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
320//                break;
321            case Small_Molecule_Header:
322                optionColumn = new SmallMoleculeColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
323                break;
324            case Small_Molecule_Feature_Header:
325                optionColumn = new SmallMoleculeFeatureColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
326                break;
327            case Small_Molecule_Evidence_Header:
328                optionColumn = new SmallMoleculeEvidenceColumn(column.getName(), column.getDataType(), column.isOptional(), column.getOrder(), id);
329                break;
330        }
331
332        if (optionColumn != null && element != null) {
333            optionColumn.setElement(element);
334        }
335
336        return optionColumn;
337    }
338
339    /** {@inheritDoc} */
340    @Override
341    public String toString() {
342        return "MZTabColumn{" +
343                "header='" + header + '\'' +
344                ", logicPosition='" + logicPosition + '\'' +
345                ", dataType=" + dataType +
346                ", optional=" + optional +
347                '}';
348    }
349
350    /** {@inheritDoc} */
351    @Override
352    public boolean equals(Object o) {
353        if (this == o) return true;
354        if (o == null || getClass() != o.getClass()) return false;
355
356        MZTabColumn column = (MZTabColumn) o;
357
358        if (optional != column.optional) return false;
359        if (dataType != null ? !dataType.equals(column.dataType) : column.dataType != null) return false;
360        return (header != null ? header.equals(column.header) : column.header == null) && (logicPosition != null ? logicPosition.equals(column.logicPosition) : column.logicPosition == null);
361    }
362
363    /** {@inheritDoc} */
364    @Override
365    public int hashCode() {
366        int result = header != null ? header.hashCode() : 0;
367        result = 31 * result + (logicPosition != null ? logicPosition.hashCode() : 0);
368        result = 31 * result + (dataType != null ? dataType.hashCode() : 0);
369        result = 31 * result + (optional ? 1 : 0);
370        return result;
371    }
372}