Example #1
0
  private static MemberBox extractSetMethod(Class<?> type, MemberBox[] methods, boolean isStatic) {
    //
    // Note: it may be preferable to allow NativeJavaMethod.findFunction()
    //       to find the appropriate setter; unfortunately, it requires an
    //       instance of the target arg to determine that.
    //

    // Make two passes: one to find a method with direct type assignment,
    // and one to find a widening conversion.
    for (int pass = 1; pass <= 2; ++pass) {
      for (int i = 0; i < methods.length; ++i) {
        MemberBox method = methods[i];
        if (!isStatic || method.isStatic()) {
          Class<?>[] params = method.argTypes;
          if (params.length == 1) {
            if (pass == 1) {
              if (params[0] == type) {
                return method;
              }
            } else {
              if (pass != 2) Kit.codeBug();
              if (params[0].isAssignableFrom(type)) {
                return method;
              }
            }
          }
        }
      }
    }
    return null;
  }
Example #2
0
 /**
  * Generates code to push typed parameters onto the operand stack prior to a direct Java method
  * call.
  */
 private static int generatePushParam(ClassFileWriter cfw, int paramOffset, Class<?> paramType) {
   if (!paramType.isPrimitive()) {
     cfw.addALoad(paramOffset);
     return 1;
   }
   String typeName = paramType.getName();
   switch (typeName.charAt(0)) {
     case 'z':
     case 'b':
     case 'c':
     case 's':
     case 'i':
       // load an int value, convert to double.
       cfw.addILoad(paramOffset);
       return 1;
     case 'l':
       // load a long, convert to double.
       cfw.addLLoad(paramOffset);
       return 2;
     case 'f':
       // load a float, convert to double.
       cfw.addFLoad(paramOffset);
       return 1;
     case 'd':
       cfw.addDLoad(paramOffset);
       return 2;
   }
   throw Kit.codeBug();
 }
  static {
    // Reflection in java is verbose
    Class<?>[] sig2 = new Class[2];
    Class<?> cl = Kit.classOrNull("org.mozilla.javascript.JavaAdapter");
    if (cl != null) {
      try {
        sig2[0] = ScriptRuntime.ObjectClass;
        sig2[1] = Kit.classOrNull("java.io.ObjectOutputStream");
        adapter_writeAdapterObject = cl.getMethod("writeAdapterObject", sig2);

        sig2[0] = ScriptRuntime.ScriptableClass;
        sig2[1] = Kit.classOrNull("java.io.ObjectInputStream");
        adapter_readAdapterObject = cl.getMethod("readAdapterObject", sig2);

      } catch (Exception ex) {
        adapter_writeAdapterObject = null;
        adapter_readAdapterObject = null;
      }
    }
  }
