1 /* 2 * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.web; 27 28 import java.lang.reflect.InvocationTargetException; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Modifier; 31 import java.lang.reflect.UndeclaredThrowableException; 32 import java.math.BigDecimal; 33 import java.math.BigInteger; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.security.PrivilegedExceptionAction; 41 42 import com.sun.javafx.reflect.ReflectUtil; 43 44 45 // Java Object exported to JavaScript 46 class ExportedJavaObject { 47 48 private final JS2JavaBridge owner; 49 private final String objId; // id to call 50 private final Object javaObject; 51 private final Class cls; 52 private Method[] methods; 53 private List<String> jsNames = new ArrayList<String>(1); 54 55 private JS2JavaBridge getJSBridge() { 56 return owner; 57 } 58 59 public ExportedJavaObject(JS2JavaBridge owner, String objId, Object javaObject) { 60 this.owner = owner; 61 this.objId = objId; 62 this.javaObject = javaObject; 63 cls = javaObject.getClass(); 64 ReflectUtil.checkPackageAccess(cls); 65 methods = getPublicMethods(cls); 66 } 67 68 public String getObjectId() { 69 return objId; 70 } 71 72 public Object getJavaObject() { 73 return javaObject; 74 } 75 76 public void addJSName(String jsName) { 77 jsNames.add(jsName); 78 } 79 80 public List<String> getJSNames() { 81 return Collections.unmodifiableList(jsNames); 82 } 83 84 public String getJSDecl() { 85 StringBuilder sb = new StringBuilder(); 86 sb.append("{\n"); 87 for (int i=0; i<methods.length; i++) { 88 if (i>0) { 89 sb.append(",\n"); 90 } 91 Method m = methods[i]; 92 String methodName = m.getName(); 93 //Class<?> retClass = m.getReturnType(); 94 Class<?>[] params = m.getParameterTypes(); 95 sb.append(" ").append(methodName).append(" : function("); 96 if (params.length > 0) { 97 sb.append("p0"); 98 for (int j=1; j<params.length; j++) { 99 sb.append(", p").append(j); 100 } 101 } 102 sb.append(") {"); 103 104 sb.append(" return ").append(owner.getJavaBridge()).append(".call('").append(objId).append(":").append(i).append("', ["); 105 if (params.length > 0) { 106 sb.append("p0"); 107 for (int j=1; j<params.length; j++) { 108 sb.append(", p").append(j); 109 } 110 } 111 sb.append("]); }"); 112 } 113 sb.append("}"); 114 return sb.toString(); 115 } 116 117 public class CallException extends Exception { 118 public CallException(String message) { 119 super(message); 120 } 121 public CallException(String message, Throwable cause) { 122 super(message, cause); 123 } 124 } 125 126 // Handle casting like Long to Integer, Integer to int, etc 127 private Object cast(Class<?> desiredType, Object value) throws Exception { 128 log("ExportedJavaObject.cast: desired=" + desiredType.getSimpleName() 129 + ", value=" + (value == null ? "null" : value.getClass().getSimpleName())); 130 if (value == null) { 131 return null; 132 } 133 // we need wrapper classes instead primitive 134 if (desiredType.isPrimitive()) { 135 Class[] wrapperClasses = new Class[]{ 136 Byte.class, Short.class, Integer.class, Long.class, 137 Float.class, Double.class, Boolean.class, Character.class}; 138 for (Class wrap: wrapperClasses) { 139 if (desiredType.equals(wrap.getField("TYPE").get(null))) { 140 log("ExportedJavaObject.cast: replace " + desiredType.getName() 141 + " with " + wrap.getName()); 142 desiredType = wrap; 143 break; 144 } 145 } 146 } 147 if (Number.class.isAssignableFrom(value.getClass())) { 148 Number n = (Number)value; 149 if (desiredType.equals(Byte.class)) { 150 value = Byte.valueOf(n.byteValue()); 151 } else if (desiredType.equals(Short.class)) { 152 value = Short.valueOf(n.shortValue()); 153 } else if (desiredType.equals(Integer.class)) { 154 value = Integer.valueOf(n.intValue()); 155 } else if (desiredType.equals(Long.class)) { 156 value = Long.valueOf(n.longValue()); 157 } else if (desiredType.equals(BigInteger.class)) { 158 // handle BigInteger as Long 159 value = BigInteger.valueOf(n.longValue()); 160 } else if (desiredType.equals(Float.class)) { 161 value = Float.valueOf(n.floatValue()); 162 } else if (desiredType.equals(Double.class)) { 163 value = Double.valueOf(n.doubleValue()); 164 } else if (desiredType.equals(BigDecimal.class)) { 165 // handle BigDecimal as Double 166 value = BigDecimal.valueOf(n.doubleValue()); 167 } 168 } 169 return desiredType.cast(value); 170 } 171 172 // returns JSON-encoded result 173 public String call(final String methodName, final String args) throws CallException { 174 try { 175 return AccessController.doPrivileged(new PrivilegedExceptionAction<String>() { 176 @Override 177 public String run() throws Exception { 178 return callWorker(methodName, args); 179 } 180 }, getJSBridge().getAccessControlContext()); 181 } catch (Exception e) { 182 if (e instanceof CallException) { 183 throw (CallException) e; 184 } 185 186 throw new UndeclaredThrowableException(e); 187 } 188 } 189 190 private String callWorker(String methodName, String args) throws CallException { 191 // methodName is an index in methods array 192 Method m; 193 try { 194 int mIndex = Integer.parseInt(methodName); // NumberFormatException 195 m = methods[mIndex]; // ArrayIndexOutOfBoundsException 196 } catch (Exception ex) { 197 throw new CallException("Wrong method id", ex); 198 } 199 200 log("call: " + javaObject.getClass().getSimpleName() 201 + "." + m.getName() + "(" + args + ")"); 202 203 // we always encode paramaters as array 204 Object[] params; 205 try { 206 Object argObj = getJSBridge().decode(args); 207 params = (Object[])argObj; 208 } catch (Exception ex) { 209 throw new CallException("Could not parse arguments", ex); 210 } 211 212 Class<?>[] paramTypes = m.getParameterTypes(); 213 if (paramTypes.length != params.length) { 214 throw new CallException("Incorrect argument number: " 215 + params.length + " instead " + paramTypes); 216 } 217 218 Object[] castedParams = new Object[paramTypes.length]; 219 int i = 0; 220 try { 221 for (i=0; i<paramTypes.length; i++) { 222 castedParams[i] = cast(paramTypes[i], params[i]); 223 } 224 } catch (Exception ex) { 225 throw new CallException("Argument " + i + " casting error", ex); 226 } 227 228 Object resObj; 229 try { 230 resObj = m.invoke(javaObject, castedParams); 231 // return null if the method is void 232 if (Void.class.equals(m.getReturnType())) { 233 return null; 234 } 235 } catch (InvocationTargetException ex) { 236 // the method throws ex.getTargetException() 237 Throwable reason = ex.getTargetException(); 238 throw new CallException("Java Exception (" 239 + reason.getClass().getSimpleName() + "): " 240 + reason.getMessage(), reason); 241 } catch (Exception ex) { 242 // IllegalAccessException, IllegalArgumentException, 243 // NullPointerException, ExceptionInInitializerError 244 throw new CallException("Invoke error (" 245 + ex.getClass().getSimpleName() + "): " 246 + ex.getMessage(), ex); 247 } 248 249 try { 250 StringBuilder sb = new StringBuilder(1024); 251 getJSBridge().encode(resObj, sb); 252 return sb.toString(); 253 } catch (Exception ex) { 254 throw new CallException("Result encoding error", ex); 255 } 256 } 257 258 private Method[] getPublicMethods(final Class clz) { 259 Method[] m = clz.getMethods(); 260 ArrayList<Method> am = new ArrayList<Method>(); 261 for (int i = 0; i < m.length; i++) { 262 if (Modifier.isPublic(m[i].getModifiers())){ 263 am.add(m[i]); 264 } 265 } 266 267 Method[] publicMethods = new Method[am.size()]; 268 return am.toArray(publicMethods); 269 } 270 271 static void log(String s) { 272 JS2JavaBridge.log(s); 273 } 274 275 static void log(Exception ex) { 276 JS2JavaBridge.log(ex); 277 } 278 279 }