001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.common.util.file;
019
020import com.fasterxml.jackson.core.json.JsonReadFeature;
021import com.fasterxml.jackson.databind.DeserializationFeature;
022import com.fasterxml.jackson.databind.ObjectMapper;
023import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
024import com.fasterxml.jackson.databind.json.JsonMapper;
025import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
026import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
027import com.fasterxml.jackson.databind.module.SimpleModule;
028import com.fasterxml.jackson.databind.ser.std.StdSerializer;
029import java.io.File;
030import java.io.FileDescriptor;
031import java.io.FileOutputStream;
032import java.io.IOException;
033import java.nio.file.Path;
034import java.util.HashMap;
035import java.util.Map;
036import org.eclipse.jetty.io.EofException;
037
038public class JacksonUtils {
039    public static class UIMap extends HashMap<String, Object> {}
040
041    public static <T> void serialize(Path path, T object) throws IOException {
042        serialize(path, object, true);
043    }
044
045    public static <T> String serializeToString(T object) throws IOException {
046        PolymorphicTypeValidator ptv =
047                BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
048        ObjectMapper objectMapper =
049                JsonMapper.builder()
050                        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
051                        .build();
052        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
053    }
054
055    public static <T> void serialize(Path path, T object, boolean forceSync) throws IOException {
056        PolymorphicTypeValidator ptv =
057                BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
058        ObjectMapper objectMapper =
059                JsonMapper.builder()
060                        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
061                        .build();
062        String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
063        saveJsonString(json, path, forceSync);
064    }
065
066    public static <T> T deserialize(Map<?, ?> s, Class<T> ref) throws IOException {
067        PolymorphicTypeValidator ptv =
068                BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
069        ObjectMapper objectMapper =
070                JsonMapper.builder()
071                        .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
072                        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
073                        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
074                        .build();
075
076        return objectMapper.convertValue(s, ref);
077    }
078
079    public static <T> T deserialize(String s, Class<T> ref) throws IOException {
080        if (s.length() == 0) {
081            throw new EofException("Provided empty string for class " + ref.getName());
082        }
083
084        PolymorphicTypeValidator ptv =
085                BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
086        ObjectMapper objectMapper =
087                JsonMapper.builder()
088                        .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
089                        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
090                        .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
091                        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
092                        .build();
093
094        return objectMapper.readValue(s, ref);
095    }
096
097    public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
098        PolymorphicTypeValidator ptv =
099                BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
100        ObjectMapper objectMapper =
101                JsonMapper.builder()
102                        .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
103                        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
104                        .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
105                        .build();
106        File jsonFile = new File(path.toString());
107        if (jsonFile.exists() && jsonFile.length() > 0) {
108            return objectMapper.readValue(jsonFile, ref);
109        }
110        return null;
111    }
112
113    public static <T> T deserialize(Path path, Class<T> ref, StdDeserializer<T> deserializer)
114            throws IOException {
115        ObjectMapper objectMapper = new ObjectMapper();
116        SimpleModule module = new SimpleModule();
117        module.addDeserializer(ref, deserializer);
118        objectMapper.registerModule(module);
119
120        File jsonFile = new File(path.toString());
121        if (jsonFile.exists() && jsonFile.length() > 0) {
122            return objectMapper.readValue(jsonFile, ref);
123        }
124        return null;
125    }
126
127    public static <T> void serialize(Path path, T object, Class<T> ref, StdSerializer<T> serializer)
128            throws IOException {
129        serialize(path, object, ref, serializer, true);
130    }
131
132    public static <T> void serialize(
133            Path path, T object, Class<T> ref, StdSerializer<T> serializer, boolean forceSync)
134            throws IOException {
135        ObjectMapper objectMapper = new ObjectMapper();
136        SimpleModule module = new SimpleModule();
137        module.addSerializer(ref, serializer);
138        objectMapper.registerModule(module);
139        String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
140        saveJsonString(json, path, forceSync);
141    }
142
143    private static void saveJsonString(String json, Path path, boolean forceSync) throws IOException {
144        var file = path.toFile();
145        if (file.getParentFile() != null && !file.getParentFile().exists()) {
146            file.getParentFile().mkdirs();
147        }
148        if (!file.exists()) {
149            if (!file.canWrite()) {
150                file.setWritable(true);
151            }
152            file.createNewFile();
153        }
154        FileOutputStream fileOutputStream = new FileOutputStream(file);
155        fileOutputStream.write(json.getBytes());
156        fileOutputStream.flush();
157        if (forceSync) {
158            FileDescriptor fileDescriptor = fileOutputStream.getFD();
159            fileDescriptor.sync();
160        }
161        fileOutputStream.close();
162    }
163}