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

net.jangaroo.jooc.mvnplugin.sencha.SenchaUtils Maven / Gradle / Ivy

The newest version!
package net.jangaroo.jooc.mvnplugin.sencha;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import net.jangaroo.jooc.mvnplugin.Type;
import net.jangaroo.jooc.mvnplugin.sencha.configbuilder.SenchaConfigBuilder;
import net.jangaroo.jooc.mvnplugin.sencha.executor.SenchaCmdExecutor;
import net.jangaroo.jooc.mvnplugin.util.FileHelper;
import net.jangaroo.jooc.mvnplugin.util.MavenDependencyHelper;
import net.jangaroo.utils.CompilerUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static net.jangaroo.jooc.mvnplugin.Type.META_INF_PATH;

public class SenchaUtils {

  public static final String SEPARATOR = "/";

  public static final String APP_DIRECTORY_NAME = "app";

  public static final String TEST_APP_DIRECTORY_NAME = "test-classes"; // for historic reasons...

  public static final String EXT_DIRECTORY_NAME = "ext";

  public static final String PACKAGES_DIRECTORY_NAME = "packages";

  public static final String APPS_DIRECTORY_NAME = "apps";

  public static final String LOCAL_PACKAGES_PATH = SEPARATOR + PACKAGES_DIRECTORY_NAME + SEPARATOR;

  public static final String LOCAL_APPS_PATH = SEPARATOR + APPS_DIRECTORY_NAME + SEPARATOR;

  public static final String APP_TARGET_DIRECTORY = SEPARATOR + APP_DIRECTORY_NAME;

  public static final String APPS_TARGET_DIRECTORY = SEPARATOR + APPS_DIRECTORY_NAME;

  /**
   * The name of the folder of the generated module inside the packages folder of the module.
   * Make sure that the name is not too long to avoid exceeding the max path length in windows.
   * The old path length relative to the target folder was 43 chars:
   * classes\META-INF\resources\joo\classes\com
   *
   * So to avoid compatiblity issues the max length for the path is:
   *
   * 43 - SENCHA_BASE_BATH.length - SENCHA_PACKAGES.length - SENCHA_PACKAGE_LOCAL.length
   *    - SENCHA_CLASS_PATH.length - 4 (Separator)
   */
  public static final String SENCHA_LOCALE_PATH = "locale";
  public static final String DEFAULT_LOCALE = "en";
  public static final String SENCHA_RESOURCES_PATH = "resources";
  public static final String SENCHA_BUNDLED_RESOURCES_PATH = "bundledResources";
  public static final String PRODUCTION_PROFILE = "production";
  public static final String TESTING_PROFILE = "testing";
  public static final String DEVELOPMENT_PROFILE = "development";

  public static final String TOOLKIT_CLASSIC = "classic";


  private static final String SENCHA_CFG = "sencha.cfg";
  public static final String SENCHA_DIRECTORYNAME = ".sencha";
  public static final String SENCHA_WORKSPACE_CONFIG = SENCHA_DIRECTORYNAME + SEPARATOR + "workspace" + SEPARATOR + SENCHA_CFG;
  public static final String SENCHA_PACKAGE_CONFIG = SENCHA_DIRECTORYNAME + SEPARATOR + PACKAGES_DIRECTORY_NAME + SEPARATOR + SENCHA_CFG;
  private static final String SENCHA_APP_CONFIG = SENCHA_DIRECTORYNAME + SEPARATOR + APP_DIRECTORY_NAME + SEPARATOR + SENCHA_CFG;

