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.model.Assay; 019import de.isas.mztab2.model.IndexedElement; 020import de.isas.mztab2.model.Parameter; 021import de.isas.mztab2.model.StudyVariable; 022import java.util.SortedMap; 023import java.util.TreeMap; 024 025/** 026 * This is a static factory class which used to generate a couple of MZTabColumn 027 * objects, and organizes them into "logicalPosition, MZTabColumn" pairs. 028 * Currently, mzTab table including three kinds of columns: 029 * <ol> 030 * <li> 031 * Stable column with stable order: header name, data type, logical position and 032 * order are stable in these columns. All of them are defined in 033 * {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeColumn}, {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeFeatureColumn}, 034 * and {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeEvidenceColumn}. 035 * </li> 036 * <li> 037 * Optional column with stable order: column name, data type and order are 038 * defined in the {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeColumn}, 039 * {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeFeatureColumn}, and 040 * {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeEvidenceColumn}. But header 041 * name, logical position dynamically depend on {@link IndexedElement}. 042 * </li> 043 * <li> 044 * Optional columns which are placed at the end of a table-based section. There 045 * are three types of optional column: 046 * {@link uk.ac.ebi.pride.jmztab2.model.AbundanceColumn}, {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} 047 * and {@link uk.ac.ebi.pride.jmztab2.model.ParameterOptionColumn}, which always 048 * are added at the end of the table. These optional columns have no stable 049 * column name, data type or order. In this factory, we use 050 * {@link #addOptionalColumn(String, Class)} to create 051 * {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn}; and 052 * {@link #addOptionalColumn(de.isas.mztab2.model.IndexedElement, java.lang.String, java.lang.Class)} 053 * or {@link #addOptionalColumn(IndexedElement, Parameter, Class)} to create 054 * {@link uk.ac.ebi.pride.jmztab2.model.ParameterOptionColumn}. 055 * </li> 056 * </ol> 057 * 058 * @author qingwei 059 * @author nilshoffmann 060 * @since 23/05/13 061 * 062 */ 063public class MZTabColumnFactory { 064 065 private final SortedMap<String, IMZTabColumn> stableColumnMapping = new TreeMap<>(); 066 private final SortedMap<String, IMZTabColumn> optionalColumnMapping = new TreeMap<>(); 067 private final SortedMap<String, IMZTabColumn> abundanceColumnMapping = new TreeMap<>(); 068 private final SortedMap<String, IMZTabColumn> columnMapping = new TreeMap<>(); 069 070 private Section section; 071 072 private MZTabColumnFactory() { 073 } 074 075 /** 076 * Retrieves the MZTabColumnFactory accordingly to the {@link #section} 077 * 078 * @param section SHOULD be 079 * {@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} 080 * or {@link uk.ac.ebi.pride.jmztab2.model.Section#Small_Molecule_Header}. 081 * @return a {@link uk.ac.ebi.pride.jmztab2.model.MZTabColumnFactory} 082 * object. 083 */ 084 public static MZTabColumnFactory getInstance(Section section) { 085 section = Section.toHeaderSection(section); 086 087 if (section == null) { 088 throw new IllegalArgumentException( 089 "Section should use Protein_Header, Peptide_Header, PSM_Header, Small_Molecule_Header, Small_Molecule_Feature_Header, or Small_Molecule_Evidence_Header."); 090 } 091 092 MZTabColumnFactory factory = new MZTabColumnFactory(); 093 factory.section = section; 094 095 return factory; 096 } 097 098 /** 099 * Get stable columns mapping. Key is logical position, and value is 100 * MZTabColumn object. Stable column with stable order: header name, data 101 * type, logical position and order are stable in these columns. All of them 102 * have been defined in null {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeColumn}, {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeFeatureColumn}, 103 * {@link uk.ac.ebi.pride.jmztab2.model.SmallMoleculeEvidenceColumn}. 104 * 105 * @return a {@link java.util.SortedMap} object. 106 */ 107 public SortedMap<String, IMZTabColumn> getStableColumnMapping() { 108 return stableColumnMapping; 109 } 110 111 /** 112 * Get all optional columns, including option column with stable order and 113 * name, abundance columns, optional columns and cv param optional columns. 114 * Key is logical position, and value is MZTabColumn object. 115 * 116 * @see AbundanceColumn 117 * @see OptionColumn 118 * @see ParameterOptionColumn 119 * @return a {@link java.util.SortedMap} object. 120 */ 121 public SortedMap<String, IMZTabColumn> getOptionalColumnMapping() { 122 return optionalColumnMapping; 123 } 124 125 /** 126 * Get all columns in the factory. In this class, we maintain the following 127 * constraint at any time: 128 * 129 * @return a {@link java.util.SortedMap} object. 130 */ 131 public SortedMap<String, IMZTabColumn> getColumnMapping() { 132 return columnMapping; 133 } 134 135 /** 136 * Extract the order from logical position. Normally, the order is coming 137 * from top two characters of logical position. For example, logical 138 * position is 092, then the order number is 9. 139 */ 140 private String getColumnOrder(String position) { 141 return position.substring(0, 2); 142 } 143 144 private void checkOptionalColumn(IMZTabColumn column) throws IllegalArgumentException { 145 if(optionalColumnMapping.containsKey(column.getLogicPosition())) { 146 throw new IllegalArgumentException("Key " + column.getLogicPosition() + " for column " + column.getName() + " is already assigned to: " + optionalColumnMapping.get(column.getLogicPosition()).getName()); 147 } 148 optionalColumnMapping.put(column.getLogicPosition(), column); 149 if(columnMapping.containsKey(column.getLogicPosition())) { 150 throw new IllegalArgumentException("Key " + column.getLogicPosition() + " for column " + column.getName() + " is already assigned to: " + columnMapping.get(column.getLogicPosition()).getName()); 151 } 152 columnMapping.put(column.getLogicPosition(), column); 153 } 154 155 private void checkAbundanceOptionalColumn(IMZTabColumn column) throws IllegalArgumentException { 156 if(abundanceColumnMapping.containsKey(column.getLogicPosition())) { 157 throw new IllegalArgumentException("Key " + column.getLogicPosition() + " for column " + column.getName() + " is already assigned to: " + abundanceColumnMapping.get(column.getLogicPosition()).getName()); 158 } 159 abundanceColumnMapping.put(column.getLogicPosition(), column); 160 } 161 162 private String addOptionColumn(IMZTabColumn column) { 163 164 checkOptionalColumn(column); 165 166 return column.getLogicPosition(); 167 } 168 169 private String addOptionColumn(IMZTabColumn column, String order) { 170 171 column.setOrder(order); 172 checkOptionalColumn(column); 173 174 return column.getLogicPosition(); 175 } 176 177 /** 178 * Add global {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} into 179 * {@link #optionalColumnMapping} and {@link #columnMapping}. The header 180 * like: opt_global_{name} 181 * 182 * @param name SHOULD NOT be empty. 183 * @param columnType SHOULD NOT be empty. 184 * @return the column's logic position. 185 */ 186 public String addOptionalColumn(String name, Class columnType) { 187 IMZTabColumn column = new OptionColumn(null, name, columnType, 188 Integer.parseInt(getColumnOrder(columnMapping.lastKey()))); 189 return addOptionColumn(column); 190 } 191 192 /** 193 * Add {@link uk.ac.ebi.pride.jmztab2.model.OptionColumn} followed by an 194 * indexed element (study variable, assay, ms run) into 195 * {@link #optionalColumnMapping} and {@link #columnMapping}. The header 196 * will look like: opt_study_variable[1]_{name} for a study variable 197 * 198 * @param <T> the type of the columnEntity. 199 * @param columnEntity SHOULD NOT be empty. 200 * @param name SHOULD NOT be empty. 201 * @param columnType SHOULD NOT be empty. 202 * @return the column's logic position. 203 */ 204 public <T extends IndexedElement> String addOptionalColumn(T columnEntity, 205 String name, Class columnType) { 206 IMZTabColumn column = new OptionColumn(columnEntity, name, columnType, 207 Integer.parseInt(getColumnOrder(columnMapping.lastKey()))); 208 return addOptionColumn(column); 209 } 210 211 /** 212 * Add global {@link uk.ac.ebi.pride.jmztab2.model.ParameterOptionColumn} 213 * into {@link #optionalColumnMapping} and {@link #columnMapping}. The 214 * header like: opt_global_cv_{accession}_{parameter name} 215 * 216 * @param param SHOULD NOT empty. 217 * @param columnType SHOULD NOT empty. 218 * @return the column's logic position. 219 */ 220 public String addOptionalColumn(Parameter param, Class columnType) { 221 IMZTabColumn column = new ParameterOptionColumn(null, param, columnType, 222 Integer.parseInt(getColumnOrder(columnMapping.lastKey()))); 223 return addOptionColumn(column); 224 } 225 226 /** 227 * Add {@link uk.ac.ebi.pride.jmztab2.model.ParameterOptionColumn} followed 228 * by an indexed element (study variable, assay, ms run) into 229 * {@link #optionalColumnMapping} and {@link #columnMapping}. The header 230 * will look like: opt_assay[1]_cv_{accession}_{parameter name} for an 231 * assay. 232 * 233 * @param <T> the type of the columnEntity. 234 * @param columnEntity SHOULD NOT empty. 235 * @param param SHOULD NOT empty. 236 * @param columnType SHOULD NOT empty. 237 * @return the column's logic position. 238 */ 239 public <T extends IndexedElement> String addOptionalColumn(T columnEntity, 240 Parameter param, Class columnType) { 241 IMZTabColumn column = new ParameterOptionColumn(columnEntity, param, 242 columnType, Integer.parseInt(getColumnOrder(columnMapping.lastKey()))); 243 return addOptionColumn(column); 244 } 245 246 /** 247 * <p> 248 * addAbundanceOptionalColumn.</p> 249 * 250 * @param assay a {@link de.isas.mztab2.model.Assay} object. 251 * @param order the order string for this column. 252 * @return the column's logic position. 253 */ 254 public String addAbundanceOptionalColumn(Assay assay, String order) { 255 IMZTabColumn column = AbundanceColumn.createOptionalColumn(section, 256 assay, Integer.parseInt(order)); 257 checkAbundanceOptionalColumn(column); 258 return addOptionColumn(column, order); 259 } 260 261 /** 262 * Add an {@link uk.ac.ebi.pride.jmztab2.model.AbundanceColumn} into 263 * {@link uk.ac.ebi.pride.jmztab2.model.AbundanceColumn}, {@link #optionalColumnMapping} 264 * and {@link #columnMapping}. The header can be one of 265 * abundance_study_variable[1], abundance_coeffvar_study_variable[1]. 266 * 267 * @see 268 * AbundanceColumn#createOptionalColumns(uk.ac.ebi.pride.jmztab2.model.Section, 269 * de.isas.mztab2.model.StudyVariable, java.lang.String, java.lang.String) 270 * @param studyVariable SHOULD NOT empty. 271 * @param columnHeader the column header without the 'abundance_' prefix. 272 * @param order the order string for this column. 273 * @return the column's logic position. 274 */ 275 public String addAbundanceOptionalColumn(StudyVariable studyVariable, 276 String columnHeader, String order) { 277 SortedMap<String, MZTabColumn> columns = AbundanceColumn. 278 createOptionalColumns(section, studyVariable, columnHeader, order); 279 for(IMZTabColumn col:columns.values()) { 280 checkAbundanceOptionalColumn(col); 281 checkOptionalColumn(col); 282 } 283 return columns.lastKey(); 284 } 285 286 /** 287 * <p> 288 * addIdConfidenceMeasureColumn.</p> 289 * 290 * @param parameter a {@link de.isas.mztab2.model.Parameter} object. 291 * @param index a {@link java.lang.Integer} object. 292 * @param columnType the class of values in this column. 293 * @return the column's logic position. 294 */ 295 public String addIdConfidenceMeasureColumn(Parameter parameter, 296 Integer index, Class columnType) { 297 if (section != Section.Small_Molecule_Evidence_Header && section != Section.Small_Molecule_Evidence) { 298 throw new IllegalArgumentException( 299 "Section should be SmallMoleculeEvidence, but is " + section. 300 getName()); 301 } 302 if (parameter == null) { 303 throw new NullPointerException("Parameter should not be null!"); 304 } 305 306 SortedMap<String, MZTabColumn> columns = new TreeMap<>(); 307 308 MZTabColumn column = new MZTabColumn("id_confidence_measure", columnType, 309 false, Integer.parseInt(getColumnOrder(columnMapping.lastKey())) + "", 310 index); 311 312 columns.put(column.getLogicPosition(), column); 313 for(IMZTabColumn col : columns.values()) { 314 checkOptionalColumn(col); 315 } 316 return columns.lastKey(); 317 } 318 319 /** 320 * The offset record the position of MZTabColumn in header line. For 321 * example, protein header line, the relationships between Logical Position, 322 * MZTabColumn, offset and order are like following structure: Logical 323 * Position MZTabColumn offset order "01" accession 1 01 "02" description 2 324 * 02 ...... "08" best_search_engine_score 8 08 "091" 325 * search_engine_score_ms_run[1] 9 09 "092" search_engine_score_ms_run[2] 10 326 * 09 "10" reliability 11 10 "111" num_psms_ms_run[1] 12 11 "112" 327 * num_psms_ms_run[2] 13 11 328 * 329 * @return a {@link java.util.SortedMap} object with the offsets for each 330 * column. 331 */ 332 public SortedMap<Integer, IMZTabColumn> getOffsetColumnsMap() { 333 SortedMap<Integer, IMZTabColumn> map = new TreeMap<>(); 334 335 int offset = 1; 336 for (IMZTabColumn column : columnMapping.values()) { 337 map.put(offset++, column); 338 } 339 340 return map; 341 } 342 343 /** 344 * Query the MZTabColumn in factory, based on column header with 345 * case-insensitive. Notice: for optional columns, header name maybe 346 * flexible. For example, num_psms_ms_run[1]. At this time, user SHOULD BE 347 * provide the full header name to query MZTabColumn. If just provide 348 * num_psms_ms_run, return null. 349 * 350 * @param header the column header to use as the search key. 351 * @return a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn} object or 352 * null. 353 */ 354 public IMZTabColumn findColumnByHeader(String header) { 355 header = header.trim(); 356 357 for (IMZTabColumn column : columnMapping.values()) { 358 if (header.equalsIgnoreCase(column.getHeader())) { 359 return column; 360 } 361 } 362 363 return null; 364 } 365}