com.balajeetm.mystique.core.JsonMystique.java Source code

Java tutorial

Introduction

Here is the source code for com.balajeetm.mystique.core.JsonMystique.java

Source

/*
 * Copyright (c) Balajee TM 2016.
 * All rights reserved.
 * License -  @see <a href="http://www.apache.org/licenses/LICENSE-2.0"></a>
 */

/*
 * Created on 25 Aug, 2016 by balajeetm
 * http://www.balajeetm.com
 */
package com.balajeetm.mystique.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.balajeetm.mystique.core.lever.MystiqueLever;
import com.balajeetm.mystique.core.util.MystiqueConstants;
import com.balajeetm.mystique.util.gson.convertor.GsonConvertor;
import com.balajeetm.mystique.util.json.convertor.JsonConvertor;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;

import lombok.extern.slf4j.Slf4j;

/**
 * The Class JsonMystique.
 *
 * @author balajeetm
 */

/** The Constant log. */
@Slf4j
public class JsonMystique {

    /** The factory. */
    private MystiqueFactory factory;

    /** The json lever. */
    private MystiqueLever jsonLever;

    /** The gson convertor. */
    private JsonConvertor gsonConvertor;

    /** The mystiques. */
    private Map<String, Map<String, List<Tarot>>> mystiques;

    /** Instantiates a new json genie. */
    private JsonMystique() {
        mystiques = new HashMap<>();
        gsonConvertor = GsonConvertor.getInstance();
        jsonLever = MystiqueLever.getInstance();
        factory = MystiqueFactory.getInstance();
        init();
    }

    /**
     * Gets the single instance of JsonMystique.
     *
     * @return single instance of JsonMystique
     */
    public static JsonMystique getInstance() {
        return Creator.INSTANCE;
    }

    // Efficient Thread safe Lazy Initialization
    // works only if the singleton constructor is non parameterized
    /** The Class Creator. */
    private static class Creator {

        /** The instance. */
        private static JsonMystique INSTANCE = new JsonMystique();
    }

    /** Inits the. */
    protected void init() {
        /* get all mystique rule files in the classpath */
        String locationPattern = "classpath*:jsonmystique/**/*.mys";
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = null;
        try {
            resources = resourceResolver.getResources(locationPattern);
        } catch (IOException e) {
            log.info(String.format("Error loading mystiques from %s - %s", locationPattern, e.getMessage()), e);
            return;
        }

        if (!ArrayUtils.isEmpty(resources)) {
            for (Resource resource : resources) {
                if (resource.exists()) {
                    String specName = FilenameUtils.removeExtension(resource.getFilename());
                    try {
                        List<Tarot> tarotList = gsonConvertor.deserializeList(resource.getInputStream(),
                                Tarot.class);
                        Map<String, List<Tarot>> tarotMap = new HashMap<>();
                        List<Tarot> depsList = new ArrayList<>();
                        List<Tarot> ruleList = new ArrayList<>();
                        tarotMap.put(MystiqueConstants.DEPS, depsList);
                        tarotMap.put(MystiqueConstants.TAROTS, ruleList);
                        mystiques.put(specName, tarotMap);
                        for (Tarot tarot : tarotList) {
                            List<Tarot> deps = tarot.getDeps();
                            if (CollectionUtils.isNotEmpty(deps)) {
                                depsList.addAll(deps);
                            }
                            tarot.setDeps(null);
                            ruleList.add(tarot);
                        }

                    } catch (JsonSyntaxException | IllegalArgumentException | IOException exception) {
                        log.info(String.format(
                                "Unable to load mystiques %s from %s - %s. Trying to load other mystiques if any",
                                specName, locationPattern, exception.getMessage()), exception);
                        continue;
                    }
                }
            }
        } else {
            log.warn(String.format("No mystiques found @ %s for transformation", locationPattern));
        }
    }

    /**
     * Transform.
     *
     * @param inputJson the input json
     * @param specName the spec name
     * @return the json element
     */
    public JsonElement transform(String inputJson, String specName) {
        JsonElement source = jsonLever.getJsonParser().parse(inputJson);
        return transform(source, specName);
    }

