/**
   * Evaluate the Array expression 'loc' on the given object, o. e.g. in 'a[2]', <code>2</code> is
   * 'loc' and <code>a</code> is 'o'.
   *
   * <p>If o or loc are null, null is returned. If o is a Map, o.get(loc) is returned. If o is a
   * List, o.get(loc) is returned. loc must resolve to an int value. If o is an Array, o[loc] is
   * returned. loc must resolve to an int value. Otherwise loc is treated as a bean property of o.
   *
   * @param o an object to be accessed using the array operator or '.' operator.
   * @param loc the index of the object to be returned.
   * @return the resulting value.
   * @throws Exception on any error.
   */
  public static Object evaluateExpr(Object o, Object loc) throws Exception {
    /*
     * following the JSTL EL rules
     */

    if (o == null) {
      return null;
    }

    if (loc == null) {
      return null;
    }

    if (o instanceof Map) {
      if (!((Map) o).containsKey(loc)) {
        return null;
      }

      return ((Map) o).get(loc);
    } else if (o instanceof List) {
      int idx = Coercion.coerceInteger(loc).intValue();

      try {
        return ((List) o).get(idx);
      } catch (IndexOutOfBoundsException iobe) {
        return null;
      }
    } else if (o.getClass().isArray()) {
      int idx = Coercion.coerceInteger(loc).intValue();

      try {
        return Array.get(o, idx);
      } catch (ArrayIndexOutOfBoundsException aiobe) {
        return null;
      }
    } else {
      /*
       * "Otherwise (a JavaBean object)..." huh? :)
       */

      String s = loc.toString();

      VelPropertyGet vg = Introspector.getUberspect().getPropertyGet(o, s, DUMMY);

      if (vg != null) {
        return vg.invoke(o);
      }
    }

    throw new Exception("Unsupported object type for array [] accessor");
  }