  public static final String SENCHA_WORKSPACE_FILENAME = "workspace.json";
  public static final String SENCHA_PACKAGE_FILENAME = "package.json";
  public static final String SENCHA_APP_FILENAME = "app.json";
  public static final String PACKAGE_CONFIG_FILENAME = "packageConfig.js";
  public static final String REQUIRED_CLASSES_FILENAME = "requiredClasses.js";
  public static final String DYNAMIC_PACKAGES_FILENAME = "dynamic-packages.json";
  public static final String ADDITIONAL_PACKAGES_PATH = "/additional-packages/*";
  public static final String APP_MANIFEST_FILENAME = "app-manifest.json";
  public static final String APP_MANIFEST_FRAGMENT_FILENAME = "app-manifest-fragment.json";
  public static final String APPS_FILENAME = "apps.json";

  public static final String SENCHA_TEST_APP_TEMPLATE_ARTIFACT_ID = "sencha-test-app-template";
  public static final String SENCHA_APP_TEMPLATE_ARTIFACT_ID = "sencha-app-template";
  public static final String SENCHA_APP_TEMPLATE_GROUP_ID = "net.jangaroo";

  private static final Pattern INTEGER_VERSION_PATTERN = Pattern.compile("[0-9]+");

  public static final String AUTO_CONTENT_COMMENT =
          "DO NOT CHANGE - This file was automatically generated by the jangaroo-maven-plugin";

  public static final Map PLACEHOLDERS = ImmutableMap.of( // TODO data structure and location??
          Type.APP, "${app.dir}",
          Type.CODE, "${package.dir}",
          Type.THEME, "${package.dir}",
          Type.WORKSPACE, "${workspace.dir}"
  );

  private static final ObjectMapper objectMapper;
  private static final String DOT_SWC_EXTENSION = '.' + Type.SWC_EXTENSION;
  private static final String REMOTE_PACKAGES_DIR = ".remote-packages";

  static {
    objectMapper = new ObjectMapper();
    objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
  }

  private SenchaUtils() {
    // hide constructor
  }

  public static String getSenchaPackageName(String groupId, String artifactId) {
    return groupId + "__" + artifactId;
  }

  public static String getSenchaPackageName(@Nonnull MavenProject project) {
    return getSenchaPackageName(project.getGroupId(), project.getArtifactId());
  }

  public static String getSenchaVersionForMavenVersion(String version) {
    StringBuilder senchaVersion = new StringBuilder();
    Matcher matcher = INTEGER_VERSION_PATTERN.matcher(version);
    for (int i = 0; i < 4 && matcher.find(); i++) {
      if (i > 0) {
        senchaVersion.append(".");
      }
      senchaVersion.append(matcher.group());
    }
    return senchaVersion.length() == 0 ? null : senchaVersion.toString();
  }

  @Nullable
  public static Dependency getDependencyByRef(@Nonnull MavenProject project, @Nullable String ref) {
    Dependency themeDependency = MavenDependencyHelper.fromKey(ref);
    // verify that provided artifact is under project dependencies
    Set dependencyArtifacts = project.getDependencyArtifacts();
    for (Artifact artifact : dependencyArtifacts) {
      Dependency artifactDependency = MavenDependencyHelper.fromArtifact(artifact);
      if (MavenDependencyHelper.equalsGroupIdAndArtifactId(artifactDependency, themeDependency)) {
        return artifactDependency;
      }
    }
    return null;
  }

  /**
   * Generates an absolute path to the module dir for the given relative path using a placeholder.
   *
   * @param packageType the Maven project's packaging type
   * @param relativePath the path relative to the Sencha module
   * @return path prefixed with a placeholder and a separator to have an absolute path
   */
  public static String absolutizeToModuleWithPlaceholder(String packageType, String relativePath) {
    return absolutizeWithPlaceholder(PLACEHOLDERS.get(packageType), relativePath);
  }