    /**
     * Transform.
     *
     * @param source the source
     * @param specName the spec name
     * @return the json element
     */
    public JsonElement transform(JsonElement source, String specName) {
        return transform(source, specName, new JsonObject());
    }

    /**
     * Transform.
     *
     * @param inputJson the input json
     * @param specName the spec name
     * @param deps the deps
     * @return the json element
     */
    public JsonElement transform(String inputJson, String specName, JsonObject deps) {
        JsonElement source = jsonLever.getJsonParser().parse(inputJson);
        return transform(source, specName, deps);
    }

    /**
     * Transform.
     *
     * @param source the source
     * @param specName the spec name
     * @param deps the deps
     * @return the json element
     */
    public JsonElement transform(JsonElement source, String specName, JsonObject deps) {
        return transform(source, specName, deps, null);
    }

    /**
     * Transform.
     *
     * @param source the source
     * @param specName the spec name
     * @param deps the deps
     * @param aces the aces
     * @return the json element
     */
    public JsonElement transform(JsonElement source, String specName, JsonObject deps, JsonObject aces) {
        JsonElement transform = JsonNull.INSTANCE;
        Map<String, List<Tarot>> map = mystiques.get(specName);
        deps = jsonLever.asJsonObject(deps, new JsonObject());
        if (null != map) {
            List<Tarot> depList = map.get(MystiqueConstants.DEPS);
            updateDependencies(source, depList, deps);
            List<Tarot> tarotList = map.get(MystiqueConstants.TAROTS);
            transform = transform(source, tarotList, deps, aces);
        }

        if (jsonLever.isNull(transform)) {
            log.info(String.format("Transformed value for spec %s is null", specName));
        }
        return transform;
    }

    /**
     * Transform to string.
     *
     * @param inputJson the input json
     * @param specName the spec name
     * @return the string
     */
    public String transformToString(String inputJson, String specName) {
        return String.valueOf(transform(inputJson, specName));
    }

    /**
     * Transform to string.
     *
     * @param inputJson the input json
     * @param specName the spec name
     * @param deps the deps
     * @return the string
     */
    public String transformToString(String inputJson, String specName, JsonObject deps) {
        return String.valueOf(transform(inputJson, specName, deps));
    }

    /**
     * Transform to string.
     *
     * @param source the source
     * @param specName the spec name
     * @return the string
     */
    public String transformToString(JsonElement source, String specName) {
        return String.valueOf(transform(source, specName));
    }

    /**
     * Transform to string.
     *
     * @param source the source
     * @param specName the spec name
     * @param deps the deps
     * @return the string
     */
    public String transformToString(JsonElement source, String specName, JsonObject deps) {
        return String.valueOf(transform(source, specName, deps));
    }

    /**
     * Transform.
     *
     * @param source the source
     * @param tarotList the tarot list
     * @param dependencies the dependencies
     * @return the json element
     */
    private JsonElement transform(JsonElement source, List<Tarot> tarotList, JsonObject dependencies) {
        return transform(source, tarotList, dependencies, null);
    }