Example #4
0
 private Object buildValue() {
   Class cl = Kit.classOrNull(className);
   if (cl != null) {
     try {
       Object value = ScriptableObject.buildClassCtor(scope, cl, sealed, false);
       if (value == null) {
         // cl has own static initializer which is expected
         // to set the property on its own.
         value = scope.get(propertyName, scope);
         if (value != Scriptable.NOT_FOUND) return value;
       }
     } catch (InvocationTargetException ex) {
       Throwable target = ex.getTargetException();
       if (target instanceof RuntimeException) {
         throw (RuntimeException) target;
       }
     } catch (RhinoException ex) {
     } catch (InstantiationException ex) {
     } catch (IllegalAccessException ex) {
     } catch (SecurityException ex) {
     }
   }
   return Scriptable.NOT_FOUND;
 }
  /**
   * Find the index of the correct function to call given the set of methods or constructors and the
   * arguments. If no function can be found to call, return -1.
   */
  static int findFunction(Context cx, MemberBox[] methodsOrCtors, Object[] args) {
    if (methodsOrCtors.length == 0) {
      return -1;
    } else if (methodsOrCtors.length == 1) {
      MemberBox member = methodsOrCtors[0];
      Class<?>[] argTypes = member.argTypes;
      int alength = argTypes.length;

      if (member.vararg) {
        alength--;
        if (alength > args.length) {
          return -1;
        }
      } else {
        if (alength != args.length) {
          return -1;
        }
      }
      for (int j = 0; j != alength; ++j) {
        if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
          if (debug) printDebug("Rejecting (args can't convert) ", member, args);
          return -1;
        }
      }
      if (debug) printDebug("Found ", member, args);
      return 0;
    }

    int firstBestFit = -1;
    int[] extraBestFits = null;
    int extraBestFitsCount = 0;

    search:
    for (int i = 0; i < methodsOrCtors.length; i++) {
      MemberBox member = methodsOrCtors[i];
      Class<?>[] argTypes = member.argTypes;
      int alength = argTypes.length;
      if (member.vararg) {
        alength--;
        if (alength > args.length) {
          continue search;
        }
      } else {
        if (alength != args.length) {
          continue search;
        }
      }
      for (int j = 0; j < alength; j++) {
        if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
          if (debug) printDebug("Rejecting (args can't convert) ", member, args);
          continue search;
        }
      }
      if (firstBestFit < 0) {
        if (debug) printDebug("Found first applicable ", member, args);
        firstBestFit = i;
      } else {
        // Compare with all currently fit methods.
        // The loop starts from -1 denoting firstBestFit and proceed
        // until extraBestFitsCount to avoid extraBestFits allocation
        // in the most common case of no ambiguity
        int betterCount = 0; // number of times member was prefered over
        // best fits
        int worseCount = 0; // number of times best fits were prefered
        // over member
        for (int j = -1; j != extraBestFitsCount; ++j) {
          int bestFitIndex;
          if (j == -1) {
            bestFitIndex = firstBestFit;
          } else {
            bestFitIndex = extraBestFits[j];
          }
          MemberBox bestFit = methodsOrCtors[bestFitIndex];
          if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
              && (bestFit.member().getModifiers() & Modifier.PUBLIC)
                  != (member.member().getModifiers() & Modifier.PUBLIC)) {
            // When FEATURE_ENHANCED_JAVA_ACCESS gives us access
            // to non-public members, continue to prefer public
            // methods in overloading
            if ((bestFit.member().getModifiers() & Modifier.PUBLIC) == 0) ++betterCount;
            else ++worseCount;
          } else {
            int preference =
                preferSignature(args, argTypes, member.vararg, bestFit.argTypes, bestFit.vararg);
            if (preference == PREFERENCE_AMBIGUOUS) {
              break;
            } else if (preference == PREFERENCE_FIRST_ARG) {
              ++betterCount;
            } else if (preference == PREFERENCE_SECOND_ARG) {
              ++worseCount;
            } else {
              if (preference != PREFERENCE_EQUAL) Kit.codeBug();
              // This should not happen in theory
              // but on some JVMs, Class.getMethods will return all
              // static methods of the class hierarchy, even if
              // a derived class's parameters match exactly.
              // We want to call the derived class's method.
              if (bestFit.isStatic()
                  && bestFit.getDeclaringClass().isAssignableFrom(member.getDeclaringClass())) {
                // On some JVMs, Class.getMethods will return all
                // static methods of the class hierarchy, even if
                // a derived class's parameters match exactly.
                // We want to call the derived class's method.
                if (debug) printDebug("Substituting (overridden static)", member, args);
                if (j == -1) {
                  firstBestFit = i;
                } else {
                  extraBestFits[j] = i;
                }
              } else {
                if (debug) printDebug("Ignoring same signature member ", member, args);
              }
              continue search;
            }
          }
        }
        if (betterCount == 1 + extraBestFitsCount) {
          // member was prefered over all best fits
          if (debug) printDebug("New first applicable ", member, args);
          firstBestFit = i;
          extraBestFitsCount = 0;
        } else if (worseCount == 1 + extraBestFitsCount) {
          // all best fits were prefered over member, ignore it
          if (debug) printDebug("Rejecting (all current bests better) ", member, args);
        } else {
          // some ambiguity was present, add member to best fit set
          if (debug) printDebug("Added to best fit set ", member, args);
          if (extraBestFits == null) {
            // Allocate maximum possible array
            extraBestFits = new int[methodsOrCtors.length - 1];
          }
          extraBestFits[extraBestFitsCount] = i;
          ++extraBestFitsCount;
        }
      }
    }

    if (firstBestFit < 0) {
      // Nothing was found
      return -1;
    } else if (extraBestFitsCount == 0) {
      // single best fit
      return firstBestFit;
    }

    // report remaining ambiguity
    StringBuffer buf = new StringBuffer();
    for (int j = -1; j != extraBestFitsCount; ++j) {
      int bestFitIndex;
      if (j == -1) {
        bestFitIndex = firstBestFit;
      } else {
        bestFitIndex = extraBestFits[j];
      }
      buf.append("\n    ");
      buf.append(methodsOrCtors[bestFitIndex].toJavaDeclaration());
    }

    MemberBox firstFitMember = methodsOrCtors[firstBestFit];
    String memberName = firstFitMember.getName();
    String memberClass = firstFitMember.getDeclaringClass().getName();

    if (methodsOrCtors[0].isMethod()) {
      throw Context.reportRuntimeError3(
          "msg.constructor.ambiguous", memberName, scriptSignature(args), buf.toString());
    } else {
      throw Context.reportRuntimeError4(
          "msg.method.ambiguous", memberClass, memberName, scriptSignature(args), buf.toString());
    }
  }
Example #6
0
  private void reflect(Scriptable scope, boolean includeProtected) {
    // We reflect methods first, because we want overloaded field/method
    // names to be allocated to the NativeJavaMethod before the field
    // gets in the way.

    Method[] methods = discoverAccessibleMethods(cl, includeProtected, includePrivate);
    for (int i = 0; i < methods.length; i++) {
      Method method = methods[i];
      int mods = method.getModifiers();
      boolean isStatic = Modifier.isStatic(mods);
      Map<String, Object> ht = isStatic ? staticMembers : members;
      String name = method.getName();
      Object value = ht.get(name);
      if (value == null) {
        ht.put(name, method);
      } else {
        ObjArray overloadedMethods;
        if (value instanceof ObjArray) {
          overloadedMethods = (ObjArray) value;
        } else {
          if (!(value instanceof Method)) Kit.codeBug();
          // value should be instance of Method as at this stage
          // staticMembers and members can only contain methods
          overloadedMethods = new ObjArray();
          overloadedMethods.add(value);
          ht.put(name, overloadedMethods);
        }
        overloadedMethods.add(method);
      }
    }

    // replace Method instances by wrapped NativeJavaMethod objects
    // first in staticMembers and then in members
    for (int tableCursor = 0; tableCursor != 2; ++tableCursor) {
      boolean isStatic = (tableCursor == 0);
      Map<String, Object> ht = isStatic ? staticMembers : members;
      for (String name : ht.keySet()) {
        MemberBox[] methodBoxes;
        Object value = ht.get(name);
        if (value instanceof Method) {
          methodBoxes = new MemberBox[1];
          methodBoxes[0] = new MemberBox((Method) value);
        } else {
          ObjArray overloadedMethods = (ObjArray) value;
          int N = overloadedMethods.size();
          if (N < 2) Kit.codeBug();
          methodBoxes = new MemberBox[N];
          for (int i = 0; i != N; ++i) {
            Method method = (Method) overloadedMethods.get(i);
            methodBoxes[i] = new MemberBox(method);
          }
        }
        NativeJavaMethod fun = new NativeJavaMethod(methodBoxes);
        if (scope != null) {
          ScriptRuntime.setFunctionProtoAndParent(fun, scope);
        }
        ht.put(name, fun);
      }
    }

    // Reflect fields.
    Field[] fields = getAccessibleFields();
    for (int i = 0; i < fields.length; i++) {
      Field field = fields[i];
      String name = field.getName();
      int mods = field.getModifiers();
      if (!includePrivate && !Modifier.isPublic(mods)) {
        continue;
      }
      try {
        boolean isStatic = Modifier.isStatic(mods);
        Map<String, Object> ht = isStatic ? staticMembers : members;
        Object member = ht.get(name);
        if (member == null) {
          ht.put(name, field);
        } else if (member instanceof NativeJavaMethod) {
          NativeJavaMethod method = (NativeJavaMethod) member;
          FieldAndMethods fam = new FieldAndMethods(scope, method.methods, field);
          Map<String, FieldAndMethods> fmht = isStatic ? staticFieldAndMethods : fieldAndMethods;
          if (fmht == null) {
            fmht = new HashMap<String, FieldAndMethods>();
            if (isStatic) {
              staticFieldAndMethods = fmht;
            } else {
              fieldAndMethods = fmht;
            }
          }
          fmht.put(name, fam);
          ht.put(name, fam);
        } else if (member instanceof Field) {
          Field oldField = (Field) member;
          // If this newly reflected field shadows an inherited field,
          // then replace it. Otherwise, since access to the field
          // would be ambiguous from Java, no field should be
          // reflected.
          // For now, the first field found wins, unless another field
          // explicitly shadows it.
          if (oldField.getDeclaringClass().isAssignableFrom(field.getDeclaringClass())) {
            ht.put(name, field);
          }
        } else {
          // "unknown member type"
          Kit.codeBug();
        }
      } catch (SecurityException e) {
        // skip this field
        Context.reportWarning(
            "Could not access field "
                + name
                + " of class "
                + cl.getName()
                + " due to lack of privileges.");
      }
    }

    // Create bean properties from corresponding get/set methods first for
    // static members and then for instance members
    for (int tableCursor = 0; tableCursor != 2; ++tableCursor) {
      boolean isStatic = (tableCursor == 0);
      Map<String, Object> ht = isStatic ? staticMembers : members;

      Map<String, BeanProperty> toAdd = new HashMap<String, BeanProperty>();

      // Now, For each member, make "bean" properties.
      for (String name : ht.keySet()) {
        // Is this a getter?
        boolean memberIsGetMethod = name.startsWith("get");
        boolean memberIsSetMethod = name.startsWith("set");
        boolean memberIsIsMethod = name.startsWith("is");
        if (memberIsGetMethod || memberIsIsMethod || memberIsSetMethod) {
          // Double check name component.
          String nameComponent = name.substring(memberIsIsMethod ? 2 : 3);
          if (nameComponent.length() == 0) continue;

          // Make the bean property name.
          String beanPropertyName = nameComponent;
          char ch0 = nameComponent.charAt(0);
          if (Character.isUpperCase(ch0)) {
            if (nameComponent.length() == 1) {
              beanPropertyName = nameComponent.toLowerCase();
            } else {
              char ch1 = nameComponent.charAt(1);
              if (!Character.isUpperCase(ch1)) {
                beanPropertyName = Character.toLowerCase(ch0) + nameComponent.substring(1);
              }
            }
          }

          // If we already have a member by this name, don't do this
          // property.
          if (ht.containsKey(beanPropertyName) || toAdd.containsKey(beanPropertyName)) {
            continue;
          }

          // Find the getter method, or if there is none, the is-
          // method.
          MemberBox getter = null;
          getter = findGetter(isStatic, ht, "get", nameComponent);
          // If there was no valid getter, check for an is- method.
          if (getter == null) {
            getter = findGetter(isStatic, ht, "is", nameComponent);
          }

          // setter
          MemberBox setter = null;
          NativeJavaMethod setters = null;
          String setterName = "set".concat(nameComponent);

          if (ht.containsKey(setterName)) {
            // Is this value a method?
            Object member = ht.get(setterName);
            if (member instanceof NativeJavaMethod) {
              NativeJavaMethod njmSet = (NativeJavaMethod) member;
              if (getter != null) {
                // We have a getter. Now, do we have a matching
                // setter?
                Class<?> type = getter.method().getReturnType();
                setter = extractSetMethod(type, njmSet.methods, isStatic);
              } else {
                // No getter, find any set method
                setter = extractSetMethod(njmSet.methods, isStatic);
              }
              if (njmSet.methods.length > 1) {
                setters = njmSet;
              }
            }
          }
          // Make the property.
          BeanProperty bp = new BeanProperty(getter, setter, setters);
          toAdd.put(beanPropertyName, bp);
        }
      }

      // Add the new bean properties.
      for (String key : toAdd.keySet()) {
        Object value = toAdd.get(key);
        ht.put(key, value);
      }
    }

    // Reflect constructors
    Constructor<?>[] constructors = getAccessibleConstructors();
    ctors = new MemberBox[constructors.length];
    for (int i = 0; i != constructors.length; ++i) {
      ctors[i] = new MemberBox(constructors[i]);
    }
  }
  /** Type-munging for field setting and method invocation. Conforms to LC3 specification */
  static Object coerceTypeImpl(Class<?> type, Object value) {
    if (value != null && value.getClass() == type) {
      return value;
    }

    switch (getJSTypeCode(value)) {
      case JSTYPE_NULL:
        // raise error if type.isPrimitive()
        if (type.isPrimitive()) {
          reportConversionError(value, type);
        }
        return null;

      case JSTYPE_UNDEFINED:
        if (type == ScriptRuntime.StringClass || type == ScriptRuntime.ObjectClass) {
          return "undefined";
        } else {
          reportConversionError("undefined", type);
        }
        break;

      case JSTYPE_BOOLEAN:
        // Under LC3, only JS Booleans can be coerced into a Boolean value
        if (type == Boolean.TYPE
            || type == ScriptRuntime.BooleanClass
            || type == ScriptRuntime.ObjectClass) {
          return value;
        } else if (type == ScriptRuntime.StringClass) {
          return value.toString();
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_NUMBER:
        if (type == ScriptRuntime.StringClass) {
          return ScriptRuntime.toString(value);
        } else if (type == ScriptRuntime.ObjectClass) {
          return coerceToNumber(Double.TYPE, value);
        } else if ((type.isPrimitive() && type != Boolean.TYPE)
            || ScriptRuntime.NumberClass.isAssignableFrom(type)) {
          return coerceToNumber(type, value);
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_STRING:
        if (type == ScriptRuntime.StringClass || type.isInstance(value)) {
          return value;
        } else if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {
          // Special case for converting a single char string to a
          // character
          // Placed here because it applies *only* to JS strings,
          // not other JS objects converted to strings
          if (((String) value).length() == 1) {
            return new Character(((String) value).charAt(0));
          } else {
            return coerceToNumber(type, value);
          }
        } else if ((type.isPrimitive() && type != Boolean.TYPE)
            || ScriptRuntime.NumberClass.isAssignableFrom(type)) {
          return coerceToNumber(type, value);
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_JAVA_CLASS:
        if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
        }

        if (type == ScriptRuntime.ClassClass || type == ScriptRuntime.ObjectClass) {
          return value;
        } else if (type == ScriptRuntime.StringClass) {
          return value.toString();
        } else {
          reportConversionError(value, type);
        }
        break;

      case JSTYPE_JAVA_OBJECT:
      case JSTYPE_JAVA_ARRAY:
        if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
        }
        if (type.isPrimitive()) {
          if (type == Boolean.TYPE) {
            reportConversionError(value, type);
          }
          return coerceToNumber(type, value);
        } else {
          if (type == ScriptRuntime.StringClass) {
            return value.toString();
          } else {
            if (type.isInstance(value)) {
              return value;
            } else {
              reportConversionError(value, type);
            }
          }
        }
        break;

      case JSTYPE_OBJECT:
        if (type == ScriptRuntime.StringClass) {
          return ScriptRuntime.toString(value);
        } else if (type.isPrimitive()) {
          if (type == Boolean.TYPE) {
            reportConversionError(value, type);
          }
          return coerceToNumber(type, value);
        } else if (type.isInstance(value)) {
          return value;
        } else if (type == ScriptRuntime.DateClass && value instanceof NativeDate) {
          double time = ((NativeDate) value).getJSTimeValue();
          // XXX: This will replace NaN by 0
          return new Date((long) time);
        } else if (type.isArray() && value instanceof NativeArray) {
          // Make a new java array, and coerce the JS array components
          // to the target (component) type.
          NativeArray array = (NativeArray) value;
          long length = array.getLength();
          Class<?> arrayType = type.getComponentType();
          Object Result = Array.newInstance(arrayType, (int) length);
          for (int i = 0; i < length; ++i) {
            try {
              Array.set(Result, i, coerceType(arrayType, array.get(i, array)));
            } catch (EvaluatorException ee) {
              reportConversionError(value, type);
            }
          }

          return Result;
        } else if (value instanceof Wrapper) {
          value = ((Wrapper) value).unwrap();
          if (type.isInstance(value)) return value;
          reportConversionError(value, type);
        } else if (type.isInterface() && value instanceof Callable) {
          // Try to use function as implementation of Java interface.
          //
          // XXX: Currently only instances of ScriptableObject are
          // supported since the resulting interface proxies should
          // be reused next time conversion is made and generic
          // Callable has no storage for it. Weak references can
          // address it but for now use this restriction.
          if (value instanceof ScriptableObject) {
            ScriptableObject so = (ScriptableObject) value;
            Object key = Kit.makeHashKeyFromPair(COERCED_INTERFACE_KEY, type);
            Object old = so.getAssociatedValue(key);
            if (old != null) {
              // Function was already wrapped
              return old;
            }
            Context cx = Context.getContext();
            Object glue = InterfaceAdapter.create(cx, type, (Callable) value);
            // Store for later retrival
            glue = so.associateValue(key, glue);
            return glue;
          }
          reportConversionError(value, type);
        } else {
          reportConversionError(value, type);
        }
        break;
    }

    return value;
  }