From d3f09bf718be33ac385c8e5817164241cdd4b00c Mon Sep 17 00:00:00 2001 From: Yusuf Cihan Date: Wed, 3 Apr 2024 17:26:47 +0300 Subject: [PATCH] Version - v2.3.0 (#67) Co-authored-by: Kumaraswamy B G <71964026+XomaDev@users.noreply.github.com> Co-authored-by: Nathan <43486313+hammerhai@users.noreply.github.com> --- .vscode/settings.json | 18 + README.md | 60 +- assets/blocks/method_createephemeral.png | Bin 0 -> 9423 bytes assets/blocks/method_removecomponent.png | Bin 0 -> 8725 bytes assets/icon.png | Bin 2435 -> 0 bytes lib/appinventor | 2 +- .../DynamicComponents/DynamicComponents.java | 683 +++++++----------- .../DynamicComponents/aiwebres/icon.png | Bin 2435 -> 4084 bytes .../DynamicComponents/classes/Metadata.java | 108 +++ .../DynamicComponents/classes/Utils.java | 239 ++++++ 10 files changed, 660 insertions(+), 450 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 assets/blocks/method_createephemeral.png create mode 100644 assets/blocks/method_removecomponent.png delete mode 100644 assets/icon.png create mode 100644 src/com/yusufcihan/DynamicComponents/classes/Metadata.java create mode 100644 src/com/yusufcihan/DynamicComponents/classes/Utils.java diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..87c9c88 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "git.detectSubmodules": true, + "java.project.referencedLibraries": [ + "lib/**/*.jar" + ], + "java.compile.nullAnalysis.mode": "automatic", + "[java]": { + "editor.tabSize": 2, + "files.trimTrailingWhitespace": true, + "editor.trimAutoWhitespace": true + }, + "[xml]": { + "editor.tabSize": 2 + }, + "indentRainbow.excludedLanguages": [ + "java" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index c4a3fc4..cce290f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,10 @@ -![Icon](assets/icon.png) +#  Dynamic Components for AI2 -# DynamicComponents-AI2 `Extension` +An extension for [MIT App Inventor 2](https://appinventor.mit.edu/) applications that allows to create components dynamically by its name at runtime with blocks. -[![forthebadge](https://forthebadge.com/images/badges/its-not-a-lie-if-you-believe-it.svg)](https://forthebadge.com) +It is based on Java's reflection feature, so this allows us to create instances of classes (components) by its name. Also, unlike other extensions that create components in runtime, this extension doesn't keep a list of all component names because it supports every component which is ever added to your App Inventor distribution by nature. So, not only can you dynamically create common components like `Button`, but you can also create `DatePicker` components. -[![Maintainability](https://api.codeclimate.com/v1/badges/31e4cd31de1bd0e186c8/maintainability)](https://codeclimate.com/github/ysfchn/DynamicComponents-AI2/maintainability) - -Fully supported Dynamic Components extension for MIT App Inventor 2. It is based on Java's reflection feature, so it creates the components by searching for a class by just typing its name. So it doesn't have a limited support for specific components, because it supports every component which is ever added to your App Inventor distribution! - -So if you use Kodular, you will able to create all Kodular components, if you use App Inventor, you will able to create all App Inventor components and so on. Extension components are supported too! - -> ⚠ The `beta` branch will be reset after every release. So stay on the `main` branch if you don't know what you do. - ---- - -### Asynchronous support - -This extension can create components asynchronously or synchronously based on your choice. If you don't want to block the main app during creating a bunch of components, go to the Designer (after importing the extension) and select between "UI" (asynchronous) and "Main" (synchronous). - - +So if you use Kodular, you will able to create all Kodular components, if you use App Inventor, you will able to create all App Inventor components and so on. Creating instances of other extensions are also supported. ## 🧩 Blocks @@ -49,7 +35,7 @@ This extension can create components asynchronously or synchronously based on yo --> - Creates a new dynamic component. It supports all component that added to your current AI2 distribution. + Creates a new dynamic component. It supports all component that added to your current AI2 distribution. Note that you can't create components in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that. componentName parameter can have these values:

@@ -68,6 +54,15 @@ This extension can create components asynchronously or synchronously based on yo
+ + + + + + + Creates a new dynamic component in given container (arrangement/canvas) and return it without saving it to the created components list, so it won't be attached to an ID. Note that you can't create components in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that. + + @@ -131,7 +126,16 @@ This extension can create components asynchronously or synchronously based on yo --> - Removes the component with specified ID from screen/layout and the component list. So you will able to use its ID again as it will be deleted. + Removes the component with specified ID from screen and the component list. So you will able to use its ID again as it will be deleted. + + + + + + + + + Removes a component from the screen. It doesn't need to be created by this extension. But if the given component is dynamically created by this extension, this block will also de-register its ID so its ID can be reused for other components that are going to be created later. @@ -431,6 +435,12 @@ This extension can create components asynchronously or synchronously based on yo +### Asynchronous support + +This extension can create components asynchronously or synchronously based on your choice. If you don't want to block the main app during creating a bunch of components, go to the Designer (after importing the extension) and select between "UI" (asynchronous) and "Main" (synchronous). + + + ## 🔨 Building You will need: @@ -438,7 +448,15 @@ You will need: - Java 1.8 (either OpenJDK or Oracle) - Ant 1.10 or higher -Then execute `ant extensions` in the root of the repository. +After cloning the repository, make sure to fetch submodules first: + +``` +git submodule update --init --recursive +``` + +Then execute `ant extensions` in the root of the repository to build the extension. + +> ⚠ The `beta` branch will be reset after every release. So stay on the `main` branch if you don't know what you do. ## 🏅 License diff --git a/assets/blocks/method_createephemeral.png b/assets/blocks/method_createephemeral.png new file mode 100644 index 0000000000000000000000000000000000000000..d5e136cfdeaf8cd3c2b22a8fef932e2c7428676f GIT binary patch literal 9423 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 BnDhVu diff --git a/lib/appinventor b/lib/appinventor index 477586b..6380b04 160000 --- a/lib/appinventor +++ b/lib/appinventor @@ -1 +1 @@ -Subproject commit 477586b7e1901db30ed6b8e76cf524c4a05b2802 +Subproject commit 6380b04f69fd0ead8b8b5ca8a06287a020c6c967 diff --git a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java index 17b437e..7e85043 100644 --- a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java +++ b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java @@ -1,8 +1,15 @@ package com.yusufcihan.DynamicComponents; +import com.yusufcihan.DynamicComponents.classes.Utils; +import com.yusufcihan.DynamicComponents.classes.Metadata; + +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.view.ViewGroup; + import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; -import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleEvent; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; @@ -14,158 +21,75 @@ import com.google.appinventor.components.runtime.Component; import com.google.appinventor.components.runtime.ComponentContainer; import com.google.appinventor.components.runtime.EventDispatcher; -import com.google.appinventor.components.runtime.Form; import com.google.appinventor.components.runtime.errors.YailRuntimeError; import com.google.appinventor.components.runtime.util.YailDictionary; import com.google.appinventor.components.runtime.util.YailList; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; -import android.os.Handler; -import android.os.Looper; -import android.view.View; -import android.view.ViewGroup; -import gnu.lists.FString; -import gnu.math.DFloNum; -import gnu.math.IntNum; - -import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; -import java.util.Set; +import java.util.LinkedList; +import java.util.Map; +import java.util.Objects; import java.util.UUID; @DesignerComponent( - description = "Dynamic Components is an extension that creates any component in your App Inventor distribution programmatically, instead of having pre-defined components. Made with ❤️ by Yusuf Cihan.", - category = ComponentCategory.EXTENSION, - helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2/blob/main/README.md", - iconName = "aiwebres/icon.png", - nonVisible = true, - version = 9, - versionName = "2.2.2" + description = + "Create any component available in your App Inventor distribution and create instances of " + + "other extensions programmatically in runtime. Made with ❤️ by Yusuf Cihan.", + category = ComponentCategory.EXTENSION, + helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2/blob/main/README.md", + iconName = "aiwebres/icon.png", + nonVisible = true, + version = 10, + versionName = "2.3.0" ) @SimpleObject(external = true) public class DynamicComponents extends AndroidNonvisibleComponent { - // Base package name for components - private final String BASE = "com.google.appinventor.components.runtime."; + private static final String TAG = Utils.TAG; // Whether component creation should happen on the UI thread private boolean postOnUiThread = false; // Components created with Dynamic Components - private final HashMap COMPONENTS = new HashMap(); + private final HashMap COMPONENTS = new HashMap<>(); // IDs of components created with Dynamic Components - private final HashMap COMPONENT_IDS = new HashMap(); + private final HashMap COMPONENT_IDS = new HashMap<>(); private Object lastUsedId = ""; - private ArrayList componentListeners = new ArrayList(); - private JSONArray propertiesArray = new JSONArray(); - private final Util UTIL_INSTANCE = new Util(); + private final ArrayList componentListeners = new ArrayList<>(); public DynamicComponents(ComponentContainer container) { super(container.$form()); } interface ComponentListener { - public void onCreation(Component component, String id); + void onCreation(Component component, String id); } - class Util { - public boolean exists(Component component) { - return COMPONENTS.containsValue(component); - } - - public boolean exists(String id) { - return COMPONENTS.containsKey(id); - } - - public String getClassName(Object componentName) { - String regex = "[^.$@a-zA-Z0-9]"; - String componentNameString = componentName.toString().replaceAll(regex, ""); - - if (componentName instanceof String && componentNameString.contains(".")) { - return componentNameString; - } else if (componentName instanceof String) { - return BASE + componentNameString; - } else if (componentName instanceof Component) { - return componentName.getClass().getName().replaceAll(regex, ""); - } else { - throw new YailRuntimeError("Component is invalid.", "DynamicComponents"); - } - } - - public Method getMethod(Method[] methods, String name, int parameterCount) { - name = name.replaceAll("[^a-zA-Z0-9]", ""); - for (Method method : methods) { - int methodParameterCount = method.getParameterTypes().length; - if (method.getName().equals(name) && methodParameterCount == parameterCount) { - return method; - } - } - - return null; - } - - public void newInstance(Constructor constructor, String id, AndroidViewComponent input) { - Component mComponent = null; - - try { - mComponent = (Component) constructor.newInstance((ComponentContainer) input); - } catch(Exception e) { - throw new YailRuntimeError(e.getMessage(), "DynamicComponents"); - } finally { - if (!isEmptyOrNull(mComponent)) { - String mComponentClassName = mComponent.getClass().getSimpleName(); - if (mComponentClassName == "ImageSprite" || mComponentClassName == "Sprite") { - Invoke(mComponent, "Initialize", new YailList()); - } - - COMPONENT_IDS.put(mComponent, id); - COMPONENTS.put(id, mComponent); - this.notifyListenersOfCreation(mComponent, id); - ComponentBuilt(mComponent, id, mComponentClassName); - } - } - } - - public void parse(String id, JSONObject json) { - JSONObject data = new JSONObject(json.toString()); - data.remove("components"); - - if (!"".equals(id)) { - data.put("in", id); - } - - propertiesArray.put(data); - - if (json.has("components")) { - for (int i = 0; i < json.getJSONArray("components").length(); i++) { - this.parse(data.optString("id", ""), json.getJSONArray("components").getJSONObject(i)); - } - } - } - - public void notifyListenersOfCreation(Component component, String id) { - for (ComponentListener listener : componentListeners) { - listener.onCreation(component, id); - } - } + public boolean isCreatedComponent(String id) { + return COMPONENTS.containsKey(id); } - public boolean isEmptyOrNull(Object item) { - if (item instanceof String) { - String mItem = item.toString(); - mItem = mItem.replace(" ", ""); - return mItem.isEmpty(); + public void notifyListenersOfCreation(Component component, String id) { + for (ComponentListener listener : componentListeners) { + listener.onCreation(component, id); } + } - return item == null; + private void dispatchEvent(final String name, final Object... parameters) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + EventDispatcher.dispatchEvent(DynamicComponents.this, name, parameters); + } + }); } @DesignerProperty( @@ -175,343 +99,244 @@ public boolean isEmptyOrNull(Object item) { ) @SimpleProperty(userVisible = false) public void Thread(String thread) { - postOnUiThread = (thread == "UI"); + if (thread.equalsIgnoreCase("UI")) { + postOnUiThread = true; + } else if (thread.equalsIgnoreCase("Main")) { + postOnUiThread = false; + } else { + throw new YailRuntimeError("Unexpected value '" + thread + "'", TAG); + } } @SimpleEvent(description = "Is called after a component has been created.") public void ComponentBuilt(final Component component, final String id, final String type) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - EventDispatcher.dispatchEvent(DynamicComponents.this, "ComponentBuilt", component, id, type); - } - }); + dispatchEvent("ComponentBuilt", component, id, type); } @SimpleEvent(description = "Is called after a schema has/mostly finished component creation.") public void SchemaCreated(final String name, final YailList parameters) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - EventDispatcher.dispatchEvent(DynamicComponents.this, "SchemaCreated", name, parameters); - } - }); + dispatchEvent("SchemaCreated", name, parameters); } @SimpleFunction(description = "Assign a new ID to a previously created dynamic component.") public void ChangeId(String id, String newId) { - if (UTIL_INSTANCE.exists(id) && !UTIL_INSTANCE.exists(newId)) { - for (String mId : UsedIDs().toStringArray()) { + if (checkBeforeReplacement(id, newId)) { + for (String mId : COMPONENTS.keySet()) { if (mId.contains(id)) { - Component mComponent = (Component) GetComponent(mId); + Component mComponent = COMPONENTS.get(mId); String mReplacementId = mId.replace(id, newId); COMPONENT_IDS.remove(mComponent); COMPONENTS.put(mReplacementId, COMPONENTS.remove(mId)); COMPONENT_IDS.put(mComponent, mReplacementId); } } - } else { - throw new YailRuntimeError("The ID you used is either not a dynamic component, or the ID you've used to replace the old ID is already taken.", "DynamicComponents"); } } - @SimpleFunction(description = "Creates a new dynamic component.") + @SimpleFunction(description = "Replace an existing ID with a new one.") + public void ReplaceId(String id, String newId) { + if (checkBeforeReplacement(id, newId)) { + final Component component = COMPONENTS.get(id); + COMPONENTS.remove(id); + COMPONENT_IDS.remove(component); + COMPONENTS.put(newId, component); + COMPONENT_IDS.put(component, newId); + } + } + + private boolean checkBeforeReplacement(String id, String newId) { + if (isCreatedComponent(id) && !isCreatedComponent(newId)) { + return true; + } + throw new YailRuntimeError( + "The ID you used is either not a dynamic component, or the ID you've used " + + "to replace the old ID is already taken.", TAG + ); + } + + @SimpleFunction(description = + "Creates a new dynamic component in given container (arrangement/canvas) and assign to an ID to reference " + + "the created component later. The 'ComponentBuilt' event will be invoked when the component has created. " + + "Note that you can't create components in Screen directly, you will need to have an arrangement beforehand " + + "inside a Screen to do that." + ) public void Create(final AndroidViewComponent in, Object componentName, final String id) throws Exception { if (!COMPONENTS.containsKey(id)) { lastUsedId = id; - - String mClassName = UTIL_INSTANCE.getClassName(componentName); - Class mClass = Class.forName(mClassName); + Class mClass = Class.forName(Utils.getClassName(componentName)); final Constructor mConstructor = mClass.getConstructor(ComponentContainer.class); - if (postOnUiThread) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { - UTIL_INSTANCE.newInstance(mConstructor, id, in); + Component mComponent = Utils.createInstance(mConstructor, in); + COMPONENT_IDS.put(mComponent, id); + COMPONENTS.put(id, mComponent); + notifyListenersOfCreation(mComponent, id); + ComponentBuilt(mComponent, id, mComponent.getClass().getSimpleName()); } }); } else { - UTIL_INSTANCE.newInstance(mConstructor, id, in); + Component mComponent = Utils.createInstance(mConstructor, in); + COMPONENT_IDS.put(mComponent, id); + COMPONENTS.put(id, mComponent); + notifyListenersOfCreation(mComponent, id); + ComponentBuilt(mComponent, id, mComponent.getClass().getSimpleName()); } } else { - throw new YailRuntimeError("Expected a unique ID, got '" + id + "'.", "DynamicComponents"); + throw new YailRuntimeError("All component IDs must be unique, the component ID '" + id + "' has already used before.", TAG); } } - @SimpleFunction(description = "Generates a random ID to create a component with.") - public String GenerateID() { - String id = ""; + @SimpleFunction(description = + "Creates a new dynamic component in given container (arrangement/canvas) and return it without saving it to the " + + "created components list, so it won't be attached to an ID. Note that you can't create components " + + "in Screen directly, you will need to have an arrangement beforehand inside a Screen to do that." + ) + public Component CreateEphemeral(final AndroidViewComponent in, Object componentName) throws Exception { + Class mClass = Class.forName(Utils.getClassName(componentName)); + final Constructor mConstructor = mClass.getConstructor(ComponentContainer.class); + return Utils.createInstance(mConstructor, in); + } + @SimpleFunction(description = "Generates a random UUID, can be useful to create components with random ID.") + public String GenerateID() { + String id; do { id = UUID.randomUUID().toString(); - } while (UTIL_INSTANCE.exists(id)); - + } while (isCreatedComponent(id)); return id; } - @SimpleFunction(description = "Returns the component associated with the specified ID.") + @SimpleFunction(description = "Returns the component associated with the specified ID. If not found, returns an empty string.") public Object GetComponent(String id) { - return COMPONENTS.get(id); + Component component = COMPONENTS.get(id); + if (component == null) { + return ""; + } + return component; } @SimpleFunction(description = "Get meta data about the specified component.") public YailDictionary GetComponentMeta(Component component) { - Class mClass = component.getClass(); - DesignerComponent mDesignerAnnotation = mClass.getAnnotation(DesignerComponent.class); - boolean mHasDesigner = !isEmptyOrNull(mDesignerAnnotation); - boolean mHasObject = false; - SimpleObject mObjectAnnotation = mClass.getAnnotation(SimpleObject.class); - YailDictionary mMeta = new YailDictionary(); - mHasObject = !isEmptyOrNull(mObjectAnnotation); - - if (mHasDesigner && mHasObject) { - // Return all metadata - mMeta.put("androidMinSdk", mDesignerAnnotation.androidMinSdk()); - mMeta.put("category", mDesignerAnnotation.category()); - mMeta.put("dateBuilt", mDesignerAnnotation.dateBuilt()); - mMeta.put("description", mDesignerAnnotation.description()); - mMeta.put("designerHelpDescription", mDesignerAnnotation.designerHelpDescription()); - mMeta.put("external", mObjectAnnotation.external()); - mMeta.put("helpUrl", mDesignerAnnotation.helpUrl()); - mMeta.put("iconName", mDesignerAnnotation.iconName()); - mMeta.put("nonVisible", mDesignerAnnotation.nonVisible()); - mMeta.put("package", mClass.getName()); - mMeta.put("showOnPalette", mDesignerAnnotation.showOnPalette()); - mMeta.put("type", mClass.getSimpleName()); - mMeta.put("version", mDesignerAnnotation.version()); - mMeta.put("versionName", mDesignerAnnotation.versionName()); - } else if (!mHasDesigner && mHasObject) { - // Return some amount of metadata even if there is no - // @DesignerComponent annotation provided - mMeta.put("external", mObjectAnnotation.external()); - mMeta.put("package", mClass.getName()); - mMeta.put("type", mClass.getSimpleName()); - } else { - // Return the least amount of metadata if no - // annotation is provided - mMeta.put("package", mClass.getName()); - mMeta.put("type", mClass.getSimpleName()); - } - - return mMeta; + return Metadata.getComponentCommonInfo(component); } @SimpleFunction(description = "Get meta data about events for the specified component.") public YailDictionary GetEventMeta(Component component) { - Method[] mMethods = component.getClass().getMethods(); - YailDictionary mEvents = new YailDictionary(); - - for (Method mMethod : mMethods) { - SimpleEvent mAnnotation = mMethod.getAnnotation(SimpleEvent.class); - boolean mIsDeprecated = !isEmptyOrNull(mMethod.getAnnotation(Deprecated.class)); - String mName = mMethod.getName(); - YailDictionary mEventMeta = new YailDictionary(); - - if (!isEmptyOrNull(mAnnotation)) { - // Return all metadata - mEventMeta.put("description", mAnnotation.description()); - mEventMeta.put("isDeprecated", mIsDeprecated); - mEventMeta.put("userVisible", mAnnotation.userVisible()); - } else { - // Return the least amount of metadata if no - // annotation is provided - mEventMeta.put("isDeprecated", mIsDeprecated); - } - - mEvents.put(mName, mEventMeta); + try { + return Metadata.getComponentAnnotationInfo(component, SimpleEvent.class); + } catch (Exception e) { + String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage(); + throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG); } - - return mEvents; } @SimpleFunction(description = "Get meta data about functions for the specified component.") public YailDictionary GetFunctionMeta(Component component) { - Method[] mMethods = component.getClass().getMethods(); - YailDictionary mFunctions = new YailDictionary(); - - for (Method mMethod : mMethods) { - SimpleFunction mAnnotation = mMethod.getAnnotation(SimpleFunction.class); - boolean mIsDeprecated = !isEmptyOrNull(mMethod.getAnnotation(Deprecated.class)); - String mName = mMethod.getName(); - YailDictionary mFunctionMeta = new YailDictionary(); - - if (!isEmptyOrNull(mAnnotation)) { - // Return all metadata - mFunctionMeta.put("description", mAnnotation.description()); - mFunctionMeta.put("isDeprecated", mIsDeprecated); - mFunctionMeta.put("userVisible", mAnnotation.userVisible()); - } else { - // Return the least amount of metadata if no - // annotation is provided - mFunctionMeta.put("isDeprecated", mIsDeprecated); - } - - mFunctions.put(mName, mFunctionMeta); + try { + return Metadata.getComponentAnnotationInfo(component, SimpleFunction.class); + } catch (Exception e) { + String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage(); + throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG); } - - return mFunctions; } - @SimpleFunction(description = "Returns the ID of the specified component.") + @SimpleFunction(description = "Returns the ID of the specified component. If not found, returns an empty string.") public String GetId(Component component) { - if (!isEmptyOrNull(component) || COMPONENT_IDS.containsKey(component)) { - return COMPONENT_IDS.get(component); - } - - return ""; - } - - @Deprecated - @SimpleFunction(description = "Do NOT use this function. Use 'GetComponentMeta' as a replacement.") - public String GetName(Component component) { - return component.getClass().getName(); + return COMPONENT_IDS.getOrDefault(component, ""); } - @SimpleFunction(description = "Returns the position of the specified component according to its parent view. Index begins at one.") + @SimpleFunction(description = + "Returns the position of the specified component according to its parent component. " + + "Indexes begins at one. If there is no parent (which shouldn't happen, as the top-most parent is Screen) " + + "then return zero (0)." + ) public int GetOrder(AndroidViewComponent component) { - View mComponent = (View) component.getView(); - int mIndex = 0; - ViewGroup mParent = (!isEmptyOrNull(mComponent) ? (ViewGroup) mComponent.getParent() : null); + // (non null) + View mComponent = component.getView(); + ViewGroup mParent = (ViewGroup) mComponent.getParent(); - if (!isEmptyOrNull(mComponent) && !isEmptyOrNull(mParent)) { - mIndex = mParent.indexOfChild(mComponent) + 1; + if (Utils.isNotEmptyOrNull(mComponent) && Utils.isNotEmptyOrNull(mParent)) { + return mParent.indexOfChild(mComponent) + 1; } - - return mIndex; + return 0; } - @SimpleFunction(description = "Get a properties value.") + @SimpleFunction(description = + "Get a property value of a component with given property name. The returned value can be " + + "any type of value, but if the property value is null, this block will return an empty string instead so " + + "it can be manipulated and compared with other App Inventor blocks." + ) public Object GetProperty(Component component, String name) { - return Invoke(component, name, YailList.makeEmptyList()); + Object returnedValue = Utils.callMethod(component, name, new Object[] { }); + return returnedValue == null ? "" : returnedValue; } @SimpleFunction(description = "Get meta data about properties for the specified component, including their values.") public YailDictionary GetPropertyMeta(Component component) { - Method[] mMethods = component.getClass().getMethods(); - YailDictionary mProperties = new YailDictionary(); - - for (Method mMethod : mMethods) { - DesignerProperty mDesignerAnnotation = mMethod.getAnnotation(DesignerProperty.class); - boolean mHasDesigner = !isEmptyOrNull(mDesignerAnnotation); - boolean mHasProperty = false; - SimpleProperty mPropertyAnnotation = mMethod.getAnnotation(SimpleProperty.class); - String mName = mMethod.getName(); - YailDictionary mPropertyMeta = new YailDictionary(); - Object mValue = Invoke(component, mName, new YailList()); - mHasProperty = !isEmptyOrNull(mPropertyAnnotation); - - if (mHasProperty) { - mPropertyMeta.put("description", mPropertyAnnotation.description()); - mPropertyMeta.put("category", mPropertyAnnotation.category()); - - if (mHasDesigner) { - YailDictionary mDesignerMeta = new YailDictionary(); - mDesignerMeta.put("defaultValue", mDesignerAnnotation.defaultValue()); - mDesignerMeta.put("editorArgs", mDesignerAnnotation.editorArgs()); - mDesignerMeta.put("editorType", mDesignerAnnotation.editorType()); - mPropertyMeta.put("designer", mDesignerMeta); - } - - mPropertyMeta.put("isDeprecated", (!isEmptyOrNull(mMethod.getAnnotation(Deprecated.class)))); - mPropertyMeta.put("isDesignerProperty", mHasDesigner); - mPropertyMeta.put("userVisible", mPropertyAnnotation.userVisible()); - mPropertyMeta.put("value", mValue); - mProperties.put(mName, mPropertyMeta); - } + try { + return Metadata.getComponentPropertyInfo(component); + } catch (Exception e) { + String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage(); + throw new YailRuntimeError("Couldn't read the metadata: " + errorMessage, TAG); } - - return mProperties; } - @SimpleFunction(description = "Invokes a method with parameters.") + @SimpleFunction(description = + "Calls any method of a component by its name and given parameters, and returns its result. " + + "The returned value can be any type of value, but if the returned value is null, this block will return " + + "an empty string instead so it can be manipulated and compared with other App Inventor blocks." + ) public Object Invoke(Component component, String name, YailList parameters) { - if (!isEmptyOrNull(component)) { - Object mInvokedMethod = null; - Method[] mMethods = component.getClass().getMethods(); - - try { - Object[] mParameters = parameters.toArray(); - Method mMethod = UTIL_INSTANCE.getMethod(mMethods, name, mParameters.length); - - Class[] mRequestedMethodParameters = mMethod.getParameterTypes(); - ArrayList mParametersArrayList = new ArrayList(); - for (int i = 0; i < mRequestedMethodParameters.length; i++) { - if ("int".equals(mRequestedMethodParameters[i].getName())) { - mParametersArrayList.add(Integer.parseInt(mParameters[i].toString())); - } else if ("float".equals(mRequestedMethodParameters[i].getName())) { - mParametersArrayList.add(Float.parseFloat(mParameters[i].toString())); - } else if ("double".equals(mRequestedMethodParameters[i].getName())) { - mParametersArrayList.add(Double.parseDouble(mParameters[i].toString())); - } else if ("java.lang.String".equals(mRequestedMethodParameters[i].getName())) { - mParametersArrayList.add(mParameters[i].toString()); - } else if ("boolean".equals(mRequestedMethodParameters[i].getName())) { - mParametersArrayList.add(Boolean.parseBoolean(mParameters[i].toString())); - } else { - mParametersArrayList.add(mParameters[i]); - } - } - - mInvokedMethod = mMethod.invoke(component, mParametersArrayList.toArray()); - } catch (Exception e) { - throw new YailRuntimeError(e.getMessage(), "DynamicComponents"); - } finally { - if (!isEmptyOrNull(mInvokedMethod)) { - return mInvokedMethod; - } else { - return ""; - } - } - } else { - throw new YailRuntimeError("Component cannot be null.", "DynamicComponents"); - } + Object returnedValue = Utils.callMethod(component, name, parameters.toArray()); + return returnedValue == null ? "" : returnedValue; } - @SimpleFunction(description = "Returns if the specified component was created by the Dynamic Components extension.") + @SimpleFunction(description = "Returns true if the specified component was created by this extension, otherwise false.") public boolean IsDynamic(Component component) { return COMPONENTS.containsValue(component); } - @SimpleFunction(description = "Returns the last used ID.") + @SimpleFunction(description = "Returns the last used ID to create a component.") public Object LastUsedID() { return lastUsedId; } - @Deprecated - @SimpleFunction(description = "Do NOT use this function. Use 'GetComponentMeta', 'GetEventMeta', 'GetFunctionMeta', and 'GetPropertyMeta' as replacements.") - public String ListDetails(Component component) { - return ""; - } - @SimpleFunction(description = "Moves the specified component to the specified view.") public void Move(AndroidViewComponent arrangement, AndroidViewComponent component) { - View mComponent = (View) component.getView(); - ViewGroup mParent = (!isEmptyOrNull(mComponent) ? (ViewGroup) mComponent.getParent() : null); - - mParent.removeView(mComponent); - + View mComponent = component.getView(); + ((ViewGroup) mComponent.getParent()).removeView(mComponent); ViewGroup mArrangement = (ViewGroup) arrangement.getView(); ViewGroup mTarget = (ViewGroup) mArrangement.getChildAt(0); - mTarget.addView(mComponent); } - @Deprecated - @SimpleFunction(description = "Do NOT use this function. Use 'GenerateID' as a replacement.") - public String RandomUUID() { - return GenerateID(); - } - - @SimpleFunction(description = "Removes the component with the specified ID from the layout/screen so the ID can be reused.") + @SimpleFunction(description = + "Removes a component from the screen with its ID. The ID will also be de-registered, " + + "so its ID can be reused for other components that are going to be created later." + ) public void Remove(String id) { Object component = COMPONENTS.get(id); + if (component == null) { + return; + } + RemoveComponent((AndroidViewComponent)component); + COMPONENTS.remove(id); + COMPONENT_IDS.remove(component); + } - if (!isEmptyOrNull(component)) { - try { - Method mMethod = component.getClass().getMethod("getView"); + @SimpleFunction(description = + "Removes a component from the screen. It doesn't need to be created by this extension. " + + "But if the given component is dynamically created by this extension, this block will also " + + "de-register its ID so its ID can be reused for other components that are going to be created later." + ) + public void RemoveComponent(AndroidViewComponent component) { + try { + Method mMethod = Utils.getMethod(component, "getView"); + if (mMethod != null) { final View mComponent = (View) mMethod.invoke(component); final ViewGroup mParent = (ViewGroup) mComponent.getParent(); - if (postOnUiThread) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override @@ -522,127 +347,129 @@ public void run() { } else { mParent.removeView(mComponent); } - } catch (Exception e) { - e.printStackTrace(); } - - COMPONENTS.remove(id); - COMPONENT_IDS.remove(component); + final String[] closeMethods = new String[] { "onPause", "onDestroy" }; + for (String methodName : closeMethods) { + final Method invokeMethod = Utils.getMethod(component, methodName); + if (invokeMethod != null) + invokeMethod.invoke(component); + } + // Also remove the component from component list if + // it has created by DynamicComponents. + Object storedComponentId = COMPONENT_IDS.get(component); + if (storedComponentId != null) { + COMPONENTS.remove(storedComponentId); + COMPONENT_IDS.remove(component); + } + } catch (Exception e) { + e.printStackTrace(); } } - @SimpleFunction(description = "Sets the order of the specified component according to its parent view. Typing zero will move the component to the end, index begins at one.") + @SimpleFunction(description = + "Sets the order of the specified component according to its parent view. " + + "Indexes begins at one, and setting to zero (0) will move the component to the end." + ) public void SetOrder(AndroidViewComponent component, int index) { - index = index - 1; - View mComponent = (View) component.getView(); + View mComponent = component.getView(); ViewGroup mParent = (ViewGroup) mComponent.getParent(); - mParent.removeView(mComponent); - - int mChildCount = mParent.getChildCount(); - int mIndex = (index > mChildCount ? mChildCount : index); - - mParent.addView(mComponent, mIndex); + mParent.addView(mComponent, Math.min(index - 1, mParent.getChildCount())); } - @SimpleFunction(description = "Set a property of the specified component, including those only available from the Designer.") + @SimpleFunction(description = + "Set a property of the specified component by its name, including properties " + + "those only available from the Designer." + ) public void SetProperty(Component component, String name, Object value) { - Invoke(component, name, YailList.makeList(new Object[] { - value - })); + Utils.callMethod(component, name, new Object[] { value }); } - @SimpleFunction(description = "Set multiple properties of the specified component using a dictionary, including those only available from the Designer.") + @SimpleFunction(description = + "Set multiple properties of the specified component using a dictionary, " + + "including those only available from the Designer." + ) public void SetProperties(Component component, YailDictionary properties) throws Exception { - JSONObject mProperties = new JSONObject(properties.toString()); - JSONArray mPropertyNames = mProperties.names(); - - for (int i = 0; i < mProperties.length(); i++) { - String name = mPropertyNames.getString(i); - Object value = mProperties.get(name); - Invoke(component, name, YailList.makeList(new Object[] { value })); + for (Map.Entry pair : properties.entrySet()) { + Utils.callMethod(component, (String)pair.getKey(), new Object[] { pair.getValue() }); } } - @SimpleFunction(description = "Uses a JSON Object to create dynamic components. Templates can also contain parameters that will be replaced with the values which are defined from the parameters list.") + @SimpleFunction(description = + "Create components in bulk with a JSON template. Templates can also contain parameters " + + "that will be replaced with the values which are defined from the parameters list. See " + + "the documentation for more information about using and creating templates." + ) public void Schema(AndroidViewComponent in, final String template, final YailList parameters) throws Exception { JSONObject mScheme = new JSONObject(template); - String newTemplate = template; - - if (!isEmptyOrNull(template) && mScheme.has("components")) { - propertiesArray = new JSONArray(); - JSONArray mKeys = (mScheme.has("keys") ? mScheme.getJSONArray("keys") : null); + if (!mScheme.optString("metadata-version", "").equals("1")) { + throw new YailRuntimeError("Metadata version ('metadata-version' key in JSON) must equal to 1.", TAG); + } - if (!isEmptyOrNull(mKeys) && mKeys.length() == parameters.length() - 1) { - for (int i = 0; i < mKeys.length(); i++) { - String keyPercent = "%" + mKeys.getString(i); - String keyBracket = "{" + mKeys.getString(i) + "}"; - String value = parameters.getString(i).replace("\"", ""); - newTemplate = newTemplate.replace(keyPercent, value); - newTemplate = newTemplate.replace(keyBracket, value); - } + if (Utils.isNotEmptyOrNull(template) && mScheme.has("components")) { + JSONArray mKeys = (mScheme.has("keys") ? mScheme.getJSONArray("keys") : new JSONArray()); + if (mKeys.length() != (parameters.length() - 1)) { + throw new YailRuntimeError( + "Given list of template parameters must contain same amount of items that defined in the schema. " + + "The template expects: " + mKeys.length() + ", but given parameters are: " + (parameters.length() - 1), TAG + ); } + LinkedList formatMapping = new LinkedList(); + for (int i = 0; i < mKeys.length(); i++) { + formatMapping.addLast(new String[] { mKeys.getString(i), parameters.getString(i) }); + } + LinkedList componentsList = Utils.componentTreeToList(mScheme.getJSONArray("components"), formatMapping); - mScheme = new JSONObject(newTemplate); - UTIL_INSTANCE.parse("", mScheme); - propertiesArray.remove(0); - - for (int i = 0; i < propertiesArray.length(); i++) { - if (!propertiesArray.getJSONObject(i).has("id")) { - throw new YailRuntimeError("One or multiple components do not have a specified ID in the template.", "DynamicComponents"); - } - - final JSONObject mJson = propertiesArray.getJSONObject(i); - final String mId = mJson.getString("id"); - AndroidViewComponent mRoot = (!mJson.has("in") ? in : (AndroidViewComponent) GetComponent(mJson.getString("in"))); - final String mType = mJson.getString("type"); + for (final JSONObject child : componentsList) { + final String mId = child.getString("id"); + AndroidViewComponent mRoot = (!child.has("parent") ? in : (AndroidViewComponent) COMPONENTS.get(child.getString("parent"))); + final String mType = child.getString("type"); ComponentListener listener = new ComponentListener() { @Override public void onCreation(Component component, String id) { - if (id == mId && mJson.has("properties")) { - JSONObject mProperties = mJson.getJSONObject("properties"); - JSONArray keys = mProperties.names(); - - for (int k = 0; k < keys.length(); k++) { - Invoke( - (Component) GetComponent(mId), - keys.getString(k), - YailList.makeList(new Object[] { - mProperties.get(keys.getString(k)) - }) - ); + try { + if (Objects.equals(id, mId)) { + JSONObject mProperties = child.getJSONObject("properties"); + JSONArray keys = mProperties.names(); + if (keys != null) { + for (int k = 0; k < keys.length(); k++) { + Utils.callMethod( + COMPONENTS.get(mId), + keys.getString(k), + new Object[] { mProperties.get(keys.getString(k)) } + ); + } + } + componentListeners.remove(this); } - - componentListeners.remove(this); + } catch (JSONException e) { + e.printStackTrace(); } } }; - componentListeners.add(listener); - Create(mRoot, mType, mId); } - SchemaCreated(mScheme.getString("name"), parameters); + SchemaCreated(mScheme.optString("name", ""), parameters); } else { - throw new YailRuntimeError("The template is empty, or is does not have any components.", "DynamicComponents"); + throw new YailRuntimeError("The template is empty, or is does not have any components.", TAG); } } - @SimpleFunction(description = "Returns all IDs of components created with the Dynamic Components extension.") + @SimpleFunction(description = "Returns all IDs of components created with this extension as a list.") public YailList UsedIDs() { - Set mKeys = COMPONENTS.keySet(); - return YailList.makeList(mKeys); + return YailList.makeList(COMPONENTS.keySet()); } - @SimpleProperty(description = "Returns the version of the Dynamic Components extension.") + @SimpleProperty(description = "Returns the version of this extension.") public int Version() { return DynamicComponents.class.getAnnotation(DesignerComponent.class).version(); } - @SimpleProperty(description = "Returns the version name of the Dynamic Components extension.") + @SimpleProperty(description = "Returns the version name of this extension.") public String VersionName() { return DynamicComponents.class.getAnnotation(DesignerComponent.class).versionName(); } diff --git a/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png b/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png index 1c688f65aadc7730f8e0bc65f85cb7325facbd6f..218fc509a7e5fc0469a54f660fd5439f65a82fb1 100644 GIT binary patch literal 4084 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 literal 2435 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 BnDhVu diff --git a/src/com/yusufcihan/DynamicComponents/classes/Metadata.java b/src/com/yusufcihan/DynamicComponents/classes/Metadata.java new file mode 100644 index 0000000..de33c4f --- /dev/null +++ b/src/com/yusufcihan/DynamicComponents/classes/Metadata.java @@ -0,0 +1,108 @@ +package com.yusufcihan.DynamicComponents.classes; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.google.appinventor.components.annotations.DesignerComponent; +import com.google.appinventor.components.annotations.DesignerProperty; +import com.google.appinventor.components.annotations.SimpleObject; +import com.google.appinventor.components.annotations.SimpleProperty; +import com.google.appinventor.components.runtime.Component; +import com.google.appinventor.components.runtime.util.YailDictionary; + +public class Metadata { + public static YailDictionary getComponentCommonInfo(Component component) { + Class mClass = component.getClass(); + DesignerComponent mDesignerAnnotation = mClass.getAnnotation(DesignerComponent.class); + SimpleObject mObjectAnnotation = mClass.getAnnotation(SimpleObject.class); + YailDictionary mMeta = new YailDictionary(); + + if ((mDesignerAnnotation != null) && (mObjectAnnotation != null)) { + // Return all metadata + mMeta.put("androidMinSdk", mDesignerAnnotation.androidMinSdk()); + mMeta.put("category", mDesignerAnnotation.category()); + mMeta.put("dateBuilt", mDesignerAnnotation.dateBuilt()); + mMeta.put("description", mDesignerAnnotation.description()); + mMeta.put("designerHelpDescription", mDesignerAnnotation.designerHelpDescription()); + mMeta.put("external", mObjectAnnotation.external()); + mMeta.put("helpUrl", mDesignerAnnotation.helpUrl()); + mMeta.put("iconName", mDesignerAnnotation.iconName()); + mMeta.put("nonVisible", mDesignerAnnotation.nonVisible()); + mMeta.put("package", mClass.getName()); + mMeta.put("showOnPalette", mDesignerAnnotation.showOnPalette()); + mMeta.put("type", mClass.getSimpleName()); + mMeta.put("version", mDesignerAnnotation.version()); + mMeta.put("versionName", mDesignerAnnotation.versionName()); + } else if (!(mDesignerAnnotation != null) && (mObjectAnnotation != null)) { + // Return some amount of metadata even if there is no @DesignerComponent annotation. + mMeta.put("external", mObjectAnnotation.external()); + mMeta.put("package", mClass.getName()); + mMeta.put("type", mClass.getSimpleName()); + } else { + // Return the least amount of metadata if no annotation is provided. + mMeta.put("package", mClass.getName()); + mMeta.put("type", mClass.getSimpleName()); + } + + return mMeta; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static YailDictionary getComponentAnnotationInfo(Component component, Class annotationClass) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + YailDictionary dictionaries = new YailDictionary(); + if (component == null) { + return dictionaries; + } + for (Method method : component.getClass().getMethods()) { + YailDictionary dictionary = new YailDictionary(); + Object annotation = method.getAnnotation(annotationClass); + Class annotationC = annotation.getClass(); + final boolean isDeprecated = method.getAnnotation(Deprecated.class) != null; + final String methodName = method.getName(); + + if (annotation != null) { + dictionary.put("description", annotationC.getMethod("description").invoke(annotation)); + dictionary.put("isDeprecated", isDeprecated); + dictionary.put("userVisible", annotationC.getMethod("userVisible").invoke(annotation)); + } + dictionary.put("isDeprecated", isDeprecated); + dictionaries.put(methodName, dictionary); + } + return dictionaries; + } + + public static YailDictionary getComponentPropertyInfo(Component component) + throws IllegalAccessException, InvocationTargetException { + Method[] mMethods = component.getClass().getMethods(); + YailDictionary mProperties = new YailDictionary(); + + for (Method mMethod : mMethods) { + DesignerProperty mDesignerAnnotation = mMethod.getAnnotation(DesignerProperty.class); + SimpleProperty mPropertyAnnotation = mMethod.getAnnotation(SimpleProperty.class); + YailDictionary mPropertyMeta = new YailDictionary(); + Object mValue = mMethod.invoke(component, new Object[] { }); + + if (mPropertyAnnotation != null) { + mPropertyMeta.put("description", mPropertyAnnotation.description()); + mPropertyMeta.put("category", mPropertyAnnotation.category()); + + if (mDesignerAnnotation != null) { + YailDictionary mDesignerMeta = new YailDictionary(); + mDesignerMeta.put("defaultValue", mDesignerAnnotation.defaultValue()); + mDesignerMeta.put("editorArgs", mDesignerAnnotation.editorArgs()); + mDesignerMeta.put("editorType", mDesignerAnnotation.editorType()); + mPropertyMeta.put("designer", mDesignerMeta); + } + + mPropertyMeta.put("isDeprecated", mMethod.getAnnotation(Deprecated.class) != null); + mPropertyMeta.put("isDesignerProperty", mDesignerAnnotation != null); + mPropertyMeta.put("userVisible", mPropertyAnnotation.userVisible()); + mPropertyMeta.put("value", mValue); + mProperties.put(mMethod.getName(), mPropertyMeta); + } + } + + return mProperties; + } +} diff --git a/src/com/yusufcihan/DynamicComponents/classes/Utils.java b/src/com/yusufcihan/DynamicComponents/classes/Utils.java new file mode 100644 index 0000000..6461dfe --- /dev/null +++ b/src/com/yusufcihan/DynamicComponents/classes/Utils.java @@ -0,0 +1,239 @@ +package com.yusufcihan.DynamicComponents.classes; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONObject; + +import com.google.appinventor.components.runtime.AndroidViewComponent; +import com.google.appinventor.components.runtime.Component; +import com.google.appinventor.components.runtime.errors.YailRuntimeError; + +import android.util.Log; + +public class Utils { + // Extension log tag + public static final String TAG = "DynamicComponents"; + // Base package name for components + private static final String BASE = "com.google.appinventor.components.runtime."; + private static final Pattern classNamePattern = Pattern.compile("[^.$@a-zA-Z0-9_]"); + private static final Pattern methodNamePattern = Pattern.compile("[^a-zA-Z0-9]"); + private static final String[] canvasComponents = {"Ball", "ImageSprite", "Sprite"}; + + public static boolean isNotEmptyOrNull(Object item) { + return item instanceof String ? !((String) item).replace(" ", "").isEmpty() : item != null; + } + + /* + Get class name from a component object, component name, + full class name. + + "Button" -> "com.google.appinventor.components.runtime.Button" + (Component block) -> class name of the component + "com.example.MyComponent" -> leave as-is + */ + public static String getClassName(Object componentName) { + String componentNameString = classNamePattern.matcher(componentName.toString()).replaceAll(""); + if (componentName instanceof String && componentNameString.contains(".")) { + return componentNameString; + } else if (componentName instanceof String) { + return BASE + componentNameString; + } else if (componentName instanceof Component) { + Matcher componentNameResolved = classNamePattern.matcher(componentName.getClass().getName()); + return componentNameResolved.replaceAll(""); + } else { + throw new YailRuntimeError("Component is invalid.", TAG); + } + } + + /* + Create a new instance of component constructor and + add it to the given "input" container. + */ + public static Component createInstance(Constructor constructor, AndroidViewComponent input) { + Component createdComponent = null; + try { + createdComponent = (Component) constructor.newInstance(input); + } catch(Exception e) { + String errorMessage = e.getMessage() == null ? "Unknown error" : e.getMessage(); + throw new YailRuntimeError("Couldn't create an instance: " + errorMessage, TAG); + } + // Canvas components needs to be initialized with invoking "Initialize" method. + String createdComponentClassName = createdComponent.getClass().getSimpleName(); + if (Arrays.asList(canvasComponents).contains(createdComponentClassName)) { + callMethod(createdComponent, "Initialize", new Object[] { }); + } + return createdComponent; + } + + /* + Find a method from list of methods by name and parameter count. + Return null if not found. + */ + public static Method getMethod(Object object, String name, int parameterCount) { + String nameString = methodNamePattern.matcher(name).replaceAll(""); + for (Method method : object.getClass().getMethods()) { + int methodParameterCount = method.getParameterTypes().length; + if (method.getName().equals(nameString) && methodParameterCount == parameterCount) { + return method; + } + } + return null; + } + + /* + Get a method of a object by its name. Return null if not found. + */ + public static Method getMethod(Object object, String name) { + try { + return object.getClass().getMethod(name); + } catch (NoSuchMethodException e) { + Log.e(TAG, "[priority=low] Method not found with name: '" + name + "'"); + } + return null; + } + + /* + Invoke a method of an object by its name and get its return value. + */ + public static Object callMethod(Object object, String name, Object[] parameters) { + if (!isNotEmptyOrNull(object)) { + throw new YailRuntimeError("Component cannot be null.", TAG); + } + try { + Method mMethod = getMethod(object, name, parameters.length); + Class[] mRequestedMethodParameters = mMethod.getParameterTypes(); + + for (int i = 0; i < mRequestedMethodParameters.length; i++) { + final String value = String.valueOf(parameters[i]); + + switch (mRequestedMethodParameters[i].getName()) { + case "int": + parameters[i] = Integer.parseInt(value); + break; + case "float": + parameters[i] = Float.parseFloat(value); + break; + case "double": + parameters[i] = Double.parseDouble(value); + break; + case "java.lang.String": + parameters[i] = value; + break; + case "boolean": + parameters[i] = Boolean.parseBoolean(value); + break; + } + } + Object mInvokedMethod = mMethod.invoke(object, parameters); + return mInvokedMethod; + } catch (InvocationTargetException e) { + String errorMessage = e.getCause().getMessage() == null ? e.getCause().toString() : e.getCause().getMessage(); + throw new YailRuntimeError("Got an error inside the invoke: " + errorMessage, TAG); + } catch (Exception e) { + e.printStackTrace(); + String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage(); + throw new YailRuntimeError("Couldn't invoke: " + errorMessage, TAG); + } + } + + /* + Replace template keys in a string for each given template + keys and their corresponding values. + + "Hello, {name}!" -> "Hello, John!" + + We don't need additional formatting options, so we just + simply replace the text here. + */ + public static String formatTemplateString(String text, final Iterable formatMapping) { + String replacedText = text; + for (String[] formatPair : formatMapping) { + final String formatString = "{" + formatPair[0] + "}"; + if (replacedText.contains(formatString)) { + replacedText = replacedText.replace(formatString, formatPair[1]); + } + } + return replacedText; + } + + /* + Recursively traverse the children of a given component and + return a flattened list with "parent" keys appended representing + the ID of the parent component which the current component + will be created in. + */ + public static LinkedList componentDataToList( + String parentId, JSONObject componentData, final Iterable formatMapping + ) { + LinkedList componentsOutput = new LinkedList(); + JSONObject currentComponent = new JSONObject(); + if (!componentData.has("id") || !componentData.has("type")) { + throw new YailRuntimeError("All components in the schema at least must have an 'id' and 'type'.", TAG); + } + currentComponent.put("type", formatTemplateString(componentData.getString("type"), formatMapping)); + currentComponent.put("id", formatTemplateString(componentData.getString("id"), formatMapping)); + if (!parentId.isEmpty()) { + currentComponent.put("parent", parentId); + } + JSONObject currentProperties = new JSONObject(); + if (componentData.has("properties")) { + final JSONObject propertyObject = componentData.getJSONObject("properties"); + final Iterator propertyObjectKeys = propertyObject.keys(); + while (propertyObjectKeys.hasNext()) { + String key = (String)propertyObjectKeys.next(); + Object value = propertyObject.get(key); + currentProperties.put( + formatTemplateString(key, formatMapping), + value instanceof String ? formatTemplateString((String)value, formatMapping) : value + ); + } + } + currentComponent.put("properties", currentProperties); + componentsOutput.addLast(currentComponent); + if (componentData.has("components")) { + final JSONArray childComponents = componentData.getJSONArray("components"); + for (int i = 0; i < childComponents.length(); i++) { + final JSONObject childObject = childComponents.getJSONObject(i); + final LinkedList childTree = componentDataToList( + formatTemplateString(currentComponent.getString("id"), formatMapping), + childObject, formatMapping + ); + for (JSONObject child : childTree) { + componentsOutput.addLast(child); + } + } + } + return componentsOutput; + } + + /* + Recursively traverse children for each component in given list and + return a flattened list in result. + */ + public static LinkedList componentTreeToList( + JSONArray componentList, final Iterable formatMapping + ) { + LinkedList componentsOutput = new LinkedList(); + try { + for (int i = 0; i < componentList.length(); i++) { + LinkedList childTree = componentDataToList("", componentList.getJSONObject(i), formatMapping); + for (JSONObject child : childTree) { + componentsOutput.addLast(child); + } + } + } catch (Exception e) { + e.printStackTrace(); + String errorMessage = e.getMessage() == null ? e.toString() : e.getMessage(); + throw new YailRuntimeError("Couldn't gather components from schema, reason: " + errorMessage, TAG); + } + return componentsOutput; + } +}