Updated editor to v0.10.7

This commit is contained in:
Akash Yadav
2022-03-13 13:23:37 +05:30
parent 54980ce763
commit 2d094eb77a
9 changed files with 859 additions and 868 deletions
@@ -17,9 +17,5 @@
package com.itsaky.androidide.fragments.attr;
/**
* @author Akash Yadav
*/
public class BooleanEditor extends BaseValueEditorFragment {
}
/** @author Akash Yadav */
public class BooleanEditor extends BaseValueEditorFragment {}
@@ -42,133 +42,130 @@ import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* @author Akash Yadav
*/
/** @author Akash Yadav */
public class DimensionEditor extends BaseValueEditorFragment {
private static final Logger LOG = Logger.instance ("DimensionEditor");
private static final Logger LOG = Logger.instance("DimensionEditor");
public TextWatcherAdapter dimensionInputWatcher;
public TextWatcherAdapter dimensionResInputWatcher;
private LayoutDimensionAttrEditorBinding binding;
private String[] dimensionUnits;
@Nullable
@Override
public View onCreateView (
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
this.binding = LayoutDimensionAttrEditorBinding.inflate (inflater, container, false);
return this.binding.getRoot ();
this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false);
return this.binding.getRoot();
}
@Override
public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated (view, savedInstanceState);
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final var dimensionInput =
Objects.requireNonNull (this.binding.dimensionInput.getEditText ());
Objects.requireNonNull(this.binding.dimensionInput.getEditText());
if (dimensionInputWatcher != null) {
dimensionInput.removeTextChangedListener (dimensionInputWatcher);
dimensionInput.removeTextChangedListener(dimensionInputWatcher);
}
dimensionInputWatcher =
new TextWatcherAdapter () {
new TextWatcherAdapter() {
@Override
public void afterTextChanged (@NonNull Editable s) {
final var text = s.toString ().trim ();
public void afterTextChanged(@NonNull Editable s) {
final var text = s.toString().trim();
final var unit =
getDimensionUnits ()[binding.unitSelector.getSelectedItemPosition ()];
getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()];
// This will call CommonParseUtils#parseDimension
notifyValueChanged (text.concat (unit));
notifyValueChanged(text.concat(unit));
}
};
dimensionInput.addTextChangedListener (dimensionInputWatcher);
dimensionInput.addTextChangedListener(dimensionInputWatcher);
final var dimensionResInput =
(MaterialAutoCompleteTextView)
Objects.requireNonNull (binding.dimensionResInput.getEditText ());
Objects.requireNonNull(binding.dimensionResInput.getEditText());
if (dimensionResInputWatcher != null) {
dimensionResInput.removeTextChangedListener (dimensionResInputWatcher);
dimensionResInput.removeTextChangedListener(dimensionResInputWatcher);
}
dimensionResInputWatcher =
new TextWatcherAdapter () {
new TextWatcherAdapter() {
@Override
public void afterTextChanged (@NonNull Editable s) {
final var text = s.toString ().trim ();
if (TextUtils.isEmpty (text)) {
public void afterTextChanged(@NonNull Editable s) {
final var text = s.toString().trim();
if (TextUtils.isEmpty(text)) {
return;
}
notifyValueChanged (text);
notifyValueChanged(text);
}
};
dimensionResInput.addTextChangedListener (dimensionResInputWatcher);
dimensionResInput.addTextChangedListener(dimensionResInputWatcher);
final var future =
CompletableFuture.supplyAsync (
CompletableFuture.supplyAsync(
() -> {
final var list = new ArrayList<String> ();
final var tables = ValuesTableFactory.getAllTables ();
for (var entry : tables.entrySet ()) {
final var dimens = entry.getValue ().getTable ("dimen");
final var list = new ArrayList<String>();
final var tables = ValuesTableFactory.getAllTables();
for (var entry : tables.entrySet()) {
final var dimens = entry.getValue().getTable("dimen");
if (dimens != null) {
list.addAll (
dimens.keySet ().stream ()
.map ("@dimen/"::concat)
.collect (Collectors.toSet ()));
list.addAll(
dimens.keySet().stream()
.map("@dimen/"::concat)
.collect(Collectors.toSet()));
}
}
list.addAll (FrameworkValues.listDimens ()
.stream ()
.map ("@android:dimen/"::concat)
.collect (Collectors.toList ())
);
list.addAll(
FrameworkValues.listDimens().stream()
.map("@android:dimen/"::concat)
.collect(Collectors.toList()));
return list;
});
future.whenComplete (
future.whenComplete(
(list, throwable) -> {
if (list == null || list.isEmpty ()) {
LOG.error ("No completion items found");
if (list == null || list.isEmpty()) {
LOG.error("No completion items found");
return;
}
LOG.debug ("Found", list.size (), "dimension resources for completion");
ThreadUtils.runOnUiThread (
LOG.debug("Found", list.size(), "dimension resources for completion");
ThreadUtils.runOnUiThread(
() -> {
final var adapter =
new AttrValueCompletionAdapter (requireContext (), list);
dimensionResInput.setThreshold (1);
dimensionResInput.setAdapter (adapter);
adapter.notifyDataSetChanged ();
new AttrValueCompletionAdapter(requireContext(), list);
dimensionResInput.setThreshold(1);
dimensionResInput.setAdapter(adapter);
adapter.notifyDataSetChanged();
});
});
}
@NonNull
private String[] getDimensionUnits () {
private String[] getDimensionUnits() {
if (dimensionUnits == null) {
dimensionUnits =
requireContext ().getResources ().getStringArray (R.array.dimension_units);
requireContext().getResources().getStringArray(R.array.dimension_units);
}
return dimensionUnits;
}
@Override
protected void notifyValueChanged (@NonNull String newValue) {
protected void notifyValueChanged(@NonNull String newValue) {
try {
super.notifyValueChanged (newValue);
super.notifyValueChanged(newValue);
} catch (Throwable e) {
LOG.error ("Unable to update dimension value to '" + newValue + "'", e);
LOG.error("Unable to update dimension value to '" + newValue + "'", e);
}
}
}
@@ -42,223 +42,223 @@ import java.util.Set;
import java.util.stream.Collectors;
public class BuildServiceHandler extends IDEHandler implements BuildListener {
private IDEService service;
private boolean shouldInstallApk = false;
public BuildServiceHandler (Provider provider) {
super (provider);
public BuildServiceHandler(Provider provider) {
super(provider);
}
public IDEService getService () {
public IDEService getService() {
return service;
}
@Override
public void start () {
Objects.requireNonNull (this.activity ());
AndroidProject project = provider.provideAndroidProject ();
Objects.requireNonNull (project);
Objects.requireNonNull (project.getProjectPath ());
this.service = new IDEService (new File (project.getProjectPath ()));
this.service.setListener (this);
public void start() {
Objects.requireNonNull(this.activity());
AndroidProject project = provider.provideAndroidProject();
Objects.requireNonNull(project);
Objects.requireNonNull(project.getProjectPath());
this.service = new IDEService(new File(project.getProjectPath()));
this.service.setListener(this);
}
@Override
public void stop () {
this.service.stopAllDaemons ();
this.service.exit ();
public void stop() {
this.service.stopAllDaemons();
this.service.exit();
}
@Override
public void onBuildModified () {
public void onBuildModified() {
// Does nothing currently
}
@Override
public void onProjectLoaded (IDEProject project, Optional<IDEModule> appModule) {
project.iconPath = provider.provideAndroidProject ().getIconPath ();
activity ().setIDEProject (project);
if (appModule.isPresent ()) {
IDEModule app = appModule.get ();
activity ().setStatus (activity ().getString (R.string.msg_starting_completion));
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
project.iconPath = provider.provideAndroidProject().getIconPath();
activity().setIDEProject(project);
if (appModule.isPresent()) {
IDEModule app = appModule.get();
activity().setStatus(activity().getString(R.string.msg_starting_completion));
final List<String> paths =
app.dependencies.stream ()
.filter (this::isClasspathValid)
.collect (Collectors.toList ());
provider.provideAndroidProject ().setClassPaths (paths);
app.dependencies.stream()
.filter(this::isClasspathValid)
.collect(Collectors.toList());
provider.provideAndroidProject().setClassPaths(paths);
File androidJar =
new File (
new File(
Environment.ANDROID_HOME,
String.format ("platforms/%s/android.jar", app.compileSdkVersion));
if (androidJar.exists ()) {
Environment.setBootClasspath (androidJar);
provider.provideAndroidProject ().addClasspath (androidJar.getAbsolutePath ());
activity ().updateServices ();
String.format("platforms/%s/android.jar", app.compileSdkVersion));
if (androidJar.exists()) {
Environment.setBootClasspath(androidJar);
provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath());
activity().updateServices();
} else {
activity ().setStatus ("android.jar not found!");
activity().setStatus("android.jar not found!");
}
} else {
activity ().setStatus ("Cannot get :app module...");
activity().setStatus("Cannot get :app module...");
}
}
@Override
public void onStartingGradleDaemon (GradleTask task) {
activity ().setStatus (activity ().getString (R.string.msg_starting_daemon));
public void onStartingGradleDaemon(GradleTask task) {
activity().setStatus(activity().getString(R.string.msg_starting_daemon));
}
@Override
public void onRunTask (GradleTask task, String name) {
if (task != null && task.canOutput ()) {
activity ().setStatus (service.typeString (task.getTaskID ()) + " " + name);
public void onRunTask(GradleTask task, String name) {
if (task != null && task.canOutput()) {
activity().setStatus(service.typeString(task.getTaskID()) + " " + name);
}
}
@Override
public void onBuildSuccessful (GradleTask task, String msg) {
appendOutput (task, msg);
if (task == null || activity () == null) {
public void onBuildSuccessful(GradleTask task, String msg) {
appendOutput(task, msg);
if (task == null || activity() == null) {
return;
}
if (task.canOutput ()) {
activity ().setStatus (msg);
if (task.canOutput()) {
activity().setStatus(msg);
}
// If this task generates APK, ask user if he/she wants to install them
if (shouldInstallApk && task instanceof ApkGeneratingTask) {
final IDEProject project = activity ().provideIDEProject ();
final IDEProject project = activity().provideIDEProject();
final Optional<IDEModule> app =
project.getModuleByPath (":app"); // TODO Handle multiple application modules
if (app.isPresent ()) {
final String path = app.get ().projectDir;
project.getModuleByPath(":app"); // TODO Handle multiple application modules
if (app.isPresent()) {
final String path = app.get().projectDir;
if (path != null) {
final File buildDir = new File (path, "build");
installApks (((ApkGeneratingTask) task).getApks (buildDir));
final File buildDir = new File(path, "build");
installApks(((ApkGeneratingTask) task).getApks(buildDir));
}
}
}
activity ()
.getApp ()
.getPrefManager ()
.putBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity ().invalidateOptionsMenu ();
activity ().getBinding ().buildProgressIndicator.setVisibility (View.GONE);
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
}
@Override
public void onBuildFailed (GradleTask task, String msg) {
appendOutput (task, msg);
public void onBuildFailed(GradleTask task, String msg) {
appendOutput(task, msg);
if (task == null) {
return;
}
if (task.canOutput ()) {
activity ().setStatus (msg);
if (task.canOutput()) {
activity().setStatus(msg);
}
activity ()
.getApp ()
.getPrefManager ()
.putBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity ().invalidateOptionsMenu ();
activity ().getBinding ().buildProgressIndicator.setVisibility (View.GONE);
activity()
.getApp()
.getPrefManager()
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
activity().invalidateOptionsMenu();
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
}
@Override
public void saveFiles () {
activity ().saveAll (false, false);
public void saveFiles() {
activity().saveAll(false, false);
}
@Override
public void appendOutput (GradleTask task, CharSequence text) {
public void appendOutput(GradleTask task, CharSequence text) {
if (text == null) {
return;
}
if (task != null && !task.canOutput ()) {
if (task != null && !task.canOutput()) {
return;
}
activity ().appendBuildOut (text.toString ());
activity().appendBuildOut(text.toString());
}
@Override
public void prepareBuild () {
public void prepareBuild() {
boolean isFirstBuild =
activity ()
.getApp ()
.getPrefManager ()
.getBoolean (PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity ()
.setStatus (
activity ()
.getString (
activity()
.getApp()
.getPrefManager()
.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true);
activity()
.setStatus(
activity()
.getString(
isFirstBuild
? R.string.preparing_first
: R.string.preparing));
if (isFirstBuild) {
activity ().showFirstBuildNotice ();
activity().showFirstBuildNotice();
}
activity ().getBinding ().buildProgressIndicator.setVisibility (View.VISIBLE);
activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE);
}
public void setShouldInstallApk (boolean install) {
public void setShouldInstallApk(boolean install) {
this.shouldInstallApk = install;
}
public void assembleDebug (boolean shouldInstallApk) {
setShouldInstallApk (shouldInstallApk);
getService ().assembleDebug (shouldInstallApk);
public void assembleDebug(boolean shouldInstallApk) {
setShouldInstallApk(shouldInstallApk);
getService().assembleDebug(shouldInstallApk);
}
private boolean isClasspathValid (String path) {
if (path == null || path.trim ().length () <= 0 || path.trim ().equals ("/")) {
private boolean isClasspathValid(String path) {
if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) {
return false;
}
File file = new File (path);
return file.isFile ()
&& file.getName ().endsWith (".jar")
File file = new File(path);
return file.isFile()
&& file.getName().endsWith(".jar")
// Release R.jar shouldn't be included in classpath
&& !file.getAbsolutePath ().endsWith ("/release/R.jar");
&& !file.getAbsolutePath().endsWith("/release/R.jar");
}
private void installApks (final Set<File> apks) {
if (activity () == null || apks == null || apks.isEmpty ()) {
private void installApks(final Set<File> apks) {
if (activity() == null || apks == null || apks.isEmpty()) {
return;
}
if (apks.size () == 1) {
activity ().install (apks.iterator ().next ());
if (apks.size() == 1) {
activity().install(apks.iterator().next());
} else {
final List<File> files = new ArrayList<> (apks);
final List<File> files = new ArrayList<>(apks);
final MaterialAlertDialogBuilder builder =
DialogUtils.newMaterialDialogBuilder (activity ());
builder.setTitle (activity ().getString (R.string.title_install_apks));
builder.setItems (
getNames (files),
DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(activity().getString(R.string.title_install_apks));
builder.setItems(
getNames(files),
(d, w) -> {
d.dismiss ();
activity ().install (files.get (w));
d.dismiss();
activity().install(files.get(w));
});
builder.show ();
builder.show();
}
}
private CharSequence[] getNames (final Collection<File> apks) {
final String[] names = new String[apks.size ()];
private CharSequence[] getNames(final Collection<File> apks) {
final String[] names = new String[apks.size()];
int i = 0;
for (File apk : apks) {
names[i] = apk.getName ();
names[i] = apk.getName();
++i;
}
return names;
}
}
@@ -41,403 +41,402 @@ import java.util.regex.Pattern;
public class FileOptionsHandler extends IDEHandler
implements OptionsListFragment.OnOptionsClickListener {
public static final String RES_PATH_REGEX = "/.*/src/.*/res";
public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout";
public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu";
public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable";
public static final String JAVA_PATH_REGEX = "/.*/src/.*/java";
public FileOptionsHandler (Provider provider) {
super (provider);
public FileOptionsHandler(Provider provider) {
super(provider);
}
@Override
public void start () {
Objects.requireNonNull (this.activity ());
public void start() {
Objects.requireNonNull(this.activity());
}
@Override
public void stop () {
}
public void stop() {}
@Override
public void onOptionsClick (SheetOption option) {
public void onOptionsClick(SheetOption option) {
if (option.extra instanceof File) {
final File f = (File) option.extra;
switch (option.id) {
case 0:
ClipboardUtils.copyText ("[AndroidIDE] Copied File Path", f.getAbsolutePath ());
activity ().getApp ().toast (R.string.copied, Toaster.Type.SUCCESS);
ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath());
activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS);
break;
case 1:
renameFile (f);
renameFile(f);
break;
case 2:
delete (f);
delete(f);
break;
case 3:
createNewFile (f);
createNewFile(f);
break;
case 4:
createNewFolder (f);
createNewFolder(f);
break;
}
}
}
private void createNewFile (File f) {
createNewFile (f, false);
private void createNewFile(File f) {
createNewFile(f, false);
}
private void createNewFile (final File f, boolean forceUnknownType) {
private void createNewFile(final File f, boolean forceUnknownType) {
if (forceUnknownType) {
createNewEmptyFile (f);
createNewEmptyFile(f);
} else {
final boolean isJava =
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ JAVA_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ JAVA_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isRes =
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isLayoutRes =
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ LAYOUT_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ LAYOUT_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isMenuRes =
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ MENU_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ MENU_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
final boolean isDrawableRes =
Pattern.compile (
Pattern.quote (androidProject ().getProjectPath ())
+ DRAWABLE_RES_PATH_REGEX)
.matcher (f.getAbsolutePath ())
.find ();
Pattern.compile(
Pattern.quote(androidProject().getProjectPath())
+ DRAWABLE_RES_PATH_REGEX)
.matcher(f.getAbsolutePath())
.find();
if (isJava) {
createJavaClass (f);
} else if (isLayoutRes && f.getName ().equals ("layout")) {
createLayoutRes (f);
} else if (isMenuRes && f.getName ().equals ("menu")) {
createMenuRes (f);
} else if (isDrawableRes && f.getName ().equals ("drawable")) {
createDrawableRes (f);
} else if (isRes && f.getName ().equals ("res")) {
createNewResource (f);
createJavaClass(f);
} else if (isLayoutRes && f.getName().equals("layout")) {
createLayoutRes(f);
} else if (isMenuRes && f.getName().equals("menu")) {
createMenuRes(f);
} else if (isDrawableRes && f.getName().equals("drawable")) {
createDrawableRes(f);
} else if (isRes && f.getName().equals("res")) {
createNewResource(f);
} else {
createNewEmptyFile (f);
createNewEmptyFile(f);
}
}
}
private void createJavaClass (final File f) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
private void createJavaClass(final File f) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
final LayoutCreateFileJavaBinding binding =
LayoutCreateFileJavaBinding.inflate (activity ().getLayoutInflater ());
builder.setView (binding.getRoot ());
builder.setTitle (R.string.new_java_class);
builder.setPositiveButton (
LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater());
builder.setView(binding.getRoot());
builder.setTitle(R.string.new_java_class);
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss ();
p1.dismiss();
final String name =
Objects.requireNonNull (binding.name.getEditText ())
.getText ()
.toString ()
.trim ();
final String pkgName = ProjectWriter.getPackageName (f);
if (pkgName == null || pkgName.trim ().length () <= 0) {
activity ()
.getApp ()
.toast (R.string.msg_get_package_failed, Toaster.Type.ERROR);
Objects.requireNonNull(binding.name.getEditText())
.getText()
.toString()
.trim();
final String pkgName = ProjectWriter.getPackageName(f);
if (pkgName == null || pkgName.trim().length() <= 0) {
activity()
.getApp()
.toast(R.string.msg_get_package_failed, Toaster.Type.ERROR);
} else {
final int id = binding.typeGroup.getCheckedButtonId ();
final var javaName = name.endsWith (".java") ? name : name.concat (".java");
final int id = binding.typeGroup.getCheckedButtonId();
final var javaName = name.endsWith(".java") ? name : name.concat(".java");
final var className =
!name.contains (".")
!name.contains(".")
? name
: name.substring (0, name.lastIndexOf ("."));
if (id == binding.typeClass.getId ()) {
createFile (
f, javaName, ProjectWriter.createJavaClass (pkgName, className));
} else if (id == binding.typeInterface.getId ()) {
createFile (
: name.substring(0, name.lastIndexOf("."));
if (id == binding.typeClass.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaClass(pkgName, className));
} else if (id == binding.typeInterface.getId()) {
createFile(
f,
javaName,
ProjectWriter.createJavaInterface (pkgName, className));
} else if (id == binding.typeEnum.getId ()) {
createFile (
f, javaName, ProjectWriter.createJavaEnum (pkgName, className));
} else if (id == binding.typeActivity.getId ()) {
createFile (
f, javaName, ProjectWriter.createActivity (pkgName, className));
ProjectWriter.createJavaInterface(pkgName, className));
} else if (id == binding.typeEnum.getId()) {
createFile(
f, javaName, ProjectWriter.createJavaEnum(pkgName, className));
} else if (id == binding.typeActivity.getId()) {
createFile(
f, javaName, ProjectWriter.createActivity(pkgName, className));
} else {
createFile (f, name, "");
createFile(f, name, "");
}
}
});
builder.setNegativeButton (android.R.string.cancel, null);
builder.setCancelable (false);
builder.create ().show ();
builder.setNegativeButton(android.R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
}
private void createLayoutRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createLayout (), ".xml");
private void createLayoutRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml");
}
private void createMenuRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createMenu (), ".xml");
private void createMenuRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml");
}
private void createDrawableRes (File f) {
createNewFileWithContent (
Environment.mkdirIfNotExits (f), ProjectWriter.createDrawable (), ".xml");
private void createDrawableRes(File f) {
createNewFileWithContent(
Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml");
}
private void createNewResource (File f) {
private void createNewResource(File f) {
final String[] labels = {
activity ().getString (R.string.restype_drawable),
activity ().getString (R.string.restype_layout),
activity ().getString (R.string.restype_menu),
activity ().getString (R.string.restype_other)
activity().getString(R.string.restype_drawable),
activity().getString(R.string.restype_layout),
activity().getString(R.string.restype_menu),
activity().getString(R.string.restype_other)
};
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
builder.setTitle (R.string.new_xml_resource);
builder.setItems (
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(R.string.new_xml_resource);
builder.setItems(
labels,
(dialogInterface, position) -> {
if (position == 0) {
createDrawableRes (new File (f, "drawable"));
createDrawableRes(new File(f, "drawable"));
} else if (position == 1) {
createLayoutRes (new File (f, "layout"));
createLayoutRes(new File(f, "layout"));
} else if (position == 2) {
createMenuRes (new File (f, "menu"));
createMenuRes(new File(f, "menu"));
} else if (position == 3) {
createNewFile (f, true);
createNewFile(f, true);
}
});
builder.create ().show ();
builder.create().show();
}
private void createNewEmptyFile (File f) {
createNewFileWithContent (f, "");
private void createNewEmptyFile(File f) {
createNewFileWithContent(f, "");
}
private void createNewFileWithContent (File f, String content) {
createNewFileWithContent (f, content, null);
private void createNewFileWithContent(File f, String content) {
createNewFileWithContent(f, content, null);
}
private void createNewFileWithContent (File folder, String content, String extension) {
private void createNewFileWithContent(File folder, String content, String extension) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate (activity ().getLayoutInflater ());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ()).setHint (R.string.file_name);
builder.setTitle (R.string.new_file);
builder.setMessage (
activity ()
.getString (R.string.msg_can_contain_slashes)
.concat ("\n\n")
.concat (
activity ()
.getString (
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name);
builder.setTitle(R.string.new_file);
builder.setMessage(
activity()
.getString(R.string.msg_can_contain_slashes)
.concat("\n\n")
.concat(
activity()
.getString(
R.string.msg_newfile_dest,
folder.getAbsolutePath ())));
builder.setView (binding.getRoot ());
builder.setCancelable (false);
builder.setPositiveButton (
folder.getAbsolutePath())));
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss ();
String name = binding.name.getEditText ().getText ().toString ().trim ();
if (extension != null && extension.trim ().length () > 0) {
name = name.endsWith (extension) ? name : name.concat (extension);
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
if (extension != null && extension.trim().length() > 0) {
name = name.endsWith(extension) ? name : name.concat(extension);
}
createFile (folder, name, content);
createFile(folder, name, content);
});
builder.setNegativeButton (android.R.string.cancel, null);
builder.create ().show ();
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
}
private void createFile (File f, String name, String content) {
if (name.length () > 0 && name.length () <= 40 && !name.startsWith ("/")) {
final File file = new File (f, name);
if (file.exists ()) {
activity ().getApp ().toast (R.string.msg_file_exists, Toaster.Type.ERROR);
private void createFile(File f, String name, String content) {
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if (file.exists()) {
activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR);
} else {
if (FileIOUtils.writeFileFromString (file, content)) {
if (FileIOUtils.writeFileFromString(file, content)) {
// TODO Notify language servers about file created event
activity ().getApp ().toast (R.string.msg_file_created, Toaster.Type.SUCCESS);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode node = new TreeNode (file);
node.setViewHolder (new FileTreeViewHolder (activity ()));
activity ().getLastHoldTreeNode ().addChild (node);
activity ()
.getFileTreeFragment ()
.expandNode (activity ().getLastHoldTreeNode ());
activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
} else {
activity ().getFileTreeFragment ().listProjectFiles ();
activity().getFileTreeFragment().listProjectFiles();
}
} else {
activity ()
.getApp ()
.toast (R.string.msg_file_creation_failed, Toaster.Type.ERROR);
activity()
.getApp()
.toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR);
}
}
} else {
activity ().getApp ().toast (R.string.msg_invalid_name, Toaster.Type.ERROR);
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
}
}
private void createNewFolder (File f) {
private void createNewFolder(File f) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate (activity ().getLayoutInflater ());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ()).setHint (R.string.folder_name);
builder.setTitle (R.string.new_folder);
builder.setMessage (R.string.msg_can_contain_slashes);
builder.setView (binding.getRoot ());
builder.setCancelable (false);
builder.setPositiveButton (
LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater());
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name);
builder.setTitle(R.string.new_folder);
builder.setMessage(R.string.msg_can_contain_slashes);
builder.setView(binding.getRoot());
builder.setCancelable(false);
builder.setPositiveButton(
R.string.text_create,
(p1, p2) -> {
p1.dismiss ();
final String name = binding.name.getEditText ().getText ().toString ().trim ();
if (name.length () > 0 && name.length () <= 40 && !name.startsWith ("/")) {
final File file = new File (f, name);
if (file.exists ()) {
activity ()
.getApp ()
.toast (R.string.msg_folder_exists, Toaster.Type.ERROR);
p1.dismiss();
final String name = binding.name.getEditText().getText().toString().trim();
if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) {
final File file = new File(f, name);
if (file.exists()) {
activity()
.getApp()
.toast(R.string.msg_folder_exists, Toaster.Type.ERROR);
} else {
if (file.mkdirs ()) {
activity ()
.getApp ()
.toast (R.string.msg_folder_created, Toaster.Type.SUCCESS);
if (activity ().getLastHoldTreeNode () != null) {
TreeNode node = new TreeNode (file);
node.setViewHolder (new FileTreeViewHolder (activity ()));
activity ().getLastHoldTreeNode ().addChild (node);
activity ()
.getFileTreeFragment ()
.expandNode (activity ().getLastHoldTreeNode ());
if (file.mkdirs()) {
activity()
.getApp()
.toast(R.string.msg_folder_created, Toaster.Type.SUCCESS);
if (activity().getLastHoldTreeNode() != null) {
TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(activity()));
activity().getLastHoldTreeNode().addChild(node);
activity()
.getFileTreeFragment()
.expandNode(activity().getLastHoldTreeNode());
} else {
activity ().getFileTreeFragment ().listProjectFiles ();
activity().getFileTreeFragment().listProjectFiles();
}
} else {
activity ()
.getApp ()
.toast (
activity()
.getApp()
.toast(
R.string.msg_folder_creation_failed,
Toaster.Type.ERROR);
}
}
} else {
activity ().getApp ().toast (R.string.msg_invalid_name, Toaster.Type.ERROR);
activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR);
}
});
builder.setNegativeButton (android.R.string.cancel, null);
builder.create ().show ();
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
}
private void delete (final File f) {
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
builder.setNegativeButton (R.string.no, null)
.setPositiveButton (
private void delete(final File f) {
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setNegativeButton(R.string.no, null)
.setPositiveButton(
R.string.yes,
(p1, p2) -> {
p1.dismiss ();
final boolean deleted = FileUtils.delete (f);
activity ()
.getApp ()
.toast (
p1.dismiss();
final boolean deleted = FileUtils.delete(f);
activity()
.getApp()
.toast(
deleted ? R.string.deleted : R.string.delete_failed,
deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (deleted) {
// TODO Notify language servers about file delete event
if (activity ().getLastHoldTreeNode () != null) {
TreeNode parent = activity ().getLastHoldTreeNode ().getParent ();
parent.deleteChild (activity ().getLastHoldTreeNode ());
activity ().getFileTreeFragment ().expandNode (parent);
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
activity().getFileTreeFragment().expandNode(parent);
} else {
activity ().getFileTreeFragment ().listProjectFiles ();
activity().getFileTreeFragment().listProjectFiles();
}
final var frag = activity ().getEditorForFile (f);
final var frag = activity().getEditorForFile(f);
if (frag != null) {
activity ()
.closeFile (
activity ()
.findIndexOfEditorByFile (
frag.getFile ()));
activity()
.closeFile(
activity()
.findIndexOfEditorByFile(
frag.getFile()));
}
}
})
.setTitle (R.string.title_confirm_delete)
.setMessage (
activity ()
.getString (
.setTitle(R.string.title_confirm_delete)
.setMessage(
activity()
.getString(
R.string.msg_confirm_delete,
String.format ("%s [%s]", f.getName (), f.getAbsolutePath ())))
.setCancelable (false)
.create ()
.show ();
String.format("%s [%s]", f.getName(), f.getAbsolutePath())))
.setCancelable(false)
.create()
.show();
}
private void renameFile (File f) {
private void renameFile(File f) {
final LayoutDialogTextInputBinding binding =
LayoutDialogTextInputBinding.inflate (LayoutInflater.from (activity ()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder (activity ());
Objects.requireNonNull (binding.name.getEditText ())
.setHint (activity ().getString (R.string.new_name));
binding.name.getEditText ().setText (f.getName ());
builder.setTitle (R.string.rename_file);
builder.setMessage (R.string.msg_rename_file);
builder.setView (binding.getRoot ());
builder.setNegativeButton (android.R.string.cancel, null);
builder.setPositiveButton (
LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity()));
MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
Objects.requireNonNull(binding.name.getEditText())
.setHint(activity().getString(R.string.new_name));
binding.name.getEditText().setText(f.getName());
builder.setTitle(R.string.rename_file);
builder.setMessage(R.string.msg_rename_file);
builder.setView(binding.getRoot());
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(
R.string.rename_file,
(p1, p2) -> {
p1.dismiss ();
String name = binding.name.getEditText ().getText ().toString ().trim ();
p1.dismiss();
String name = binding.name.getEditText().getText().toString().trim();
boolean renamed =
name.length () > 0 && name.length () <= 40 && FileUtils.rename (f, name);
activity ()
.getApp ()
.toast (
name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name);
activity()
.getApp()
.toast(
renamed ? R.string.renamed : R.string.rename_failed,
renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR);
if (renamed) {
// TODO Notify language servers about file rename event
if (activity ().getLastHoldTreeNode () != null) {
TreeNode parent = activity ().getLastHoldTreeNode ().getParent ();
parent.deleteChild (activity ().getLastHoldTreeNode ());
TreeNode node = new TreeNode (new File (f.getParentFile (), name));
node.setViewHolder (new FileTreeViewHolder (activity ()));
parent.addChild (node);
activity ().getFileTreeFragment ().expandNode (parent);
if (activity().getLastHoldTreeNode() != null) {
TreeNode parent = activity().getLastHoldTreeNode().getParent();
parent.deleteChild(activity().getLastHoldTreeNode());
TreeNode node = new TreeNode(new File(f.getParentFile(), name));
node.setViewHolder(new FileTreeViewHolder(activity()));
parent.addChild(node);
activity().getFileTreeFragment().expandNode(parent);
} else {
activity ().getFileTreeFragment ().listProjectFiles ();
activity().getFileTreeFragment().listProjectFiles();
}
}
});
builder.create ().show ();
builder.create().show();
}
}
@@ -35,16 +35,16 @@ import java.util.Properties;
@SuppressLint("SdCardPath")
public final class Environment {
public static final Map<String, String> IDE_PROPS = new HashMap<> ();
public static final Map<String, String> ENV_VARS = new HashMap<> ();
public static final Map<String, String> IDE_PROPS = new HashMap<>();
public static final Map<String, String> ENV_VARS = new HashMap<>();
public static final String PROJECTS_FOLDER = "AndroidIDEProjects";
private static final String DEFAULT_JAVA_HOME =
"/data/data/com.itsaky.androidide/files/framework/jdk";
private static final String DEFAULT_ANDROID_HOME =
"/data/data/com.itsaky.androidide/files/framework/android-sdk";
private static final Logger LOG = Logger.instance ("Environment");
private static final List<String> blacklist = new ArrayList<> ();
private static final Logger LOG = Logger.instance("Environment");
private static final List<String> blacklist = new ArrayList<>();
public static File ROOT;
public static File SYSROOT;
public static File HOME;
@@ -63,7 +63,7 @@ public final class Environment {
* contains only the classes included in android.jar
*/
public static File COMPILER_MODULE;
public static File INIT_SCRIPT;
public static File PROJECT_DATA_FILE;
public static File GRADLE_PROPS;
@@ -73,193 +73,193 @@ public final class Environment {
public static File SHELL;
public static File LOGIN_SHELL;
public static File BOOTCLASSPATH;
public static void init () {
final BaseApplication app = BaseApplication.getBaseInstance ();
ROOT = app.getIDEDataDir ();
SYSROOT = mkdirIfNotExits (new File (app.getIDEDataDir (), "sysroot"));
HOME = mkdirIfNotExits (app.getRootDir ());
ANDROIDIDE_HOME = mkdirIfNotExits (new File (HOME, ".androidide"));
TMP_DIR = mkdirIfNotExits (new File (SYSROOT, "tmp"));
BIN_DIR = mkdirIfNotExits (new File (SYSROOT, "bin"));
LIB_DIR = mkdirIfNotExits (new File (SYSROOT, "lib"));
PROJECTS_DIR = mkdirIfNotExits (new File (FileUtil.getExternalStorageDir (), PROJECTS_FOLDER));
COMPILER_MODULE = mkdirIfNotExits (new File (ANDROIDIDE_HOME, "compiler-module"));
IDE_PROPS_FILE = new File (SYSROOT, "etc/ide-environment.properties");
LIB_HOOK = new File (LIB_DIR, "libhook.so");
LIB_HOOK2 = new File (LIB_DIR, "libhook2.so");
PROJECT_DATA_FILE = new File (TMP_DIR, "ide_project");
public static void init() {
final BaseApplication app = BaseApplication.getBaseInstance();
ROOT = app.getIDEDataDir();
SYSROOT = mkdirIfNotExits(new File(app.getIDEDataDir(), "sysroot"));
HOME = mkdirIfNotExits(app.getRootDir());
ANDROIDIDE_HOME = mkdirIfNotExits(new File(HOME, ".androidide"));
TMP_DIR = mkdirIfNotExits(new File(SYSROOT, "tmp"));
BIN_DIR = mkdirIfNotExits(new File(SYSROOT, "bin"));
LIB_DIR = mkdirIfNotExits(new File(SYSROOT, "lib"));
PROJECTS_DIR = mkdirIfNotExits(new File(FileUtil.getExternalStorageDir(), PROJECTS_FOLDER));
COMPILER_MODULE = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "compiler-module"));
IDE_PROPS_FILE = new File(SYSROOT, "etc/ide-environment.properties");
LIB_HOOK = new File(LIB_DIR, "libhook.so");
LIB_HOOK2 = new File(LIB_DIR, "libhook2.so");
PROJECT_DATA_FILE = new File(TMP_DIR, "ide_project");
INIT_SCRIPT =
new File (
mkdirIfNotExits (new File (app.getIDEDataDir (), "init")),
new File(
mkdirIfNotExits(new File(app.getIDEDataDir(), "init")),
"data/common/androidide.init.gradle");
BOOTCLASSPATH = new File ("");
GRADLE_USER_HOME = new File (HOME, ".gradle");
GRADLE_PROPS = new File (GRADLE_USER_HOME, "gradle.properties");
IDE_PROPS.putAll (readProperties ());
ANDROID_HOME = new File (readProp ("ANDROID_HOME", DEFAULT_ANDROID_HOME));
JAVA_HOME = new File (readProp ("JAVA_HOME", DEFAULT_JAVA_HOME));
JAVA = new File (JAVA_HOME, "bin/java");
BUSYBOX = new File (BIN_DIR, "busybox");
SHELL = new File (BIN_DIR, "bash");
LOGIN_SHELL = new File (BIN_DIR, "login");
setExecutable (JAVA);
setExecutable (BUSYBOX);
setExecutable (SHELL);
System.setProperty ("user.home", HOME.getAbsolutePath ());
System.setProperty ("java.home", JAVA_HOME.getAbsolutePath ());
BOOTCLASSPATH = new File("");
GRADLE_USER_HOME = new File(HOME, ".gradle");
GRADLE_PROPS = new File(GRADLE_USER_HOME, "gradle.properties");
IDE_PROPS.putAll(readProperties());
ANDROID_HOME = new File(readProp("ANDROID_HOME", DEFAULT_ANDROID_HOME));
JAVA_HOME = new File(readProp("JAVA_HOME", DEFAULT_JAVA_HOME));
JAVA = new File(JAVA_HOME, "bin/java");
BUSYBOX = new File(BIN_DIR, "busybox");
SHELL = new File(BIN_DIR, "bash");
LOGIN_SHELL = new File(BIN_DIR, "login");
setExecutable(JAVA);
setExecutable(BUSYBOX);
setExecutable(SHELL);
System.setProperty("user.home", HOME.getAbsolutePath());
System.setProperty("java.home", JAVA_HOME.getAbsolutePath());
}
public static void setExecutable (@NonNull final File file) {
if (!file.setExecutable (true)) {
LOG.error ("Unable to set executable permissions to file", file);
public static void setExecutable(@NonNull final File file) {
if (!file.setExecutable(true)) {
LOG.error("Unable to set executable permissions to file", file);
}
}
@NonNull
private static Map<String, String> readProperties () {
final Map<String, String> props = new HashMap<> ();
if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists ()) {
private static Map<String, String> readProperties() {
final Map<String, String> props = new HashMap<>();
if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists()) {
return props;
}
try {
Properties p = new Properties ();
p.load (new StringReader (FileIOUtils.readFile2String (IDE_PROPS_FILE)));
for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet ()) {
props.put (entry.getKey () + "", entry.getValue () + "");
Properties p = new Properties();
p.load(new StringReader(FileIOUtils.readFile2String(IDE_PROPS_FILE)));
for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet()) {
props.put(entry.getKey() + "", entry.getValue() + "");
}
} catch (Throwable th) {
LOG.error ("Unable to read properties file", th);
LOG.error("Unable to read properties file", th);
}
return props;
}
public static void setBootClasspath (@NonNull File file) {
BOOTCLASSPATH = new File (file.getAbsolutePath ());
public static void setBootClasspath(@NonNull File file) {
BOOTCLASSPATH = new File(file.getAbsolutePath());
}
public static Map<String, String> getEnvironment () {
if (!ENV_VARS.isEmpty ()) {
public static Map<String, String> getEnvironment() {
if (!ENV_VARS.isEmpty()) {
return ENV_VARS;
}
ENV_VARS.put ("HOME", HOME.getAbsolutePath ());
ENV_VARS.put ("ANDROID_HOME", ANDROID_HOME.getAbsolutePath ());
ENV_VARS.put ("JAVA_HOME", JAVA_HOME.getAbsolutePath ());
ENV_VARS.put ("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath ());
ENV_VARS.put ("TMPDIR", TMP_DIR.getAbsolutePath ());
ENV_VARS.put ("PROJECTS", PROJECTS_DIR.getAbsolutePath ());
ENV_VARS.put ("LANG", "en_US.UTF-8");
ENV_VARS.put ("LC_ALL", "en_US.UTF-8");
ENV_VARS.put ("SYSROOT", SYSROOT.getAbsolutePath ());
ENV_VARS.put ("BUSYBOX", BUSYBOX.getAbsolutePath ());
ENV_VARS.put ("SHELL", SHELL.getAbsolutePath ());
ENV_VARS.put ("CONFIG_SHELL", SHELL.getAbsolutePath ());
ENV_VARS.put ("TERM", "screen");
ENV_VARS.put("HOME", HOME.getAbsolutePath());
ENV_VARS.put("ANDROID_HOME", ANDROID_HOME.getAbsolutePath());
ENV_VARS.put("JAVA_HOME", JAVA_HOME.getAbsolutePath());
ENV_VARS.put("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath());
ENV_VARS.put("TMPDIR", TMP_DIR.getAbsolutePath());
ENV_VARS.put("PROJECTS", PROJECTS_DIR.getAbsolutePath());
ENV_VARS.put("LANG", "en_US.UTF-8");
ENV_VARS.put("LC_ALL", "en_US.UTF-8");
ENV_VARS.put("SYSROOT", SYSROOT.getAbsolutePath());
ENV_VARS.put("BUSYBOX", BUSYBOX.getAbsolutePath());
ENV_VARS.put("SHELL", SHELL.getAbsolutePath());
ENV_VARS.put("CONFIG_SHELL", SHELL.getAbsolutePath());
ENV_VARS.put("TERM", "screen");
// If LD_LIBRARY_PATH is set, append $SYSROOT/lib to it,
// else set it to $SYSROOT/lib
String ld = System.getenv ("LD_LIBRARY_PATH");
if (ld == null || ld.trim ().length () <= 0) {
String ld = System.getenv("LD_LIBRARY_PATH");
if (ld == null || ld.trim().length() <= 0) {
ld = "";
} else {
ld += File.pathSeparator;
}
ld += LIB_DIR.getAbsolutePath ();
ENV_VARS.put ("LD_LIBRARY_PATH", ld);
ld += LIB_DIR.getAbsolutePath();
ENV_VARS.put("LD_LIBRARY_PATH", ld);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& BaseApplication.isAarch64 ()
&& LIB_HOOK.exists ()) {
&& BaseApplication.isAarch64()
&& LIB_HOOK.exists()) {
// Required for JDK 11
ENV_VARS.put ("LD_PRELOAD", LIB_HOOK.getAbsolutePath ());
ENV_VARS.put("LD_PRELOAD", LIB_HOOK.getAbsolutePath());
}
addToEnvIfPresent (ENV_VARS, "ANDROID_ART_ROOT");
addToEnvIfPresent (ENV_VARS, "DEX2OATBOOTCLASSPATH");
addToEnvIfPresent (ENV_VARS, "ANDROID_I18N_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_TZDATA_ROOT");
addToEnvIfPresent (ENV_VARS, "ANDROID_DATA");
addToEnvIfPresent (ENV_VARS, "ANDROID_ROOT");
String path = createPath ();
ENV_VARS.put ("PATH", path);
for (String key : IDE_PROPS.keySet ()) {
if (!blacklistedVariables ().contains (key.trim ())) {
ENV_VARS.put (key, readProp (key, ""));
addToEnvIfPresent(ENV_VARS, "ANDROID_ART_ROOT");
addToEnvIfPresent(ENV_VARS, "DEX2OATBOOTCLASSPATH");
addToEnvIfPresent(ENV_VARS, "ANDROID_I18N_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_TZDATA_ROOT");
addToEnvIfPresent(ENV_VARS, "ANDROID_DATA");
addToEnvIfPresent(ENV_VARS, "ANDROID_ROOT");
String path = createPath();
ENV_VARS.put("PATH", path);
for (String key : IDE_PROPS.keySet()) {
if (!blacklistedVariables().contains(key.trim())) {
ENV_VARS.put(key, readProp(key, ""));
}
}
return ENV_VARS;
}
@NonNull
private static String createPath () {
private static String createPath() {
String path = "";
path += String.format ("%s/bin", JAVA_HOME.getAbsolutePath ());
path += String.format (":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath ());
path += String.format (":%s/cmake/bin", ANDROID_HOME.getAbsolutePath ());
path += String.format (":%s/bin", SYSROOT.getAbsolutePath ());
path += String.format (":%s", System.getenv ("PATH"));
path += String.format("%s/bin", JAVA_HOME.getAbsolutePath());
path += String.format(":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath());
path += String.format(":%s/cmake/bin", ANDROID_HOME.getAbsolutePath());
path += String.format(":%s/bin", SYSROOT.getAbsolutePath());
path += String.format(":%s", System.getenv("PATH"));
return path;
}
public static void addToEnvIfPresent (Map<String, String> environment, String name) {
String value = System.getenv (name);
public static void addToEnvIfPresent(Map<String, String> environment, String name) {
String value = System.getenv(name);
if (value != null) {
environment.put (name, value);
environment.put(name, value);
}
}
public static String readProp (String key, String defaultValue) {
String value = IDE_PROPS.getOrDefault (key, defaultValue);
public static String readProp(String key, String defaultValue) {
String value = IDE_PROPS.getOrDefault(key, defaultValue);
if (value == null) {
return defaultValue;
}
if (value.contains ("$HOME")) {
value = value.replace ("$HOME", HOME.getAbsolutePath ());
if (value.contains("$HOME")) {
value = value.replace("$HOME", HOME.getAbsolutePath());
}
if (value.contains ("$SYSROOT")) {
value = value.replace ("$SYSROOT", SYSROOT.getAbsolutePath ());
if (value.contains("$SYSROOT")) {
value = value.replace("$SYSROOT", SYSROOT.getAbsolutePath());
}
if (value.contains ("$PATH")) {
value = value.replace ("$PATH", createPath ());
if (value.contains("$PATH")) {
value = value.replace("$PATH", createPath());
}
return value;
}
public static File mkdirIfNotExits (File in) {
if (in != null && !in.exists ()) {
FileUtils.createOrExistsDir (in);
public static File mkdirIfNotExits(File in) {
if (in != null && !in.exists()) {
FileUtils.createOrExistsDir(in);
}
return in;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isCompilerModuleInstalled () {
final var modules = new File (COMPILER_MODULE, "lib/modules");
final var release = new File (COMPILER_MODULE, "release");
return modules.exists () && modules.isFile () && release.exists () && release.isFile ();
public static boolean isCompilerModuleInstalled() {
final var modules = new File(COMPILER_MODULE, "lib/modules");
final var release = new File(COMPILER_MODULE, "release");
return modules.exists() && modules.isFile() && release.exists() && release.isFile();
}
private static List<String> blacklistedVariables () {
if (blacklist.isEmpty ()) {
blacklist.add ("HOME");
blacklist.add ("SYSROOT");
blacklist.add ("JLS_HOME");
private static List<String> blacklistedVariables() {
if (blacklist.isEmpty()) {
blacklist.add("HOME");
blacklist.add("SYSROOT");
blacklist.add("JLS_HOME");
}
return blacklist;
}
+1 -1
View File
@@ -1,6 +1,6 @@
ext {
common = [
editor: 'io.github.Rosemoe.sora-editor:editor:0.10.2',
editor: 'io.github.Rosemoe.sora-editor:editor:0.10.7',
utilcode: 'com.blankj:utilcodex:1.30.6',
file_picker: 'com.github.abhishekti7:UnicornFilePicker:1.0.2',
glide: 'com.github.bumptech.glide:glide:4.12.0',
@@ -32,37 +32,37 @@ import java.util.Objects;
* @author Akash Yadav
*/
public class FrameworkValues {
@NonNull
public static List<String> listDimens () {
return listFields (android.R.dimen.class);
public static List<String> listDimens() {
return listFields(android.R.dimen.class);
}
@NonNull
public static List<String> listStrings () {
return listFields (android.R.string.class);
public static List<String> listStrings() {
return listFields(android.R.string.class);
}
@NonNull
public static List<String> listColors () {
return listFields (android.R.color.class);
public static List<String> listColors() {
return listFields(android.R.color.class);
}
@NonNull
private static List<String> listFields (final Class<?> klass) {
Objects.requireNonNull (klass);
final var list = new ArrayList<String> ();
final var fields = klass.getDeclaredFields ();
private static List<String> listFields(final Class<?> klass) {
Objects.requireNonNull(klass);
final var list = new ArrayList<String>();
final var fields = klass.getDeclaredFields();
for (var field : fields) {
final var mods = field.getModifiers ();
if (!isPublic (mods) || !isStatic (mods)) {
final var mods = field.getModifiers();
if (!isPublic(mods) || !isStatic(mods)) {
continue;
}
list.add (field.getName ());
list.add(field.getName());
}
return list;
}
}
@@ -48,8 +48,8 @@ import java.util.Objects;
* @author Akash Yadav
*/
public class ValuesTable {
private static final Logger LOG = Logger.instance ("ValuesTable");
private static final Logger LOG = Logger.instance("ValuesTable");
// TODO Parse styles and styleables
private final Map<String, IResource> arrays;
private final Map<String, IResource> strings;
@@ -57,185 +57,185 @@ public class ValuesTable {
private final Map<String, IResource> integers;
private final Map<String, IResource> dimens;
private final Map<String, IResource> booleans;
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<> ();
protected ValuesTable () {
this.arrays = new HashMap<> ();
this.strings = new HashMap<> ();
this.colors = new HashMap<> ();
this.integers = new HashMap<> ();
this.dimens = new HashMap<> ();
this.booleans = new HashMap<> ();
this.resourceMap.put ("array", this.arrays);
this.resourceMap.put ("string", this.strings);
this.resourceMap.put ("color", this.colors);
this.resourceMap.put ("integer", this.integers);
this.resourceMap.put ("dimen", this.dimens);
this.resourceMap.put ("bool", this.booleans);
private final Map<String, Map<String, IResource>> resourceMap = new HashMap<>();
protected ValuesTable() {
this.arrays = new HashMap<>();
this.strings = new HashMap<>();
this.colors = new HashMap<>();
this.integers = new HashMap<>();
this.dimens = new HashMap<>();
this.booleans = new HashMap<>();
this.resourceMap.put("array", this.arrays);
this.resourceMap.put("string", this.strings);
this.resourceMap.put("color", this.colors);
this.resourceMap.put("integer", this.integers);
this.resourceMap.put("dimen", this.dimens);
this.resourceMap.put("bool", this.booleans);
}
/**
* Create a new table by reading the files from the given values directory.
*
* @param valuesDirectory The "res/values" directory.
* @return The values table or {@code null} if the directory is invalid or there are no files in
* this directory.
* this directory.
* @throws XmlPullParserException Thrown by {@link XmlPullParserFactory}.
* @throws IOException Thrown by {@link XmlPullParser}.
* @throws IOException Thrown by {@link XmlPullParser}.
*/
@Nullable
public static ValuesTable forDirectory (File valuesDirectory) throws Exception {
public static ValuesTable forDirectory(File valuesDirectory) throws Exception {
if (valuesDirectory == null) {
LOG.error ("Cannot create values table for directory: ", null);
LOG.error("Cannot create values table for directory: ", null);
return null;
}
if (!valuesDirectory.exists ()) {
LOG.error (
if (!valuesDirectory.exists()) {
LOG.error(
"Cannot create values table for directory as it does not exists. Directory:",
valuesDirectory);
return null;
}
final var files = valuesDirectory.listFiles (ValuesTable::isValid);
final var files = valuesDirectory.listFiles(ValuesTable::isValid);
if (files == null) {
LOG.error ("No files found in the given values directory");
LOG.error("No files found in the given values directory");
return null;
}
final var start = System.currentTimeMillis ();
final var values = new ValuesTable ();
final var start = System.currentTimeMillis();
final var values = new ValuesTable();
for (var file : files) {
final var factory = XmlPullParserFactory.newInstance ();
final var parser = factory.newPullParser ();
parser.setInput (new FileInputStream (file), null);
var event = parser.getEventType ();
final var factory = XmlPullParserFactory.newInstance();
final var parser = factory.newPullParser();
parser.setInput(new FileInputStream(file), null);
var event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final var tag = parser.getName ();
if ("resources".equals (tag)) {
event = parser.next ();
final var tag = parser.getName();
if ("resources".equals(tag)) {
event = parser.next();
continue;
}
readTag (tag, parser, values);
readTag(tag, parser, values);
}
event = parser.next ();
event = parser.next();
}
}
final var duration = System.currentTimeMillis () - start;
LOG.info (
String.format (
Locale.getDefault (),
final var duration = System.currentTimeMillis() - start;
LOG.info(
String.format(
Locale.getDefault(),
"Took %d ms to read %d files in directory: %s",
duration,
files.length,
valuesDirectory));
return values;
}
private static void readTag (String tag, XmlPullParser parser, ValuesTable values)
private static void readTag(String tag, XmlPullParser parser, ValuesTable values)
throws Exception {
if ("style".equals (tag) || "declare-styleable".equals (tag)) {
if ("style".equals(tag) || "declare-styleable".equals(tag)) {
// These tags are not supported yet
// Skip parsing until we reach next tag with same depth as of this tag
skip (parser);
skip(parser);
return;
}
// TODO Should we check if these tags are specified within <resources>?
final var name = getName (parser);
final var name = getName(parser);
if (name == null) {
LOG.error (String.format ("A <%s> resource was found with no 'name' attribute", tag));
LOG.error(String.format("A <%s> resource was found with no 'name' attribute", tag));
return;
}
if ("item".equals (tag)) {
tag = parser.getAttributeValue (null, "type");
if ("item".equals(tag)) {
tag = parser.getAttributeValue(null, "type");
if (tag == null) {
LOG.error ("<item> resource value found but no 'type' was specified.");
LOG.error("<item> resource value found but no 'type' was specified.");
return;
}
}
// check for array before reading value
// because array resources do not have text values
if ("array".equals (tag) || tag.endsWith ("-array")) {
readArray (parser, values, name);
if ("array".equals(tag) || tag.endsWith("-array")) {
readArray(parser, values, name);
return;
}
// Value must be read after all other required values have been read
final var value = parser.nextText ();
final var value = parser.nextText();
IResource resourceValue;
switch (tag) {
case "string":
resourceValue = new StringResource (name, value);
values.strings.put (name, resourceValue);
resourceValue = new StringResource(name, value);
values.strings.put(name, resourceValue);
break;
case "color":
resourceValue = new ColorResource (name, value);
values.colors.put (name, resourceValue);
resourceValue = new ColorResource(name, value);
values.colors.put(name, resourceValue);
break;
case "bool":
resourceValue = new BooleanResource (name, value);
values.booleans.put (name, resourceValue);
resourceValue = new BooleanResource(name, value);
values.booleans.put(name, resourceValue);
break;
case "dimen":
resourceValue = new DimensionResource (name, value);
values.dimens.put (name, resourceValue);
resourceValue = new DimensionResource(name, value);
values.dimens.put(name, resourceValue);
break;
case "integer":
resourceValue = new IntegerResource (name, value);
values.integers.put (name, resourceValue);
resourceValue = new IntegerResource(name, value);
values.integers.put(name, resourceValue);
break;
default:
LOG.warn ("Unknown or unsupported resource value tag:", tag);
LOG.warn("Unknown or unsupported resource value tag:", tag);
break;
}
}
private static void readArray (@NonNull XmlPullParser parser, ValuesTable values, String name)
private static void readArray(@NonNull XmlPullParser parser, ValuesTable values, String name)
throws XmlPullParserException, IOException {
final var minDepth = parser.getDepth ();
final var items = new ArrayList<String> ();
var event = parser.getEventType ();
while ((event = parser.next ()) != XmlPullParser.END_DOCUMENT
&& parser.getDepth () >= minDepth) {
final var minDepth = parser.getDepth();
final var items = new ArrayList<String>();
var event = parser.getEventType();
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& parser.getDepth() >= minDepth) {
if (event == XmlPullParser.START_TAG) {
final var child = parser.getName ();
if ("item".equals (child)) {
final var value = parser.nextText ();
items.add (value);
final var child = parser.getName();
if ("item".equals(child)) {
final var value = parser.nextText();
items.add(value);
}
}
}
final var arr = new String[items.size ()];
final var arr = new String[items.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = items.get (i);
arr[i] = items.get(i);
}
values.arrays.put (name, new ArrayResource (name, arr));
values.arrays.put(name, new ArrayResource(name, arr));
}
private static void skip (@NonNull XmlPullParser parser)
private static void skip(@NonNull XmlPullParser parser)
throws XmlPullParserException, IOException {
if (parser.getEventType () != XmlPullParser.START_TAG) {
throw new IllegalStateException ();
if (parser.getEventType() != XmlPullParser.START_TAG) {
throw new IllegalStateException();
}
int depth = 1;
while (depth != 0) {
switch (parser.next ()) {
switch (parser.next()) {
case XmlPullParser.END_TAG:
depth--;
break;
@@ -245,21 +245,21 @@ public class ValuesTable {
}
}
}
private static String getName (@NonNull XmlPullParser parser) {
return parser.getAttributeValue (null, "name");
private static String getName(@NonNull XmlPullParser parser) {
return parser.getAttributeValue(null, "name");
}
private static boolean isValid (File file) {
return file != null && file.isFile () && FileUtils.isUtf8 (file);
private static boolean isValid(File file) {
return file != null && file.isFile() && FileUtils.isUtf8(file);
}
@Nullable
public Map<String, IResource> getTable (final String name) {
Objects.requireNonNull (name);
return this.resourceMap.get (name);
public Map<String, IResource> getTable(final String name) {
Objects.requireNonNull(name);
return this.resourceMap.get(name);
}
/**
* Finds a resource value of any type.
*
@@ -267,133 +267,133 @@ public class ValuesTable {
* @return The resource value or {@code null} if there is no resource with the given name.
*/
@SuppressWarnings("unused")
public IResource findResource (final String name) {
public IResource findResource(final String name) {
IResource value;
value = findString (name);
value = findString(name);
if (value != null) {
return value;
}
value = findColor (name);
value = findColor(name);
if (value != null) {
return value;
}
value = findInteger (name);
value = findInteger(name);
if (value != null) {
return value;
}
value = findDimension (name);
value = findDimension(name);
if (value != null) {
return value;
}
value = findArray (name);
value = findArray(name);
if (value != null) {
return value;
}
value = findBoolean (name);
value = findBoolean(name);
//noinspection RedundantIfStatement
if (value != null) {
return value;
}
return null;
}
public ArrayResource findArray (final String name) {
return (ArrayResource) arrays.getOrDefault (name, null);
public ArrayResource findArray(final String name) {
return (ArrayResource) arrays.getOrDefault(name, null);
}
/**
* Find a string value with the given name.
*
* @param name The name of the string resource.
* @return The string resource or {@code null} if there is no resource with the given name.
*/
public StringResource findString (final String name) {
return (StringResource) strings.getOrDefault (name, null);
public StringResource findString(final String name) {
return (StringResource) strings.getOrDefault(name, null);
}
/**
* Find a color value with the given name.
*
* @param name The name of the color resource.
* @return The color resource or {@code null} if there is no resource with the given name.
*/
public ColorResource findColor (final String name) {
return (ColorResource) colors.getOrDefault (name, null);
public ColorResource findColor(final String name) {
return (ColorResource) colors.getOrDefault(name, null);
}
/**
* Find a integer value with the given name.
*
* @param name The name of the integer resource.
* @return The integer resource or {@code null} if there is no resource with the given name.
*/
public IntegerResource findInteger (final String name) {
return (IntegerResource) integers.getOrDefault (name, null);
public IntegerResource findInteger(final String name) {
return (IntegerResource) integers.getOrDefault(name, null);
}
/**
* Find a dimension value with the given name.
*
* @param name The name of the dimension resource.
* @return The dimension resource or {@code null} if there is no resource with the given name.
*/
public DimensionResource findDimension (final String name) {
return (DimensionResource) dimens.getOrDefault (name, null);
public DimensionResource findDimension(final String name) {
return (DimensionResource) dimens.getOrDefault(name, null);
}
/**
* Find a boolean value with the given name.
*
* @param name The name of the boolean resource.
* @return The boolean resource or {@code null} if there is no resource with the given name.
*/
public BooleanResource findBoolean (final String name) {
return (BooleanResource) booleans.getOrDefault (name, null);
public BooleanResource findBoolean(final String name) {
return (BooleanResource) booleans.getOrDefault(name, null);
}
/**
* Reads the contents of the given file and updates this table.
*
* @param file The file to sync with.
*/
public void syncWithFile (final File file) throws Exception {
final var start = System.currentTimeMillis ();
final var factory = XmlPullParserFactory.newInstance ();
final var parser = factory.newPullParser ();
parser.setInput (new FileInputStream (file), null);
var event = parser.getEventType ();
public void syncWithFile(final File file) throws Exception {
final var start = System.currentTimeMillis();
final var factory = XmlPullParserFactory.newInstance();
final var parser = factory.newPullParser();
parser.setInput(new FileInputStream(file), null);
var event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final var tag = parser.getName ();
if ("resources".equals (tag)) {
event = parser.next ();
final var tag = parser.getName();
if ("resources".equals(tag)) {
event = parser.next();
continue;
}
ValuesTable.readTag (tag, parser, this);
ValuesTable.readTag(tag, parser, this);
}
event = parser.next ();
event = parser.next();
}
LOG.debug (
LOG.debug(
"Syncing with file",
file.getName (),
file.getName(),
"took",
(System.currentTimeMillis () - start),
(System.currentTimeMillis() - start),
"ms");
}
@NonNull
@Override
public String toString () {
public String toString() {
return "ValuesTable{"
+ "strings="
+ strings
@@ -31,110 +31,109 @@ import java.util.regex.Pattern;
* @author Akash Yadav
*/
public class ValuesTableFactory {
private static final Map<File, ValuesTable> valueTables = new HashMap<> ();
private static final Logger LOG = Logger.instance ("ValuesTableFactory");
public static ValuesTable getTable (final File resDir) {
return valueTables.getOrDefault (resDir, null);
private static final Map<File, ValuesTable> valueTables = new HashMap<>();
private static final Logger LOG = Logger.instance("ValuesTableFactory");
public static ValuesTable getTable(final File resDir) {
return valueTables.getOrDefault(resDir, null);
}
public static Map<File, ValuesTable> getAllTables () {
public static Map<File, ValuesTable> getAllTables() {
return valueTables;
}
/**
* Setup this ValuesTableFactory with the given "res" directories.
*
* @param resDirs The resource directories.
*/
public static void setupWithResDirectories (File... resDirs) {
public static void setupWithResDirectories(File... resDirs) {
if (resDirs == null || resDirs.length <= 0) {
LOG.error ("Cannot create value tables. No directories were specified.");
LOG.error("Cannot create value tables. No directories were specified.");
return;
}
valueTables.clear ();
final var start = System.currentTimeMillis ();
valueTables.clear();
final var start = System.currentTimeMillis();
for (var res : resDirs) {
if (res == null || !res.exists ()) {
if (res == null || !res.exists()) {
continue;
}
final var table = createTable (res);
final var table = createTable(res);
if (table == null) {
continue;
}
valueTables.put (res, table);
valueTables.put(res, table);
}
LOG.debug (
String.format (
Locale.getDefault (),
LOG.debug(
String.format(
Locale.getDefault(),
"Created value tables for %d resource directories in %d ms",
resDirs.length,
(System.currentTimeMillis () - start)));
(System.currentTimeMillis() - start)));
}
/**
* Notify that the given file's contents were changed and the corresponding table must be
* updated with new values.
*
* @param file The file that was changed.
*/
public static void syncWithFile (final File file) {
public static void syncWithFile(final File file) {
if (file == null) {
LOG.error ("Cannot update value tables. Given file is null.");
LOG.error("Cannot update value tables. Given file is null.");
return;
}
final var pattern = Pattern.compile (".*/src/.*/res/values/(\\w|_)+\\.xml");
if (!pattern.matcher (file.getAbsolutePath ()).matches ()) {
final var pattern = Pattern.compile(".*/src/.*/res/values/(\\w|_)+\\.xml");
if (!pattern.matcher(file.getAbsolutePath()).matches()) {
// This file is not a values resource file.
return;
}
final var resDir =
Objects.requireNonNull (file.getParentFile ()) /* values dir */
.getParentFile () /* res dir*/;
var table = valueTables.getOrDefault (resDir, null);
Objects.requireNonNull(file.getParentFile()) /* values dir */
.getParentFile() /* res dir*/;
var table = valueTables.getOrDefault(resDir, null);
if (table == null) {
table = createTable (resDir);
table = createTable(resDir);
if (table != null) {
valueTables.put (resDir, table);
valueTables.put(resDir, table);
}
return;
}
try {
table.syncWithFile (file);
table.syncWithFile(file);
} catch (Throwable e) {
LOG.error ("Failed to sync values table", e);
LOG.error("Failed to sync values table", e);
}
}
private static ValuesTable createTable (File res) {
private static ValuesTable createTable(File res) {
try {
final var values = new File (res, "values");
if (!values.exists ()) {
final var values = new File(res, "values");
if (!values.exists()) {
return null;
}
var table = ValuesTable.forDirectory (values);
var table = ValuesTable.forDirectory(values);
if (table == null) {
throw new ParseException ();
throw new ParseException();
}
return table;
} catch (Throwable e) {
LOG.error ("Failed to create values table", e);
LOG.error("Failed to create values table", e);
return null;
}
}
static class ParseException extends RuntimeException {
}
static class ParseException extends RuntimeException {}
}