  /**
   * Generates an absolute path to the module dir for the given relative path using a placeholder.
   *
   * @param placeholder the placeholder, e.g. "${workspace.dir}"
   * @param relativePath the path relative to the Sencha module
   * @return path prefixed with a placeholder and a separator to have an absolute path
   */
  public static String absolutizeWithPlaceholder(String placeholder, String relativePath) {
    // make sure only normal slashes are used and no backslashes (e.g. on windows systems)
    String normalizedRelativePath = FilenameUtils.separatorsToUnix(relativePath);
    String result = placeholder;
    if (StringUtils.isNotEmpty(normalizedRelativePath) && !normalizedRelativePath.startsWith(SEPARATOR)) {
      result += SEPARATOR + normalizedRelativePath;
    }
    return result;
  }

  public static ObjectMapper getObjectMapper() {
    return objectMapper;
  }

  public static boolean isRequiredSenchaDependency(@Nonnull Dependency dependency, boolean includeTestDependencies) {
    return isRequiredSenchaDependency(dependency, includeTestDependencies, includeTestDependencies);
  }

  public static boolean isRequiredSenchaDependency(@Nonnull Dependency dependency,
                                                   boolean includeTestDependencies,
                                                   boolean includeProvidedDependencies) {
    return isSenchaDependency(dependency)
            && (includeProvidedDependencies || !Artifact.SCOPE_PROVIDED.equals(dependency.getScope()))
            && (includeTestDependencies || !Artifact.SCOPE_TEST.equals(dependency.getScope()));
  }

  public static boolean isSenchaDependency(@Nonnull Dependency dependency) {
    return isSenchaDependency(dependency.getType());
  }

  public static boolean isSenchaDependency(@Nonnull String type) {
    return Type.SWC_EXTENSION.equals(type) || Type.PKG_EXTENSION.equals(type);
  }

  public static boolean doesSenchaAppExist(File directory) {
    File senchaCfg = new File(directory, SenchaUtils.SENCHA_APP_CONFIG);
    return senchaCfg.exists();
  }

  public static String getPackagesPath(MavenProject project) {
    return LOCAL_PACKAGES_PATH + getSenchaPackageName(project);
  }

  public static void generateSenchaAppFromTemplate(File workingDirectory,
                                                   String appName,
                                                   String applicationClass,
                                                   String toolkit,
                                                   Log log,
                                                   String logLevel,
                                                   String senchaJvmArgs) throws MojoExecutionException {
    String templateName = getSenchaPackageName(SENCHA_APP_TEMPLATE_GROUP_ID, SENCHA_APP_TEMPLATE_ARTIFACT_ID) + "/tpl";
    ImmutableMap properties = ImmutableMap.of("appName", appName, "applicationClass", applicationClass);
    generateSenchaAppFromTemplate(workingDirectory, appName, toolkit, templateName, properties, log, logLevel, senchaJvmArgs);
  }

  public static void generateSenchaTestAppFromTemplate(File workingDirectory,
                                                       MavenProject project,
                                                       String appName,
                                                       String testSuite,
                                                       String toolkit,
                                                       Log log,
                                                       String logLevel,
                                                       String senchaJvmArgs) throws MojoExecutionException {
    String templateName = getSenchaPackageName(SENCHA_APP_TEMPLATE_GROUP_ID, SENCHA_TEST_APP_TEMPLATE_ARTIFACT_ID) + "/tpl";
    ImmutableMap properties = ImmutableMap.of("moduleName", getSenchaPackageName(project), "testSuite", testSuite);
    generateSenchaAppFromTemplate(workingDirectory, appName, toolkit, templateName, properties, log, logLevel, senchaJvmArgs);
  }

  private static void generateSenchaAppFromTemplate(File workingDirectory,
                                                    String appName,
                                                    String toolkit,
                                                    String templateName,
                                                    Map properties,
                                                    Log log,
                                                    String logLevel,
                                                    String senchaJvmArgs) throws MojoExecutionException {
    List arguments = new ArrayList<>();
    arguments.add("generate app");
    arguments.add("-ext " + toolkit);
    arguments.add("--template " + templateName);
    arguments.add("--path=\".\"");
    arguments.add("--refresh=false");

    addSwitchFullIfCmd6_5(arguments);

    if (properties != null) {
      for (Map.Entry entry: properties.entrySet()) {
        arguments.add(String.format("-D%s=%s",entry.getKey(), entry.getValue()));
      }
    }
    arguments.add(appName);

    SenchaCmdExecutor senchaCmdExecutor = new SenchaCmdExecutor(workingDirectory, StringUtils.join(arguments, ' '), senchaJvmArgs, log, logLevel);
    senchaCmdExecutor.execute();
  }

