All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.balajeetm.mystique.core.lever.MystiqueLever Maven / Gradle / Ivy

/*
 * Copyright (c) Balajee TM 2016.
 * All rights reserved.
 * License -  @see 
 */

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

import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.balajeetm.mystique.core.MystTurn;
import com.balajeetm.mystique.core.MystiqueFactory;
import com.balajeetm.mystique.core.util.MystiqueConstants;
import com.balajeetm.mystique.util.gson.lever.JsonLever;
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;

import lombok.extern.slf4j.Slf4j;

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

/** The Constant log. */
@Slf4j
public class MystiqueLever extends JsonLever {

  /** The loopy pat. */
  private static String loopyPat = "^\\[\\*\\]$";

  /** The ace pat. */
  private static String acePat = "^@ace\\(([a-zA-Z_0-9$#@!%^&*]+)\\)$";

  /** The value pat. */
  private static String valuePat = "^@value\\(([a-zA-Z_0-9, $#@!%^&*]+)\\)$";

  /** The loopy pattern. */
  private Pattern loopyPattern;

  /** The ace pattern. */
  private Pattern acePattern;

  /** The ace pattern. */
  private Pattern valuePattern;

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

  /** Instantiates a new mystique lever. */
  private MystiqueLever() {
    loopyPattern = Pattern.compile(loopyPat);
    acePattern = Pattern.compile(acePat);
    valuePattern = Pattern.compile(valuePat);
  }

  /**
   * Factory.
   *
   * @return the mystique factory
   */
  private MystiqueFactory factory() {
    if (null == factory) {
      factory = MystiqueFactory.getInstance();
    }
    return factory;
  }

  /**
   * Gets the single instance of MystiqueLever.
   *
   * @return single instance of MystiqueLever
   */
  public static MystiqueLever 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 MystiqueLever INSTANCE = new MystiqueLever();
  }

  /**
   * Checks if is loopy.
   *
   * @param path the path
   * @return the boolean
   */
  private Boolean isLoopy(String path) {
    Boolean loopy = Boolean.FALSE;
    Matcher matcher = loopyPattern.matcher(path);
    while (matcher.find()) {
      loopy = Boolean.TRUE;
    }
    return loopy;
  }

  /**
   * Update fields.
   *
   * @param source the source
   * @param dependencies the dependencies
   * @param aces the aces
   * @param fields the fields
   * @param path the path
   * @return the boolean
   */
  public Boolean updateFields(
      JsonElement source,
      JsonObject dependencies,
      JsonObject aces,
      List fields,
      JsonArray path) {
    Boolean isLoopy = Boolean.FALSE;
    try {
      JsonElement field = source;
      if (null != path) {
        if (path.size() > 0) {
          try {
            for (int count = 0; count < path.size(); count++) {
              JsonElement p = path.get(count);
              if (isNumber(p)) {
                field = get(field, p);
              } else {
                String key = asString(p);
                if (count == 0 && MystiqueConstants.AT_DEPS.equals(key)) {
                  field = dependencies;
                  continue;
                }
                String ace = getAce(key);
                if (null != ace) {
                  field = aces.get(ace);
                  continue;
                }
                if (isLoopy(key)) {
                  isLoopy = Boolean.TRUE;
                  fields.clear();
                  break;
                } else {
                  p = getPathField(p, aces);
                  field = get(field, p);
                }
              }
            }
          } catch (IllegalStateException e) {
            log.info(
                String.format("Invalid json path %s for %s : %s", path, source, e.getMessage()), e);
            field = JsonNull.INSTANCE;
          }
        }
        fields.add(field);
      }
    }
    /** Would throw an exception for any invalid path, which is logged and ignored */
    catch (RuntimeException e) {
      log.warn(
          String.format(
              "Error getting field from source for %s - %s. Skipping the same",
              path, e.getMessage()),
          e);
    }
    return isLoopy;
  }

  /**
   * Gets the subset.
   *
   * @param source the source
   * @param deps the deps
   * @param aces the aces
   * @param valueObject the value object
   * @return the subset
   */
  public JsonElement subset(
      JsonElement source, JsonObject deps, JsonObject aces, JsonElement valueObject) {
    JsonElement finalValue = null;
    if (valueObject.isJsonArray()) {
      finalValue = subset(source, deps, aces, valueObject.getAsJsonArray());
    } else if (isString(valueObject)) {
      finalValue = subset(source, deps, aces, getJPath(valueObject));
    } else if (isJsonObject(valueObject)) {
      // This is a turn
      JsonObject valueJson = valueObject.getAsJsonObject();
      MystTurn mystique = factory().getMystTurn(valueJson);
      finalValue =
          mystique.transform(Lists.newArrayList(source), deps, aces, valueJson, new JsonObject());
    }
    return finalValue;
  }

  /**
   * Gets the updated aces.
   *
   * @param source the source
   * @param aces the aces
   * @param dependencies the dependencies
   * @param updated the updated
   * @return the updated aces
   */
  public JsonObject getUpdatedAces(
      JsonElement source, JsonObject aces, JsonObject dependencies, JsonObject updated) {
    if (isNotNull(aces)) {
      for (Entry entry : aces.entrySet()) {
        JsonObject value = asJsonObject(entry.getValue());
        // Null check required, since for all other purposes, no turn
        // means a default turn. In this case, turn needs to be executed
        // only if it is explicitly specified
        MystTurn mystique = null != value ? factory().getMystTurn(value) : null;
        if (null != mystique) {
          JsonElement transform =
              mystique.transform(
                  Lists.newArrayList(source), dependencies, aces, value, new JsonObject());
          updated.add(entry.getKey(), transform);
        }
      }
    }
    return updated;
  }

  /**
   * Gets the updated aces.
   *
   * @param source the source
   * @param aces the aces
   * @param dependencies the dependencies
   * @return the updated aces
   */
  protected JsonObject getUpdatedAces(
      JsonElement source, JsonObject aces, JsonObject dependencies) {
    return getUpdatedAces(source, aces, dependencies, aces);
  }

  /**
   * Gets the ace.
   *
   * @param path the path
   * @return the ace
   */
  private String getAce(String path) {
    String index = null;
    Matcher matcher = acePattern.matcher(path);
    while (matcher.find()) {
      index = matcher.group(1);
    }
    return index;
  }

  /**
   * Gets the ace value.
   *
   * @param path the path
   * @return the ace value
   */
  private String getAceValue(String path) {
    String index = null;
    Matcher matcher = valuePattern.matcher(path);
    while (matcher.find()) {
      index = matcher.group(1);
    }
    return index;
  }

  /**
   * Gets the processed path field from an ace.
   *
   * @param field the string representing part of the json path which may include processing of an
   *     ace via @value notations
   * @param aces the pre-processed dependency list in the form of key value pair (json)
   * @return the processed json path field string
   */
  private JsonElement getPathField(JsonElement field, JsonObject aces) {
    if (isNumber(field)) {
      return field;
    } else {
      String ace = getAceValue(asString(field));
      JsonElement output = field;
      if (null != ace) {
        JsonArray newJsonArray = newJsonArray(ace.split(","));
        output = get(aces, newJsonArray);
      }
      return output;
    }
  }

  /**
   * Subset.
   *
   * @param source the source
   * @param deps the deps
   * @param aces the aces
   * @param jPathArray the j path array
   * @return the json element
   */
  private JsonElement subset(
      JsonElement source, JsonObject deps, JsonObject aces, JsonArray jPathArray) {
    JsonElement finalValue = null;
    if (jPathArray.size() == 0) {
      finalValue = getField(source, jPathArray, deps, aces);
    } else {
      JsonElement first = getFirst(jPathArray);
      if (isJsonArray(first)) {
        finalValue = new JsonObject();
        for (JsonElement jsonElement : jPathArray) {
          JsonArray pathArray = asJsonArray(jsonElement);
          JsonElement subset = getField(source, pathArray, deps, aces);
          set(finalValue.getAsJsonObject(), pathArray, subset, aces);
        }
        finalValue = finalValue.getAsJsonObject().get(MystiqueConstants.RESULT);
      } else {
        finalValue = getField(source, jPathArray, deps, aces);
      }
    }
    return finalValue;
  }

  /**
   * Gets the field.
   *
   * @param source the source
   * @param jPath the j path
   * @param deps the deps
   * @param aces the aces
   * @return the field
   */
  public JsonElement getField(
      JsonElement source, JsonArray jPath, JsonObject deps, JsonObject aces) {
    JsonElement field = null;
    try {
      field = source;
      if (null != jPath) {
        for (int count = 0; count < jPath.size(); count++) {
          JsonElement path = jPath.get(count);
          if (isNumber(path)) {
            field = get(field, path);
          } else {
            String key = asString(path);
            if (count == 0 && MystiqueConstants.AT_DEPS.equals(key)) {
              field = deps;
              continue;
            }
            String ace = getAce(key);
            if (null != ace) {
              field = aces.get(ace);
              continue;
            }
            path = getPathField(path, aces);
            field = get(field, path);
          }
        }
      }
    }
    /** Would throw an exception for any invalid path, which is logged and ignored */
    catch (RuntimeException e) {
      log.warn(
          String.format(
              "Error getting field from source for %s - %s. Skipping the same",
              jPath, e.getMessage()),
          e);
      field = null;
    }
    return field;
  }

  /**
   * Sets the field of a json source.
   *
   * @param resultWrapper the json object that wraps the result json. The result is wrapped to
   *     ensure it passed across by reference and fields are updated appropriately
   * @param to the jPath json array defining the full qualified json path to the destination field
   *     where the value must be set
   * @param value the json value that needs to be set to the destination. This can be an Object,
   *     Array or a Primitive
   * @param aces the pre-processed dependency list in the form of key value pair (json)
   * @return the json result wraper object which contains the result in the field called "result"
   */
  public JsonObject set(
      JsonObject resultWrapper, JsonArray to, JsonElement value, JsonObject aces) {
    return set(resultWrapper, to, value, aces, Boolean.FALSE);
  }

  /**
   * Sets the field of a json source.
   *
   * @param resultWrapper the json object that wraps the result json. The result is wrapped to
   *     ensure it passed across by reference and fields are updated appropriately
   * @param to the jPath json array defining the full qualified json path to the destination field
   *     where the value must be set
   * @param value the json value that needs to be set to the destination. This can be an Object,
   *     Array or a Primitive
   * @param aces the pre-processed dependency list in the form of key value pair (json)
   * @param optional the flag that determines if a null value must be set in the destination. If
   *     optional is TRUE, null values are not set in the destination
   * @return the json result wraper object which contains the result in the field called "result"
   */
  public JsonObject set(
      JsonObject resultWrapper,
      JsonArray to,
      JsonElement value,
      JsonObject aces,
      Boolean optional) {

    /**
     * Holding a mutex on result wrapper and not making the method synchronized because, when
     * multiple unrelated threads might be calling mystique for transformation. The resource of
     * contention is only the result wrapper
     */
    synchronized (resultWrapper) {
      if (optional && isNull(value)) {
        // Do Not update result wrapper
        return resultWrapper;
      }
      if (isNotNull(to)) {
        JsonElement result = resultWrapper.get(MystiqueConstants.RESULT);
        JsonElement field = result;
        if (to.size() > 0) {
          JsonElement previousPath = null;
          JsonElement currentPath = null;
          Iterator iterator = to.iterator();
          if (iterator.hasNext()) {
            previousPath = getPathField(iterator.next(), aces);
          }

          while (iterator.hasNext()) {
            currentPath = getPathField(iterator.next(), aces);

            // get the field
            field = getRepleteField(field, previousPath, currentPath);
            result = updateResult(result, field);
            field =
                isNumber(previousPath)
                    ? field.getAsJsonArray().get(previousPath.getAsInt())
                    : field.getAsJsonObject().get(previousPath.getAsString());
            previousPath = currentPath;
          }

          field = setField(field, previousPath, value);
          result = updateResult(result, field);
        } else {
          result = merge(result, value);
        }
        resultWrapper.add(MystiqueConstants.RESULT, result);
      }
      return resultWrapper;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy