diff --git a/TotalCrossSDK/src/main/java/totalcross/json/JSONFactory.java b/TotalCrossSDK/src/main/java/totalcross/json/JSONFactory.java index 3d62f5a914..ca817cb505 100644 --- a/TotalCrossSDK/src/main/java/totalcross/json/JSONFactory.java +++ b/TotalCrossSDK/src/main/java/totalcross/json/JSONFactory.java @@ -10,48 +10,55 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; /** - The JSONFactory class helps converting json objects into Java objects, using reflection. - - Some examples: - -
-    class Car 
-    {
-       private int id;
-       private String description;
-
-       public int getId()
-       {
-          return id;
-       }
-       public void setId(int id)
-       {
-          this.id = id;
-       }
-       public String getDescription()
-       {
-          return description;
-       }
-       public void setDescription(String description)
-       {
-          this.description = description;
-       }
-    }
-    
- - You can retrieve a new Car object using: -
-    Car cc = JSONFactory.parse("{\"carro\":{\"id\":-1,\"descricao\":\"GOL\"}}", Carro.class);
-    
- - You may also retrieve a list or an array. See the JSONSample in the TotalCrossAPI. + * The JSONFactory class helps converting json objects into Java objects, using + * reflection. + * + * Some examples: + * + *
+ * class Car {
+ *   private int id;
+ *   private String description;
+ * 
+ *   public int getId() {
+ *     return id;
+ *   }
+ * 
+ *   public void setId(int id) {
+ *     this.id = id;
+ *   }
+ * 
+ *   public String getDescription() {
+ *     return description;
+ *   }
+ * 
+ *   public void setDescription(String description) {
+ *     this.description = description;
+ *   }
+ * }
+ * 
+ * + * You can retrieve a new Car object using: + * + *
+ * Car cc = JSONFactory.parse("{\"carro\":{\"id\":-1,\"descricao\":\"GOL\"}}", Carro.class);
+ * 
+ * + * You may also retrieve a list or an array. See the JSONSample in the + * TotalCrossAPI. */ public class JSONFactory { - public static List asList(String json, Class classOfT) throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { + private static Map, Map> classes = new HashMap<>(); + + public static List asList(String json, Class classOfT) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { List list = new ArrayList(); try { JSONArray jsonArray = new JSONArray(json); @@ -88,13 +95,15 @@ public static T parse(String json, Class classOfT) throws InstantiationEx return parse(new JSONObject(json), classOfT); } - public static T parse(JSONArray jsonArray, Class classOfT) throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { - return parse(null, jsonArray, classOfT); + public static T parse(JSONArray jsonArray, Class classOfT) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { + return parse(null, jsonArray, classOfT); } - - private static T parse(Object outerObject, JSONArray jsonArray, Class classOfT) throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { + + private static T parse(Object outerObject, JSONArray jsonArray, Class classOfT) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + JSONException, ArrayIndexOutOfBoundsException, NoSuchMethodException, SecurityException { if (classOfT.isArray()) { T array; try { @@ -110,95 +119,112 @@ private static T parse(Object outerObject, JSONArray jsonArray, Class cla } return parse(outerObject, jsonArray, classOfT); } - - public static T parse(JSONObject jsonObject, Class classOfT) throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, NoSuchMethodException, SecurityException { - return parse(null, jsonObject, classOfT); + + public static T parse(JSONObject jsonObject, Class classOfT) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + JSONException, NoSuchMethodException, SecurityException { + return parse(null, jsonObject, classOfT); } - private static T parse(Object outerObject, JSONObject jsonObject, Class classOfT) throws InstantiationException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, JSONException, NoSuchMethodException, SecurityException { + private static T parse(Object outerObject, JSONObject jsonObject, Class classOfT) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + JSONException, NoSuchMethodException, SecurityException { if (classOfT.isArray()) { throw new IllegalArgumentException(); } T object = null; try { - object = classOfT.newInstance(); + object = classOfT.newInstance(); } catch (InstantiationException e) { - if (outerObject != null && classOfT.getName().indexOf(outerObject.getClass().getName()) != -1) { - Constructor constructorOfT = classOfT.getDeclaredConstructor(outerObject.getClass()); - if (constructorOfT != null) { - object = constructorOfT.newInstance(outerObject); - } + if (outerObject != null && classOfT.getName().indexOf(outerObject.getClass().getName()) != -1) { + Constructor constructorOfT = classOfT.getDeclaredConstructor(outerObject.getClass()); + if (constructorOfT != null) { + object = constructorOfT.newInstance(outerObject); } - if (object == null) { - throw e; + } + if (object == null) { + throw e; + } + } + + Map methodCache = classes.get(classOfT); + if (methodCache == null) { + methodCache = mapClassMethodsToJsonNames(classOfT); + classes.put(classOfT, methodCache); + } + + Iterator keyIterator = jsonObject.keys(); + while (keyIterator.hasNext()) { + final String name = keyIterator.next(); + Method method = methodCache.get(name); + if (method == null || jsonObject.isNull(name)) { + continue; + } + + Class parameterType = method.getParameterTypes()[0]; + if (parameterType.isPrimitive()) { + if (parameterType.isAssignableFrom(boolean.class)) { + method.invoke(object, jsonObject.getBoolean(name)); + } else if (parameterType.isAssignableFrom(int.class)) { + method.invoke(object, jsonObject.getInt(name)); + } else if (parameterType.isAssignableFrom(long.class)) { + method.invoke(object, jsonObject.getLong(name)); + } else if (parameterType.isAssignableFrom(double.class)) { + method.invoke(object, jsonObject.getDouble(name)); } + } else if (parameterType.isAssignableFrom(String.class)) { + method.invoke(object, jsonObject.getString(name)); + } else if (parameterType.isAssignableFrom(Double.class)) { + method.invoke(object, jsonObject.getDouble(name)); + } else if (parameterType.isAssignableFrom(Integer.class)) { + method.invoke(object, jsonObject.getInt(name)); + } else if (parameterType.isAssignableFrom(Long.class)) { + method.invoke(object, jsonObject.getLong(name)); + } else if (parameterType.isAssignableFrom(Boolean.class)) { + method.invoke(object, jsonObject.getBoolean(name)); + } else if (parameterType.isArray()) { + method.invoke(object, parse(object, jsonObject.getJSONArray(name), parameterType)); + } else { + method.invoke(object, parse(object, jsonObject.getJSONObject(name), parameterType)); + } } + return object; + } + + private static Map mapClassMethodsToJsonNames(Class classOfT) { + final Map methodCache = new HashMap<>(); Method[] methods = classOfT.getMethods(); for (Method method : methods) { String methodName = method.getName(); Class[] paramTypes = method.getParameterTypes(); if (paramTypes != null && paramTypes.length == 1 && methodName.length() > 3 && methodName.startsWith("set")) { final String originalName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); - String name = null; - // look for the field name in the json based on the method name - if (jsonObject.has(originalName)) { - name = originalName; - } else if (!jsonObject.has(name = originalName.toLowerCase())) { - // not found as-is or lowercased? try replacing camel case with underscore - /* - * originally done using regex, but totalcross implementation has some bugs and - * until they are fixed this is done looping through the characters - * originalName.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(); - */ - StringBuilder sb = new StringBuilder(); - boolean lastWasUnderscored = false; - for(int i = 0 ; i < originalName.length() ; i++) { - char c = originalName.charAt(i); - if (!lastWasUnderscored && Character.isUpperCase(c)) { - lastWasUnderscored = true; - sb.append('_'); - } else { - lastWasUnderscored = false; - } - sb.append(Character.toLowerCase(c)); - } - final String underscoredName = sb.toString(); - if (jsonObject.has(underscoredName)) { - name = underscoredName; - } - } - if (!jsonObject.isNull(name)) { - Class parameterType = method.getParameterTypes()[0]; - if (parameterType.isPrimitive()) { - if (parameterType.isAssignableFrom(boolean.class)) { - method.invoke(object, jsonObject.getBoolean(name)); - } else if (parameterType.isAssignableFrom(int.class)) { - method.invoke(object, jsonObject.getInt(name)); - } else if (parameterType.isAssignableFrom(long.class)) { - method.invoke(object, jsonObject.getLong(name)); - } else if (parameterType.isAssignableFrom(double.class)) { - method.invoke(object, jsonObject.getDouble(name)); - } - } else if (parameterType.isAssignableFrom(String.class)) { - method.invoke(object, jsonObject.getString(name)); - } else if (parameterType.isAssignableFrom(Double.class)) { - method.invoke(object, jsonObject.getDouble(name)); - } else if (parameterType.isAssignableFrom(Integer.class)) { - method.invoke(object, jsonObject.getInt(name)); - } else if (parameterType.isAssignableFrom(Long.class)) { - method.invoke(object, jsonObject.getLong(name)); - } else if (parameterType.isAssignableFrom(Boolean.class)) { - method.invoke(object, jsonObject.getBoolean(name)); - } else if (parameterType.isArray()) { - method.invoke(object, parse(object, jsonObject.getJSONArray(name), parameterType)); + // name as-is + methodCache.put(originalName, method); + // lowercased name + methodCache.put(originalName.toLowerCase(), method); + // not found as-is or lowercased? try replacing camel case with underscore + /* + * originally done using regex, but totalcross implementation has some bugs and + * until they are fixed this is done looping through the characters + * originalName.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(); + */ + StringBuilder sb = new StringBuilder(); + boolean lastWasUnderscored = false; + for (int i = 0; i < originalName.length(); i++) { + char c = originalName.charAt(i); + if (!lastWasUnderscored && Character.isUpperCase(c)) { + lastWasUnderscored = true; + sb.append('_'); } else { - method.invoke(object, parse(object, jsonObject.getJSONObject(name), parameterType)); + lastWasUnderscored = false; } + sb.append(Character.toLowerCase(c)); } + final String underscoredName = sb.toString(); + methodCache.put(underscoredName, method); } } - return object; + return methodCache; } } diff --git a/TotalCrossSDK/src/main/java/totalcross/lang/Class4D.java b/TotalCrossSDK/src/main/java/totalcross/lang/Class4D.java index 99f547e314..34b1528b64 100644 --- a/TotalCrossSDK/src/main/java/totalcross/lang/Class4D.java +++ b/TotalCrossSDK/src/main/java/totalcross/lang/Class4D.java @@ -32,6 +32,7 @@ public final class Class4D { Object nativeStruct; // TClass String targetName; // java.lang.String String ncached, cached; + Method[] methods; /** The TotalCross deployer can find classes that are instantiated using Class.forName if, and only if, they are * String constants. If you build the className dynamically, then you must include the file passing it to the tc.Deploy diff --git a/TotalCrossVM/src/nm/instancefields.h b/TotalCrossVM/src/nm/instancefields.h index 39bfdb8b98..1f5a8641df 100644 --- a/TotalCrossVM/src/nm/instancefields.h +++ b/TotalCrossVM/src/nm/instancefields.h @@ -47,10 +47,6 @@ #define StringBuffer_charsStart(o) ((JCharP)(ARRAYOBJ_START(StringBuffer_chars(o)))) #define StringBuffer_count(o) FIELD_I32(o, 0) -// java.lang.Class -#define Class_nativeStruct(o) FIELD_OBJ(o, OBJ_CLASS(o), 0) -#define Class_targetName(o) FIELD_OBJ(o, OBJ_CLASS(o), 1) - // java.lang.reflect.Field #define Field_index(o) FIELD_I32(o, 0) #define Field_mod(o) FIELD_I32(o, 1) diff --git a/TotalCrossVM/src/nm/lang/Class.c b/TotalCrossVM/src/nm/lang/Class.c index 57115fc378..3cda9cfecb 100644 --- a/TotalCrossVM/src/nm/lang/Class.c +++ b/TotalCrossVM/src/nm/lang/Class.c @@ -5,7 +5,7 @@ -#include "tcvm.h" +#include "Class.h" TC_API void jlC_forName_s(NMParams p); @@ -129,20 +129,23 @@ static void createMethodObject(Context currentContext, Method m, TCClass declari setObjectLock(Method_name(*ret) = createStringObjectFromCharP(currentContext,isConstructor ? declaringClass->name : m->name,-1),UNLOCKED); createClassObject(currentContext, declaringClass->name, Type_Null, &Method_declaringClass(*ret),null); // parameters and exceptions - Method_parameterTypes(*ret) = createArrayObject(currentContext, "[java.lang.Class", n = m->paramCount); - if (Method_parameterTypes(*ret) && n > 0) + ptrObj = createArrayObject(currentContext, "[java.lang.Class", n = m->paramCount); + if (ptrObj && n > 0) { - TCObject* oa = (TCObject*)ARRAYOBJ_START(Method_parameterTypes(*ret)); + TCObject* oa = (TCObject*)ARRAYOBJ_START(ptrObj); for (i=0; i < n; i++) createClassObject(currentContext, declaringClass->cp->cls[m->cpParams[i]], m->cpParams[i] < Type_Object ? m->cpParams[i] : Type_Null, oa++, null); } - Method_exceptionTypes(*ret) = createArrayObject(currentContext, "[java.lang.Class", n = 0); // thrown exceptions is not stored in TCClass! - if (Method_exceptionTypes(*ret) && n > 0) + setObjectLock(Method_parameterTypes(*ret) = ptrObj, UNLOCKED); + + ptrObj = createArrayObject(currentContext, "[java.lang.Class", n = 0); // thrown exceptions is not stored in TCClass! + if (ptrObj && n > 0) { - TCObject* oa = (TCObject*)ARRAYOBJ_START(Method_exceptionTypes(*ret)); + TCObject* oa = (TCObject*)ARRAYOBJ_START(ptrObj); for (i=0; i < n; i++) createClassObject(currentContext, m->exceptionHandlers[i].className, Type_Null, oa++, null); } + setObjectLock(Method_exceptionTypes(*ret) = ptrObj, UNLOCKED); // return and type if (!isConstructor) @@ -566,7 +569,27 @@ TC_API void jlC_getFields(NMParams p) // java/lang/Class public native java.lang ////////////////////////////////////////////////////////////////////////// TC_API void jlC_getMethods(NMParams p) // java/lang/Class public native java.lang.reflect.Method[] getMethods() throws SecurityException; { - getMCarray(p,false,true); + TCObject this_ = p->obj[0]; + TCObject methods = Class_methods(this_); + TCObject ret; + + if (methods == null) + { + getMCarray(p,false,true); + Class_methods(this_) = methods = p->retO; + } + + ret = createArrayObject(p->currentContext, "[java.lang.reflect.Method", ARRAYOBJ_LEN(methods)); + if (ret) + { + int32 length = ARRAYOBJ_LEN(methods); + TCObjectArray psrc = (TCObjectArray) ARRAYOBJ_START(methods); + TCObjectArray pdst = (TCObjectArray) ARRAYOBJ_START(ret); + + while (length-- >= 0) + *pdst++ = *psrc++; + } + setObjectLock(p->retO = ret, UNLOCKED); } ////////////////////////////////////////////////////////////////////////// TC_API void jlC_getConstructors(NMParams p) // java/lang/Class public native java.lang.reflect.Constructor[] getConstructors() throws SecurityException; diff --git a/TotalCrossVM/src/nm/lang/Class.h b/TotalCrossVM/src/nm/lang/Class.h new file mode 100644 index 0000000000..c0c39c5df5 --- /dev/null +++ b/TotalCrossVM/src/nm/lang/Class.h @@ -0,0 +1,15 @@ +// Copyright (C) 2021 TotalCross Global Mobile Platform Ltda. +// +// SPDX-License-Identifier: LGPL-2.1-only + +#ifndef Class_h +#define Class_h + +#include "tcvm.h" + +// java.lang.Class +#define Class_nativeStruct(o) FIELD_OBJ(o, OBJ_CLASS(o), 0) +#define Class_targetName(o) FIELD_OBJ(o, OBJ_CLASS(o), 1) +#define Class_methods(o) FIELD_OBJ(o, OBJ_CLASS(o), 4) + +#endif diff --git a/TotalCrossVM/src/nm/lang/Reflection.c b/TotalCrossVM/src/nm/lang/Reflection.c index 8786fad842..e85798f459 100644 --- a/TotalCrossVM/src/nm/lang/Reflection.c +++ b/TotalCrossVM/src/nm/lang/Reflection.c @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: LGPL-2.1-only -#include "tcvm.h" +#include "Class.h" typedef char NameBuf[256]; diff --git a/TotalCrossVM/src/tcvm/objectmemorymanager.c b/TotalCrossVM/src/tcvm/objectmemorymanager.c index a92c3e5bcc..5ce9ffdaeb 100644 --- a/TotalCrossVM/src/tcvm/objectmemorymanager.c +++ b/TotalCrossVM/src/tcvm/objectmemorymanager.c @@ -501,7 +501,7 @@ static TCObject privateCreateObject(Context currentContext, CharP className, boo if (IS_VMTWEAK_ON(VMTWEAK_TRACE_CREATED_CLASSOBJS)) { if (!htObjsPerClass.items) htObjsPerClass = htNew(511, null); - htInc(&htObjsPerClass, (int32)c, 1); + htInc(&htObjsPerClass, (size_t)c, 1); } if (_TRACE_OBJCREATION) debug("G %X obj created %s of size %d at %d. lock: %d. mark: %d. context: %X", o, className, objectSize, size2idx(objectSize), OBJ_ISLOCKED(o), markedAsUsed, currentContext); @@ -547,7 +547,7 @@ TCObject createArrayObject(Context currentContext, CharP type, int32 len) if (IS_VMTWEAK_ON(VMTWEAK_TRACE_CREATED_CLASSOBJS)) { if (!htObjsPerClass.items) htObjsPerClass = htNew(511, null); - htInc(&htObjsPerClass, (int32)c, 1); + htInc(&htObjsPerClass, (size_t)c, 1); } if (_TRACE_OBJCREATION) debug("G %X array obj created %s len %d, size = %d at %d. lock: %d", o, c->name,len, objectSize, size2idx(objectSize), OBJ_ISLOCKED(o)); end: @@ -785,7 +785,7 @@ static int32 countObjectsInList(TCObject o, bool dump, int32 mark, int32* size, { ObjectProperties op = OBJ_PROPERTIES(o); if (htOut) - htInc(htOut, (int)OBJ_CLASS(o),1); + htInc(htOut, (size_t)OBJ_CLASS(o),1); if (size) *size += op->size; if (_TRACE_OBJCREATION && dump) debug("G %X",o); @@ -928,12 +928,12 @@ static void finalizeObject(TCObject o, TCClass c) { MUTEX_TYPE* mutex; - mutex = htGetPtr(&htMutexes, (int32)o); + mutex = htGetPtr(&htMutexes, (size_t)o); if (mutex) { DESTROY_MUTEX_VAR(*mutex); xfree(mutex); - htRemove(&htMutexes, (int32)o); + htRemove(&htMutexes, (size_t)o); } if (c->finalizeMethod == null) @@ -1150,7 +1150,7 @@ void gc2(Context currentContext, bool lockOMM) { if (strEq(OBJ_CLASS(o)->name,BYTE_ARRAY)) debug("locked ba: %X",o); - htInc(&htCount, (int32)OBJ_CLASS(o), 1); + htInc(&htCount, (size_t)OBJ_CLASS(o), 1); lockCount++; } //if (_TRACE_OBJCREATION) debug("G marking locked obj %X",o); @@ -1189,7 +1189,7 @@ void gc2(Context currentContext, bool lockOMM) if ((c = OBJ_CLASS(o)) != null) { if (_TRACE_OBJCREATION) debug("G object being freed: %X (%s)",o, OBJ_CLASS(o)->name); - if (traceCreatedClassObjs) htInc(&htObjsPerClass, (int32)OBJ_CLASS(o),-1); + if (traceCreatedClassObjs) htInc(&htObjsPerClass, (size_t)OBJ_CLASS(o),-1); OBJ_CLASS(o) = null; // set the object "free" } diff --git a/TotalCrossVM/src/tcvm/tcmethod.c b/TotalCrossVM/src/tcvm/tcmethod.c index 445fd96be5..b141c20cb0 100644 --- a/TotalCrossVM/src/tcvm/tcmethod.c +++ b/TotalCrossVM/src/tcvm/tcmethod.c @@ -8,7 +8,6 @@ TC_API Method getMethod(TCClass c, bool searchSuperclasses, CharP methodName, int32 nparams, ...) // not used internally { int32 i,j; - va_list params; if (c) do { @@ -20,18 +19,22 @@ TC_API Method getMethod(TCClass c, bool searchSuperclasses, CharP methodName, in if (strEq(methodName,mn) && nparams == mm->paramCount) { bool found = true; - va_start(params, nparams); - for (j = 0; j < nparams; j++) // do NOT invert the loop! + if (nparams > 0) { - CharP pt = (CharP)(va_arg(params, CharP)); - CharP po = c->cp->cls[mm->cpParams[j]]; - if (!strEq(pt,po)) + va_list params; + va_start(params, nparams); + for (j = 0; j < nparams; j++) // do NOT invert the loop! { - found = false; - break; + CharP pt = (CharP)(va_arg(params, CharP)); + CharP po = c->cp->cls[mm->cpParams[j]]; + if (!strEq(pt,po)) + { + found = false; + break; + } } + va_end(params); } - va_end(params); if (found && (mm->code || mm->flags.isNative)) // not an abstract class? return mm; } diff --git a/TotalCrossVM/src/tcvm/tcvm.c b/TotalCrossVM/src/tcvm/tcvm.c index f4cb275976..5df418fc86 100644 --- a/TotalCrossVM/src/tcvm/tcvm.c +++ b/TotalCrossVM/src/tcvm/tcvm.c @@ -490,6 +490,7 @@ TC_API TValue executeMethod(Context context, Method method, ...) goto throwNullPointerException; } thisClass = OBJ_CLASS(regO[code->mtd.this_]); +callVirtualClass: if (thisClass == null) { exceptionMsg = "Obj class is null"; @@ -764,6 +765,11 @@ TC_API TValue executeMethod(Context context, Method method, ...) if (c == null) goto throwClassNotFoundException; } + if (thisClass != c) + { + thisClass = c; + goto callVirtualClass; + } } else if (className == class_->name || strEq(className, class_->name)) // calling a method inside this class? (first comparison is always true, but keep 2nd for safety)