Removed usage of 'printDependencies' task

Added 'initializeIDEProject' task in every module project which writes information about the whole project to a file
This commit is contained in:
Akash Yadav
2021-09-07 11:39:01 +05:30
parent a97b52504f
commit a5a10ff428
11 changed files with 532 additions and 235 deletions
@@ -119,6 +119,7 @@ import java.util.Stack;
import java.util.regex.Pattern;
import me.piruin.quickaction.ActionItem;
import me.piruin.quickaction.QuickAction;
import com.itsaky.androidide.models.project.IDEProject;
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
IDEService.BuildListener,
@@ -292,7 +293,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
}
private void startServices() {
getBuildService().showDependencies();
getBuildService().initProject();
}
private void removeFromParent(View v) {
@@ -702,13 +703,18 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
.setPositiveButton(android.R.string.yes, (p1, p2) -> {
p1.dismiss();
if(getBuildService() != null)
getBuildService().showDependencies();
getBuildService().initProject();
})
.setTitle(R.string.failed)
.setMessage(R.string.msg_first_prepare_failed)
.create().show();
}
@Override
public void onProjectLoaded(IDEProject project) {
}
@Override
public void saveFiles() {
saveAll();
@@ -34,8 +34,7 @@ public class SplashActivity extends StudioActivity
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
}
@@ -16,7 +16,7 @@ import net.lingala.zip4j.ZipFile;
public class ToolsManager {
private static PreferenceManager prefs;
public static final int JLS_VERSION = 1;
public static final int LOGSENDER_VERSION = 1;
public static final int CLEANER_VERSION = 1;
@@ -37,10 +37,16 @@ public class ToolsManager {
writeInitScript();
writeBashrc();
rewriteProjectData();
if(onFinish != null)
onFinish.run();
}
private static void rewriteProjectData() {
FileIOUtils.writeFileFromString(Environment.PROJECT_DATA_FILE, "/**********************/");
}
private static void copyBusyboxIfNeeded() {
File exec = Environment.BUSYBOX;
if(exec.exists()) return;
@@ -111,7 +117,7 @@ public class ToolsManager {
"PS1=\'\\[\\e[0;34m\\]\\w\\[\\e[0m\\] \\[\\e[0;97m\\]$\\[\\e[0m\\] \'";
private static final String INIT_SCRIPT =
"import org.gradle.api.Plugin"
( "import org.gradle.api.Plugin"
+ "\nimport org.gradle.api.Project"
+ "\nimport groovy.json.*"
+ "\n"
@@ -123,117 +129,6 @@ public class ToolsManager {
+ "\n }"
+ "\n}"
+ "\n"
+ "\ngradle.projectsEvaluated {"
+ "\n IDEProject project = new IDEProject()"
+ "\n project.name = rootProject.getName()"
+ "\n project.displayName = rootProject.getDisplayName()"
+ "\n project.description = rootProject.getDescription()"
+ "\n project.path = rootProject.getPath()"
+ "\n "
+ "\n def tasks = rootProject.tasks"
+ "\n if(tasks != null && tasks.size() > 0) {"
+ "\n addTasks(project, rootProject)"
+ "\n }"
+ "\n "
+ "\n rootProject.subprojects.forEach { sub ->"
+ "\n addIDEProject(project, sub);"
+ "\n }"
+ "\n "
+ "\n File out = new File(\"" + Environment.PROJECT_DATA_FILE.getAbsolutePath() + "\")"
+ "\n out << new JsonBuilder( project ).toPrettyString()"
+ "\n "
+ "\n println \">>> PROJECT INITIALIZED <<<\""
+ "\n}"
+ "\n"
+ "\ndef addIDEProject (IDEProject parent, Project sub) {"
+ "\n if(parent == null || sub == null) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n def isApp = sub.getPluginManager().hasPlugin(\"com.android.application\")"
+ "\n def isLibrary = sub.getPluginManager().hasPlugin(\"com.android.library\")"
+ "\n "
+ "\n if(!(isApp || isLibrary)) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n IDEModule module = new IDEModule()"
+ "\n if(isApp) {"
+ "\n module = new IDEAppModule()"
+ "\n }"
+ "\n "
+ "\n def android = sub.android"
+ "\n def config = android.defaultConfig"
+ "\n "
+ "\n module.name = sub.getName()"
+ "\n module.displayName = sub.getDisplayName()"
+ "\n module.description = sub.getDescription()"
+ "\n module.path = sub.getPath()"
+ "\n module.buildToolsVersion = android.buildToolsVersion"
+ "\n module.compileSdkVersion = android.compileSdkVersion"
+ "\n module.minSdk = config.minSdkVersion"
+ "\n module.targetSdk = config.targetSdkVersion"
+ "\n module.versionCode = config.versionCode"
+ "\n module.versionName = config.versionName"
+ "\n "
+ "\n if(isApp) {"
+ "\n module.applicationId = config.applicationId"
+ "\n }"
+ "\n "
+ "\n def tasks = sub.tasks"
+ "\n if(tasks != null && tasks.size() > 0) {"
+ "\n addTasks(module, sub)"
+ "\n }"
+ "\n "
+ "\n def variants = null"
+ "\n if(isApp) {"
+ "\n variants = project.android.applicationVariants"
+ "\n } else if (isLibrary) {"
+ "\n variants = project.android.libraryVariants"
+ "\n } else {"
+ "\n println \"AndroidIDE: Cannot build variants of project: ${module.name}\""
+ "\n }"
+ "\n "
+ "\n if(variants != null) {"
+ "\n variants.all { variant ->"
+ "\n addDependencies(module, variant);"
+ "\n }"
+ "\n }"
+ "\n "
+ "\n def subs = sub.getSubprojects()"
+ "\n if(subs != null && subs.size() > 0) {"
+ "\n subs.forEach { subOfSub -> "
+ "\n addIDEProject(module, subOfSub)"
+ "\n }"
+ "\n }"
+ "\n parent.modules.add(module)"
+ "\n}"
+ "\n"
+ "\ndef addDependencies(def module, def variant) {"
+ "\n variant.getCompileClasspath().each { dependency ->"
+ "\n def path = dependency.absolutePath"
+ "\n if(!module.dependencies.contains(path)) {"
+ "\n module.dependencies.add(dependency.absolutePath);"
+ "\n }"
+ "\n }"
+ "\n}"
+ "\n"
+ "\ndef addTasks (IDEProject project, Project gradleProject) {"
+ "\n if(project == null || gradleProject == null) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n gradleProject.tasks.forEach { gradleTask -> "
+ "\n IDETask task = new IDETask()"
+ "\n task.name = gradleTask.getName()"
+ "\n task.description = gradleTask.getDescription()"
+ "\n task.group = gradleTask.getGroup()"
+ "\n task.path = gradleTask.getPath()"
+ "\n "
+ "\n project.tasks.add(task)"
+ "\n }"
+ "\n}"
+ "\n"
+ "\nclass AndroidIDEPlugin implements Plugin<Project> {"
+ "\n "
+ "\n def AAPT2_VERSION = \"4.0.1-6197926\""
@@ -258,7 +153,7 @@ public class ToolsManager {
+ "\n "
+ "\n project.configurations.matching { it.name == \'_internal_aapt2_binary\' }.all { config ->"
+ "\n config.resolutionStrategy.eachDependency { details ->"
+ "\n details.useVersion(\"$AAPT2_VERSION\")"
+ "\n details.useVersion(\"${AAPT2_VERSION}\")"
+ "\n }"
+ "\n }"
+ "\n "
@@ -272,8 +167,49 @@ public class ToolsManager {
+ "\n return"
+ "\n }"
+ "\n "
+ "\n project.tasks.create("
+ "\n name: \"initializeIDEProject\","
+ "\n group: \"AndroidIDE\","
+ "\n description: \"Initializes the project in AndroidIDE. AndroidIDE manages the proper execution of this task. You\'re not supposed to execute this task manually!\""
+ "\n )"
+ "\n {"
+ "\n outputs.upToDateWhen { false }"
+ "\n dependsOn assembleDebug"
+ "\n "
+ "\n doLast {"
+ "\n def root = project.getRootProject()"
+ "\n "
+ "\n IDEProject ideProject = new IDEProject()"
+ "\n ideProject.name = root.getName()"
+ "\n ideProject.displayName = root.getDisplayName()"
+ "\n ideProject.description = root.getDescription()"
+ "\n ideProject.path = root.getPath()"
+ "\n "
+ "\n def tasks = root.tasks"
+ "\n if(tasks != null && tasks.size() > 0) {"
+ "\n addTasks(ideProject, root)"
+ "\n }"
+ "\n "
+ "\n root.subprojects.forEach { sub ->"
+ "\n addIDEProject(ideProject, sub);"
+ "\n }"
+ "\n "
+ "\n File out = new File(\"@@PROJECT_DATA_FILE@@\")"
+ "\n out << new JsonBuilder( ideProject ).toPrettyString()"
+ "\n "
+ "\n println \">>> PROJECT INITIALIZED <<<\""
+ "\n }"
+ "\n }"
+ "\n "
+ "\n project.afterEvaluate {"
+ "\n project.tasks.getByName(\'assembleDebug\').finalizedBy(\'initializeIDEProject\')"
+ "\n project.tasks.getByName(\'assembleRelease\').finalizedBy(\'initializeIDEProject\')"
+ "\n }"
+ "\n "
+ "\n variants.all { variant ->"
+ "\n project.tasks.create(name: \"printDependencies${variant.name.capitalize()}\", group: \"Android IDE\", description: \"[AndroidIDE] List all dependencies in project including those in subprojects for ${variant.name.capitalize()} build.\") {"
+ "\n "
+ "\n def variantName = variant.name.capitalize()"
+ "\n project.tasks.create(name: \"printDependencies${variantName}\", group: \"AndroidIDE\", description: \"List all dependencies in project including those in subprojects for ${variant.name.capitalize()} build.\") {"
+ "\n doLast {"
+ "\n printDependencies(project, variant)"
+ "\n }"
@@ -281,6 +217,95 @@ public class ToolsManager {
+ "\n }"
+ "\n }"
+ "\n "
+ "\n def addIDEProject (IDEProject parent, Project sub) {"
+ "\n if(parent == null || sub == null) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n def isApp = sub.getPluginManager().hasPlugin(\"com.android.application\")"
+ "\n def isLibrary = sub.getPluginManager().hasPlugin(\"com.android.library\")"
+ "\n "
+ "\n if(!(isApp || isLibrary)) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n IDEModule module = new IDEModule()"
+ "\n if(isApp) {"
+ "\n module = new IDEAppModule()"
+ "\n }"
+ "\n "
+ "\n def android = sub.android"
+ "\n def config = android.defaultConfig"
+ "\n "
+ "\n module.name = sub.getName()"
+ "\n module.displayName = sub.getDisplayName()"
+ "\n module.description = sub.getDescription()"
+ "\n module.path = sub.getPath()"
+ "\n module.buildToolsVersion = android.buildToolsVersion"
+ "\n module.compileSdkVersion = android.compileSdkVersion"
+ "\n module.minSdk = config.minSdkVersion"
+ "\n module.targetSdk = config.targetSdkVersion"
+ "\n module.versionCode = config.versionCode"
+ "\n module.versionName = config.versionName"
+ "\n "
+ "\n if(isApp) {"
+ "\n module.applicationId = config.applicationId"
+ "\n }"
+ "\n "
+ "\n def tasks = sub.tasks"
+ "\n if(tasks != null && tasks.size() > 0) {"
+ "\n addTasks(module, sub)"
+ "\n }"
+ "\n "
+ "\n def variants = null"
+ "\n if(isApp) {"
+ "\n variants = sub.android.applicationVariants"
+ "\n } else if (isLibrary) {"
+ "\n variants = sub.android.libraryVariants"
+ "\n } else {"
+ "\n println \"AndroidIDE: Cannot find build variants of project: ${module.name}(${module.path})\""
+ "\n }"
+ "\n "
+ "\n if(variants != null) {"
+ "\n variants.all { variant ->"
+ "\n addDependencies(module, variant);"
+ "\n }"
+ "\n }"
+ "\n "
+ "\n def subs = sub.getSubprojects()"
+ "\n if(subs != null && subs.size() > 0) {"
+ "\n subs.forEach { subOfSub -> "
+ "\n addIDEProject(module, subOfSub)"
+ "\n }"
+ "\n }"
+ "\n parent.modules.add(module)"
+ "\n }"
+ "\n "
+ "\n def addDependencies(def module, def variant) {"
+ "\n variant.getCompileClasspath().each { dependency ->"
+ "\n def path = dependency.absolutePath"
+ "\n if(!module.dependencies.contains(path)) {"
+ "\n module.dependencies.add(dependency.absolutePath);"
+ "\n }"
+ "\n }"
+ "\n }"
+ "\n "
+ "\n def addTasks (IDEProject project, Project gradleProject) {"
+ "\n if(project == null || gradleProject == null) {"
+ "\n return"
+ "\n }"
+ "\n "
+ "\n gradleProject.tasks.forEach { gradleTask -> "
+ "\n IDETask task = new IDETask()"
+ "\n task.name = gradleTask.getName()"
+ "\n task.description = gradleTask.getDescription()"
+ "\n task.group = gradleTask.getGroup()"
+ "\n task.path = gradleTask.getPath()"
+ "\n "
+ "\n project.tasks.add(task)"
+ "\n }"
+ "\n }"
+ "\n "
+ "\n def printDependencies(def project, def variant) {"
+ "\n println \">> DEPENDENCIES START <<\""
+ "\n variant.getCompileClasspath().each { dep ->"
@@ -324,7 +349,7 @@ public class ToolsManager {
+ "\n def description = \"Not defined\""
+ "\n def group = \"Not defined\""
+ "\n def path = \"Not defined\""
+ "\n}";
+ "\n}").replace("@@PROJECT_DATA_FILE@@", Environment.PROJECT_DATA_FILE.getAbsolutePath());
private static final Logger LOG = Logger.instance("ToolsManager");
}
@@ -12,27 +12,27 @@ public class IDEProject {
* Name of this project
*/
@SerializedName("name")
public String name;
public String name = "";
/**
* Name of this project which should be displayed to user
*/
@SerializedName("displayName")
public String displayName;
public String displayName = "";
/**
* Description of this project. Maybe null.
*/
@SerializedName("description")
public String description;
public String description = "";
/**
* Path of this project. This is NOT the directory path of this project.
*
* For example, ':app: or ':module:subModule'
* For example, ':app or ':module:subModule'
*/
@SerializedName("path")
public String path;
public String path = "";
/**
* Subprojects of this project
@@ -21,6 +21,15 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.json.JSONObject;
import com.itsaky.androidide.models.project.IDEProject;
import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.ReadFileTask;
import com.itsaky.androidide.utils.RuntimeTypeAdapterFactory;
import com.itsaky.androidide.models.project.IDEModule;
import com.itsaky.androidide.models.project.IDEAppModule;
import com.google.gson.GsonBuilder;
import com.blankj.utilcode.util.FileIOUtils;
import com.itsaky.androidide.utils.Logger;
public class IDEService implements ShellServer.Callback {
@@ -29,21 +38,19 @@ public class IDEService implements ShellServer.Callback {
private GradleTask currentTask;
private StudioApp app;
private IDEProject mIDEProject;
private AndroidProject project;
private List<String> dependencies;
private boolean isBuilding = false;
private boolean isRunning = false;
private boolean outputtingJars = false;
private boolean recreateShellOnDone = false;
private final String RUN_TASK = "> Task";
private final String STARTING_DAEMON = "Starting a ";
private final String BUILD_SUCCESS = "BUILD SUCCESSFUL";
private final String BUILD_FAILED = "BUILD FAILED";
private final String SHOW_DEPENDENCIES_START = ">> DEPENDENCIES START <<";
private final String SHOW_DEPENDENCIES_END = ">> DEPENDENCIES END <<";
private final String PROJECT_INITIALIZED = ">>> PROJECT INITIALIZED <<<";
public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES;
@@ -59,13 +66,15 @@ public class IDEService implements ShellServer.Callback {
public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG;
public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE;
public static final Logger LOG = Logger.instance("IDEService");
public IDEService(AndroidProject project) {
this.app = StudioApp.getInstance();
this.project = project;
this.shell = app.newShell(this);
isRunning = true;
this.isRunning = true;
}
public IDEService setListener(BuildListener listener) {
@@ -87,42 +96,7 @@ public class IDEService implements ShellServer.Callback {
}
boolean shouldOutput = true;
if(line.contains(SHOW_DEPENDENCIES_START)) {
shouldOutput = false;
dependencies = new ArrayList<>();
outputtingJars = true;
if(listener != null)
listener.onRunTask(currentTask, "> Task :app:startCompletionServices");
return;
} else if(line.contains(SHOW_DEPENDENCIES_END)) {
shouldOutput = false;
outputtingJars = false;
if(listener != null
&& dependencies != null) {
listener.onGetDependencies(dependencies);
}
StudioApp.getInstance().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
return;
}
if(outputtingJars) {
JSONObject o = asObjectOrNull(line);
try {
if(o.has("type")) {
String type = o.getString("type");
if(type.equals("ANDROID_JAR")) {
shouldOutput = false;
Environment.setBootClasspath(new File(o.getString("requested")));
}
} else if(o.has("file")) {
shouldOutput = false;
String jar = o.getString("file");
dependencies.add(jar);
}
} catch (Throwable th) {}
}
if(shouldOutput) {
String text = line.replace(StudioApp.getInstance().getRootDir().getAbsolutePath(), "ANDROIDIDE_HOME");
listener.appendOutput(currentTask, text);
@@ -135,7 +109,6 @@ public class IDEService implements ShellServer.Callback {
} else
if(charSequence.toString().contains(BUILD_SUCCESS)) {
isBuilding = false;
outputtingJars = false;
if(recreateShellOnDone)
createShell();
if(currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES)
@@ -144,7 +117,6 @@ public class IDEService implements ShellServer.Callback {
} else
if(charSequence.toString().contains(BUILD_FAILED)) {
isBuilding = false;
outputtingJars = false;
if(recreateShellOnDone)
createShell();
if(currentTask != null) {
@@ -159,6 +131,27 @@ public class IDEService implements ShellServer.Callback {
}
private void readIdeProject() {
// Parsing project data is meaningless if there is no one listening...
if(listener == null)
return;
final BuildListener local = listener;
new Thread(() -> {
RuntimeTypeAdapterFactory<IDEProject> typeAdapter = RuntimeTypeAdapterFactory.of(IDEProject.class)
.registerSubtype(IDEProject.class, "ide_project")
.registerSubtype(IDEModule.class, "ide_module")
.registerSubtype(IDEAppModule.class, "ide_app_module");
IDEProject ideProject = new GsonBuilder()
.registerTypeAdapterFactory(typeAdapter)
.create()
.fromJson(
FileIOUtils.readFile2String(Environment.PROJECT_DATA_FILE),
IDEProject.class
);
if(local != null) local.onProjectLoaded(ideProject);
LOG.i("Deserialized project: " + ideProject, (ideProject != null ? String.format("Name: %s\nPath: %s\nTask count: %d\nSubproject count:%s", ideProject.name, ideProject.path, ideProject.tasks.size(), ideProject.modules.size()) : ""));
}).start();
}
private void appendOutputSeparator() {
@@ -256,8 +249,8 @@ public class IDEService implements ShellServer.Callback {
}
}
public void showDependencies() {
execTask(BaseGradleTasks.SHOW_DEPENDENCIES);
public void initProject() {
execTask(BaseGradleTasks.INITIALIZE_IDE_PROJECT);
}
public void assembleDebug(boolean installApk) {
@@ -343,6 +336,7 @@ public class IDEService implements ShellServer.Callback {
}
public static interface BuildListener {
void onProjectLoaded(IDEProject project);
void onGetDependencies(List<String> dependencies);
void onGetDependenciesFailed();
@@ -37,5 +37,5 @@ public interface GradleTask {
public static final int TASK_LINT_DEBUG = 10;
public static final int TASK_LINT_RELEASE = 11;
public static final int TASK_TASKS = 12;
public static final int TASK_INIT_PROJECT = 13;
}
@@ -14,7 +14,7 @@ import com.itsaky.androidide.tasks.gradle.dex.MergeLibAndProjectDex;
import com.itsaky.androidide.tasks.gradle.lint.Lint;
import com.itsaky.androidide.tasks.gradle.lint.LintDebug;
import com.itsaky.androidide.tasks.gradle.lint.LintRelease;
import com.itsaky.androidide.tasks.gradle.plugin.PrintDependencies;
import com.itsaky.androidide.tasks.gradle.plugin.InitializeIDEProject;
public class BaseGradleTasks {
@@ -36,5 +36,5 @@ public class BaseGradleTasks {
MERGE_EXT_DEX = new MergeExtDex(),
MERGE_LIB_AND_PROJECT_DEX = new MergeLibAndProjectDex(),
SHOW_DEPENDENCIES = new PrintDependencies();
INITIALIZE_IDE_PROJECT = new InitializeIDEProject();
}
@@ -0,0 +1,50 @@
package com.itsaky.androidide.tasks.gradle.plugin;
import com.itsaky.androidide.tasks.BaseGradleTask;
import com.itsaky.androidide.tasks.GradleTask;
import java.io.File;
import java.util.List;
import java.util.Arrays;
public class InitializeIDEProject extends BaseGradleTask {
@Override
public String getName() {
return "initializeIDEProject";
}
@Override
public String getCommands() {
return "initializeIDEProject";
}
@Override
public List<String> getTasks() {
return Arrays.asList(getCommands());
}
@Override
public int getTaskID() {
return TASK_INIT_PROJECT;
}
@Override
public boolean canOutput() {
return false;
}
@Override
public boolean buildsApk() {
return false;
}
@Override
public File getApk(String buildFolder, String moduleName) {
return null;
}
@Override
public GradleTask.Type getType() {
return Type.ANDROIDIDE_PLUGIN;
}
}
@@ -1,57 +0,0 @@
package com.itsaky.androidide.tasks.gradle.plugin;
import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.managers.PreferenceManager;
import com.itsaky.androidide.services.IDEService;
import com.itsaky.androidide.tasks.BaseGradleTask;
import java.io.File;
import java.util.List;
import java.util.ArrayList;
public class PrintDependencies extends BaseGradleTask {
@Override
public List<String> getTasks() {
List<String> tasks = new ArrayList<>();
for(String task : getCommands().split("\\s")) {
if(task != null && task.trim().length() > 0)
tasks.add(task);
}
return tasks;
}
@Override
public String getName() {
return "Start Completion Services";
}
@Override
public String getCommands() {
return (StudioApp.getInstance().getPrefManager().getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true) ? "assembleDebug " : "") + "printDependenciesDebug";
}
@Override
public int getTaskID() {
return IDEService.TASK_SHOW_DEPENDENCIES;
}
@Override
public boolean canOutput() {
return true;
}
@Override
public boolean buildsApk() {
return false;
}
@Override
public File getApk(String buildFolder, String moduleName) {
return null;
}
@Override
public Type getType() {
return Type.ANDROIDIDE_PLUGIN;
}
}
@@ -11,8 +11,8 @@ import com.itsaky.androidide.interfaces.ProjectWriterCallback;
import com.itsaky.androidide.models.NewProjectDetails;
import com.itsaky.androidide.models.ProjectTemplate;
import java.io.File;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ProjectWriter {
@@ -31,9 +31,7 @@ public class ProjectWriter {
MSDK = "$min_sdk",
TSDK = "$target_sdk",
ANDROIDIDE_PLUGIN = "$androidide_gradle_plugin_version";
private static final String ANDROIDIDE_PLUGIN_VERSION = "1.0.6";
public static String createMenu() {
return "<menu"
+ "\n\txmlns:android=\"http://schemas.android.com/apk/res/android\""
@@ -129,10 +127,15 @@ public class ProjectWriter {
public static String createActivity(String packageName, String className) {
return "package " + packageName + ";\n"
+ "\n"
+ "import android.os.Bundle;"
+ "import androidx.appcompat.app.AppCompatActivity;"
+ "\n"
+ "public class " + className + " extends AppCompatActivity {\n"
+ "\t\n"
+ " \n"
+ "\n @Override"
+ "\n protected void onCreate(Bundle savedInstanceState) {"
+ "\n super.onCreate(savedInstanceState);"
+ "\n }"
+ "}";
}
@@ -180,7 +183,7 @@ public class ProjectWriter {
.replace(PNAME, details.packageName)
.replace(MSDK, String.valueOf(details.minSdk))
.replace(TSDK, String.valueOf(details.targetSdk))
.replace(ANDROIDIDE_PLUGIN, ANDROIDIDE_PLUGIN_VERSION);
.replace(ANDROIDIDE_PLUGIN, "1.0.6");
if(FileIOUtils.writeFileFromString(file, read, false)) {
continue;
} else {
@@ -0,0 +1,277 @@
package com.itsaky.androidide.utils;
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed 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.
*/
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Adapts values whose runtime type may differ from their declaration type. This
* is necessary when a field's type is not the same type that GSON should create
* when deserializing that field. For example, consider these types:
* <pre> {@code
* abstract class Shape {
* int x;
* int y;
* }
* class Circle extends Shape {
* int radius;
* }
* class Rectangle extends Shape {
* int width;
* int height;
* }
* class Diamond extends Shape {
* int width;
* int height;
* }
* class Drawing {
* Shape bottomShape;
* Shape topShape;
* }
* }</pre>
* <p>Without additional type information, the serialized JSON is ambiguous. Is
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
* {
* "bottomShape": {
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* This class addresses this problem by adding type information to the
* serialized JSON and honoring that type information when the JSON is
* deserialized: <pre> {@code
* {
* "bottomShape": {
* "type": "Diamond",
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "type": "Circle",
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* Both the type field name ({@code "type"}) and the type labels ({@code
* "Rectangle"}) are configurable.
*
* <h3>Registering Types</h3>
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
* name to the {@link #of} factory method. If you don't supply an explicit type
* field name, {@code "type"} will be used. <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre>
* Next register all of your subtypes. Every subtype must be explicitly
* registered. This protects your application from injection attacks. If you
* don't supply an explicit type label, the type's simple name will be used.
* <pre> {@code
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
* }</pre>
* Finally, register the type adapter factory in your application's GSON builder:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
* }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* }</pre>
*
* <h3>Serialization and deserialization</h3>
* In order to serialize and deserialize a polymorphic object,
* you must specify the base type explicitly.
* <pre> {@code
* Diamond diamond = new Diamond();
* String json = gson.toJson(diamond, Shape.class);
* }</pre>
* And then:
* <pre> {@code
* Shape shape = gson.fromJson(json, Shape.class);
* }</pre>
*/
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private final boolean maintainType;
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
this.maintainType = maintainType;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
* {@code maintainType} flag decide if the type will be stored in pojo or not.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, maintainType);
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName, false);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<T>(baseType, "type", false);
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type.getRawType() != baseType) {
return null;
}
final Map<String, TypeAdapter<?>> labelToDelegate
= new LinkedHashMap<String, TypeAdapter<?>>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
JsonElement labelJsonElement;
if (maintainType) {
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
} else {
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
}
if (labelJsonElement == null) {
throw new JsonParseException("cannot deserialize " + baseType
+ " because it does not define a field named " + typeFieldName);
}
String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
if (maintainType) {
Streams.write(jsonObject, out);
return;
}
JsonObject clone = new JsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
Streams.write(clone, out);
}
}.nullSafe();
}
}