  /**
   * Special case: Sencha Cmd 6.5 made a breaking change in "generate workspace/app", and to restore
   * the original behavior, we have to specify the new parameter "--full" (which Cmd < 6.5 prompts with an error).
   *
   * @param arguments the arguments list to add "--full" to if Sencha Cmd version is 6.5 or higher
   * @throws MojoExecutionException if Sencha Cmd version cannot be determined
   */
  public static void addSwitchFullIfCmd6_5(List arguments) throws MojoExecutionException {
    try {
      int[] cmdVersion = SenchaCmdExecutor.queryVersion();
      if (cmdVersion == null) {
        throw new MojoExecutionException("No version information found in output of 'sencha switch -l'.");
      }
      // version is 6.5 or above?
      if (cmdVersion[0] > 6 || cmdVersion[0] == 6 && cmdVersion[1] >= 5) {
        // add new command line switch:
        arguments.add("--full");
      }
    } catch (IOException ioe) {
      throw new MojoExecutionException("Could not run 'sencha'. Please install Sencha Cmd >= 6.2.1 or check your PATH environment variable.", ioe);
    }
  }

  public static void createSenchaCfg(Path senchaCfgSource, Path senchaCfgTarget, Map properties)
          throws MojoExecutionException {
    if (Files.exists(senchaCfgSource)) {
      try {
        List senchaCfgTmpContent = Files.readAllLines(senchaCfgSource, Charset.forName("UTF-8"));
        List updatedSenchaCfgContent = updateSenchaCfgContent(senchaCfgTmpContent,
                properties);
        Files.write(senchaCfgTarget, updatedSenchaCfgContent, Charset.forName("UTF-8"));
      } catch (IOException e) {
        throw new MojoExecutionException("Modifying sencha.cfg file failed", e);
      }
    } else {
      throw new MojoExecutionException("Could not find sencha.cfg file in " + senchaCfgSource);
    }
  }

  private static List updateSenchaCfgContent(@Nonnull List currentContent, Map properties) {
    // prepend comment and delete first comment line with usually contains the date time
    if (currentContent.get(0).startsWith("#")) { // if the first
      currentContent.remove(0);
    }
    List newSenchaCfg = new ArrayList<>(currentContent.size());
    newSenchaCfg.add("#");
    newSenchaCfg.add("# " + AUTO_CONTENT_COMMENT);
    newSenchaCfg.add("#");
    newSenchaCfg.add("");
    newSenchaCfg.addAll(currentContent);
    for (Map.Entry property : properties.entrySet()) {
      newSenchaCfg.add(String.format("%s=%s", property.getKey(), property.getValue()));
    }
    return newSenchaCfg;
  }

  public static void configureDefaults(SenchaConfigBuilder configBuilder, String defaultsFileName) throws MojoExecutionException {
    try {
      configBuilder.defaults(defaultsFileName);
    } catch (IOException e) {
      throw new MojoExecutionException("Cannot load " + defaultsFileName, e);
    }
  }

  public static void writeFile(@Nonnull SenchaConfigBuilder configBuilder,
                               @Nonnull String destinationFileDir,
                               @Nonnull String destinationFileName,
                               @Nullable String comment,
                               Log log)
          throws MojoExecutionException {

    String tmpDestFileName = destinationFileName + ".tmp";
    final File tmpDestFile = new File(destinationFileDir, tmpDestFileName);
    final File destFile = new File(destinationFileDir, destinationFileName);
    configBuilder.destFile(tmpDestFile);
    if (comment != null ) {
      configBuilder.destFileComment(comment);
    }
    try {
      configBuilder.buildFile();
    } catch (IOException io) {
      try {
        Files.delete(tmpDestFile.toPath());
      } catch (IOException e) {
        log.warn("Unable to delete temporary file " + tmpDestFile.getAbsolutePath(), e);
      }
      throw new MojoExecutionException(String.format("Writing %s failed", tmpDestFile.getName()), io);
    }
    try {
      Files.move(tmpDestFile.toPath(), destFile.toPath(),
              StandardCopyOption.REPLACE_EXISTING,
              StandardCopyOption.ATOMIC_MOVE);
    } catch (IOException e) {
      throw new MojoExecutionException(String.format("Moving %s to %s failed", tmpDestFile.getName(), destFile.getAbsolutePath()), e);
    }
  }

  /**
   * Unfortunately, we need to implement our own extractor because
   * the maven unarchiver is unable to strip a given path prefix from the entry paths
   */
  public static void extractPkg(File archive, File targetDir) throws MojoExecutionException {
    try (ZipFile zipFile = new ZipFile(archive)) {
      boolean isSwc = archive.getName().endsWith(DOT_SWC_EXTENSION);
      Enumeration entries = zipFile.entries();
      while (entries.hasMoreElements()) {
        ZipEntry entry = entries.nextElement();
        String targetName = entry.getName();
        boolean isSwcPkgFile = isSwc && targetName.startsWith(Type.SWC_PKG_PATH);
        // extract only package contents
        if (isSwcPkgFile || (!isSwc && !targetName.startsWith(META_INF_PATH))) {
          if (isSwcPkgFile) {
            targetName = targetName.substring(Type.SWC_PKG_PATH.length());
          }
          File target = new File(targetDir, targetName);
          if (entry.isDirectory()) {
            FileHelper.ensureDirectory(target);
          } else {
            FileHelper.ensureDirectory(target.getParentFile());
            try (InputStream in = zipFile.getInputStream(entry);
                 OutputStream out = new FileOutputStream(target)) {
              IOUtils.copy(in, out);
            }
          }
        }
      }
    } catch (IOException e) {
      throw new MojoExecutionException("IO Error while extracting archive", e);
    }
  }

  public static File baseDir(MavenSession mavenSession) {
    File baseDir = mavenSession.getRequest().getMultiModuleProjectDirectory();
    if (baseDir == null) {
      baseDir = new File(mavenSession.getRequest().getBaseDirectory());
    }
    return baseDir;
  }

  public static File remotePackagesDir(MavenSession mavenSession) {
    File currentBaseDir = baseDir(mavenSession);

    for (;;) {
      File remotePackagesDir = new File(currentBaseDir, REMOTE_PACKAGES_DIR);
      File baseParent = currentBaseDir.getParentFile();
      if (remotePackagesDir.exists() || baseParent == null) {
        return remotePackagesDir;
      }
      File pom = new File(baseParent, "pom.xml");
      if (!pom.exists()) {
        // don't go out of maven projects
        return remotePackagesDir;
      }
      currentBaseDir = baseParent;
    }
  }

  public static Map getClassMapping(File sourceDir, String extNamespace, File currentDir) {
    Map result = new HashMap<>();
    File[] children = currentDir.listFiles();
    if (children != null) {
      for (File child : children) {
        if (child.isDirectory()) {
          result.putAll(getClassMapping(sourceDir, extNamespace, child));
        } else if (child.getName().endsWith(".ts")) {
          result.put(
                  CompilerUtils.qName(extNamespace, CompilerUtils.qNameFromFile(sourceDir, child)),
                  sourceDir.toPath().relativize(child.toPath()).toString().replaceAll("\\\\", "/")
          );
        }
      }
    }
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy