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

tech.jhipster.lite.module.infrastructure.secondary.FileSystemPackageJsonHandler Maven / Gradle / Ivy

There is a newer version: 1.22.0
Show newest version
package tech.jhipster.lite.module.infrastructure.secondary;

import static tech.jhipster.lite.module.domain.JHipsterModule.LINE_BREAK;

import java.io.IOException;
import java.nio.file.*;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import tech.jhipster.lite.module.domain.Indentation;
import tech.jhipster.lite.module.domain.JHipsterModuleContext;
import tech.jhipster.lite.module.domain.file.TemplateRenderer;
import tech.jhipster.lite.module.domain.npm.NpmVersionSource;
import tech.jhipster.lite.module.domain.npm.NpmVersions;
import tech.jhipster.lite.module.domain.packagejson.*;
import tech.jhipster.lite.module.domain.properties.JHipsterProjectFolder;
import tech.jhipster.lite.shared.enumeration.domain.Enums;
import tech.jhipster.lite.shared.error.domain.Assert;
import tech.jhipster.lite.shared.error.domain.GeneratorException;
import tech.jhipster.lite.shared.generation.domain.ExcludeFromGeneratedCodeCoverage;

@Service
class FileSystemPackageJsonHandler {

  private static final String QUOTE = "\"";
  private static final String LINE_END = ",";
  private static final String LINE_SEPARATOR = LINE_END + LINE_BREAK;

  private final NpmVersions npmVersions;
  private final TemplateRenderer templateRenderer;

  public FileSystemPackageJsonHandler(NpmVersions npmVersions, TemplateRenderer templateRenderer) {
    Assert.notNull("npmVersions", npmVersions);
    Assert.notNull("templateRenderer", templateRenderer);

    this.npmVersions = npmVersions;
    this.templateRenderer = templateRenderer;
  }

  public void handle(
    Indentation indentation,
    JHipsterProjectFolder projectFolder,
    JHipsterModulePackageJson packageJson,
    JHipsterModuleContext context
  ) {
    Assert.notNull("indentation", indentation);
    Assert.notNull("projectFolder", projectFolder);
    Assert.notNull("packageJson", packageJson);
    Assert.notNull("context", context);

    if (packageJson.isEmpty()) {
      return;
    }

    Path file = getPackageJsonFile(projectFolder);

    String content = readContent(file);
    content = replaceType(indentation, packageJson.type(), content);
    content = replaceScripts(indentation, packageJson.scripts(), content);
    content = replaceDevDependencies(indentation, packageJson.devDependencies(), content);
    content = replaceDependencies(indentation, packageJson.dependencies(), content);
    content = removeDependencies(indentation, packageJson.dependenciesToRemove(), content);
    content = removeDevDependencies(indentation, packageJson.devDependenciesToRemove(), content);

    content = replacePlaceholders(content, context);
    content = cleanupLineBreaks(indentation, content);

    write(file, content);
  }

  private Path getPackageJsonFile(JHipsterProjectFolder projectFolder) {
    Path file = projectFolder.filePath("package.json");

    if (Files.notExists(file)) {
      throw new MissingPackageJsonException(projectFolder);
    }

    return file;
  }

  private String replacePlaceholders(String content, JHipsterModuleContext context) {
    return templateRenderer.render(content, context);
  }

  private String cleanupLineBreaks(Indentation indentation, String content) {
    return content.replaceAll(",(\\s*|[\r\n]*)}", LINE_BREAK + indentation.spaces() + "}");
  }

  private String replaceScripts(Indentation indentation, Scripts scripts, String content) {
    return JsonAction.replace().blockName("scripts").jsonContent(content).indentation(indentation).entries(scriptEntries(scripts)).apply();
  }

  private List scriptEntries(Scripts scripts) {
    return scripts.stream().map(script -> new JsonEntry(script.key().get(), script.command().get())).toList();
  }

  private String replaceDevDependencies(Indentation indentation, PackageJsonDependencies devDependencies, String content) {
    return JsonAction.replace()
      .blockName("devDependencies")
      .jsonContent(content)
      .indentation(indentation)
      .entries(dependenciesEntries(devDependencies))
      .apply();
  }

  private String removeDevDependencies(Indentation indentation, PackageNames dependenciesToRemove, String content) {
    return JsonAction.remove()
      .blockName("devDependencies")
      .jsonContent(content)
      .indentation(indentation)
      .entries(dependenciesEntries(dependenciesToRemove))
      .apply();
  }

  private String replaceDependencies(Indentation indentation, PackageJsonDependencies dependencies, String content) {
    return JsonAction.replace()
      .blockName("dependencies")
      .jsonContent(content)
      .indentation(indentation)
      .entries(dependenciesEntries(dependencies))
      .apply();
  }

  private String removeDependencies(Indentation indentation, PackageNames dependenciesToRemove, String content) {
    return JsonAction.remove()
      .blockName("dependencies")
      .jsonContent(content)
      .indentation(indentation)
      .entries(dependenciesEntries(dependenciesToRemove))
      .apply();
  }

  private String replaceType(Indentation indentation, PackageJsonType packageJsonType, String content) {
    return JsonAction.replace().blockName("type").jsonContent(content).indentation(indentation).blockValue(packageJsonType.type()).apply();
  }

  private List dependenciesEntries(PackageJsonDependencies dependencies) {
    return dependencies.stream().map(dependency -> new JsonEntry(dependency.packageName().get(), getNpmVersion(dependency))).toList();
  }

  private Collection dependenciesEntries(PackageNames packageNames) {
    return packageNames.stream().map(packageName -> new JsonEntry(packageName.get(), "")).toList();
  }

  private String getNpmVersion(PackageJsonDependency dependency) {
    PackageName packageName = dependency.versionPackageName().orElse(dependency.packageName());
    return npmVersions.get(packageName.get(), Enums.map(dependency.versionSource(), NpmVersionSource.class)).get();
  }

  @ExcludeFromGeneratedCodeCoverage(reason = "The error handling is an hard to test implementation detail")
  private String readContent(Path file) {
    try {
      return Files.readString(file);
    } catch (IOException e) {
      throw GeneratorException.technicalError("Error reading " + file.toAbsolutePath() + " content" + e.getMessage(), e);
    }
  }

  @ExcludeFromGeneratedCodeCoverage(reason = "The error handling is an hard to test implementation detail")
  private void write(Path file, String content) {
    try {
      Files.writeString(file, content, StandardOpenOption.TRUNCATE_EXISTING);
    } catch (IOException e) {
      throw GeneratorException.technicalError("Error writing " + file.toAbsolutePath() + ": " + e.getMessage(), e);
    }
  }

  private static final class JsonAction {

    private final String blockName;
    private final String jsonContent;
    private final Indentation indentation;
    private final Collection entries;
    private final JsonActionType action;
    private final String blockValue;

    private JsonAction(JsonActionBuilder builder) {
      blockName = builder.blockName;
      jsonContent = builder.jsonContent;
      indentation = builder.indentation;
      entries = builder.entries;
      action = builder.action;
      blockValue = builder.blockValue;
    }

    public static JsonActionBuilder replace() {
      return new JsonActionBuilder(JsonActionType.REPLACE);
    }

    public static JsonActionBuilder remove() {
      return new JsonActionBuilder(JsonActionType.REMOVE);
    }

    public String handle() {
      Assert.notNull("action", action);

      if (blockValue != null) {
        return appendNewRootEntry(jsonContent);
      }

      if (entries == null || entries.isEmpty()) {
        return jsonContent;
      }

      return switch (action) {
        case REPLACE -> replaceEntries();
        case REMOVE -> removeEntries();
      };
    }

    private String replaceEntries() {
      String result = removeExistingEntries();

      Matcher blockMatcher = buildBlockMatcher(result);

      if (blockMatcher.find()) {
        result = appendEntries(blockMatcher);
      } else {
        result = appendNewBlock(result);
      }

      return result;
    }

    private String removeEntries() {
      return removeExistingEntries();
    }

