org.codice.ddf.configuration.migration.JsonUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.migration.JsonUtils.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.configuration.migration;

import com.google.common.annotations.VisibleForTesting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.apache.commons.collections4.map.LinkedMap;
import org.codice.ddf.migration.MigrationException;

/** This class provides utility functions for dealing with Json objects. */
public class JsonUtils {
    @SuppressWarnings("PMD.DefaultPackage" /* designed as an internal service within this package */)
    @VisibleForTesting
    static final ObjectMapper MAPPER = new ObjectMapper();

    private JsonUtils() {
    }

    /**
     * Converts a given Json object to a Json map.
     *
     * @param o the Json object to return as a map
     * @return the corresponding map
     * @throws MigrationException if the given object is not a map
     */
    public static Map<String, Object> convertToMap(@Nullable Object o) {
        if (o == null) {
            return Collections.emptyMap();
        }
        if (!(o instanceof Map)) {
            throw new MigrationException(Messages.IMPORT_METADATA_FORMAT_ERROR, "expecting a Json map");
        }
        return (Map<String, Object>) o;
    }

    /**
     * Retrieves a Json map from the specified Json map given its key.
     *
     * @param map the map to retrieve an entry from
     * @param key the key for the entry to retrieve
     * @return the corresponding Json map or an empty one if none defined
     * @throws MigrationException if the specified entry is not a Json map
     */
    public static Map<String, Object> getMapFrom(@Nullable Map<String, Object> map, @Nullable String key) {
        final Map<String, Object> m = JsonUtils.getObjectFrom(Map.class, map, key, false);

        return (m != null) ? m : Collections.emptyMap();
    }

    /**
     * Retrieves a Json array from the specified Json map given its key.
     *
     * @param map the map to retrieve an entry from
     * @param key the key for the entry to retrieve
     * @return the corresponding Json array as a list or an empty one if none defined
     * @throws MigrationException if the specified entry is not a Json array
     */
    public static List<Object> getListFrom(@Nullable Map<String, Object> map, @Nullable String key) {
        final List<Object> l = JsonUtils.getObjectFrom(List.class, map, key, false);

        return (l != null) ? l : Collections.emptyList();
    }

    /**
     * Retrieves a Json string from the specified Json map given its key.
     *
     * @param map the map to retrieve an entry from
     * @param key the key for the entry to retrieve
     * @param required <code>true</code> if the entry must exist in the map otherwise an error is
     *     generated; <code>false</code> to return <code>null</code> if it doesn't exist
     * @return the corresponding Json string or <code>null</code> if it doesn't exist and <code>
     *     required</code> is <code>false</code>
     * @throws MigrationException if the specified entry is not a Json string or if it doesn't exist
     *     and <code>required</code> is true
     */
    public static String getStringFrom(@Nullable Map<String, Object> map, @Nullable String key, boolean required) {
        return JsonUtils.getObjectFrom(String.class, map, key, required);
    }

    /**
     * Retrieves a Json number as a long from the specified Json map given its key.
     *
     * @param map the map to retrieve an entry from
     * @param key the key for the entry to retrieve
     * @param required <code>true</code> if the entry must exist in the map otherwise an error is
     *     generated; <code>false</code> to return <code>null</code> if it doesn't exist
     * @return the corresponding Json number as a long or <code>null</code> if it doesn't exist and
     *     <code>required</code> is <code>false</code>
     * @throws MigrationException if the specified entry is not a Json number or if it doesn't exist
     *     and <code>required</code> is true
     */
    public static Long getLongFrom(@Nullable Map<String, Object> map, @Nullable String key, boolean required) {
        final Number n = JsonUtils.getObjectFrom(Number.class, map, key, required);

        return (n != null) ? n.longValue() : null;
    }

    /**
     * Retrieves a Json boolean from the specified Json map given its key.
     *
     * @param map the map to retrieve an entry from
     * @param key the key for the entry to retrieve
     * @param required <code>true</code> if the entry must exist in the map otherwise an error is
     *     generated; <code>false</code> to return <code>null</code> if it doesn't exist
     * @return the corresponding Json boolean or <code>false</code> if it doesn't exist and <code>
     *     required</code> is <code>false</code>
     * @throws MigrationException if the specified entry is not a Json boolean or if it doesn't exist
     *     and <code>required</code> is true
     */
    public static boolean getBooleanFrom(@Nullable Map<String, Object> map, @Nullable String key,
            boolean required) {
        final Boolean b = JsonUtils.getObjectFrom(Boolean.class, map, key, required);

        return (b != null) ? b : false;
    }

    private static <T> T getObjectFrom(Class<T> clazz, @Nullable Map<String, Object> info, @Nullable String key,
            boolean required) {
        final Object v = (info != null) ? info.get(key) : null;

        if (v == null) {
            if (required) {
                throw new MigrationException(Messages.IMPORT_METADATA_FORMAT_ERROR,
                        String.format("missing required [%s]", key));
            }
            return null;
        }
        if (!clazz.isInstance(v)) {
            throw new MigrationException(Messages.IMPORT_METADATA_FORMAT_ERROR,
                    String.format("[%s] is not a Json %s", key, clazz.getSimpleName().toLowerCase()));
        }
        return clazz.cast(v);
    }

    /** Dummy class used to replace Boon with Gson. */
    public static class ObjectMapper {
        private final Gson gson;

        private ObjectMapper() {
            final CustomizedObjectTypeAdapter adapter = new CustomizedObjectTypeAdapter();

            this.gson = new GsonBuilder().serializeNulls().registerTypeHierarchyAdapter(Map.class, adapter)
                    .registerTypeHierarchyAdapter(List.class, adapter).registerTypeAdapter(Double.class, adapter)
                    .registerTypeAdapter(String.class, adapter).create();
        }

        /**
         * Dummy method used to simulate the old boon library into.
         *
         * @return this
         */
        public ObjectMapper parser() {
            return this;
        }

        /**
         * Serializes any Java value as JSON output, using output stream provided (using encoding UTF8).
         *
         * <p><i>Note:</i> This method does not close the underlying stream explicitly here.
         *
         * @param out the output stream where to write the correspond Json
         * @param value the value to be converted to Json
         * @throws IOException if an I/O error occurred
         */
        public void writeValue(OutputStream out, Object value) throws IOException {
            final OutputStreamWriter writer = new OutputStreamWriter(out);

            gson.toJson(value, writer);
            writer.flush();
        }

        /**
         * Parses the given Json string into a corresponding map.
         *
         * @param json the Json string to be parsed
         * @return the corresponding map
         */
        public Map<String, Object> parseMap(String json) {
            return gson.fromJson(json, new TypeToken<Map<String, Object>>() {
            }.getType());
        }
    }

    /** Gson type adapter used to handle numbers as long the way Boon was doing. */
    private static class CustomizedObjectTypeAdapter extends TypeAdapter<Object> {
        private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

        @Override
        public void write(JsonWriter out, Object value) throws IOException {
            delegate.write(out, value);
        }

        @Override
        public Object read(JsonReader in) throws IOException {
            final JsonToken token = in.peek();

            switch (token) {
            case BEGIN_ARRAY:
                final List<Object> list = new ArrayList<>();

                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;
            case BEGIN_OBJECT:
                final Map<String, Object> map = new LinkedMap<>();

                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;
            case STRING:
                return in.nextString();
            case NUMBER:
                final String n = in.nextString();

                if (n.indexOf('.') == -1) {
                    try {
                        return Long.parseLong(n);
                    } catch (NumberFormatException e) { // ignore and handle it as a double
                    }
                }
                return Double.parseDouble(n);
            case BOOLEAN:
                return in.nextBoolean();
            case NULL:
                in.nextNull();
                return null;
            default:
                throw new IllegalStateException("unknown gson token: " + token);
            }
        }
    }
}