    /**
     * Transform.
     *
     * @param source the source
     * @param tarotList the tarot list
     * @param dependencies the dependencies
     * @param parentAces the parent aces
     * @return the json element
     */
    private JsonElement transform(JsonElement source, List<Tarot> tarotList, JsonObject dependencies,
            JsonObject parentAces) {
        JsonObject resultWrapper = new JsonObject();
        resultWrapper.add(MystiqueConstants.RESULT, JsonNull.INSTANCE);
        if (CollectionUtils.isNotEmpty(tarotList)) {
            List<CompletableFuture<JsonObject>> cfs = new ArrayList<>();
            for (Tarot tarot : tarotList) {
                JsonObject turn = tarot.getTurn();

                CompletableFuture<JsonObject> getAces = CompletableFuture.supplyAsync(() -> {
                    JsonObject aces = tarot.getAces();
                    JsonObject updatedAces = jsonLever.getUpdatedAces(source, aces, dependencies,
                            jsonLever.deepClone(aces));
                    jsonLever.simpleMerge(updatedAces, parentAces);
                    return updatedAces;
                }).exceptionally(e -> {
                    String msg = String.format("Error updating aces for turn %s - %s", turn, e.getMessage());
                    log.info(msg, e);
                    return parentAces;
                });

                CompletableFuture<JsonElement> transformAsync = getAces.thenApplyAsync((aces) -> {
                    JsonElement transform = JsonNull.INSTANCE;
                    Spell spell = getSpell(source, jsonLever.getJpath(tarot.getFrom()), dependencies, aces, turn,
                            resultWrapper);
                    MystTurn mystique = factory.getMystTurn(turn);
                    transform = spell.cast(mystique);
                    return transform;
                }).exceptionally((e) -> {
                    String msg = String.format("Error transforming input with specification for turn %s - %s", turn,
                            e.getMessage());
                    log.info(msg, e);
                    return JsonNull.INSTANCE;
                });

                CompletableFuture<JsonObject> setResult = getAces
                        .thenCombine(transformAsync, (aces, transform) -> jsonLever.set(resultWrapper,
                                jsonLever.getJpath(tarot.getTo()), transform, aces, tarot.getOptional()))
                        .exceptionally(e -> {
                            String msg = String.format("Error setting output for turn %s - %s", turn,
                                    e.getMessage());
                            log.info(msg, e);
                            return resultWrapper;
                        });

                cfs.add(setResult);
            }

            for (CompletableFuture<JsonObject> completableFuture : cfs) {
                completableFuture.join();
            }
        } else {
            log.info(String.format("Invalid tarots. Tarots cannot be empty"));
        }
        return resultWrapper.get(MystiqueConstants.RESULT);
    }

    /**
     * Gets the spell.
     *
     * @param source the source
     * @param from the from
     * @param dependencies the dependencies
     * @param aces the aces
     * @param turn the turn
     * @param resultWrapper the result wrapper
     * @return the spell
     */
    private Spell getSpell(JsonElement source, JsonArray from, JsonObject dependencies, JsonObject aces,
            JsonObject turn, JsonObject resultWrapper) {
        List<JsonElement> fields = new ArrayList<>();
        Boolean isFromLoopy = getFields(source, dependencies, aces, from, fields);
        // Ideally isDeps should never be loopy
        Spell spell = isFromLoopy ? new LoopySpell(fields, dependencies, aces, turn, resultWrapper)
                : new SimpleSpell(fields, dependencies, aces, turn, resultWrapper);
        return spell;
    }

    /**
     * Gets the fields.
     *
     * @param source the source
     * @param dependencies the dependencies
     * @param aces the aces
     * @param path the path
     * @param fields the fields
     * @return the fields
     */
    private Boolean getFields(JsonElement source, JsonObject dependencies, JsonObject aces, JsonArray path,
            List<JsonElement> fields) {
        Boolean isLoopy = Boolean.FALSE;
        if (null != path) {
            if (path.size() > 0) {
                for (JsonElement jsonElement : path) {
                    if (jsonElement.isJsonArray()) {
                        JsonArray fromArray = jsonElement.getAsJsonArray();
                        isLoopy = isLoopy || jsonLever.updateFields(source, dependencies, aces, fields, fromArray);
                        // Once isloopy, the loop doesn't execute anymore
                    } else {
                        isLoopy = isLoopy || jsonLever.updateFields(source, dependencies, aces, fields, path);
                        break;
                    }
                }
            } else {
                isLoopy = isLoopy || jsonLever.updateFields(source, dependencies, aces, fields, path);
            }
        }
        return isLoopy;
    }

    /**
     * Update dependencies.
     *
     * @param source the source
     * @param deps the deps
     * @param dependencies the dependencies
     */
    private void updateDependencies(JsonElement source, List<Tarot> deps, JsonObject dependencies) {
        if (CollectionUtils.isNotEmpty(deps)) {
            try {
                JsonObject transformJson = jsonLever.asJsonObject(transform(source, deps, dependencies),
                        new JsonObject());
                jsonLever.simpleMerge(dependencies, transformJson);
            } catch (RuntimeException e) {
                log.info(String.format("Could not update dependencies : %s", e.getMessage()));
            }
        }
    }

    /**
     * Register a custom turn.
     *
     * @param turn the turn
     */
    public void register(MystTurn turn) {
        factory.register(turn);
    }
}