    private String appendEntries(Matcher blockMatcher) {
      return blockMatcher.replaceFirst(match -> {
        StringBuilder result = new StringBuilder().append(match.group(1)).append(LINE_BREAK);

        result.append(entriesBlock(indentation, entries));

        result.append(LINE_END);
        return result.toString();
      });
    }

    private String appendNewRootEntry(String result) {
      String jsonBlock = new StringBuilder()
        .append(LINE_SEPARATOR)
        .append(indentation.spaces())
        .append(QUOTE)
        .append(blockName)
        .append(QUOTE)
        .append(": ")
        .append(QUOTE)
        .append(blockValue)
        .append(QUOTE)
        .toString();

      return result.replaceFirst("(\\s{1,10})}(\\s{1,10})$", jsonBlock + "$1}$2");
    }

    private String appendNewBlock(String result) {
      String jsonBlock = new StringBuilder()
        .append(LINE_SEPARATOR)
        .append(indentation.spaces())
        .append(QUOTE)
        .append(blockName)
        .append(QUOTE)
        .append(": {")
        .append(LINE_BREAK)
        .append(entriesBlock(indentation, entries))
        .append(LINE_BREAK)
        .append(indentation.spaces())
        .append("}")
        .toString();

      return result.replaceFirst("(\\s{1,10})}(\\s{1,10})$", jsonBlock + "$1}$2");
    }

    private Matcher buildBlockMatcher(String result) {
      return Pattern.compile("(\"" + blockName + "\"\\s*:\\s*\\{)").matcher(result);
    }

    @ExcludeFromGeneratedCodeCoverage(reason = "Combiner can't be tested and an implementation detail")
    private String removeExistingEntries() {
      return entries
        .stream()
        .map(toEntryPattern(blockName, indentation))
        .reduce(jsonContent, (json, entryPattern) -> entryPattern.matcher(json).replaceAll("$1$2"), (first, second) -> first);
    }

    private Function toEntryPattern(String blockName, Indentation indentation) {
      return entry -> {
        String pattern = new StringBuilder()
          .append("(\"")
          .append(blockName)
          .append("\"\\s*:\\s*\\{)([^}]*)(\"")
          .append(entry.key())
          .append("\"\\s*:\\s*\"[^\\r\\n]+[\\r\\n]{1,2}(\\s{")
          .append(indentation.spacesCount() * 2)
          .append("})?)")
          .toString();

        return Pattern.compile(pattern, Pattern.DOTALL);
      };
    }

    private String entriesBlock(Indentation indentation, Collection entries) {
      return entries.stream().map(entry -> entry.toJson(indentation)).collect(Collectors.joining(LINE_SEPARATOR));
    }

    private static final class JsonActionBuilder {

      private String blockName;
      private String jsonContent;
      private Indentation indentation;
      private Collection entries;
      private final JsonActionType action;
      private String blockValue;

      private JsonActionBuilder(JsonActionType action) {
        this.action = action;
      }

      private JsonActionBuilder blockName(String blockName) {
        this.blockName = blockName;

        return this;
      }

      private JsonActionBuilder jsonContent(String jsonContent) {
        this.jsonContent = jsonContent;

        return this;
      }

      private JsonActionBuilder indentation(Indentation indentation) {
        this.indentation = indentation;

        return this;
      }

      private JsonActionBuilder entries(Collection entries) {
        this.entries = entries;

        return this;
      }

      private String apply() {
        return new JsonAction(this).handle();
      }

      public JsonActionBuilder blockValue(String blockValue) {
        this.blockValue = blockValue;

        return this;
      }
    }
  }

  private enum JsonActionType {
    REPLACE,
    REMOVE,
  }

  private record JsonEntry(String key, String value) {
    String toJson(Indentation indentation) {
      return new StringBuilder()
        .append(indentation.times(2))
        .append(QUOTE)
        .append(key())
        .append(QUOTE)
        .append(": ")
        .append(QUOTE)
        .append(value())
        .append(QUOTE)
        .toString();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy