1919
2020package com .labymedia .ultralight ;
2121
22+ import com .labymedia .ultralight .cache .JavascriptClassCache ;
2223import com .labymedia .ultralight .call .CallData ;
2324import com .labymedia .ultralight .javascript .*;
2425import com .labymedia .ultralight .javascript .interop .JavascriptInteropException ;
2526import com .labymedia .ultralight .call .MethodChooser ;
26- import com .labymedia .ultralight .javascript .*;
2727import com .labymedia .ultralight .utils .JavascriptConversionUtils ;
2828
29+ import java .lang .invoke .MethodHandles ;
2930import java .lang .reflect .*;
3031import java .util .*;
3132
@@ -51,9 +52,14 @@ public final class DatabindJavascriptClass {
5152 * @param className The name of the Javascript class
5253 */
5354 private DatabindJavascriptClass (
54- DatabindConfiguration configuration , JavascriptConversionUtils conversionUtils , String className ) {
55+ DatabindConfiguration configuration ,
56+ JavascriptConversionUtils conversionUtils ,
57+ String className ,
58+ JavascriptClass parentClass
59+ ) {
5560 this .definition = new JavascriptClassDefinition ()
5661 .name (className )
62+ .parentClass (parentClass )
5763 .attributes (JavascriptClassAttributes .NO_AUTOMATIC_PROTOTYPE );
5864
5965 this .configuration = configuration ;
@@ -76,16 +82,16 @@ private void registerCallbacks() {
7682 *
7783 * @param constructors The constructors to index
7884 */
79- private void addConstructors (Constructor <?>... constructors ) {
80- this .constructors .addAll (Arrays . asList ( constructors ) );
85+ private void addConstructors (Collection < Constructor <?>> constructors ) {
86+ this .constructors .addAll (constructors );
8187 }
8288
8389 /**
8490 * Indexes methods into this class.
8591 *
8692 * @param methods The methods to index
8793 */
88- private void addMethods (Method ... methods ) {
94+ private void addMethods (Collection < Method > methods ) {
8995 for (Method method : methods ) {
9096 String name = method .getName ();
9197
@@ -108,7 +114,7 @@ private void addMethods(Method... methods) {
108114 *
109115 * @param fields The fields to index
110116 */
111- private void addFields (Field ... fields ) {
117+ private void addFields (Collection < Field > fields ) {
112118 for (Field field : fields ) {
113119 this .fields .put (field .getName (), field );
114120 }
@@ -146,20 +152,20 @@ private JavascriptObject onCallAsConstructor(
146152 try {
147153 // Invoke constructor with constructed arguments
148154 return context .makeObject (bake (), new Data (method .newInstance (parameters .toArray ()), null ));
149- } catch (IllegalAccessException exception ) {
155+ } catch (IllegalAccessException exception ) {
150156 throw new JavascriptInteropException ("Unable to access constructor: " + method .getName (), exception );
151- } catch (InvocationTargetException exception ) {
157+ } catch (InvocationTargetException exception ) {
152158 throw new JavascriptInteropException ("Constructor threw an exception" , exception );
153- } catch (InstantiationException exception ) {
159+ } catch (InstantiationException exception ) {
154160 throw new JavascriptInteropException ("Unable to create instance" , exception );
155161 }
156162 }
157163
158164 /**
159165 * Determines whether a property exists.
160166 *
161- * @param context The context the check is executed in
162- * @param object The object to check for the property on
167+ * @param context The context the check is executed in
168+ * @param object The object to check for the property on
163169 * @param propertyName The name of the property to check for
164170 * @return {@code true} if the property could be found, {@code false} otherwise
165171 */
@@ -168,23 +174,23 @@ private boolean onHasProperty(JavascriptContext context, JavascriptObject object
168174 boolean instanceAvailable = ((Data ) object .getPrivate ()).instance != null ;
169175
170176 Field f = fields .get (propertyName );
171- if (f != null && (Modifier .isStatic (f .getModifiers ()) || instanceAvailable )) {
177+ if (f != null && (Modifier .isStatic (f .getModifiers ()) || instanceAvailable )) {
172178 // Field found and usable
173179 return true ;
174180 }
175181
176182 Set <Method > methodsWithName = methods .get (propertyName );
177- if (methodsWithName == null || methodsWithName .isEmpty ()) {
183+ if (methodsWithName == null || methodsWithName .isEmpty ()) {
178184 // No methods available with that name
179185 return false ;
180- } else if (instanceAvailable ) {
186+ } else if (instanceAvailable ) {
181187 // There is an instance and methods with this name are available
182188 return true ;
183189 }
184190
185191 // There are methods with this name available, check if any of them is static
186- for (Method method : methodsWithName ) {
187- if (Modifier .isStatic (method .getModifiers ())) {
192+ for (Method method : methodsWithName ) {
193+ if (Modifier .isStatic (method .getModifiers ())) {
188194 // Found a static variant
189195 return true ;
190196 }
@@ -211,15 +217,15 @@ private JavascriptValue onGetProperty(
211217 if (field != null ) {
212218 try {
213219 return conversionUtils .toJavascript (context , field .get (privateData .instance ), field .getType ());
214- } catch (IllegalAccessException exception ) {
220+ } catch (IllegalAccessException exception ) {
215221 throw new JavascriptInteropException ("Unable to access field: " + field .getName (), exception );
216222 }
217223 }
218224
219225 Set <Method > methodSet = methods .get (propertyName );
220226 if (methodSet == null ) {
221- // Property does not exist
222- return context . makeUndefined () ;
227+ // Property does not exist, delegate to parent
228+ return null ;
223229 }
224230
225231 return context .makeObject (
@@ -253,8 +259,10 @@ private boolean onSetProperty(
253259 if (field != null ) {
254260 try {
255261 field .set (privateData .instance , conversionUtils .fromJavascript (value , field .getType ()));
256- } catch (IllegalAccessException exception ) {
257- throw new JavascriptInteropException ("Unable to access field: " + field .getName (), exception );
262+ return true ;
263+ } catch (IllegalAccessException exception ) {
264+ throw new JavascriptInteropException ("Unable to access field: " + field .getName () +
265+ " (" + exception .getMessage () + ")" , exception );
258266 }
259267 }
260268
@@ -280,30 +288,110 @@ public JavascriptClass bake() {
280288 * @param configuration The configuration to use
281289 * @param conversionUtils The conversion utilities to use for converting objects
282290 * @param javaClass The java class to create a binding for
291+ * @param classCache The class cache to retrieve or cache parent classes with
283292 * @return The created binding
284293 */
285294 static DatabindJavascriptClass create (
286- DatabindConfiguration configuration , JavascriptConversionUtils conversionUtils , Class <?> javaClass ) {
295+ DatabindConfiguration configuration ,
296+ JavascriptConversionUtils conversionUtils ,
297+ Class <?> javaClass ,
298+ JavascriptClassCache classCache
299+ ) {
300+ Class <?> superClass = javaClass .getSuperclass ();
301+
302+ JavascriptClass parentClass = null ;
303+ if (superClass != null && javaClass != Object .class ) {
304+ String parentClassName = superClass .getName ();
305+
306+ if (!classCache .contains (parentClassName )) {
307+ DatabindJavascriptClass databindParent = create (
308+ configuration ,
309+ conversionUtils ,
310+ superClass ,
311+ classCache
312+ );
313+
314+ parentClass = classCache .put (parentClassName , databindParent .bake ());
315+ } else {
316+ parentClass = classCache .get (parentClassName );
317+ }
318+ }
319+
287320 DatabindJavascriptClass javascriptClass = new DatabindJavascriptClass (
288- configuration , conversionUtils , javaClass .getName ());
321+ configuration , conversionUtils , javaClass .getName (), parentClass );
289322
290323 javascriptClass .registerCallbacks ();
291- javascriptClass .addConstructors (javaClass .getDeclaredConstructors ());
292- javascriptClass .addMethods (javaClass .getDeclaredMethods ());
293- javascriptClass .addFields (javaClass .getDeclaredFields ());
324+
325+ javascriptClass .addConstructors (filterAccessible (javaClass .getConstructors (), javaClass ));
326+ javascriptClass .addMethods (filterAccessible (javaClass .getMethods (), javaClass ));
327+ javascriptClass .addFields (filterAccessible (javaClass .getFields (), javaClass ));
294328
295329 // Iteratively scan all interfaces
296330 Queue <Class <?>> toAdd = new LinkedList <>(Arrays .asList (javaClass .getInterfaces ()));
297331 while (!toAdd .isEmpty ()) {
298332 Class <?> iface = toAdd .remove ();
299333 toAdd .addAll (Arrays .asList (iface .getInterfaces ()));
300334
301- javascriptClass .addMethods (iface .getMethods ());
335+ javascriptClass .addMethods (filterAccessible (iface .getMethods (), iface ));
336+ javascriptClass .addFields (filterAccessible (iface .getFields (), iface ));
302337 }
303338
304339 return javascriptClass ;
305340 }
306341
342+ /**
343+ * Filters an array of reflection objects by their accessibility.
344+ *
345+ * @param objects The objects to filter
346+ * @param owner The wanted owner class
347+ * @param <T> The type of the objects
348+ * @return The filtered objects
349+ */
350+ private static <T extends AccessibleObject & Member > Set <T > filterAccessible (T [] objects , Class <?> owner ) {
351+ Set <T > accessible = new HashSet <>();
352+
353+ for (T object : objects ) {
354+ if (object .getDeclaringClass () != owner || !allPublic (object )) {
355+ continue ;
356+ }
357+
358+ accessible .add (object );
359+ }
360+
361+ return accessible ;
362+ }
363+
364+ /**
365+ * Determines if a members is accessible by checking if everything is public.
366+ *
367+ * @param member The member to check
368+ * @return {@code true} if the member is accessible using a public path, {@code false} otherwise
369+ */
370+ private static boolean allPublic (Member member ) {
371+ Class <?> classToCheck = null ;
372+
373+ do {
374+ if (classToCheck == null ) {
375+ if ((member .getModifiers () & Modifier .PUBLIC ) == 0 ) {
376+ return false ;
377+ }
378+
379+ classToCheck = member .getDeclaringClass ();
380+ } else {
381+ if ((classToCheck .getModifiers () & Modifier .PUBLIC ) == 0 ) {
382+ return false ;
383+ }
384+
385+ classToCheck = classToCheck .getSuperclass ();
386+ if (classToCheck == Object .class ) {
387+ return true ;
388+ }
389+ }
390+ } while (classToCheck != null );
391+
392+ return true ;
393+ }
394+
307395 /**
308396 * Represents the internal state of a Javascript class.
309397 */
0 commit comments