public enum Extension {
  JAVA(".java"), XML(".xml"), GRADLE(".gradle"), PRO(".pro"), JSON(".json");

  private String extensionName;

  Extension(String extensionName) {
    this.extensionName = extensionName;
  }

  @Override
  public String toString() {
    return extensionName.toLowerCase();
  }
} public class AttributeContraints {
  public static final String NAME = "android:name";
} public class CommandParser {
  private String path;
  private boolean hasIcon;
  private boolean hasHelpCommand;
  private List<String> layouts;
  private List<String> modules;

  public CommandParser(String[] args) throws CommandException {
    layouts = new ArrayList<>();
    modules = new ArrayList<>();

    if (args.length <= 0) {
      throw new CommandException(CommandException.INVALID_NO_OPTIONS);
    }

    for (int i = 0, li = args.length; i < li; i++) {
      final String key = args[i];

      if (key.contains(CommandOption.HELP_KEY) || key.contains(CommandOption.HELP_LONG_KEY)) {
        hasHelpCommand = true;
      } else if (key.contains(CommandOption.ICON_KEY) || key.contains(CommandOption.ICON_LONG_KEY)) {
        hasIcon = true;
      } else if (key.contains(CommandOption.LAYOUT_KEY) || key.contains(CommandOption.LAYOUT_LONG_KEY)) {
        if (i + 1 < li && !isCommand(args[++i])) {
          layouts = Arrays.asList(args[i].split(","));
        } else {
          throw new CommandException(CommandException.INVALID_WIDGET);
        }
      } else if (key.contains(CommandOption.TOOL_KEY) || key.contains(CommandOption.TOOL_LONG_KEY)) {
        if (i + 1 < li && !isCommand(args[++i])) {
          modules = Arrays.asList(args[i].split(","));
        } else {
          throw new CommandException(CommandException.INVALID_WIDGET);
        }
      } else if (isCommand(key)) {
        throw new CommandException(CommandException.OPTION_NOT_FOUND, key);
      } else {
        path = key;
      }
    }
  }

  public String getPath() {
    return path;
  }

  public TabType getTabType() {
    if (layouts.size() > 1) {
      if (hasIcon) {
        return TabType.SlidingIconTab;
      } else {
        return TabType.SlidingTab;
      }
    } else {
      return null;
    }
  }

  public boolean hasIcon() {
    return hasIcon;
  }

  public boolean hasHelpCommand() {
    return hasHelpCommand;
  }

  public List<String> getLayoutCommands() {
    return layouts;
  }

  public List<String> getModuleCommands() {
    return modules;
  }

  private boolean isCommand(String command) {
    String reg = "^(-|--)[a-z]+$";

    Pattern pat = Pattern.compile(reg);
    Matcher matcher = pat.matcher(command);

    return matcher.find();
  }
} public enum TabType {
  SlidingTab("SlidingTab"),
  SlidingIconTab("SlidingIconTab");

  private String fileName;

  TabType(String fileName) {
    this.fileName = fileName;
  }

  public String getFragmentName() {
    return fileName + "Fragment";
  }
} public class AskConfig {
  public static final String DEFAULT_ASK_APP_NAME = "ask-app";
  public static final String DEFAULT_SAMPLE_MODULE_NAME = "ask-sample";
  public static final String DEFAULT_REMOTE_MODULE_NAME = "ask-remote-module";

  public static final int DEVELOPMENT = 0;
  public static final int PRODUCTION = 1;

  public static final int OUTPUT_PROD_PROJECT = 0;
  public static final int OUTPUT_DEV_ASK_SAMPLE = 1;
} public class RemoteDirectory extends Directory {
  public static final String MODULE_NAME = "ask-remote-module";
  public static final String SAMPLE_ACTIVITY_NAME = "SampleActivity.java";
  public static final String PACKAGE_NAME = "com/androidstarterkit/module";

  private MainActivity mainActivity;
  private SlidingTabFragment sampleTabFragment;

  private MainActivity bakMainActivity;
  private SlidingTabFragment bakSampleTabFragment;

  public RemoteDirectory(String projectPathname) throws CommandException {
    super(projectPathname + "/" + MODULE_NAME
        , new String[] { "java", "xml", "gradle", "json" }
        , new String[] { "build", "libs" });

    mainActivity = new MainActivity(getChildDirPath(SAMPLE_ACTIVITY_NAME) + "/" + SAMPLE_ACTIVITY_NAME);
    sampleTabFragment = new SlidingTabFragment(getChildDirPath(SlidingTabFragment.FILE_NAME) + "/" + SlidingTabFragment.FILE_NAME);
  }

  public String getFilePathFromJavaDir(String key) {
    String applicationIdPath = FileUtils.changeDotToSlash(applicationId);

    int index;
    try {
      index = getChildDirPath(key).indexOf(applicationIdPath);
    } catch (NullPointerException exception) {
      return null;
    }

    try {
      return getChildDirPath(key).substring(index).replace(applicationIdPath, "");
    } catch (StringIndexOutOfBoundsException exception) {
      return getChildDirPath(key);
    }
  }

  public MainActivity getMainActivity() {
    return mainActivity;
  }

  public void changeMainFragmentByTabType(TabType tabType, List<LayoutGroup> layoutGroups) {
    bakMainActivity = new MainActivity(mainActivity.getPath());

    String fragmentName;
    if (tabType != null) {
      fragmentName = tabType.getFragmentName();
    } else {
      fragmentName = layoutGroups.get(0).getClassName();
    }

    Pattern pattern = Pattern.compile("\\.add\\(R\\.id\\.\\w+,\\s*new\\s*(\\w+)\\s*\\(");

    List<String> codelines = mainActivity.getCodelines();
    for (int i = 0, li = codelines.size(); i < li; i++) {
      String codeline = codelines.get(i);
      Matcher matcher = pattern.matcher(codeline);
      if (matcher.find()) {
        codelines.set(i, codeline.replace(matcher.group(1), fragmentName));
      }
    }

    mainActivity.apply();

    List<ClassInfo> classInfos = new ArrayList<>();
    classInfos.add(new ClassInfo(fragmentName));
    defineImportClassString(mainActivity, classInfos);
  }

  public void injectLayoutModulesToFragment(List<LayoutGroup> layoutGroups) {
    if (layoutGroups.size() <= 1) {
      return;
    }

    bakSampleTabFragment = new SlidingTabFragment(sampleTabFragment.getPath());

    List<String> codelines = sampleTabFragment.getCodelines();

    Iterator<String> iterator = codelines.iterator();
    while (iterator.hasNext()) {
      String codeline = iterator.next();
      if (codeline.contains("fragmentInfos.add")) {
        iterator.remove();
      }
    }

    for (int i = 0, li = codelines.size(); i < li; i++) {
      String codeline = codelines.get(i);

      if (codeline.contains("List<FragmentInfo> fragmentInfos = new ArrayList<>();") && layoutGroups.size() > 0) {
        final String ADD_FRAGMENT_TO_LIST_STRING = "fragmentInfos.add(new FragmentInfo(" + SyntaxConstraints.REPLACE_STRING + ".class));";

        String layoutCodeline = "";

        final String intent = FileUtils.extractIndentInLine(codeline);

        for (LayoutGroup layoutGroup : layoutGroups) {
          layoutCodeline += intent + ADD_FRAGMENT_TO_LIST_STRING.replace(SyntaxConstraints.REPLACE_STRING, layoutGroup.getClassName()) + "\n";
        }

        sampleTabFragment.setCodeline(i + 1, layoutCodeline);
      }
    }

    sampleTabFragment.apply();
  }

  private void defineImportClassString(InjectionJavaFile javaFile, List<ClassInfo> addedClassInfos) {
    List<String> importedClassStrings = new ArrayList<>();
    for (int i = 0, li = addedClassInfos.size(); i < li; i++) {
      String classname = addedClassInfos.get(i).getName();
      if (getFilePathFromJavaDir(classname + Extension.JAVA.toString()) != null) {
        String importedClassString = SyntaxConstraints.IDENTIFIER_IMPORT + " "
            + applicationId
            + getFilePathFromJavaDir(classname + Extension.JAVA.toString()).replaceAll("/", ".") + "."
            + classname + ";";
        if (!importedClassStrings.contains(importedClassString)) {
          importedClassStrings.add(importedClassString);
        }
      }
    }

    List<String> codelines = javaFile.getCodelines();
    for (int i = 0, li = codelines.size(); i < li; i++) {
      String codeline = codelines.get(i);

      if (codeline.contains(SyntaxConstraints.IDENTIFIER_PACKAGE)) {
        javaFile.addCodelines(i + 1, importedClassStrings);
        break;
      }
    }

    javaFile.apply();
  }

  public void recover() {
    if (bakMainActivity != null) {
      bakMainActivity.apply();
    }

    if (bakSampleTabFragment != null) {
      bakSampleTabFragment.apply();
    }
  }
} public class SourceDirectory extends Directory {
  private RemoteDirectory remoteDirectory;

  private String projectPathname;
  private String mainPath;
  private String javaPath;
  private String resPath;
  private String layoutPath;

  private ResourceTransfer resourceTransfer;

  private List<ClassInfo> transformedClassInfos;

  public SourceDirectory(String projectPathname, String sourceModuleName, RemoteDirectory remoteDirectory) {
    super(projectPathname + "/" + sourceModuleName
        , new String[]{"java", "gradle", "xml"}
        , new String[]{"build", "libs", "test", "androidTest", "res"});
    this.projectPathname = projectPathname;
    this.remoteDirectory = remoteDirectory;

    transformedClassInfos = new ArrayList<>();

    resourceTransfer = new ResourceTransfer(this);

    mainPath = FileUtils.linkPathWithSlash(getPath(), "src/main");
    javaPath = FileUtils.linkPathWithSlash(mainPath, "java", applicationId.replaceAll("\\.", "/"));
    resPath = FileUtils.linkPathWithSlash(mainPath, "res");
    layoutPath = FileUtils.linkPathWithSlash(resPath, ResourceType.LAYOUT.toString());
  }

  public BuildGradle getProjectBuildGradle() {
    return new BuildGradle(projectPathname);
  }

  public ProguardRules getProguardRules() {
    return new ProguardRules(getPath());
  }

  public MainActivity getMainActivity() {
    return new MainActivity(getChildDirPath(getMainActivityExtName()), getMainActivityExtName());
  }

  public String getMainPath() {
    return mainPath;
  }

  public String getJavaPath() {
    return javaPath;
  }

  public String getResPath() {
    return resPath;
  }

  public String getLayoutPath() {
    return layoutPath;
  }

  public void transformAndroidManifest() {
    AndroidManifest remoteAndroidManifestFile = remoteDirectory.getAndroidManifestFile();

    System.out.println(PrintUtils.prefixDash(0) + remoteAndroidManifestFile.getName());

    Map<String, String> remoteActivityAttributes = remoteAndroidManifestFile.getMainActivityAttributes();
    remoteActivityAttributes.keySet().stream()
        .filter(key -> !key.equals(AttributeContraints.NAME))
        .forEach(key -> {
          androidManifestFile.addMainActivityAttribute(key, remoteActivityAttributes.get(key));

          resourceTransfer.extractValuesInXml(remoteActivityAttributes.get(key)
              , (String resourceTypeName, String elementName) -> {
                try {
                  resourceTransfer.transferValueFromRemote(remoteDirectory, resourceTypeName, elementName, 1);
                } catch (FileNotFoundException e) {
                  e.printStackTrace();
                }
              });
        });

    androidManifestFile.apply();
  }

  public void takeFileFromRemote(File remoteFile, int depth) throws CommandException {
    final String remoteFileNameEx = remoteFile.getName();

    String sourceFullPathname;

    if (remoteFile instanceof MainActivity) {
      sourceFullPathname = FileUtils.linkPathWithSlash(javaPath
          , androidManifestFile.getMainActivityNameEx());
    } else {
      sourceFullPathname = FileUtils.linkPathWithSlash(javaPath
          , FileUtils.removeFirstSlash(remoteDirectory.getFilePathFromJavaDir(remoteFileNameEx))
          , remoteFileNameEx);
    }

    System.out.println(PrintUtils.prefixDash(depth) + remoteFileNameEx);

    Scanner scanner;
    try {
      scanner = new Scanner(remoteFile);
    } catch (FileNotFoundException e) {
      throw new CommandException(CommandException.FILE_NOT_FOUND, remoteFile.getName());
    }

    List<String> codeLines = new ArrayList<>();

    while (scanner.hasNext()) {
      String codeLine = scanner.nextLine();

      codeLine = changePackage(codeLine);

      if (remoteFile instanceof MainActivity) {
        codeLine = changeMainActivityName(codeLine);
      }

      resourceTransfer.extractResourceFileInJava(codeLine, (resourceTypeName, layoutName) -> {
        try {
          resourceTransfer.transferResourceFileFromRemote(remoteDirectory, resourceTypeName, layoutName, depth + 1);
        } catch (FileNotFoundException e) {
          e.printStackTrace();
        }
      });

      resourceTransfer.extractValuesInJava(codeLine, (resourceTypeName, elementName) -> {
        try {
          resourceTransfer.transferValueFromRemote(remoteDirectory, resourceTypeName, elementName, depth + 1);
        } catch (FileNotFoundException e) {
          e.printStackTrace();
        }
      });

      codeLines.add(codeLine);
    }

    File sourceFile = new File(sourceFullPathname);
    FileUtils.writeFile(sourceFile, codeLines);
    fileMap.put(sourceFile.getName(), sourceFile.getPath().replaceAll("/" + sourceFile.getName(), ""));

    findDeclaredClasses(depth, remoteFile.getPath());
  }

  private String changePackage(String codeLine) {
    if (codeLine.contains("package") || codeLine.contains("import")) {
      return codeLine.replace(remoteDirectory.getApplicationId(), getApplicationId());
    }

    return codeLine;
  }

  private String changeMainActivityName(String codeLine) {
    return codeLine.replace(remoteDirectory.getMainActivityName(), getMainActivityName());
  }

  private void findDeclaredClasses(int depth, String pathname) throws CommandException {
    Pattern pattern = Pattern.compile(".+(com/androidstarterkit/module/.+)");
    Matcher matcher = pattern.matcher(pathname);

    while (matcher.find()) {
      pathname = FileUtils.removeExtension(matcher.group(1));
    }

    ClassDisassembler javap = new ClassDisassembler(FileUtils.getRootPath() + "/ask-remote-module/build/intermediates/classes/debug"
        , RemoteDirectory.PACKAGE_NAME);
    javap.extractClasses(pathname);

    javap.getInternalClassInfos().stream()
        .filter(classInfo -> remoteDirectory.getFilePathFromJavaDir(classInfo.getName() + Extension.JAVA.toString()) != null)
        .filter(classInfo -> !isTransformed(classInfo))
        .forEach(classInfo -> {
          transformedClassInfos.add(classInfo);

          takeFileFromRemote(remoteDirectory.getChildFile(classInfo.getName(), Extension.JAVA), depth + 1);
        });

    javap.getExternalClassInfos().forEach(classInfo -> {
      for (String dependencyKey : externalLibrary.getKeys()) {
        if (classInfo.getName().equals(dependencyKey)) {
          appBuildGradleFile.addDependency(externalLibrary.getInfo(dependencyKey).getLibrary());
          androidManifestFile.addPermissions(externalLibrary.getInfo(dependencyKey).getPermissions());
          break;
        }
      }
    });

    androidManifestFile.apply();
  }

  private boolean isTransformed(ClassInfo classInfo) {
    for (ClassInfo transformedClassInfo : transformedClassInfos) {
      if (classInfo.equals(transformedClassInfo)) {
        return true;
      }
    }
    return false;
  }
} public class CommandException extends RuntimeException {
  public static final String ERROR_MESSAGE_PREFIX = "ask: ";

  public static final int INVALID_NO_OPTIONS = 1;
  public static final int FILE_NOT_FOUND = 2;
  public static final int APP_MODULE_NOT_FOUND = 3;
  public static final int WIDGET_NOT_FOUND = 4;
  public static final int INVALID_WIDGET = 5;
  public static final int OPTION_NOT_FOUND = 6;
  public static final int INVALID_MODULE = 7;
  public static final int NOT_FOUND_ASK_JSON = 8;

  private int errCode;

  public CommandException(String message) {
    this(0, message);
  }

  public CommandException(int errCode) {
    this(errCode, null);
  }

  public CommandException(int errCode, String message) {
    super(message);

    this.errCode = errCode;
  }

  @Override
  public String getMessage() {
    if (errCode == INVALID_NO_OPTIONS) {
      return ERROR_MESSAGE_PREFIX + "there is no options";
    } else if (errCode == FILE_NOT_FOUND) {
      return ERROR_MESSAGE_PREFIX + "failed to find project: " + super.getMessage();
    } else if (errCode == APP_MODULE_NOT_FOUND) {
      return ERROR_MESSAGE_PREFIX + "failed to find module: " + super.getMessage();
    } else if (errCode == WIDGET_NOT_FOUND) {
      return ERROR_MESSAGE_PREFIX + "failed to find widget: " + super.getMessage();
    } else if (errCode == INVALID_WIDGET) {
      return ERROR_MESSAGE_PREFIX + "there is no widgets";
    } else if (errCode == OPTION_NOT_FOUND) {
      return ERROR_MESSAGE_PREFIX + "failed to find option: " + super.getMessage();
    } else if (errCode == INVALID_MODULE) {
      return ERROR_MESSAGE_PREFIX + "failed to find module: " + super.getMessage();
    } else if (errCode == NOT_FOUND_ASK_JSON) {
      return ERROR_MESSAGE_PREFIX + "failed to find ask json file";
    }

    return super.getMessage();
  }

  public boolean shudShowHelp() {
    return errCode == INVALID_NO_OPTIONS
        || errCode == INVALID_WIDGET;
  }
} public class SettingsGradle extends InjectionGradleFile {
  private static final String TAG = SettingsGradle.class.getSimpleName();

  public static final String FILE_NAME = "settings.gradle";

  private String appModuleName;

  public SettingsGradle(String pathname) {
    super(pathname + "/" + FILE_NAME);

    Scanner scanner;
    try {
      scanner = new Scanner(this);
    } catch (FileNotFoundException e) {
      throw new ModuleFileNotFoundException(FILE_NAME);
    }

    while (scanner.hasNextLine()) {
      String codeLine = scanner.nextLine();

      MatcherTask task = new MatcherTask(":([\\w_-]+)", codeLine);
      task.match(1, matched -> appModuleName = matched);
    }
  }

  @Override
  public void apply() {
    super.apply();
  }

  public String getAppModuleName() {
    return appModuleName;
  }
} public class AskJsonBuilder {
  private PropertyLineConverter propertyLineConverter;
  private String dirPath;
  private AskJson askJson;

  public AskJsonBuilder(String dirPath) {
    this.dirPath = dirPath;
    propertyLineConverter = new PropertyLineConverter();
  }

  public AskJsonBuilder addProperty(String propertyPatternStr, String convertedStr) {
    propertyLineConverter.addProperty(propertyPatternStr, convertedStr);
    return this;
  }

  public AskJson build() {
    askJson = null;
    List<File> jsonFiles = getJsonFiles(dirPath);
    Gson gson = new Gson();

    for (File jsonFile : jsonFiles) {
      try {
        List<String> codelines = Files.readAllLines(Paths.get(jsonFile.getPath()), Charset.defaultCharset());
        AskJson newAskJson = gson.fromJson(FileUtils.toString(propertyLineConverter.replace(codelines))
            , AskJson.class);

        if (askJson == null) {
          askJson = newAskJson;
        } else {
          askJson.append(newAskJson);
        }
      } catch (IOException e) {
        throw new CommandException(CommandException.NOT_FOUND_ASK_JSON);
      }
    }

    return askJson;
  }

  private List<File> getJsonFiles(String dirPath) {
    List<File> jsonFiles = new ArrayList<>();

    File directory = new File(dirPath);

    File[] files = directory.listFiles(file
        -> file.getName().toLowerCase().endsWith(Extension.JSON.toString()));

    if (files == null) {
      throw new CommandException(CommandException.NOT_FOUND_ASK_JSON);
    }

    Collections.addAll(jsonFiles, files);

    return jsonFiles;
  }

  private class PropertyLineConverter {
    private Map<String, String> proerties;

    private PropertyLineConverter() {
      proerties = new HashMap<>();
    }

    private void addProperty(String propertyStr, String convertedStr) {
      proerties.put(propertyStr, convertedStr);
    }

    private String replace(String codeline) {
      Set<String> keySets = proerties.keySet();
      for (String propertyStr : keySets) {
        codeline = codeline.replaceAll(propertyStr, proerties.get(propertyStr));
      }
      return codeline;
    }

    private List<String> replace(List<String> codelines) throws IOException {
      for (int i = 0, li = codelines.size(); i < li; i++) {
        codelines.set(i, replace(codelines.get(i)));
      }

      return codelines;
    }
  }
} public class ModuleLoader {

  private List<CodeGenerator> codeGenerators;

  public ModuleLoader() {
    codeGenerators = new ArrayList<>();
  }

  public void generateCode() {
    codeGenerators.forEach(CodeGenerator::apply);
  }

  public void addCodeGenerator(CodeGenerator codeGenerator) {
    if (!codeGenerators.contains(codeGenerator)) {
      codeGenerators.add(codeGenerator);
    }
  }

  public void addManifestConfigs(List<ManifestConfig> configs) {
    if (configs != null) {
      configs.forEach(this::addConfig);
    }
  }

  public void addJavaConfigs(List<JavaConfig> configs) {
    if (configs != null) {
      configs.forEach(this::addConfig);
    }
  }

  public void addGradleConfigs(List<GradleConfig> configs) {
    if (configs != null) {
      configs.forEach(this::addConfig);
    }
  }

  private void addConfig(Config config) {
    if (config != null) {
      CodeGenerator codeGenerator = findCodeGenerator(config);
      if (codeGenerator != null) {
        codeGenerator.addConfig(config);
      }
    }
  }

  private CodeGenerator findCodeGenerator(Config config) {
    for (CodeGenerator codeGenerator : codeGenerators) {
      File file = (File) codeGenerator;
      if (config.getFullPathname().equalsIgnoreCase(file.getPath())) {
        return codeGenerator;
      }
    }
    return null;
  }
} public class AskJson {
  @SerializedName("layoutGroup")
  private List<LayoutGroup> layoutGroups;

  @SerializedName("moduleGroup")
  private List<ModuleGroup> moduleGroups;

  public List<LayoutGroup> getLayoutGroups() {
    return layoutGroups;
  }

  public List<ModuleGroup> getModuleGroups() {
    return moduleGroups;
  }

  public void append(AskJson askJson) {
    if (askJson.getLayoutGroups() != null) {
      if (layoutGroups == null) {
        layoutGroups = new ArrayList<>();
      }

      layoutGroups.addAll(askJson.getLayoutGroups());
    }

    if (askJson.getModuleGroups() != null) {
      if (moduleGroups == null) {
        moduleGroups = new ArrayList<>();
      }

      moduleGroups.addAll(askJson.getModuleGroups());
    }
  }

  public LayoutGroup getLayoutGroupByCommand(String command) {
    try {
      for (LayoutGroup layoutGroup : layoutGroups) {
        if (layoutGroup.containCommand(command.toLowerCase())) {
          return layoutGroup;
        }
      }
    } catch (NullPointerException exception) {
      return null;
    }
    return null;
  }

  public ModuleGroup getModuleGroupByCommand(String command) {
    try {
      for (ModuleGroup moduleGroup : moduleGroups) {
        for (Module module : moduleGroup.getModules()) {
          if (module.containCommand(command.toLowerCase())) {
            return moduleGroup;
          }
        }
      }
    } catch (NullPointerException exception) {
      return null;
    }
    return null;
  }

  public Module getModuleByCommand(String command) {
    try {
      for (Module module : getModuleGroupByCommand(command).getModules()) {
        if (module.containCommand(command.toLowerCase())) {
          return module;
        }
      }
    } catch (NullPointerException exception) {
      return null;
    }
    return null;
  }

  public boolean hasApplicationProperty(List<Module> inputModules) {
    for (Module module : inputModules) {
      for (JavaConfig javaConfig : module.getJavaConfigs()) {
        if (javaConfig.getFileName().contains("<application>")) {
          return true;
        }
      }
    }
    return false;
  }
} public class GradleConfig extends Config {
  @SerializedName("snippet")
  private List<Snippet> snippets;

  public GradleConfig(String path, String fileNameEx) {
    this(path, fileNameEx, null);
  }

  private GradleConfig(String path, String fileNameEx, List<Snippet> snippets) {
    super(path, fileNameEx);
    this.snippets = snippets;
  }

  public List<Snippet> getSnippets() {
    return snippets;
  }

  public void addSnippet(Snippet snippet) {
    if (snippets == null) {
      snippets = new ArrayList<>();
    }

    snippets.add(snippet);
  }

  @Override
  public String toString() {
    return super.toString() + ", GradleConfig{" +
        "path='" + path + '\'' +
        ", fileNameEx='" + fileNameEx + '\'' +
        ", configs=" + snippets +
        '}';
  }
} public class LayoutGroup {
  @SerializedName("class")
  private String name;

  @SerializedName("command")
  private List<String> commands;

  public String getClassName() {
    return name;
  }

  public List<String> getCommands() {
    return commands;
  }

  public boolean containCommand(String key) {
    return commands.contains(key);
  }

  @Override
  public String toString() {
    return "Layout{" +
        "name='" + name + '\'' +
        ", commands=" + commands +
        '}';
  }
} public class ManifestConfig extends Config {
  @SerializedName("element")
  private List<ManifestElement> manifestElements;

  public ManifestConfig(String path, String fileNameEx) {
    super(path, fileNameEx);
  }

  public List<ManifestElement> getManifestElements() {
    return manifestElements;
  }

  @Override
  public String toString() {
    return "ManifestConfig{" +
        "manifestElements=" + manifestElements +
        '}';
  }
} public class Module {
  @SerializedName("class")
  private List<String> names;

  @SerializedName("command")
  private List<String> commands;

  @SerializedName("manifest_config")
  private List<ManifestConfig> manifestConfigs;

  @SerializedName("java_config")
  private List<JavaConfig> javaConfigs;

  @SerializedName("gradle_config")
  private List<GradleConfig> gradleConfigs;

  public List<String> getClassNames() {
    return names;
  }

  public List<String> getCommands() {
    return commands;
  }

  public List<String> getNames() {
    return names;
  }

  public List<ManifestConfig> getManifestConfigs() {
    return manifestConfigs;
  }

  public List<JavaConfig> getJavaConfigs() {
    return javaConfigs;
  }

  public List<GradleConfig> getGradleConfigs() {
    return gradleConfigs;
  }

  public boolean containCommand(String key) {
    return commands.contains(key);
  }

  @Override
  public String toString() {
    return "Module{" +
        "names=" + names +
        ", commands=" + commands +
        ", manifestConfigs=" + manifestConfigs +
        ", javaConfigs=" + javaConfigs +
        ", gradleConfigs=" + gradleConfigs +
        '}';
  }
} public class ModuleGroup {
  @SerializedName("name")
  private String name;

  @SerializedName("config_file")
  private List<String> configFilenames;

  @SerializedName("alert_message")
  private String alertMessage;

  @SerializedName("page")
  private String page;

  @SerializedName("group_gradle_config")
  private List<GradleConfig> groupGradleConfigs;

  @SerializedName("manifest_gradle_config")
  private List<ManifestConfig> groupManifestConfigs;

  @SerializedName("module")
  private List<Module> modules;

  public String getName() {
    return name;
  }

  public List<String> getConfigFilenames() {
    return configFilenames;
  }

  public String getAlertMessage() {
    return alertMessage;
  }

  public String getPage() {
    return page;
  }

  public List<GradleConfig> getGroupGradleConfigs() {
    return groupGradleConfigs;
  }

  public List<ManifestConfig> getGroupManifestConfigs() {
    return groupManifestConfigs;
  }

  public List<Module> getModules() {
    return modules;
  }

  @Override
  public String toString() {
    return "ModuleGroup{" +
        "name='" + name + '\'' +
        ", configFilenames=" + configFilenames +
        ", page='" + page + '\'' +
        ", groupGradleConfigs=" + groupGradleConfigs +
        ", modules=" + modules +
        '}';
  }
} public class ExecuteShellComand {
  public static String executeCommand(String command, boolean isConsolePrinted) {
    StringBuilder output = new StringBuilder();
    try {
      Process proc = Runtime.getRuntime().exec(new String[] { "sh", "-c", command });
      BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));

      String line;
      while ((line = reader.readLine())!= null) {
        output.append(line + "\n");

        if (isConsolePrinted) {
          System.out.println(line);
        }
      }
      proc.waitFor();
    } catch (Exception e) {
      e.printStackTrace();
    }

    return output.toString();
  }

  public static void executeAssemble(String pathname, boolean isConsolePrinted) {
    try {
      Process proc = Runtime.getRuntime().exec(pathname);
      BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));

      String line;
      while ((line = reader.readLine())!= null) {
        if (isConsolePrinted) {
          System.out.print("\r" + line);
        }
      }
      proc.waitFor();
      System.out.println();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
} public class FileUtils {

  public static String getRootPath() {
    File projectDir = new File(".");

    try {
      int index = projectDir.getCanonicalPath().indexOf(AskConfig.DEFAULT_ASK_APP_NAME);

      if (index > 0) {
        return projectDir.getCanonicalPath().substring(0, index - 1);
      }
      return projectDir.getCanonicalPath();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }

  public static String linkPathWithSlash(String... args) {
    String path = "";
    for (int i = 0, li = args.length; i < li; i++) {
      path += args[i];

      if (i != li - 1) {
        path += "/";
      }
    }
    return path;
  }

  public static void writeFile(File file, List<String> lineList) {
    writeFile(file, toString(lineList));
  }

  public static void writeFile(File file, String content) {
    if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) {
      return;
    }

    FileWriter fileWriter;
    try {
      fileWriter = new FileWriter(file);
      BufferedWriter bw = new BufferedWriter(fileWriter);
      bw.write(content);
      bw.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public static void writeFile(String pathName, String fileName, String content) {
    File destDir = new File(pathName);
    if (!destDir.exists() && !destDir.mkdirs()) {
      return;
    }

    writeFile(FileUtils.linkPathWithSlash(pathName, fileName), content);
  }

  public static void writeFile(String filePath, String content) {
    File file = new File(filePath);

    try {
      if (!file.exists()) {
        file.createNewFile();
      }

      writeFile(file, content);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public static List<String> readFileAsString(File file) {
    Scanner scanner;
    List<String> stringList = new ArrayList<>();

    try {
      scanner = new Scanner(file);

      while (scanner.hasNextLine()) {
        String e = scanner.nextLine();
        stringList.add(e);
      }
    } catch (FileNotFoundException e) {
      throw new RuntimeException("Failed to find : " + file.getPath());
    }

    return stringList;
  }

  public static void copyFile(File moduleFile,
                              String sourceFilePath) throws IOException {
    File destDir = new File(sourceFilePath);
    if (!destDir.exists() && !destDir.mkdirs()) {
      return;
    }

    File sourceFile = moduleFile;
    File destFile = new File(linkPathWithSlash(sourceFilePath, moduleFile.getName()));

    if (!sourceFile.exists()) {
      return;
    }

    if (!destFile.exists()) {
      destFile.createNewFile();
    }

    FileChannel sourceChannel;
    FileChannel destinationChannel;

    sourceChannel = new FileInputStream(sourceFile).getChannel();
    destinationChannel = new FileOutputStream(destFile).getChannel();

    if (destinationChannel != null && sourceChannel != null) {
      destinationChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
    }

    if (sourceChannel != null) {
      sourceChannel.close();
    }

    if (destinationChannel != null) {
      destinationChannel.close();
    }
  }

  public static void copyDirectory(File src, File dest) throws IOException{
    if (src.isDirectory()) {
      if (!dest.exists()) {
        dest.mkdirs();
      }

      String files[] = src.list();

      for (String file : files) {
        File srcFile = new File(src, file);
        File destFile = new File(dest, file);
        copyDirectory(srcFile,destFile);
      }

    } else {
      InputStream in = new FileInputStream(src);
      OutputStream out = new FileOutputStream(dest);

      byte[] buffer = new byte[1024];

      int length;
      while ((length = in.read(buffer)) > 0) {
        out.write(buffer, 0, length);
      }

      in.close();
      out.close();
    }
  }

  public static String changeDotToSlash(String str) {
    return str.replaceAll("\\.", "/");
  }

  public static String getStringBetweenQuotes(String str) {
    Pattern pattern = Pattern.compile("\"([^\"]*)\"");
    Matcher matcher = pattern.matcher(str);
    while (matcher.find()) {
      return matcher.group(1);
    }
    return null;
  }

  public static String getFilenameFromDotPath(String pathname) {
    int index = pathname.lastIndexOf(".");
    if (index < 0) {
      index = 0;
    } else {
      index += 1;
    }
    return pathname.substring(index, pathname.length());
  }

  public static String getFilenameFromSlashPath(String pathname) {
    int index = pathname.lastIndexOf("/");
    if (index < 0) {
      index = 0;
    } else {
      index += 1;
    }
    return pathname.substring(index, pathname.length());
  }

  public static String removeExtension(String filename) {
    return filename.substring(0, filename.lastIndexOf('.'));
  }

  public static String removeFirstSlash(String path) {
    if (path.length() > 0 && path.charAt(0) == '/') {
      path = path.substring(1);
    }

    return path;
  }

  public static String extractIndentInLine(String line) {
    String intent = "";
    for (int i = 0, li = line.length(); i < li; i++) {
      if (line.toCharArray()[i] == ' ') {
        intent += " ";
      } else {
        return intent;
      }
    }
    return intent;
  }

  public static String toString(List<String> strList) {
    final String EoL = System.getProperty("line.separator");

    StringBuilder sb = new StringBuilder();
    for (String line : strList) {
      sb.append(line).append(EoL);
    }

    return sb.toString();
  }

  public static String getStringWithRemovedComment(String line) {
    return line.replaceAll("//.*", "");
  }

  public static int getOpenCurlyBracketCount(String line) {
    int count = 0;
    for ( int i = 0; i < line.length(); i++ ) {
      if (line.charAt(i) == '{') {
        count++;
      }
    }
    return count;
  }

  public static int getCloseCurlyBracketCount(String line) {
    int count = 0;
    for ( int i = 0; i < line.length(); i++ ) {
      if (line.charAt(i) == '}') {
        count++;
      }
    }
    return count;
  }
}
import com.androidstarterkit.android.api.Extension;
import com.androidstarterkit.android.api.resource.AttributeContraints;
import com.androidstarterkit.command.CommandParser;
import com.androidstarterkit.command.TabType;
import com.androidstarterkit.config.AskConfig;
import com.androidstarterkit.directory.RemoteDirectory;
import com.androidstarterkit.directory.SourceDirectory;
import com.androidstarterkit.exception.CommandException;
import com.androidstarterkit.file.SettingsGradle;
import com.androidstarterkit.injection.AskJsonBuilder;
import com.androidstarterkit.injection.ModuleLoader;
import com.androidstarterkit.injection.model.AskJson;
import com.androidstarterkit.injection.model.GradleConfig;
import com.androidstarterkit.injection.model.LayoutGroup;
import com.androidstarterkit.injection.model.ManifestConfig;
import com.androidstarterkit.injection.model.Module;
import com.androidstarterkit.injection.model.ModuleGroup;
import com.androidstarterkit.tool.ExecuteShellComand;
import com.androidstarterkit.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
package com.androidstarterkit;



public class Ask {
  static final int env = AskConfig.PRODUCTION;
  static final int output = AskConfig.OUTPUT_PROD_PROJECT;

  public static void main(String[] args) {
    CommandParser commandParser;