001/*
002 * Copyright 2020  nils.hoffmann.
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.lipidomics.generator;
017
018import com.opencsv.CSVReader;
019import com.squareup.javapoet.AnnotationSpec;
020import com.squareup.javapoet.ClassName;
021import com.squareup.javapoet.CodeBlock;
022import com.squareup.javapoet.JavaFile;
023import com.squareup.javapoet.MethodSpec;
024import com.squareup.javapoet.ParameterizedTypeName;
025import com.squareup.javapoet.TypeName;
026import com.squareup.javapoet.TypeSpec;
027import com.squareup.javapoet.TypeSpec.Builder;
028import de.isas.lipidomics.domain.ElementTable;
029import de.isas.lipidomics.domain.LipidCategory;
030import de.isas.lipidomics.domain.LipidClass;
031import java.io.IOException;
032import java.io.Reader;
033import java.net.URISyntaxException;
034import java.nio.file.Files;
035import java.nio.file.Paths;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Optional;
041import java.util.logging.Level;
042import java.util.logging.Logger;
043import java.util.stream.Collectors;
044import java.util.stream.Stream;
045import javax.lang.model.element.Modifier;
046import lombok.Value;
047import lombok.extern.slf4j.Slf4j;
048
049/**
050 *
051 * @author  nils.hoffmann
052 */
053public class LipidClassGenerator {
054
055    public static void main(String... args) {
056        LipidClassGenerator gne = new LipidClassGenerator();
057        try {
058            System.out.println(gne.getEnumFromTable(gne.getEnumEntries()));
059        } catch (IOException ex) {
060            Logger.getLogger(LipidClassGenerator.class.getName()).log(Level.SEVERE, null, ex);
061        }
062    }
063
064    @Value
065    public static class LipidClassEntry {
066
067        private final String lipidName;
068        private final String lipidCategory;
069        private final String lipidDescription;
070        private final Integer maxNumFa;
071        private final String allowedNumFa;
072        private final String sumFormula;
073        private final List<String> synonyms;
074    }
075
076    public Stream<LipidClassEntry> getEnumEntries() throws IOException {
077        try (Reader reader = Files.newBufferedReader(Paths.get(ClassLoader.getSystemResource("lipid-list.csv").toURI()))) {;
078            CSVReader csvReader = new CSVReader(reader);
079            csvReader.skip(1);
080            Iterator<String[]> iter = csvReader.iterator();
081            List<LipidClassEntry> entries = new ArrayList<>();
082            while (iter.hasNext()) {
083                String[] s = iter.next();
084                int len = s.length;
085                List<String> synonyms = new ArrayList<>();
086                for (int i = 6; i < len; i++) {
087                    if (!s[i].trim().isEmpty()) {
088                        synonyms.add(s[i]);
089                    }
090                }
091                LipidClassEntry entry = new LipidClassEntry(
092                        s[0],
093                        s[1],
094                        s[2],
095                        Integer.parseInt(s[3]),
096                        s[4],
097                        s[5],
098                        synonyms
099                );
100                System.out.println("Entry: " + entry);
101                entries.add(entry);
102            }
103            return entries.stream();
104        } catch (URISyntaxException ex) {
105            Logger.getLogger(LipidClassGenerator.class.getName()).log(Level.SEVERE, null, ex);
106        }
107        return Stream.empty();
108    }
109
110    public String sanitizeToEnumConstant(String lipidName, String lipidCategory) {
111        String enumName = lipidName.replaceAll("[/\\-\\s(),.\\[\\]:;]", "_").replaceAll("'", "p")
112                .replaceAll("^([_0-9]+)", lipidCategory + "_$1").replaceAll("[_]+", "_").toUpperCase();
113        if(enumName.endsWith("_")) {
114            return enumName.substring(0, enumName.length()-1);
115        }
116        return enumName;
117    }
118
119    public String getEnumFromTable(Stream<LipidClassEntry> stream) {
120        final Builder lipidClassBuilder = TypeSpec.enumBuilder("LipidClass").addJavadoc(
121                "Enumeration of lipid classes. The shorthand names / abbreviations are used to\n"
122                + "look up the lipid class association of a lipid head group. We try to map each\n"
123                + "abbreviation and synonyms thereof to LipidMAPS main class. However, not all\n"
124                + "described head groups are categorized in LipidMAPS, or only occur in other\n"
125                + "databases, so they do not have such an association at the moment.\n"
126                + "\n"
127                + "Example: Category=Glyerophospholipids maps to Class=Glycerophosphoinositols (PI)\n"
128                + "\n"
129                + "@author nils.hoffmann"
130        ).
131                addModifiers(Modifier.PUBLIC);
132        lipidClassBuilder.addField(LipidCategory.class, "category", Modifier.PRIVATE, Modifier.FINAL);
133        lipidClassBuilder.addField(String.class, "lipidMapsClassName", Modifier.PRIVATE, Modifier.FINAL);
134        ClassName synonymsClass = ClassName.get("java.lang", "String");
135        ClassName integersClass = ClassName.get("java.lang", "Integer");
136        ClassName list = ClassName.get("java.util", "List");
137        lipidClassBuilder.addField(String.class, "allowedNumFaStr", Modifier.PRIVATE, Modifier.FINAL);
138        TypeName listOfIntegers = ParameterizedTypeName.get(list, integersClass);
139        lipidClassBuilder.addField(listOfIntegers, "allowedNumFa", Modifier.PRIVATE, Modifier.FINAL);
140        lipidClassBuilder.addField(Integer.class, "maxNumFa", Modifier.PRIVATE, Modifier.FINAL);
141        lipidClassBuilder.addField(String.class, "sumFormula", Modifier.PRIVATE, Modifier.FINAL);
142        lipidClassBuilder.addField(ElementTable.class, "elementTable", Modifier.PRIVATE, Modifier.FINAL);
143        ClassName arrayList = ClassName.get("java.util", "ArrayList");
144        TypeName listOfSynonyms = ParameterizedTypeName.get(list, synonymsClass);
145        lipidClassBuilder.addField(listOfSynonyms, "synonyms", Modifier.PRIVATE, Modifier.FINAL);
146        TypeName optionalLipidClass = ParameterizedTypeName.get(Optional.class, LipidClass.class);
147
148        lipidClassBuilder.addMethod(
149                MethodSpec.constructorBuilder().
150                        addModifiers(Modifier.PRIVATE).
151                        addParameter(LipidCategory.class, "category").
152                        addStatement("this.$N = $N", "category", "category").
153                        addParameter(String.class, "lipidMapsClassName").
154                        addStatement("this.$N = $N", "lipidMapsClassName", "lipidMapsClassName").
155                        addParameter(Integer.class, "maxNumFa").
156                        addStatement("this.$N = $N", "maxNumFa", "maxNumFa").
157                        addParameter(String.class, "allowedNumFaStr").
158                        addStatement("this.$N = $N", "allowedNumFaStr", "allowedNumFaStr").
159                        addParameter(String.class, "sumFormula").
160                        addStatement("this.$N = $N", "sumFormula", "sumFormula").
161                        addStatement(
162                                CodeBlock.of(
163                                        "ElementTable et = new ElementTable();\n"
164                                        + "        if(this.sumFormula!=null && !this.sumFormula.isEmpty()) {\n"
165                                        + "            try {\n"
166                                        + "                et = new ElementTable(this.sumFormula);\n"
167                                        + "            } catch (de.isas.lipidomics.palinom.exceptions.ParsingException ex) {\n"
168                                        + "                et = new ElementTable();\n"
169                                        + "            }\n"
170                                        + "        }\n"
171                                        + "        this.elementTable = et;"
172                                )
173                        ).
174                        addParameter(listOfSynonyms, "synonyms").
175                        addStatement(
176                                CodeBlock.of(
177                                        "if ($N.isEmpty()) {\n"
178                                        + " throw new IllegalArgumentException(\"Must supply at least one synonym!\");\n"
179                                        + "}", "synonyms")
180                        ).
181                        addStatement(
182                                CodeBlock.of(
183                                        "this.$N = Arrays.asList(allowedNumFaStr.split(\"\\\\|\")).stream().map((t) -> {\n"
184                                        + "   return Integer.parseInt(t);\n"
185                                        + "}).collect(java.util.stream.Collectors.toList())", "allowedNumFa")
186                        ).
187                        addStatement("this.$N = $N", "synonyms", "synonyms").
188                        build()
189        );
190
191        lipidClassBuilder.addMethod(
192                MethodSpec.methodBuilder("getCategory").addModifiers(Modifier.PUBLIC).returns(LipidCategory.class).addCode("return this.$N;", "category").build()
193        );
194        lipidClassBuilder.addMethod(
195                MethodSpec.methodBuilder("getAbbreviation").addModifiers(Modifier.PUBLIC).returns(String.class).addCode("return this.$N.get(0);", "synonyms").build()
196        );
197        lipidClassBuilder.addMethod(
198                MethodSpec.methodBuilder("getLipidMapsClassName").addModifiers(Modifier.PUBLIC).returns(String.class).addCode("return this.$N;", "lipidMapsClassName").build()
199        );
200        lipidClassBuilder.addMethod(
201                MethodSpec.methodBuilder("getMaxNumFa").addModifiers(Modifier.PUBLIC).returns(Integer.class).addCode("return this.$N;", "maxNumFa").build()
202        );
203        lipidClassBuilder.addMethod(
204                MethodSpec.methodBuilder("getAllowedNumFa").addModifiers(Modifier.PUBLIC).returns(listOfIntegers).addCode("return this.$N;", "allowedNumFa").build()
205        );
206        lipidClassBuilder.addMethod(
207                MethodSpec.methodBuilder("getSumFormula").addModifiers(Modifier.PUBLIC).returns(String.class).addCode("return this.$N.getSumFormula();", "elementTable").build()
208        );
209        lipidClassBuilder.addMethod(
210                MethodSpec.methodBuilder("getElements").addModifiers(Modifier.PUBLIC).returns(ElementTable.class).addCode("return this.$N.copy();", "elementTable").build()
211        );
212        lipidClassBuilder.addMethod(
213                MethodSpec.methodBuilder("getSynonyms").addModifiers(Modifier.PUBLIC).returns(listOfSynonyms).addCode("return this.$N;", "synonyms").build()
214        );
215
216        lipidClassBuilder.addMethod(
217                MethodSpec.methodBuilder("matchesAbbreviation").
218                        addModifiers(Modifier.PUBLIC).
219                        addParameter(String.class, "headGroup").
220                        addStatement(
221                                CodeBlock.of(
222                                        "return this.synonyms.stream().anyMatch((synonym) -> {\n"
223                                        + "return synonym.equals($N);\n"
224                                        + "})", "headGroup")).
225                        returns(boolean.class).
226                        build()
227        );
228
229        lipidClassBuilder.addMethod(
230                MethodSpec.methodBuilder("getLysoAbbreviation").
231                        addModifiers(Modifier.PUBLIC).
232                        addParameter(LipidClass.class, "lipidClass").
233                        addStatement(
234                                CodeBlock.of(
235                                        "if ($N.getCategory() == LipidCategory.GP) { "
236                                        + "return \"L\" + $N.getAbbreviation();\n"
237                                        + "}\n"
238                                        + "throw new ConstraintViolationException(\"Lipid category must be \" + LipidCategory.GP + \" for lyso-classes!\")", "lipidClass", "lipidClass")).
239                        returns(String.class).
240                        build()
241        );
242
243        lipidClassBuilder.addMethod(
244                MethodSpec.methodBuilder("forHeadGroup").
245                        addModifiers(Modifier.PUBLIC, Modifier.STATIC).
246                        addParameter(String.class, "headGroup").
247                        addStatement(
248                                CodeBlock.of(
249                                        "return Arrays.asList(values()).stream().filter((lipidClass) -> {\n"
250                                        + "    return lipidClass.matchesAbbreviation($N.trim());\n"
251                                        + "}).findFirst()", "headGroup")).
252                        returns(optionalLipidClass).
253                        build()
254        );
255
256        stream.forEach((lipidClassEntry) -> {
257            String sanitizedName = sanitizeToEnumConstant(lipidClassEntry.getLipidName(), lipidClassEntry.getLipidCategory());
258            LipidCategory category = LipidCategory.valueOf(lipidClassEntry.getLipidCategory());
259            List<String> template = new ArrayList<>();
260            template.addAll(
261                    Arrays.asList(
262                            "LipidCategory." + category.name(),
263                            "\"" + lipidClassEntry.lipidDescription + "\"",
264                            lipidClassEntry.maxNumFa + "",
265                            "\"" + lipidClassEntry.allowedNumFa + "\"",
266//                            "\"" + lipidClassEntry.lipidName + "\"",
267                            "\"" + lipidClassEntry.sumFormula + "\""
268                    )
269            );
270            List<String> synonyms = new ArrayList<>();
271            synonyms.add("\"" + lipidClassEntry.lipidName + "\"");
272            synonyms.addAll(lipidClassEntry.getSynonyms().stream().map((t) -> {
273                return "\"" + t + "\"";
274            }).collect(Collectors.toList()));
275            template.add("java.util.Arrays.asList(" + synonyms.stream().collect(Collectors.joining(",")) + ")");
276            CodeBlock cb = CodeBlock.of(String.join(", ", template));
277            lipidClassBuilder.addEnumConstant(sanitizedName, TypeSpec.anonymousClassBuilder(cb).build());
278        });
279
280        return JavaFile.builder("de.isas.lipidomics.domain", lipidClassBuilder.build()).addFileComment(
281                " Copyright 2019 nils.hoffmann.\n"
282                + "\n"
283                + " Licensed under the Apache License, Version 2.0 (the \"License\");\n"
284                + " you may not use this file except in compliance with the License.\n"
285                + " You may obtain a copy of the License at\n"
286                + "\n"
287                + "      http://www.apache.org/licenses/LICENSE-2.0\n"
288                + "\n"
289                + " Unless required by applicable law or agreed to in writing, software\n"
290                + " distributed under the License is distributed on an \"AS IS\" BASIS,\n"
291                + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
292                + " See the License for the specific language governing permissions and\n"
293                + " limitations under the License."
294        ).build().toString();
295    }
296}