content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.camel.quarkus.maven;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.Reader;
  import java.net.URL;
  import java.nio.charset.Charset;
  import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.Collection;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Set;
  import java.util.function.Predicate;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  import freemarker.cache.ClassTemplateLoader;
  import freemarker.cache.FileTemplateLoader;
  import freemarker.cache.MultiTemplateLoader;
  import freemarker.cache.TemplateLoader;
  import freemarker.template.Configuration;
  import freemarker.template.TemplateExceptionHandler;
  import org.apache.camel.tooling.model.ArtifactModel;
  import org.apache.camel.tooling.model.ComponentModel;
  import org.apache.camel.tooling.model.DataFormatModel;
  import org.apache.camel.tooling.model.DevConsoleModel;
  import org.apache.camel.tooling.model.JsonMapper;
  import org.apache.camel.tooling.model.Kind;
  import org.apache.camel.tooling.model.LanguageModel;
  import org.apache.camel.tooling.model.OtherModel;
  import org.apache.camel.tooling.model.PojoBeanModel;
  import org.apache.maven.model.Model;
  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
  import org.apache.maven.plugin.logging.Log;
  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
  import org.l2x6.maven.utils.MavenSourceTree.Module;
  
  public class CqUtils {
      public static final String CLASSPATH_PREFIX = "classpath:";
      public static final String FILE_PREFIX = "file:";
      private static final String NAME_SUFFIX = " :: Runtime";
  
      static TemplateLoader createTemplateLoader(Path basePath, String defaultUriBase, String templatesUriBase) {
          final TemplateLoader defaultLoader = new ClassTemplateLoader(CqUtils.class,
                  defaultUriBase.substring(CLASSPATH_PREFIX.length()));
          if (defaultUriBase.equals(templatesUriBase)) {
              return defaultLoader;
          } else if (templatesUriBase.startsWith(CLASSPATH_PREFIX)) {
              return new MultiTemplateLoader( //
                      new TemplateLoader[] { //
                              new ClassTemplateLoader(CqUtils.class,
                                      templatesUriBase.substring(CLASSPATH_PREFIX.length())), //
                              defaultLoader //
                      });
          } else if (templatesUriBase.startsWith(FILE_PREFIX)) {
              final Path resolvedTemplatesDir = basePath.resolve(templatesUriBase.substring(FILE_PREFIX.length()));
              try {
                  return new MultiTemplateLoader( //
                          new TemplateLoader[] { //
                                  new FileTemplateLoader(resolvedTemplatesDir.toFile()),
                                  defaultLoader //
                          });
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          } else {
              throw new IllegalStateException(String.format(
                      "Cannot handle templatesUriBase '%s'; only value starting with '%s' or '%s' are supported",
                      templatesUriBase, CLASSPATH_PREFIX, FILE_PREFIX));
          }
      }
  
      public static Stream<ExtensionModule> findExtensions(Path basePath, Collection<Module> modules,
              Predicate<String> artifactIdBaseFilter) {
          return modules.stream()
                  .filter(p -> p.getGav().getArtifactId().asConstant().endsWith("-deployment"))
                  .map(p -> {
                      final Path extensionDir = basePath.resolve(p.getPomPath()).getParent().getParent().toAbsolutePath()
                              .normalize();
                      final String deploymentArtifactId = p.getGav().getArtifactId().asConstant();
                      if (!deploymentArtifactId.startsWith("camel-quarkus-")) {
                          throw new IllegalStateException("Should start with 'camel-quarkus-': " + deploymentArtifactId);
                      }
                      final String artifactIdBase = deploymentArtifactId.substring("camel-quarkus-".length(),
                              deploymentArtifactId.length() - "-deployment".length());
                      return new ExtensionModule(extensionDir, artifactIdBase);
                  })
                  .filter(e -> artifactIdBaseFilter.test(e.getArtifactIdBase()))
                  .sorted();
      }
  
      public static Stream<String> findExtensionArtifactIdBases(Path extensionDir) {
          final Path extListPomPath = extensionDir.resolve("pom.xml");
          final List<String> modules;
          try (Reader r = Files.newBufferedReader(extListPomPath, StandardCharsets.UTF_8)) {
              final MavenXpp3Reader rxppReader = new MavenXpp3Reader();
              final Model extListPom = rxppReader.read(r);
              modules = extListPom.getModules();
          } catch (IOException | XmlPullParserException e) {
              throw new RuntimeException("Could not read " + extListPomPath);
          }
  
          return modules.stream()
                  .filter(m -> Files.isRegularFile(extensionDir.resolve(m + "/pom.xml"))
                          && Files.isDirectory(extensionDir.resolve(m + "/runtime")));
      }
  
      public static Configuration getTemplateConfig(Path basePath, String defaultUriBase, String templatesUriBase,
              String encoding) {
          final Configuration templateCfg = new Configuration(Configuration.VERSION_2_3_28);
          templateCfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
          templateCfg.setTemplateLoader(createTemplateLoader(basePath, defaultUriBase, templatesUriBase));
          templateCfg.setDefaultEncoding(encoding);
          templateCfg.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
          templateCfg.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
          return templateCfg;
      }
  
      static String getVersion(Model basePom) {
          return basePom.getVersion() != null ? basePom.getVersion()
                  : basePom.getParent() != null && basePom.getParent().getVersion() != null
                          ? basePom.getParent().getVersion()
                          : null;
      }
  
      public static String getArtifactIdBase(ArtifactModel<?> model) {
          final String artifactId = model.getArtifactId();
          if (artifactId.startsWith("camel-quarkus-")) {
              return artifactId.substring("camel-quarkus-".length());
          } else if (artifactId.startsWith("camel-")) {
              return artifactId.substring("camel-".length());
          }
          throw new IllegalStateException(
                  "Unexpected artifactId " + artifactId + "; expected one starting with camel-quarkus- or camel-");
      }
  
      public static String getArtifactIdBase(String cqArtifactId) {
          if (cqArtifactId.startsWith("camel-quarkus-")) {
              return cqArtifactId.substring("camel-quarkus-".length());
          }
          throw new IllegalStateException(
                  "Unexpected artifactId " + cqArtifactId + "; expected one starting with camel-quarkus-");
      }
  
      public static String getNameBase(String name) {
          if (!name.endsWith(NAME_SUFFIX)) {
              throw new IllegalStateException(
                      "Unexpected Maven module name '" + name + "'; expected to end with " + NAME_SUFFIX);
          }
          final int startDelimPos = name.lastIndexOf(" :: ", name.length() - NAME_SUFFIX.length() - 1);
          if (startDelimPos < 0) {
              throw new IllegalStateException(
                      "Unexpected Maven module name '" + name + "'; expected to start with with '<whatever> :: '");
          }
          return name.substring(startDelimPos + 4, name.length() - NAME_SUFFIX.length());
      }
  
      public static String humanReadableKind(Kind kind) {
          switch (kind) {
          case bean:
              return "bean";
          case component:
              return "component";
          case console:
              return "console";
          case dataformat:
              return "data format";
          case language:
              return "language";
          case transformer:
              return "transformer";
          case other:
              return "misc. component";
          default:
              throw new IllegalStateException("Unexpected kind " + kind);
          }
      }
  
      public static String humanReadableKindPlural(Kind kind) {
          return humanReadableKind(kind) + "s";
      }
  
      public static String kindPlural(Kind kind) {
          return kind.name() + "s";
      }
  
      public static String getDescription(List<ArtifactModel<?>> models, String descriptionFromPom, Log log) {
          if (descriptionFromPom != null) {
              return descriptionFromPom;
          } else if (models.size() == 1) {
              return models.get(0).getDescription();
          } else {
              final Set<String> uniqueDescriptions = models.stream()
                      .map(m -> m.getDescription())
                      .collect(Collectors.toCollection(LinkedHashSet::new));
              final String description = uniqueDescriptions
                      .stream()
                      .collect(Collectors.joining(" "));
              if (uniqueDescriptions.size() > 1) {
                  log.warn("Consider adding and explicit <description> if you do not like the concatenated description: "
                          + description);
              }
              return description;
          }
      }
  
      public static Path findExtensionDirectory(Path sourceTreeRoot, String artifactId) {
          if (artifactId.startsWith("camel-quarkus-support-")) {
              return sourceTreeRoot.resolve("extensions-support")
                      .resolve(artifactId.substring("camel-quarkus-support-".length()));
          } else {
              final String depArtifactIdBase = artifactId.substring("camel-quarkus-".length());
              return Stream.of("extensions-core", "extensions")
                      .map(dir -> sourceTreeRoot.resolve(dir).resolve(depArtifactIdBase))
                      .filter(Files::exists)
                      .findFirst()
                      .orElseThrow(
                              () -> new IllegalStateException("Could not find directory of " + depArtifactIdBase + " extension"));
          }
      }
  
      public static boolean isDeprecated(String title, Collection<ArtifactModel<?>> models, boolean deprecated) {
          return deprecated || title.contains("(deprecated)") || models.stream().anyMatch(m -> m.isDeprecated());
      }
  
      static Path copyJar(Path localRepository, String groupId, String artifactId, String version) {
          final String relativeJarPath = groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId + "-"
                  + version + ".jar";
          final Path localPath = localRepository.resolve(relativeJarPath);
          final boolean localExists = Files.exists(localPath);
          final String remoteUri = "https://repository.apache.org/content/groups/public/" + relativeJarPath;
          Path result;
          try {
              result = Files.createTempFile(null, localPath.getFileName().toString());
              try (InputStream in = (localExists ? Files.newInputStream(localPath) : new URL(remoteUri).openStream());
                      OutputStream out = Files.newOutputStream(result)) {
                  final byte[] buf = new byte[4096];
                  int len;
                  while ((len = in.read(buf)) >= 0) {
                      out.write(buf, 0, len);
                  }
              } catch (IOException e) {
                  throw new RuntimeException("Could not copy " + (localExists ? localPath : remoteUri) + " to " + result, e);
              }
          } catch (IOException e) {
              throw new RuntimeException("Could not create temp file", e);
          }
          return result;
      }
  
      public static ArtifactModel<?> cloneArtifactModel(ArtifactModel<?> model) {
          final Kind kind = model.getKind();
          switch (kind) {
          case bean:
              return JsonMapper.generatePojoBeanModel(JsonMapper.asJsonObject((PojoBeanModel) model));
          case component:
              return JsonMapper.generateComponentModel(JsonMapper.asJsonObject((ComponentModel) model));
          case console:
              return JsonMapper.generateDevConsoleModel(JsonMapper.asJsonObject((DevConsoleModel) model));
          case dataformat:
              return JsonMapper.generateDataFormatModel(JsonMapper.asJsonObject((DataFormatModel) model));
          case language:
              return JsonMapper.generateLanguageModel(JsonMapper.asJsonObject((LanguageModel) model));
          case other:
              return JsonMapper.generateOtherModel(JsonMapper.asJsonObject((OtherModel) model));
          default:
              throw new IllegalArgumentException("Unexpected kind " + kind);
          }
  
      }
  
      public static Model readPom(final Path path, Charset charset) {
          try (Reader r = Files.newBufferedReader(path, charset)) {
              return new MavenXpp3Reader().read(r);
          } catch (XmlPullParserException | IOException e) {
              throw new RuntimeException("Could not parse " + path, e);
          }
      }
  }
  