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

com.devonfw.tools.ide.configurator.merge.JsonMerger Maven / Gradle / Ivy

Go to download

Code for configurator the creates or updates configuration of IDEs (Eclipse, etc.).

There is a newer version: 3.0.0-beta25
Show newest version
package com.devonfw.tools.ide.configurator.merge;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonString;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import javax.json.JsonWriter;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;

import com.devonfw.tools.ide.configurator.resolve.VariableResolver;
import com.devonfw.tools.ide.logging.Log;

/**
 * TODO hohwille This type ...
 *
 * @since 3.0.0
 */
public class JsonMerger extends FileTypeMerger {

  @Override
  public void merge(File setupFile, File updateFile, VariableResolver resolver, File workspaceFile) {

    JsonStructure json = null;
    boolean updateFileExists = updateFile.exists();
    if (workspaceFile.exists()) {
      if (!updateFileExists) {
        return; // nothing to do ...
      }
      json = load(workspaceFile);
    } else if (setupFile.exists()) {
      json = load(setupFile);
    }
    JsonStructure mergeJson = null;
    if (updateFileExists) {
      if (json == null) {
        json = load(updateFile);
      } else {
        mergeJson = load(updateFile);
      }
    }
    Status status = new Status();
    JsonStructure result = (JsonStructure) mergeAndResolve(json, mergeJson, resolver, status);
    if (status.updated) {
      save(result, workspaceFile);
      Log.debug("Saving created/updated file: " + workspaceFile.getAbsolutePath());
    } else {
      Log.trace("No changes for file: " + workspaceFile.getAbsolutePath());
    }
  }

  private static JsonStructure load(File file) {

    try (FileInputStream in = new FileInputStream(file);
        Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {

      JsonReader jsonReader = Json.createReader(new BufferedReader(reader));
      return jsonReader.read();
    } catch (Exception e) {
      throw new IllegalStateException("Failed to read JSON from " + file.getPath(), e);
    }
  }

  private static void save(JsonStructure json, File file) {

    ensureParentDirecotryExists(file);
    try (FileOutputStream out = new FileOutputStream(file)) {

      Map config = new HashMap<>();
      config.put(JsonGenerator.PRETTY_PRINTING, Boolean.TRUE);
      // JSON-P API sucks: no way to set the indentation string
      // preferred would be two spaces, implementation has four whitespaces hardcoded
      // See org.glassfish.json.JsonPrettyGeneratorImpl
      // when will they ever learn...?
      JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config);
      JsonWriter jsonWriter = jsonWriterFactory.createWriter(out);
      jsonWriter.write(json);
      jsonWriter.close();
    } catch (Exception e) {
      throw new IllegalStateException("Failed to save JSON to " + file.getPath(), e);
    }
  }

  @Override
  public void inverseMerge(File workspaceFile, VariableResolver resolver, boolean addNewProperties, File updateFile) {

    if (!workspaceFile.exists() || !updateFile.exists()) {
      return;
    }
    JsonStructure updateDocument = load(updateFile);
    JsonStructure workspaceDocument = load(workspaceFile);
    Status status = new Status(addNewProperties);
    JsonStructure result = (JsonStructure) mergeAndResolve(workspaceDocument, updateDocument, resolver, status);
    if (status.updated) {
      save(result, updateFile);
      Log.debug("Saved changes from " + workspaceFile.getName() + " to " + updateFile.getAbsolutePath());
    } else {
      Log.trace("No changes for " + updateFile.getAbsolutePath());
    }
  }

  private JsonValue mergeAndResolve(JsonValue json, JsonValue mergeJson, VariableResolver resolver, Status status) {

    if (json instanceof JsonObject) {
      return mergeAndResolveObject((JsonObject) json, (JsonObject) mergeJson, resolver, status);
    } else if (json instanceof JsonArray) {
      return mergeAndResolveArray((JsonArray) json, (JsonArray) mergeJson, resolver, status);
    } else if (json instanceof JsonString) {
      return mergeAndResolveString((JsonString) json, (JsonString) mergeJson, resolver, status);
    } else if (json instanceof JsonNumber) {
      return mergeAndResolveNumber((JsonNumber) json, (JsonNumber) mergeJson, resolver, status);
    } else if (json == null) {
      if (mergeJson == null) {
        return null;
      } else {
        return mergeAndResolve(mergeJson, null, resolver, status);
      }
    } else {
      Log.err("Undefined JSON type: " + json);
      return null;
    }
  }

  private JsonObject mergeAndResolveObject(JsonObject json, JsonObject mergeJson, VariableResolver resolver,
      Status status) {

    // json = workspace/setup
    // mergeJson = update
    JsonObjectBuilder builder = Json.createObjectBuilder();
    Set mergeKeySet = Collections.emptySet();
    if (mergeJson != null) {
      mergeKeySet = mergeJson.keySet();
      for (String key : mergeKeySet) {
        JsonValue mergeValue = mergeJson.get(key);
        JsonValue value = json.get(key);
        value = mergeAndResolve(value, mergeValue, resolver, status);
        builder.add(key, value);
      }
    }
    if (status.addNewProperties || !status.inverse) {
      for (String key : json.keySet()) {
        if (!mergeKeySet.contains(key)) {
          JsonValue value = json.get(key);
          value = mergeAndResolve(value, null, resolver, status);
          builder.add(key, value);
          if (status.inverse) {
            // added new property on inverse merge...
            status.updated = true;
          }
        }
      }
    }
    return builder.build();
  }

  private JsonArray mergeAndResolveArray(JsonArray json, JsonArray mergeJson, VariableResolver resolver,
      Status status) {

    JsonArrayBuilder builder = Json.createArrayBuilder();
    // KISS: Merging JSON arrays could be very complex. We simply let mergeJson override json...
    JsonArray source = json;
    if (mergeJson != null) {
      source = mergeJson;
    }
    for (JsonValue value : source) {
      JsonValue resolvedValue = mergeAndResolve(value, null, resolver, status);
      builder.add(resolvedValue);
    }
    return builder.build();
  }

  private JsonString mergeAndResolveString(JsonString json, JsonString mergeJson, VariableResolver resolver,
      Status status) {

    JsonString jsonString = json;
    if (mergeJson != null) {
      jsonString = mergeJson;
    }
    String string = jsonString.getString();
    String resolvedString;
    if (status.inverse) {
      resolvedString = resolver.inverseResolve(string);
    } else {
      resolvedString = resolver.resolve(string);
    }
    if (!resolvedString.equals(string)) {
      status.updated = true;
    }
    return Json.createValue(resolvedString);
  }

  private JsonNumber mergeAndResolveNumber(JsonNumber json, JsonNumber mergeJson, VariableResolver resolver,
      Status status) {

    if (mergeJson == null) {
      return json;
    } else {
      return mergeJson;
    }
  }

  private static class Status {

    /** {@code true} for inverse merge, {@code false} otherwise (for regular forward merge). */
    private final boolean inverse;

    private final boolean addNewProperties;

    private boolean updated;

    /**
     * The constructor.
     */
    public Status() {

      this(false, false);
    }

    /**
     * The constructor.
     *
     * @param addNewProperties - {@code true} to add new properties from workspace on reverse merge, {@code false}
     *        otherwise.
     */
    public Status(boolean addNewProperties) {

      this(true, addNewProperties);
    }

    private Status(boolean inverse, boolean addNewProperties) {

      super();
      this.inverse = inverse;
      this.addNewProperties = addNewProperties;
      this.updated = false;
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy