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 de.isas.mztab2.io.serialization;
017
018import com.fasterxml.jackson.annotation.JsonProperty;
019import com.fasterxml.jackson.core.JsonGenerator;
020import com.fasterxml.jackson.databind.ObjectMapper;
021import com.fasterxml.jackson.databind.SerializerProvider;
022import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
023import de.isas.mztab2.model.Assay;
024import de.isas.mztab2.model.IndexedElement;
025import de.isas.mztab2.model.OptColumnMapping;
026import de.isas.mztab2.model.Parameter;
027import de.isas.mztab2.model.StudyVariable;
028import de.isas.mztab2.model.Uri;
029import java.io.IOException;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.List;
033import java.util.Map;
034import java.util.Optional;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
037import java.util.stream.Collectors;
038import java.util.stream.IntStream;
039import javax.validation.ValidationException;
040import lombok.extern.slf4j.Slf4j;
041import uk.ac.ebi.pride.jmztab2.model.IMZTabColumn;
042import uk.ac.ebi.pride.jmztab2.model.MZTabConstants;
043import static uk.ac.ebi.pride.jmztab2.model.MZTabConstants.NULL;
044import uk.ac.ebi.pride.jmztab2.model.MetadataElement;
045import uk.ac.ebi.pride.jmztab2.model.MetadataProperty;
046
047/**
048 * <p>
049 * Utility class providing helper methods for other serializers.</p>
050 *
051 * @author nilshoffmann
052 * @since 11/30/17
053 *
054 */
055@Slf4j
056public class Serializers {
057
058    /**
059     * <p>
060     * getReference.</p>
061     *
062     * @param element a {@link java.lang.Object} object.
063     * @param idx a {@link java.lang.Integer} object.
064     * @return a {@link java.lang.String} object.
065     */
066    public static String getReference(Object element, Integer idx) {
067        StringBuilder sb = new StringBuilder();
068        sb.append(getElementName(element).
069                orElseThrow(()
070                        -> {
071                    return new IllegalArgumentException(
072                            "No mzTab element name mapping available for " + element.
073                                    getClass().
074                                    getName());
075                }));
076
077        return sb.toString();
078    }
079
080    /**
081     * <p>
082     * printAbundanceAssay.</p>
083     *
084     * @param a a {@link de.isas.mztab2.model.Assay} object.
085     * @return a {@link java.lang.String} object.
086     */
087    public static String printAbundanceAssay(Assay a) {
088        StringBuilder sb = new StringBuilder();
089        return sb.append("abundance_assay[").
090                append(a.getId()).
091                append("]").
092                toString();
093    }
094
095    /**
096     * <p>
097     * printAbundanceStudyVar.</p>
098     *
099     * @param sv a {@link de.isas.mztab2.model.StudyVariable} object.
100     * @return a {@link java.lang.String} object.
101     */
102    public static String printAbundanceStudyVar(StudyVariable sv) {
103        StringBuilder sb = new StringBuilder();
104        return sb.append("abundance_study_variable[").
105                append(sv.getId()).
106                append("]").
107                toString();
108    }
109
110    /**
111     * <p>
112     * printAbundanceCoeffVarStudyVar.</p>
113     *
114     * @param sv a {@link de.isas.mztab2.model.StudyVariable} object.
115     * @return a {@link java.lang.String} object.
116     */
117    public static String printAbundanceCoeffVarStudyVar(StudyVariable sv) {
118        StringBuilder sb = new StringBuilder();
119        return sb.append("abundance_coeffvar_study_variable[").
120                append(sv.getId()).
121                append("]").
122                toString();
123    }
124
125    /**
126     * <p>
127     * printOptColumnMapping.</p>
128     *
129     * @param ocm a {@link de.isas.mztab2.model.OptColumnMapping} object.
130     * @return a {@link java.lang.String} object.
131     */
132    public static String printOptColumnMapping(OptColumnMapping ocm) {
133        StringBuilder sb = new StringBuilder();
134        log.debug("Identifier={}; OptColumnMapping: {}", ocm.getIdentifier(), ocm);
135        if ("global".equals(ocm.getIdentifier())||ocm.getIdentifier().startsWith("global")) {
136            sb.append("opt_");
137            sb.append(ocm.getIdentifier());
138            if (ocm.getParam() != null) {
139                sb.append("_cv_").
140                        append(ocm.getParam().
141                                getCvAccession()).
142                        append("_").
143                        append(ocm.getParam().
144                                getName().
145                                replaceAll(" ", "_"));
146            }
147        } else {
148            log.debug("OptColumnMapping: {}", ocm);
149            // object reference case, value is now the actual value
150            sb.append(ocm.getIdentifier());
151        }
152        log.debug("asString: {}", sb.toString());
153//        //TODO: check for valid characters in definition
154//        //valid characters: ‘A’-‘Z’, ‘a’-‘z’, ‘0’-‘9’, ‘’, ‘-’, ‘[’, ‘]’, and ‘:’.
155//        //[A-Za-z0-9\[\]-:]+
156        return sb.toString();
157    }
158
159    /**
160     * <p>
161     * addIndexedLine for elements like assay[1] that have an id and one
162     * additional property element</p>
163     *
164     * @param <T> the type of {@link IndexedElement}.
165     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
166     * @param sp a {@link com.fasterxml.jackson.databind.SerializerProvider}
167     * object.
168     * @param prefix a {@link java.lang.String} object.
169     * @param element a {@link java.lang.Object} object.
170     * @param indexedElement a {@link de.isas.mztab2.model.Parameter} object.
171     */
172    public static <T extends IndexedElement> void addIndexedLine(
173            JsonGenerator jg, SerializerProvider sp, String prefix,
174            Object element, T indexedElement) {
175        addIndexedLine(jg, sp, prefix, element, Arrays.asList(indexedElement));
176    }
177
178    /**
179     * <p>
180     * addIndexedLine for elements like assay[1] that have an id and a list of
181     * additional property elements</p>
182     *
183     * @param <T> the type of {@link IndexedElement}.
184     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
185     * @param sp a {@link com.fasterxml.jackson.databind.SerializerProvider}
186     * object.
187     * @param prefix a {@link java.lang.String} object.
188     * @param element a {@link java.lang.Object} object.
189     * @param indexedElementList a {@link java.util.List} object.
190     */
191    public static <T extends IndexedElement> void addIndexedLine(
192            JsonGenerator jg, SerializerProvider sp, String prefix,
193            Object element,
194            List<T> indexedElementList) {
195        Optional<List<T>> iel = Optional.ofNullable(indexedElementList);
196        if (!iel.isPresent() || indexedElementList.isEmpty()) {
197
198            log.debug(
199                    "Skipping null or empty indexed element list values for {}",
200                    getElementName(
201                            element));
202            return;
203        }
204        try {
205            jg.writeStartArray();
206            //prefix
207            jg.writeString(prefix);
208            //key
209            jg.writeString(new StringBuilder().append(getElementName(element).
210                    orElseThrow(()
211                            -> {
212                        return new ElementNameMappingException("unknown", element);
213                    })).
214                    toString());
215            //value
216            jg.writeString(indexedElementList.stream().
217                    map((indexedElement)
218                            -> {
219                        if (indexedElement instanceof Parameter) {
220                            return new ParameterConverter().convert(
221                                    (Parameter) indexedElement);
222                        } else if (indexedElement instanceof Uri) {
223                            return new UriConverter().convert((Uri) indexedElement);
224                        } else {
225                            throw new IllegalArgumentException(
226                                    "Serialization of type " + indexedElement.getClass() + " currently not supported!");
227                        }
228                    }).
229                    collect(Collectors.joining("|")));
230            jg.writeEndArray();
231        } catch (IOException ex) {
232
233            log.error("Caught IO Exception while trying to write indexed line:",
234                    ex);
235        }
236    }
237
238    /**
239     * <p>
240     * addLineWithParameters.</p>
241     *
242     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
243     * @param prefix a {@link java.lang.String} object.
244     * @param element a {@link java.lang.Object} object.
245     * @param parameterList a {@link java.util.List} object.
246     */
247    public static void addLineWithParameters(JsonGenerator jg, String prefix,
248            Object element,
249            List<Parameter> parameterList) {
250        if (parameterList == null || parameterList.isEmpty()) {
251
252            log.debug(
253                    "Skipping null or empty parameter list values for " + getElementName(
254                            element));
255            return;
256        }
257        try {
258            jg.writeStartArray();
259            //prefix
260            jg.writeString(prefix);
261            //key
262            jg.writeString(new StringBuilder().append(getElementName(element).
263                    orElseThrow(()
264                            -> {
265                        return new ElementNameMappingException("unknown", element);
266                    })).
267                    toString());
268            //value
269            jg.writeString(parameterList.stream().
270                    map((parameter)
271                            -> {
272                        return new ParameterConverter().convert(parameter);
273                    }).
274                    collect(Collectors.joining("|")));
275            jg.writeEndArray();
276        } catch (IOException ex) {
277            log.error(
278                    "Caught IO Exception while trying to write line with parameters:",
279                    ex);
280        }
281    }
282
283    /**
284     * <p>
285     * addLineWithPropertyParameters.</p>
286     *
287     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
288     * @param prefix a {@link java.lang.String} object.
289     * @param propertyName a {@link java.lang.String} object.
290     * @param element a {@link java.lang.Object} object.
291     * @param value a {@link java.util.List} object.
292     */
293    public static void addLineWithPropertyParameters(JsonGenerator jg,
294            String prefix,
295            String propertyName, Object element,
296            List<Parameter> value) {
297        if (value == null || value.isEmpty()) {
298
299            log.debug("Skipping null or empty values for {}",
300                    getElementName(
301                            element));
302            return;
303        }
304        addLineWithProperty(jg, prefix, propertyName, element, value.stream().
305                map((parameter)
306                        -> {
307                    return new ParameterConverter().convert(parameter);
308                }).
309                collect(Collectors.joining("|")));
310    }
311
312    /**
313     * <p>
314     * addLineWithMetadataProperty.</p>
315     *
316     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
317     * @param prefix a {@link java.lang.String} object.
318     * @param property a {@link uk.ac.ebi.pride.jmztab2.model.MetadataProperty}
319     * object.
320     * @param element a {@link java.lang.Object} object.
321     * @param value a {@link java.lang.Object} object.
322     */
323    public static void addLineWithMetadataProperty(JsonGenerator jg,
324            String prefix, MetadataProperty property, Object element,
325            Object... value) {
326        addLineWithProperty(jg, prefix, property.getName(), element, value);
327    }
328
329    /**
330     * <p>
331     * addLineWithNullProperty.</p>
332     *
333     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
334     * @param prefix a {@link java.lang.String} object.
335     * @param propertyName a {@link java.lang.String} object.
336     * @param element a {@link java.lang.Object} object.
337     */
338    public static void addLineWithNullProperty(JsonGenerator jg, String prefix,
339            String propertyName, Object element) {
340        try {
341            jg.writeStartArray();
342            //prefix
343            jg.writeString(prefix);
344            //key
345            String key = getElementName(element).
346                    orElseThrow(()
347                            -> {
348                        return new ElementNameMappingException(propertyName, element);
349                    });
350            if (propertyName == null) {
351                jg.writeString(key);
352            } else {
353                jg.writeString(key + "-" + propertyName);
354            }
355            //value
356            jg.writeString(NULL);
357            jg.writeEndArray();
358        } catch (IOException ex) {
359
360            log.error(
361                    "Caught exception while trying to write line with null property:",
362                    ex);
363        }
364    }
365
366    /**
367     * <p>
368     * addLineWithProperty.</p>
369     *
370     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
371     * @param prefix a {@link java.lang.String} object.
372     * @param propertyName a {@link java.lang.String} object.
373     * @param element a {@link java.lang.Object} object.
374     * @param value a {@link java.lang.Object} object.
375     */
376    public static void addLineWithProperty(JsonGenerator jg, String prefix,
377            String propertyName, Object element,
378            Object... value) {
379        if (value == null || value.length == 0) {
380
381            log.debug("Skipping null or empty values for {}",
382                    getElementName(
383                            element));
384            return;
385        }
386        if (value.length == 1 && (value[0] == null)) {
387
388            log.debug("Skipping empty value for {}", getElementName(
389                    element));
390            return;
391        }
392        try {
393            jg.writeStartArray();
394            //prefix
395            jg.writeString(prefix);
396            //key
397            String key = getElementName(element).
398                    orElseThrow(()
399                            -> {
400                        return new ElementNameMappingException(propertyName, element);
401                    });
402            if (propertyName == null) {
403                jg.writeString(key);
404            } else {
405                jg.writeString(key + "-" + propertyName);
406            }
407            //value
408            for (Object o : value) {
409                jg.writeObject(o);
410            }
411            jg.writeEndArray();
412        } catch (IOException ex) {
413
414            log.error(
415                    "Caught IO exception while trying to write line with property:",
416                    ex);
417        }
418    }
419
420    /**
421     * <p>
422     * addLine.</p>
423     *
424     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
425     * @param prefix a {@link java.lang.String} object.
426     * @param element a {@link java.lang.Object} object.
427     * @param value a {@link java.lang.Object} object.
428     */
429    public static void addLine(JsonGenerator jg, String prefix, Object element,
430            Object... value) {
431        addLineWithProperty(jg, prefix, null, element, value);
432    }
433
434    /**
435     * <p>
436     * getElementName.</p>
437     *
438     * @param element a {@link java.lang.Object} object.
439     * @return a {@link java.util.Optional} object.
440     */
441    public static Optional<String> getElementName(Object element) {
442        if (element instanceof String) {
443            return Optional.of((String) element);
444        }
445        if (element instanceof MetadataElement) {
446            return Optional.ofNullable(((MetadataElement) element).getName());
447        }
448        JacksonXmlRootElement rootElement = element.getClass().
449                getAnnotation(JacksonXmlRootElement.class);
450        if (rootElement != null) {
451            String underscoreName = camelCaseToUnderscoreLowerCase(
452                    rootElement.localName());
453            if (element instanceof IndexedElement) {
454                Integer id = Optional.ofNullable(((IndexedElement) element).getId()).orElseThrow(() -> 
455                        new NullPointerException(
456                            "Field 'id' must not be null for element '" + underscoreName + "'!")
457                );
458                return Optional.of(
459                        underscoreName + "[" + id + "]");
460            }
461            return Optional.ofNullable(underscoreName);
462        }
463        return Optional.empty();
464    }
465
466    /**
467     * <p>
468     * getPropertyNames.</p>
469     *
470     * @param element a {@link java.lang.Object} object.
471     * @return a {@link java.util.List} object.
472     */
473    public static List<String> getPropertyNames(Object element) {
474        return Arrays.asList(element.getClass().
475                getAnnotationsByType(JsonProperty.class)).
476                stream().
477                map((jsonProperty)
478                        -> {
479                    return jsonProperty.value();
480                }).
481                collect(Collectors.toList());
482    }
483
484    /**
485     * <p>
486     * asMap.</p>
487     *
488     * @param element a {@link java.lang.Object} object.
489     * @return a {@link java.util.Map} object.
490     */
491    public static Map<String, Object> asMap(Object element) {
492        ObjectMapper objectMapper = new ObjectMapper();
493        return objectMapper.convertValue(element, Map.class);
494    }
495
496    /**
497     * <p>
498     * camelCaseToUnderscoreLowerCase.</p>
499     *
500     * @param camelCase a {@link java.lang.String} object.
501     * @return a {@link java.lang.String} object.
502     */
503    public static String camelCaseToUnderscoreLowerCase(String camelCase) {
504        Matcher m = Pattern.compile("(?<=[a-z])[A-Z]").
505                matcher(camelCase);
506
507        StringBuffer sb = new StringBuffer();
508        while (m.find()) {
509            m.appendReplacement(sb, "_" + m.group().
510                    toLowerCase());
511        }
512        m.appendTail(sb);
513        return sb.toString().
514                toLowerCase();
515    }
516
517    /**
518     * <p>
519     * addSubElementStrings.</p>
520     *
521     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
522     * @param prefix a {@link java.lang.String} object.
523     * @param element a {@link java.lang.Object} object.
524     * @param subElementName a {@link java.lang.String} object.
525     * @param subElements a {@link java.util.List} object.
526     * @param oneLine a boolean.
527     */
528    public static void addSubElementStrings(JsonGenerator jg, String prefix,
529            Object element, String subElementName, List<?> subElements,
530            boolean oneLine) {
531        if (checkForNull(element, subElements, subElementName)) {
532            return;
533        }
534        Serializers.getElementName(element).
535                ifPresent((elementName)
536                        -> {
537                    if (oneLine) {
538                        addLine(jg, prefix,
539                                elementName + "-" + subElementName,
540                                subElements.stream().
541                                        map((t)
542                                                -> {
543                                            return t.toString();
544                                        }).
545                                        collect(Collectors.joining("" + MZTabConstants.BAR)));
546                    } else {
547                        IntStream.range(0, subElements.
548                                size()).
549                                forEachOrdered(i
550                                        -> {
551                                    addLine(jg, prefix,
552                                            elementName + "-" + subElementName + "[" + (i + 1) + "]",
553                                            subElements.
554                                                    get(i));
555                                });
556                    }
557                });
558
559    }
560
561    /**
562     * <p>
563     * addSubElementParameter.</p>
564     *
565     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
566     * @param prefix a {@link java.lang.String} object.
567     * @param element a {@link java.lang.Object} object.
568     * @param subElementName a {@link java.lang.String} object.
569     * @param subElement a {@link de.isas.mztab2.model.Parameter} object.
570     */
571    public static void addSubElementParameter(JsonGenerator jg, String prefix,
572            Object element, String subElementName, Parameter subElement) {
573        if (subElement == null) {
574            String elementName = Serializers.getElementName(element).
575                    orElse("undefined");
576
577            log.debug("''{}-{}'' is null or empty!", new Object[]{
578                elementName,
579                subElementName});
580            return;
581        }
582        addSubElementStrings(jg, prefix, element, subElementName, Arrays.asList(
583                new ParameterConverter().convert(subElement)), true);
584    }
585
586    /**
587     * <p>
588     * addSubElementParameters.</p>
589     *
590     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
591     * @param prefix a {@link java.lang.String} object.
592     * @param element a {@link java.lang.Object} object.
593     * @param subElementName a {@link java.lang.String} object.
594     * @param subElements a {@link java.util.List} object.
595     * @param oneLine a boolean.
596     */
597    public static void addSubElementParameters(JsonGenerator jg, String prefix,
598            Object element, String subElementName, List<Parameter> subElements,
599            boolean oneLine) {
600        if (checkForNull(element, subElements, subElementName)) {
601            return;
602        }
603        addSubElementStrings(jg, prefix, element, subElementName,
604                subElements.stream().
605                        map((parameter)
606                                -> {
607                            try {
608                                return new ParameterConverter().convert(parameter);
609                            } catch (IllegalArgumentException npe) {
610
611                                log.debug("parameter is null for {}",
612                                        subElementName);
613                                return "null";
614                            }
615                        }).
616                        collect(Collectors.toList()), oneLine);
617    }
618
619    /**
620     * <p>
621     * checkForNull.</p>
622     *
623     * @param element a {@link java.lang.Object} object.
624     * @param subElements a {@link java.util.List} object.
625     * @param subElementName a {@link java.lang.String} object.
626     * @return a boolean.
627     */
628    public static boolean checkForNull(Object element, List<?> subElements,
629            String subElementName) {
630        String elementName = Serializers.getElementName(element).
631                orElse("undefined");
632        if (subElements == null || subElements.isEmpty()) {
633
634            log.debug("''{}-{}'' is null or empty!", new Object[]{
635                elementName,
636                subElementName});
637            return true;
638        }
639        return false;
640    }
641
642    /**
643     * <p>
644     * writeString.</p>
645     *
646     * @param columnName a {@link java.lang.String} object.
647     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
648     * @param value a {@link java.lang.String} object.
649     * @throws java.io.IOException if an operation on the JsonGenerator object
650     * fails.
651     */
652    public static void writeString(String columnName, JsonGenerator jg,
653            String value) throws IOException {
654        if (value == null) {
655            jg.writeNullField(columnName);
656        } else {
657            jg.writeStringField(columnName, value);
658        }
659    }
660
661    /**
662     * <p>
663     * writeString.</p>
664     *
665     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
666     * object.
667     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
668     * @param value a {@link java.lang.String} object.
669     * @throws java.io.IOException if an operation on the JsonGenerator object
670     * fails.
671     */
672    public static void writeString(IMZTabColumn column, JsonGenerator jg,
673            String value) throws IOException {
674        writeString(column.getHeader(), jg, value);
675    }
676
677    /**
678     * <p>
679     * writeObject.</p>
680     *
681     * @param columnName a {@link java.lang.String} object.
682     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
683     * @param sp a {@link com.fasterxml.jackson.databind.SerializerProvider}
684     * object.
685     * @param value a {@link java.lang.Object} object.
686     * @throws java.io.IOException if an operation on the JsonGenerator object
687     * fails.
688     */
689    public static void writeObject(String columnName, JsonGenerator jg,
690            SerializerProvider sp,
691            Object value) throws IOException {
692        if (value == null) {
693            jg.writeNullField(columnName);
694        } else {
695            if (value instanceof Parameter) {
696                jg.writeStringField(columnName, new ParameterConverter().
697                        convert((Parameter) value));
698            } else if (value instanceof String) {
699                jg.writeStringField(columnName, (String) value);
700            } else {
701                throw new IllegalArgumentException(
702                        "Serialization of objects of type " + value.getClass()
703                        + " currently not supported!");
704            }
705
706        }
707    }
708
709    /**
710     * <p>
711     * writeObject.</p>
712     *
713     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
714     * object.
715     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
716     * @param sp a {@link com.fasterxml.jackson.databind.SerializerProvider}
717     * object.
718     * @param value a {@link java.lang.Object} object.
719     * @throws java.io.IOException if an operation on the JsonGenerator object
720     * fails.
721     */
722    public static void writeObject(IMZTabColumn column, JsonGenerator jg,
723            SerializerProvider sp, Object value) throws IOException {
724        writeObject(column.getHeader(), jg, sp, value);
725    }
726
727    /**
728     * <p>
729     * writeAsNumberArray.</p>
730     *
731     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
732     * object.
733     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
734     * @param elements a {@link java.util.List} object.
735     */
736    public static void writeAsNumberArray(IMZTabColumn column, JsonGenerator jg,
737            List<? extends Number> elements) {
738        writeAsNumberArray(column.getHeader(), jg, elements);
739    }
740
741    /**
742     * <p>
743     * writeAsNumberArray.</p>
744     *
745     * @param columnName a {@link java.lang.String} object.
746     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
747     * @param elements a {@link java.util.List} object.
748     */
749    public static void writeAsNumberArray(String columnName, JsonGenerator jg,
750            List<? extends Number> elements) {
751        try {
752            if (elements == null) {
753                jg.writeNullField(columnName);
754            } else {
755                String arrayElements = elements.stream().
756                        map((number)
757                                -> {
758                            if (number == null) {
759                                return MZTabConstants.NULL;
760                            } else if (number instanceof Short) {
761                                return "" + number.shortValue();
762                            } else if (number instanceof Integer) {
763                                return "" + number.intValue();
764                            } else if (number instanceof Long) {
765                                return "" + number.longValue();
766                            } else if (number instanceof Float) {
767                                return "" + number.floatValue();
768                            } else {
769                                return "" + number.doubleValue();
770                            }
771                        }).
772                        collect(Collectors.joining("" + MZTabConstants.BAR));
773                if (arrayElements.isEmpty()) {
774                    jg.writeNullField(columnName);
775                } else {
776                    jg.writeStringField(columnName, arrayElements);
777                }
778            }
779        } catch (IOException ex) {
780            log.error(
781                    "Caught IO exception while trying to write as number array: ",
782                    ex);
783        }
784    }
785
786    /**
787     * <p>
788     * writeAsStringArray.</p>
789     *
790     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
791     * object.
792     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
793     * @param elements a {@link java.util.List} object.
794     */
795    public static void writeAsStringArray(IMZTabColumn column, JsonGenerator jg,
796            List<String> elements) {
797        writeAsStringArray(column.getHeader(), jg, elements);
798    }
799
800    /**
801     * <p>
802     * writeAsStringArray.</p>
803     *
804     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
805     * @param elements a {@link java.util.List} object.
806     */
807    public static void writeAsStringArray(JsonGenerator jg,
808            List<String> elements) {
809        try {
810            if (elements == null) {
811                jg.writeNull();
812            } else {
813                String arrayElements = elements.stream().
814                        map((t)
815                                -> {
816                            if (t == null) {
817                                return MZTabConstants.NULL;
818                            }
819                            return t;
820                        }).
821                        collect(Collectors.joining("" + MZTabConstants.BAR));
822                if (arrayElements.isEmpty()) {
823                    jg.writeNull();
824                } else {
825                    jg.writeString(arrayElements);
826                }
827            }
828        } catch (IOException ex) {
829            log.error("Error while trying to write as string array:", ex);
830        }
831    }
832
833    /**
834     * <p>
835     * writeAsStringArray.</p>
836     *
837     * @param columnName a {@link java.lang.String} object.
838     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
839     * @param elements a {@link java.util.List} object.
840     */
841    public static void writeAsStringArray(String columnName, JsonGenerator jg,
842            List<String> elements) {
843        try {
844            if (elements == null) {
845                jg.writeNullField(columnName);
846            } else {
847                String arrayElements = elements.stream().
848                        map((t)
849                                -> {
850                            if (t == null) {
851                                return MZTabConstants.NULL;
852                            }
853                            return t;
854                        }).
855                        collect(Collectors.joining("" + MZTabConstants.BAR));
856                if (arrayElements.isEmpty()) {
857                    jg.writeNullField(columnName);
858                } else {
859                    jg.writeStringField(columnName, arrayElements);
860                }
861            }
862        } catch (IOException ex) {
863            log.error("Error while trying to write as string array: ", ex);
864        }
865    }
866
867    /**
868     * <p>
869     * writeNumber.</p>
870     *
871     * @param columnName a {@link java.lang.String} object.
872     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
873     * @param value a {@link java.lang.Integer} object.
874     * @throws java.io.IOException if an operation on the JsonGenerator object
875     * fails.
876     */
877    public static void writeNumber(String columnName, JsonGenerator jg,
878            Integer value) throws IOException {
879        if (value == null) {
880            jg.writeNullField(columnName);
881        } else {
882            jg.writeNumberField(columnName, value);
883        }
884    }
885
886    /**
887     * <p>
888     * writeNumber.</p>
889     *
890     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
891     * object.
892     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
893     * @param value a {@link java.lang.Integer} object.
894     * @throws java.io.IOException if an operation on the JsonGenerator object
895     * fails.
896     */
897    public static void writeNumber(IMZTabColumn column, JsonGenerator jg,
898            Integer value) throws IOException {
899        writeNumber(column.getHeader(), jg, value);
900    }
901
902    /**
903     * <p>
904     * writeNumber.</p>
905     *
906     * @param columnName a {@link java.lang.String} object.
907     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
908     * @param value a {@link java.lang.Double} object.
909     * @throws java.io.IOException if an operation on the JsonGenerator object
910     * fails.
911     */
912    public static void writeNumber(String columnName, JsonGenerator jg,
913            Double value) throws IOException {
914        if (value == null) {
915            jg.writeNullField(columnName);
916        } else {
917            if (value.equals(Double.NaN)) {
918                jg.writeStringField(columnName, MZTabConstants.CALCULATE_ERROR);
919            } else if (value.equals(Double.POSITIVE_INFINITY)) {
920                jg.writeStringField(columnName, MZTabConstants.INFINITY);
921            } else {
922                jg.writeNumberField(columnName, value);
923            }
924        }
925    }
926
927    /**
928     * <p>
929     * writeNumber.</p>
930     *
931     * @param column a {@link uk.ac.ebi.pride.jmztab2.model.IMZTabColumn}
932     * object.
933     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
934     * @param value a {@link java.lang.Double} object.
935     * @throws java.io.IOException if an operation on the JsonGenerator object
936     * fails.
937     */
938    public static void writeNumber(IMZTabColumn column, JsonGenerator jg,
939            Double value) throws IOException {
940        writeNumber(column.getHeader(), jg, value);
941    }
942
943    /**
944     * <p>
945     * writeNumber.</p>
946     *
947     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
948     * @param value a {@link java.lang.Integer} object.
949     * @throws java.io.IOException if an operation on the JsonGenerator object
950     * fails.
951     */
952    public static void writeNumber(JsonGenerator jg, Integer value) throws IOException {
953        if (value == null) {
954            jg.writeNull();
955        } else {
956            jg.writeNumber(value);
957        }
958    }
959
960    /**
961     * <p>
962     * writeNumber.</p>
963     *
964     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
965     * @param value a {@link java.lang.Double} object.
966     * @throws java.io.IOException if an operation on the JsonGenerator object
967     * fails.
968     */
969    public static void writeNumber(JsonGenerator jg, Double value) throws IOException {
970        if (value == null) {
971            jg.writeNull();
972        } else {
973            jg.writeNumber(value);
974        }
975    }
976
977    /**
978     * <p>
979     * writeOptColumnMappings.</p>
980     *
981     * @param optColumnMappings a {@link java.util.List} object.
982     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
983     * @param sp the serialization provider.
984     * @throws java.io.IOException if an operation on the JsonGenerator object
985     * fails.
986     */
987    public static void writeOptColumnMappings(
988            List<OptColumnMapping> optColumnMappings,
989            JsonGenerator jg, SerializerProvider sp) throws IOException {
990        for (OptColumnMapping ocm : Optional.ofNullable(
991                optColumnMappings).
992                orElse(Collections.emptyList())) {
993            // write global or indexed element param objects
994            if (ocm.getParam() != null) {
995                writeObject(Serializers.printOptColumnMapping(ocm), jg, sp, ocm.
996                        getValue() == null ? 
997                        (ocm.getParam().getValue() == null || ocm.getParam().getValue().isEmpty() ? 
998                                NULL : ocm.getParam().getValue()) : ocm.getValue());
999            } else { // write global opt column objects with the value
1000                writeObject(Serializers.printOptColumnMapping(ocm), jg, sp, ocm.
1001                        getValue() == null ? NULL : ocm.getValue());
1002            }
1003        }
1004    }
1005
1006    /**
1007     * <p>
1008     * writeIndexedValues.</p>
1009     *
1010     * @param prefix a {@link java.lang.String} object.
1011     * @param jg a {@link com.fasterxml.jackson.core.JsonGenerator} object.
1012     * @param values a {@link java.util.List} object.
1013     */
1014    public static void writeIndexedDoubles(String prefix,
1015            JsonGenerator jg, List<Double> values) {
1016        IntStream.range(0, values.
1017                size()).
1018                forEachOrdered(i
1019                        -> {
1020                    try {
1021                        Serializers.writeNumber(
1022                                prefix + "[" + (i + 1) + "]",
1023                                jg,
1024                                values.
1025                                        get(i));
1026                    } catch (IOException ex) {
1027                        log.error(
1028                                "Caught IO exception while trying to write indexed doubles:",
1029                                ex);
1030                    }
1031                });
1032    }
1033
1034    public static void checkIndexedElement(IndexedElement element) {
1035        Integer id = Optional.ofNullable(element.getId()).orElseThrow(() ->
1036            new ValidationException(
1037                    "'id' field of " + element.toString() + " must not be null!"));
1038        if (id < 1) {
1039            throw new ValidationException(
1040                    "'id' field of " + element.toString() + " must have a value greater to equal to 1!");
1041        }
1042    }
1043
1044}