From d71e736ecf5fe15bab56ededd40653a782156258 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Wed, 16 Mar 2022 19:19:01 +0530 Subject: [PATCH] Show more items in attribute value completion Auto-fill already set value in input fields in value editor --- .idea/misc.xml | 1 + .../main/assets/data/common/gradle-plugin.zip | Bin 37265 -> 37271 bytes .../attr/AbstractReferenceEditor.java | 106 ++++++ .../fragments/attr/BooleanEditor.java | 70 ++-- .../fragments/attr/ColorEditor.java | 15 +- .../fragments/attr/DimensionEditor.java | 127 ++++--- .../androidide/fragments/attr/EnumEditor.java | 43 ++- .../androidide/fragments/attr/FlagEditor.java | 9 + .../fragments/attr/ReferenceEditor.java | 115 +++--- .../fragments/attr/StringEditor.java | 8 +- .../sheets/AttrValueEditorSheet.java | 1 + .../project/ProjectResourceTable.java | 289 +++++++++------ .../layout/layout_reference_attr_editor.xml | 37 ++ app/src/main/res/values/strings.xml | 1 + .../com/itsaky/inflater/IResourceTable.java | 54 +-- .../inflater/values/FrameworkValues.java | 59 +-- .../itsaky/inflater/values/ValuesTable.java | 335 +++++++++--------- 17 files changed, 776 insertions(+), 494 deletions(-) create mode 100644 app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java create mode 100644 app/src/main/res/layout/layout_reference_attr_editor.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 4387e023b..bbb481e9c 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -61,6 +61,7 @@ + diff --git a/app/src/main/assets/data/common/gradle-plugin.zip b/app/src/main/assets/data/common/gradle-plugin.zip index 02a5a89769d53db0f792c00fea89f8e6d8eea9a5..43b244430dd825a9bf92c30d323443add87bc113 100644 GIT binary patch delta 2134 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 diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java new file mode 100644 index 000000000..69f461657 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java @@ -0,0 +1,106 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ +package com.itsaky.androidide.fragments.attr; + +import static com.itsaky.androidide.utils.Logger.instance; + +import android.text.Editable; +import android.text.TextUtils; +import android.widget.AutoCompleteTextView; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.ThreadUtils; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.itsaky.androidide.adapters.AttrValueCompletionAdapter; +import com.itsaky.androidide.utils.Logger; +import com.itsaky.androidide.utils.TextWatcherAdapter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * @author Akash Yadav + */ +public abstract class AbstractReferenceEditor extends BaseValueEditorFragment { + + private static final Logger LOG = instance ("ReferenceEditor"); + public TextWatcherAdapter resInputWatcher; + + @Override + protected void notifyValueChanged (@NonNull String newValue) { + try { + super.notifyValueChanged (newValue); + } catch (Throwable e) { + LOG.error ("Unable to update resource value to '" + newValue + "'", e); + } + } + + protected void setupReferenceInput (MaterialAutoCompleteTextView referenceInput) { + Objects.requireNonNull (referenceInput); + + referenceInput.setText (attribute.getValue ()); + + if (resInputWatcher != null) { + referenceInput.removeTextChangedListener (resInputWatcher); + } + + resInputWatcher = + new TextWatcherAdapter () { + @Override + public void afterTextChanged (@NonNull Editable s) { + final var text = s.toString ().trim (); + if (TextUtils.isEmpty (text)) { + return; + } + + notifyValueChanged (text); + } + }; + + referenceInput.addTextChangedListener (resInputWatcher); + + CompletableFuture.supplyAsync (this::computeReferenceItems) + .whenComplete ( + (list, error) -> handleAutoCompleteResult (list, error, referenceInput)); + } + + protected void handleAutoCompleteResult ( + List list, Throwable error, AutoCompleteTextView referenceInput) { + if (list == null || list.isEmpty ()) { + LOG.error ("No completion items found"); + LOG.error ("Error was:", error); + return; + } + + LOG.debug ("Found", list.size (), "resource items for completion"); + + ThreadUtils.runOnUiThread ( + () -> { + final var adapter = new AttrValueCompletionAdapter (requireContext (), list); + referenceInput.setThreshold (1); + referenceInput.setAdapter (adapter); + adapter.notifyDataSetChanged (); + }); + } + + protected List computeReferenceItems () { + return new ArrayList<> (); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java index dd0247772..8bd02650e 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java @@ -35,51 +35,61 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -/** @author Akash Yadav */ -public class BooleanEditor extends ReferenceEditor { - +/** + * @author Akash Yadav + */ +public class BooleanEditor extends AbstractReferenceEditor { + private LayoutBooleanAttrEditorBinding binding; - + @Nullable @Override - public View onCreateView( + public View onCreateView ( @NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - this.binding = LayoutBooleanAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + this.binding = LayoutBooleanAttrEditorBinding.inflate (inflater, container, false); + return this.binding.getRoot (); } - + @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - this.binding.choiceGroup.setOnCheckedChangeListener( + public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated (view, savedInstanceState); + + this.binding.choiceGroup.setOnCheckedChangeListener ( (group, checkedId) -> { - final var checked = (RadioButton) group.findViewById(checkedId); - notifyValueChanged(checked.getText().toString().trim()); + final var checked = (RadioButton) group.findViewById (checkedId); + notifyValueChanged (checked.getText ().toString ().trim ()); }); - - setupReferenceInput( - (MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText()); + + switch (attribute.getValue ()) { + case "true": + this.binding.boolTrue.setChecked (true); + break; + case "false": + this.binding.boolFalse.setChecked (true); + } + + setupReferenceInput ( + (MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText ()); } - + @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var dimens = entry.getValue().getTable("bool"); + protected List computeReferenceItems () { + final var list = new ArrayList (); + final var tables = ValuesTableFactory.getAllTables (); + for (var entry : tables.entrySet ()) { + final var dimens = entry.getValue ().getTable ("bool"); if (dimens != null) { - list.addAll( - dimens.keySet().stream().map("@bool/"::concat).collect(Collectors.toSet())); + list.addAll ( + dimens.keySet ().stream ().map ("@bool/"::concat).collect (Collectors.toSet ())); } } - - list.addAll( - FrameworkValues.listBools().stream() - .map("@android:bool/"::concat) - .collect(Collectors.toList())); + + list.addAll ( + FrameworkValues.listBools ().stream () + .map ("@android:bool/"::concat) + .collect (Collectors.toList ())); return list; } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java index e6559cc49..c146f2d46 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.fragments.attr; import android.app.Dialog; +import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -27,6 +28,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.colorpicker.ColorPickerView; import com.itsaky.androidide.databinding.LayoutColorAttrEditorBinding; import com.itsaky.androidide.utils.DialogUtils; @@ -40,7 +42,7 @@ import java.util.stream.Collectors; /** * @author Akash Yadav */ -public class ColorEditor extends ReferenceEditor { +public class ColorEditor extends AbstractReferenceEditor { private LayoutColorAttrEditorBinding binding; private Dialog colorPickerDialog; @@ -58,6 +60,13 @@ public class ColorEditor extends ReferenceEditor { this.binding.pickColor.setOnClickListener (v -> getColorPickerDialog ().show ()); setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.colorResInput.getEditText ()); + + try { + final var col = Color.parseColor (attribute.getValue ()); + this.binding.colorPreview.setCardBackgroundColor (col); + } catch (Throwable th) { + // ignored + } } @Override @@ -78,6 +87,10 @@ public class ColorEditor extends ReferenceEditor { FrameworkValues.listColors ().stream () .map ("@android:color/"::concat) .collect (Collectors.toList ())); + + final var resTable = StudioApp.getInstance ().getResourceTable (); + list.addAll (resTable.listResourceNames ("color")); + return list; } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java index 08a279ae3..381393485 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java @@ -19,9 +19,11 @@ package com.itsaky.androidide.fragments.attr; import android.os.Bundle; import android.text.Editable; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,88 +37,105 @@ import com.itsaky.inflater.values.FrameworkValues; import com.itsaky.inflater.values.ValuesTableFactory; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -/** @author Akash Yadav */ -public class DimensionEditor extends ReferenceEditor { - - private static final Logger LOG = Logger.instance("DimensionEditor"); +/** + * @author Akash Yadav + */ +public class DimensionEditor extends AbstractReferenceEditor { + + private static final Logger LOG = Logger.instance ("DimensionEditor"); public TextWatcherAdapter dimensionInputWatcher; - + 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 ()); + setDimensionValue (dimensionInput); 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()); - setupReferenceInput(dimensionResInput); + Objects.requireNonNull (binding.dimensionResInput.getEditText ()); + setupReferenceInput (dimensionResInput); } - - @NonNull - private String[] getDimensionUnits() { - if (dimensionUnits == null) { - dimensionUnits = - requireContext().getResources().getStringArray(R.array.dimension_units); - } - - return dimensionUnits; - } - - @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - 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())); + + private void setDimensionValue (EditText dimensionInput) { + final var val = attribute.getValue (); + var dimension = val.substring (0, val.length () - 2); + if (TextUtils.isDigitsOnly (dimension)) { + dimensionInput.setText (dimension); + final var arr = requireContext ().getResources ().getStringArray (R.array.dimension_units); + final var index = Arrays.asList (arr).indexOf (val.substring (dimension.length ())); + if (index >= 0) { + this.binding.unitSelector.setSelection (index); } } - - list.addAll( - FrameworkValues.listDimens().stream() - .map("@android:dimen/"::concat) - .collect(Collectors.toList())); + } + + @NonNull + private String[] getDimensionUnits () { + if (dimensionUnits == null) { + dimensionUnits = + requireContext ().getResources ().getStringArray (R.array.dimension_units); + } + + return dimensionUnits; + } + + @Override + protected List computeReferenceItems () { + final var list = new ArrayList (); + 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 ( + FrameworkValues.listDimens ().stream () + .map ("@android:dimen/"::concat) + .collect (Collectors.toList ())); return list; } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java index 3c40809a5..d152527fe 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java @@ -26,28 +26,39 @@ import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.itsaky.androidide.utils.Logger; -/** @author Akash Yadav */ +/** + * @author Akash Yadav + */ public class EnumEditor extends FixedValueEditor { - - private static final Logger LOG = Logger.instance("EnumEditor"); - + + private static final Logger LOG = Logger.instance ("EnumEditor"); + @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - this.chipGroup.setSingleSelection(true); - this.chipGroup.setSelectionRequired(true); + public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated (view, savedInstanceState); + + this.chipGroup.setSingleSelection (true); + this.chipGroup.setSelectionRequired (true); + + final var val = this.attribute.getValue (); + for (int i = 0; i < this.chipGroup.getChildCount (); i++) { + final var chip = (Chip) this.chipGroup.getChildAt (i); + if (val.equals (chip.getText ().toString ().trim ())) { + chip.setChecked (true); + break; + } + } } - + @Override - protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) { - final var chip = (Chip) group.findViewById(group.getCheckedChipId()); + protected void onCheckChanged (@NonNull ChipGroup group, int checkedId) { + final var chip = (Chip) group.findViewById (group.getCheckedChipId ()); if (chip == null) { - LOG.error("Unable to update enum value. Checked Chip view is null."); + LOG.error ("Unable to update enum value. Checked Chip view is null."); return; } - - final var val = chip.getText().toString().trim(); - notifyValueChanged(val); + + final var val = chip.getText ().toString ().trim (); + notifyValueChanged (val); } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java index c046ed258..fed9b02b3 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java @@ -32,6 +32,7 @@ import com.google.android.material.chip.ChipGroup; import com.itsaky.androidide.utils.Logger; import java.util.ArrayList; +import java.util.Arrays; /** * @author Akash Yadav @@ -47,6 +48,14 @@ public class FlagEditor extends FixedValueEditor { this.chipGroup.setSingleSelection(false); this.chipGroup.setSelectionRequired(true); this.chipGroup.setOnCheckedChangeListener(null); // does not work for multiple selections + + final var values = Arrays.asList (this.attribute.getValue ().split ("\\|")); + for (int i = 0; i < this.chipGroup.getChildCount (); i++) { + final var chip = (Chip) this.chipGroup.getChildAt (i); + if (values.contains (chip.getText ().toString ().trim ())) { + chip.setChecked (true); + } + } } @Override diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java index 671ca973b..b13da8e06 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java @@ -14,91 +14,72 @@ * You should have received a copy of the GNU General Public License * along with AndroidIDE. If not, see . */ + package com.itsaky.androidide.fragments.attr; -import static com.itsaky.androidide.utils.Logger.instance; - -import android.text.Editable; -import android.text.TextUtils; -import android.widget.AutoCompleteTextView; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import com.blankj.utilcode.util.ThreadUtils; import com.google.android.material.textfield.MaterialAutoCompleteTextView; -import com.itsaky.androidide.adapters.AttrValueCompletionAdapter; -import com.itsaky.androidide.utils.Logger; -import com.itsaky.androidide.utils.TextWatcherAdapter; +import com.itsaky.androidide.app.StudioApp; +import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding; +import com.itsaky.inflater.values.FrameworkValues; +import com.itsaky.inflater.values.ValuesTableFactory; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; /** * @author Akash Yadav */ -public class ReferenceEditor extends BaseValueEditorFragment { +public class ReferenceEditor extends AbstractReferenceEditor { - private static final Logger LOG = instance ("ReferenceEditor"); - public TextWatcherAdapter resInputWatcher; + private LayoutReferenceAttrEditorBinding binding; + + @Nullable + @Override + public View onCreateView (@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + this.binding = LayoutReferenceAttrEditorBinding.inflate (inflater, container, false); + return this.binding.getRoot (); + } @Override - protected void notifyValueChanged (@NonNull String newValue) { - try { - super.notifyValueChanged (newValue); - } catch (Throwable e) { - LOG.error ("Unable to update resource value to '" + newValue + "'", e); - } - } - - protected void setupReferenceInput (MaterialAutoCompleteTextView referenceInput) { - Objects.requireNonNull (referenceInput); - - if (resInputWatcher != null) { - referenceInput.removeTextChangedListener (resInputWatcher); - } - - resInputWatcher = - new TextWatcherAdapter () { - @Override - public void afterTextChanged (@NonNull Editable s) { - final var text = s.toString ().trim (); - if (TextUtils.isEmpty (text)) { - return; - } - - notifyValueChanged (text); - } - }; - - referenceInput.addTextChangedListener (resInputWatcher); - - CompletableFuture.supplyAsync (this::computeReferenceItems) - .whenComplete ( - (list, error) -> handleAutoCompleteResult (list, error, referenceInput)); - } - - protected void handleAutoCompleteResult ( - List list, Throwable error, AutoCompleteTextView referenceInput) { - if (list == null || list.isEmpty ()) { - LOG.error ("No completion items found"); - LOG.error ("Error was:", error); - return; - } - - LOG.debug ("Found", list.size (), "resource items for completion"); - - ThreadUtils.runOnUiThread ( - () -> { - final var adapter = new AttrValueCompletionAdapter (requireContext (), list); - referenceInput.setThreshold (1); - referenceInput.setAdapter (adapter); - adapter.notifyDataSetChanged (); - }); + public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated (view, savedInstanceState); + + setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.resInput.getEditText ()); } + @Override protected List computeReferenceItems () { - return new ArrayList<> (); + final var resTable = StudioApp.getInstance ().getResourceTable (); + final var list = new ArrayList<> (resTable.listResourceNames (null)); + + final var tables = ValuesTableFactory.getAllTables (); + for (var entry : tables.entrySet ()) { + final var resourceMap = entry.getValue ().getResourceMap (); + for (var resourceEntries : resourceMap.entrySet ()) { + final var name = resourceEntries.getKey (); + list.addAll ( + resourceEntries + .getValue () + .keySet () + .stream () + .map (("@android:" + name + "/")::concat) + .collect (Collectors.toList () + ) + ); + } + } + + list.addAll (FrameworkValues.listAllResources ()); + + return list; } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java index 2dd5ccec6..25e932144 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java @@ -39,7 +39,7 @@ import java.util.stream.Collectors; /** * @author Akash Yadav */ -public class StringEditor extends ReferenceEditor { +public class StringEditor extends AbstractReferenceEditor { private LayoutStringAttrEditorBinding binding; @@ -54,13 +54,15 @@ public class StringEditor extends ReferenceEditor { public void onViewCreated (@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated (view, savedInstanceState); - Objects.requireNonNull (this.binding.stringInput.getEditText ()).addTextChangedListener (new TextWatcherAdapter () { + final var stringInput = Objects.requireNonNull (this.binding.stringInput.getEditText ()); + stringInput.addTextChangedListener (new TextWatcherAdapter () { @Override public void afterTextChanged (@NonNull Editable s) { notifyValueChanged (s.toString ()); } }); + stringInput.setText (this.attribute.getValue ()); setupReferenceInput ((MaterialAutoCompleteTextView) this.binding.stringResInput.getEditText ()); } @@ -79,7 +81,7 @@ public class StringEditor extends ReferenceEditor { } list.addAll ( - FrameworkValues.listDimens ().stream () + FrameworkValues.listStrings ().stream () .map ("@android:string/"::concat) .collect (Collectors.toList ())); return list; diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java index f46057ab0..3d4563bf6 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java @@ -49,6 +49,7 @@ import com.itsaky.androidide.fragments.attr.FlagEditor; import com.itsaky.androidide.fragments.attr.FloatEditor; import com.itsaky.androidide.fragments.attr.FractionEditor; import com.itsaky.androidide.fragments.attr.IntegerEditor; +import com.itsaky.androidide.fragments.attr.AbstractReferenceEditor; import com.itsaky.androidide.fragments.attr.ReferenceEditor; import com.itsaky.androidide.fragments.attr.StringEditor; import com.itsaky.androidide.models.XMLAttribute; diff --git a/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java b/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java index 0f1a846a9..b65369e36 100644 --- a/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java +++ b/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.project; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.itsaky.androidide.utils.Logger; import com.itsaky.inflater.IResourceTable; @@ -25,229 +26,287 @@ import com.itsaky.inflater.values.ValuesTableFactory; import java.io.File; import java.io.FileFilter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Locale; public class ProjectResourceTable implements IResourceTable { - + + private static final Logger LOG = Logger.instance ("ProjectResourceFinder"); private File resDir; private File color; private File layout; - private File[] drawables; private File[] mipmaps; - - private static final Logger LOG = Logger.instance("ProjectResourceFinder"); - + @Override - public File findDrawable(@NonNull String name) { + public File findDrawable (@NonNull String name) { for (File drawable : drawables) { - final File file = findFileWithName(drawable.listFiles(), name); + final File file = findFileWithName (drawable.listFiles (), name); if (file != null) { return file; } } - + return null; } - + @Override - public File findLayout(@NonNull String name) { - return findFileWithName(this.layout.listFiles(), name); + public File findLayout (@NonNull String name) { + return findFileWithName (this.layout.listFiles (), name); } - + @Override - public String findString(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - + public String findString (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); + if (table == null) { return name; } - - final var resource = table.findString(name); + + final var resource = table.findString (name); if (resource == null) { return name; } - - final var value = resource.getValue(); - if (value.startsWith("@string/")) { - var ref = value.substring("@string/".length()); - if (name.equals(ref)) { + + final var value = resource.getValue (); + if (value.startsWith ("@string/")) { + var ref = value.substring ("@string/".length ()); + if (name.equals (ref)) { return name; } } - + return value; } - + @Override - public String findBoolean(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - + public String findBoolean (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); + if (table == null) { return name; } - - final var resource = table.findBoolean(name); + + final var resource = table.findBoolean (name); if (resource == null) { return name; } - - final var val = resource.getValue(); - if (val.startsWith("@bool/")) { - final var ref = val.substring("@bool/".length()); - if (ref.equals(name)) { + + final var val = resource.getValue (); + if (val.startsWith ("@bool/")) { + final var ref = val.substring ("@bool/".length ()); + if (ref.equals (name)) { // recursive reference return name; } } - + return val; } - + @Override - public String findInteger(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - + public String findInteger (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); + if (table == null) { return name; } - - final var resource = table.findInteger(name); + + final var resource = table.findInteger (name); if (resource == null) { return name; } - - final var val = resource.getValue(); - if (val.startsWith("@integer/")) { - final var ref = val.substring("@integer/".length()); - if (ref.equals(name)) { + + final var val = resource.getValue (); + if (val.startsWith ("@integer/")) { + final var ref = val.substring ("@integer/".length ()); + if (ref.equals (name)) { // recursive reference return name; } } - + return val; } - + @Override - public String findColor(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - + public String findColor (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); + if (table == null) { return name; } - - final var resource = table.findColor(name); + + final var resource = table.findColor (name); if (resource == null) { return name; } - - final var value = resource.getValue(); - if (value.startsWith("@color/")) { - var ref = value.substring("@color/".length()); - if (name.equals(ref)) { + + final var value = resource.getValue (); + if (value.startsWith ("@color/")) { + var ref = value.substring ("@color/".length ()); + if (name.equals (ref)) { return name; } } - + return value; } - + @Override - public String[] findArray(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); + public String[] findArray (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); if (table == null) { - return new String[] {name}; + return new String[]{name}; } - - final var resource = table.findArray(name); + + final var resource = table.findArray (name); if (resource == null) { - return new String[] {name}; + return new String[]{name}; } - - return resource.getValues(); + + return resource.getValues (); } - + @Override - public String findDimension(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - + public String findDimension (@NonNull String name) { + final var table = ValuesTableFactory.getTable (resDir); + if (table == null) { return name; } - - final var resource = table.findDimension(name); + + final var resource = table.findDimension (name); if (resource == null) { return name; } - - final var value = resource.getValue(); - if (value.startsWith("@dimen/")) { - var ref = value.substring("@dimen/".length()); - if (name.equals(ref)) { + + final var value = resource.getValue (); + if (value.startsWith ("@dimen/")) { + var ref = value.substring ("@dimen/".length ()); + if (name.equals (ref)) { return name; } } - + return value; } - + + @NonNull @Override - public void setInflatingFile(@NonNull File file) { - if (file.getParentFile() == null) { - throw new IllegalArgumentException("Invalid inflating file"); + public Collection listResourceNames (@Nullable String type) { + if (type == null) { + return listAllResourceNames (); + } else { + final var set = new HashSet (); + switch (type) { + case "drawable": + for (var folder : this.drawables) { + listAndAddTo (set, folder, type); + } + break; + case "layout": + listAndAddTo (set, this.layout, type); + break; + case "color": + listAndAddTo (set, this.color, type); + break; + case "mipmap": + for (var folder : this.mipmaps) { + listAndAddTo (set, folder, type); + } + break; + } + + return set; } - - setupDirectories( - file // layout file - .getParentFile() // layout dir - .getParentFile()); // res dir } - - private void setupDirectories(File resDir) { - - if (resDir == null) { - LOG.error("Null resource directory passed to resource finder. Ignoring."); + + private void listAndAddTo (HashSet set, @NonNull File dir, String type) { + final var files = dir.listFiles (); + if (files == null) { return; } - + + for (var file : files) { + var name = file.getName (); + if (!name.contains (".")) { + continue; + } + + name = name.substring (0, name.lastIndexOf ('.')); + name = String.format (Locale.ROOT, "@%s/%s", type, name); + set.add (name); + } + } + + @NonNull + private Collection listAllResourceNames () { + final var set = new HashSet<> (listResourceNames ("color")); + set.addAll (listResourceNames ("drawable")); + set.addAll (listResourceNames ("mipmap")); + set.addAll (listResourceNames ("layout")); + return set; + } + + @Override + public void setInflatingFile (@NonNull File file) { + if (file.getParentFile () == null) { + throw new IllegalArgumentException ("Invalid inflating file"); + } + + setupDirectories ( + file // layout file + .getParentFile () // layout dir + .getParentFile ()); // res dir + } + + private void setupDirectories (File resDir) { + + if (resDir == null) { + LOG.error ("Null resource directory passed to resource finder. Ignoring."); + return; + } + this.resDir = resDir; - this.color = new File(resDir, "color"); - this.layout = new File(resDir, "layout"); - - final File[] drawables = resDir.listFiles(new NameStartsWith("drawable")); + this.color = new File (resDir, "color"); + this.layout = new File (resDir, "layout"); + + final File[] drawables = resDir.listFiles (new NameStartsWith ("drawable")); this.drawables = drawables == null ? new File[0] : drawables; - - final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap")); + + final File[] mipmaps = resDir.listFiles (new NameStartsWith ("mipmap")); this.mipmaps = mipmaps == null ? new File[0] : mipmaps; } - - private File findFileWithName(File[] files, String name) { + + private File findFileWithName (File[] files, String name) { if (files == null || name == null) { return null; } - + for (File file : files) { - var simpleName = file.getName(); - - if (simpleName.startsWith(name)) { + var simpleName = file.getName (); + + if (simpleName.startsWith (name)) { return file; } } - + return null; } - + private static class NameStartsWith implements FileFilter { - + private final String name; - - public NameStartsWith(String name) { + + public NameStartsWith (String name) { this.name = name; } - + @Override - public boolean accept(@NonNull File file) { - return file.getName().startsWith(name); + public boolean accept (@NonNull File file) { + return file.getName ().startsWith (name); } } } diff --git a/app/src/main/res/layout/layout_reference_attr_editor.xml b/app/src/main/res/layout/layout_reference_attr_editor.xml new file mode 100644 index 000000000..4c2b70484 --- /dev/null +++ b/app/src/main/res/layout/layout_reference_attr_editor.xml @@ -0,0 +1,37 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 569bd465c..d39b2eef1 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -342,4 +342,5 @@ Color picker String value String resource + Any resource reference diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/IResourceTable.java b/xml-inflater/src/main/java/com/itsaky/inflater/IResourceTable.java index 02d66f14d..7fa6b0d92 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/IResourceTable.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/IResourceTable.java @@ -18,12 +18,16 @@ package com.itsaky.inflater; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.File; +import java.util.Collection; -/** Finds values in resources of a project */ +/** + * Finds values in resources of a project + */ public interface IResourceTable { - + /** * Find the path of the drawable with the provided name * @@ -32,69 +36,79 @@ public interface IResourceTable { * @param name The name of the drawable * @return The drawable resource. */ - File findDrawable(@NonNull String name); - + File findDrawable (@NonNull String name); + /** * Find the layout file with the specified name. * * @param name The name of the layout file * @return The found layout resource file */ - File findLayout(@NonNull String name); - + File findLayout (@NonNull String name); + /** * Find a string resource. Must be a valid string or {@code null} * * @param name The name of the resource * @return Value of the resource */ - String findString(@NonNull String name); - + String findString (@NonNull String name); + /** * Find a color resource. The returned value must be a valid color value or {@code -1} * * @param name The name of the resource * @return Value of the resource */ - String findColor(@NonNull String name); - + String findColor (@NonNull String name); + /** * Find an array resource. Must be a valid array or {@code null} * * @param name The name of the resource * @return Value of the resource */ - String[] findArray(@NonNull String name); - + String[] findArray (@NonNull String name); + /** * Find the dimension value with the given name. * * @param name The name of the dimension resource * @return The String representation of the dimension. It could one of the following format : - * 1dp, 12sp, 123px, etc. + * 1dp, 12sp, 123px, etc. */ - String findDimension(@NonNull String name); - + String findDimension (@NonNull String name); + /** * Find the boolean value with the given name. * * @param name The name of the boolean resource. * @return The string value of the boolean resource. */ - String findBoolean(@NonNull String name); - + String findBoolean (@NonNull String name); + /** * Find the integer value with the given name. * * @param name The name of the integer resource. * @return The string value of the integer resource. */ - String findInteger(@NonNull String name); - + String findInteger (@NonNull String name); + /** * Set the file which is currently being inflated. * * @param file The file. */ - void setInflatingFile(@NonNull File file); + void setInflatingFile (@NonNull File file); + + /** + * Lists the resources with the given type. + * + * @param type The type of the resources to list. Might be null. + * @return A collection of resource names. If type is null, + * all the resources in the resource table must be listed. + */ + @NonNull + Collection listResourceNames (@Nullable String type); } diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/values/FrameworkValues.java b/xml-inflater/src/main/java/com/itsaky/inflater/values/FrameworkValues.java index 6284bf9f3..10e57b85f 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/values/FrameworkValues.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/values/FrameworkValues.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * Provides framework resource values. @@ -32,42 +33,54 @@ import java.util.Objects; * @author Akash Yadav */ public class FrameworkValues { - + @NonNull - public static List listDimens() { - return listFields(android.R.dimen.class); + public static List listDimens () { + return listFields (android.R.dimen.class); } - + @NonNull - public static List listBools() { - return listFields(android.R.bool.class); + public static List listBools () { + return listFields (android.R.bool.class); } - + @NonNull - public static List listStrings() { - return listFields(android.R.string.class); + public static List listStrings () { + return listFields (android.R.string.class); } - + @NonNull - public static List listColors() { - return listFields(android.R.color.class); + public static List listColors () { + return listFields (android.R.color.class); } - + @NonNull - private static List listFields(final Class klass) { - Objects.requireNonNull(klass); - - final var list = new ArrayList(); - final var fields = klass.getDeclaredFields(); + private static List listFields (final Class klass) { + Objects.requireNonNull (klass); + + final var list = new ArrayList (); + 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; + } + + @NonNull + public static List listAllResources () { + final var list = new ArrayList (); + for (var klass : android.R.class.getDeclaredClasses ()) { + list.addAll (listFields (klass).stream () + .map (("@android:" + klass.getSimpleName () + "/")::concat) + .collect (Collectors.toList ()) + ); } - return list; } } diff --git a/xml-inflater/src/main/java/com/itsaky/inflater/values/ValuesTable.java b/xml-inflater/src/main/java/com/itsaky/inflater/values/ValuesTable.java index 7ca55d7d2..06f673b50 100644 --- a/xml-inflater/src/main/java/com/itsaky/inflater/values/ValuesTable.java +++ b/xml-inflater/src/main/java/com/itsaky/inflater/values/ValuesTable.java @@ -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 arrays; private final Map strings; @@ -57,185 +57,185 @@ public class ValuesTable { private final Map integers; private final Map dimens; private final Map booleans; - - private final Map> 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> 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 ? - 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(" resource value found but no 'type' was specified."); + LOG.error (" 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(); - - var event = parser.getEventType(); - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT - && parser.getDepth() >= minDepth) { + final var minDepth = parser.getDepth (); + final var items = new ArrayList (); + + 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,26 @@ 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 getTable(final String name) { - Objects.requireNonNull(name); - return this.resourceMap.get(name); + public Map getTable (final String name) { + Objects.requireNonNull (name); + return this.resourceMap.get (name); } - + + @NonNull + public Map> getResourceMap () { + return resourceMap; + } + /** * Finds a resource value of any type. * @@ -267,133 +272,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