1 16 package com.google.gwt.dev.shell; 17 18 import com.google.gwt.core.ext.TreeLogger; 19 import com.google.gwt.core.ext.UnableToCompleteException; 20 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider; 21 import com.google.gwt.core.ext.typeinfo.JClassType; 22 import com.google.gwt.core.ext.typeinfo.JMethod; 23 import com.google.gwt.core.ext.typeinfo.JParameter; 24 import com.google.gwt.core.ext.typeinfo.JPrimitiveType; 25 import com.google.gwt.core.ext.typeinfo.JType; 26 import com.google.gwt.core.ext.typeinfo.NotFoundException; 27 import com.google.gwt.core.ext.typeinfo.TypeOracle; 28 import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource; 29 import com.google.gwt.dev.js.ast.JsBlock; 30 import com.google.gwt.dev.util.Jsni; 31 import com.google.gwt.dev.util.StringCopier; 32 33 import java.util.ArrayList ; 34 import java.util.Arrays ; 35 import java.util.IdentityHashMap ; 36 import java.util.List ; 37 import java.util.Map ; 38 39 43 public class JsniInjector { 44 45 49 private class CoreTypes { 50 static final String PKG_JSOBJECT = "com.google.gwt.core.client"; 51 static final String CLS_JSOBJECT = "JavaScriptObject"; 52 static final String PKG_STRING = "java.lang"; 53 static final String CLS_STRING = "String"; 54 55 public final JClassType javaLangString; 56 57 public final JClassType javaScriptObject; 58 59 public CoreTypes(TreeLogger logger) throws UnableToCompleteException { 60 javaScriptObject = getCoreType(logger, PKG_JSOBJECT, CLS_JSOBJECT); 61 javaLangString = getCoreType(logger, PKG_STRING, CLS_STRING); 62 } 63 64 private JClassType getCoreType(TreeLogger logger, String pkg, String cls) 65 throws UnableToCompleteException { 66 try { 67 return oracle.getType(pkg, cls); 68 } catch (NotFoundException e) { 69 String msg = "Unable to find core type '" + pkg + "." + cls + "'"; 70 logger.log(TreeLogger.ERROR, msg, e); 71 throw new UnableToCompleteException(); 72 } 73 } 74 } 75 76 79 private static class Replacement implements Comparable { 80 public final int end; 81 82 public final int start; 83 84 public final char[] text; 85 86 public Replacement(int start, int end, char[] text) { 87 this.start = start; 88 this.end = end; 89 this.text = text; 90 } 91 92 public int compareTo(Object o) { 93 Replacement other = (Replacement) o; 94 if (start < other.start) { 95 assert (end <= other.start) : "Overlapping changes not supported"; 96 return -1; 97 } else if (start > other.start) { 98 assert (start >= other.end) : "Overlapping changes not supported"; 99 return 1; 100 } else { 101 return 0; 102 } 103 } 104 } 105 106 private final TypeOracle oracle; 107 108 private CoreTypes coreTypes; 109 110 private final Map parsedJsByMethod = new IdentityHashMap (); 111 112 public JsniInjector(TypeOracle oracle) { 113 this.oracle = oracle; 114 } 115 116 public CompilationUnitProvider inject(TreeLogger logger, 117 CompilationUnitProvider cup) throws UnableToCompleteException { 118 119 logger = logger.branch(TreeLogger.SPAM, 120 "Checking for JavaScript native methods", null); 121 122 if (coreTypes == null) { 125 coreTypes = new CoreTypes(logger); 126 } 127 128 char[] source = cup.getSource(); 131 List changes = new ArrayList (); 132 rewriteCompilationUnit(logger, source, changes, cup); 133 134 int n = changes.size(); 137 if (n > 0) { 138 Replacement[] repls = (Replacement[]) changes.toArray(new Replacement[n]); 139 Arrays.sort(repls); 140 StringCopier copier = new StringCopier(source); 141 for (int i = 0; i < n; ++i) { 142 Replacement repl = repls[i]; 143 copier.commit(repl.text, repl.start, repl.end); 144 } 145 146 char[] results = copier.finish(); 147 148 return new CompilationUnitProviderWithAlternateSource(cup, results); 149 } else { 150 logger.log(TreeLogger.SPAM, "No JavaScript native methods were found", 153 null); 154 return cup; 155 } 156 } 157 158 163 private char[] genInitializerBlock(String file, char[] source, 164 JMethod[] methods) { 165 166 String escapedFile = Jsni.escapeQuotesAndSlashes(file); 167 168 StringBuffer sb = new StringBuffer (); 169 sb.append(" static {"); 170 for (int i = 0; i < methods.length; ++i) { 171 JMethod method = methods[i]; 172 173 JsBlock jsniBody = (JsBlock) parsedJsByMethod.get(method); 174 if (jsniBody == null) { 175 continue; 178 } 179 180 JParameter[] params = method.getParameters(); 181 String paramNamesArray = getParamNamesArrayExpr(params); 182 183 final String jsTry = "try "; 184 final String jsCatch = " catch (e) {\\n" 185 + " __static[\\\"@" 186 + Jsni.JAVASCRIPTHOST_NAME 187 + "::exceptionCaught" 188 + "(ILjava/lang/String;Ljava/lang/String;)\\\"]" 189 + "((e && e.number) || 0, (e && e.name) || null , (e && e.message) || null);\\n" 190 + "}\\n"; 191 192 String js = jsTry + Jsni.generateEscapedJavaScriptForHostedMode(jsniBody) 198 + jsCatch; 199 String jsniSig = Jsni.getJsniSignature(method); 200 201 int bodyStart = method.getBodyStart(); 203 int line = Jsni.countNewlines(source, 0, bodyStart) + 1; 204 205 sb.append(" " + Jsni.JAVASCRIPTHOST_NAME + ".createNative(\"" 206 + escapedFile + "\", " + line + ", " + "\"@" + jsniSig + "\", " 207 + paramNamesArray + ", \"" + js + "\");"); 208 } 209 sb.append("}"); 210 return sb.toString().toCharArray(); 211 } 212 213 222 private String genNonNativeVersionOfJsniMethod(JMethod method, 223 int expectedHeaderLines, int expectedBodyLines) { 224 StringBuffer sb = new StringBuffer (); 225 226 for (int i = 0; i < expectedHeaderLines; ++i) { 229 sb.append('\n'); 230 } 231 232 String methodDecl = method.getReadableDeclaration(false, true, false, 233 false, false); 234 235 sb.append(methodDecl + " {"); 236 sb.append("try {"); 238 239 JType returnType = method.getReturnType(); 243 boolean isJavaScriptObject = isJavaScriptObject(returnType); 244 JPrimitiveType primType; 245 if (isJavaScriptObject) { 246 String returnTypeName = returnType.getQualifiedSourceName(); 249 sb.append("return (" + returnTypeName + ")" + Jsni.JAVASCRIPTHOST_NAME 250 + ".invokeNativeHandle"); 251 } else if (null != (primType = returnType.isPrimitive())) { 252 char[] primTypeSuffix = primType.getSimpleSourceName().toCharArray(); 255 primTypeSuffix[0] = Character.toUpperCase(primTypeSuffix[0]); 256 String invokeMethodName = "invokeNative" + String.valueOf(primTypeSuffix); 257 if (primType != JPrimitiveType.VOID) { 258 sb.append("return "); 259 } 260 sb.append(Jsni.JAVASCRIPTHOST_NAME); 261 sb.append("."); 262 sb.append(invokeMethodName); 263 } else if (returnType == coreTypes.javaLangString) { 264 sb.append("return "); 265 sb.append(Jsni.JAVASCRIPTHOST_NAME); 266 sb.append(".invokeNativeString"); 267 } else { 268 String returnTypeName = returnType.getQualifiedSourceName(); 272 sb.append("return ("); 273 sb.append(returnTypeName); 274 sb.append(")"); 275 sb.append(Jsni.JAVASCRIPTHOST_NAME); 276 sb.append(".invokeNativeObject"); 277 } 278 279 sb.append("(\"@"); 282 String jsniSig = Jsni.getJsniSignature(method); 283 sb.append(jsniSig); 284 if (method.isStatic()) { 285 sb.append("\", null, "); 286 } else { 287 sb.append("\", this, "); 288 } 289 290 if (isJavaScriptObject) { 291 String returnTypeName = returnType.getQualifiedSourceName(); 294 sb.append(returnTypeName); 295 sb.append(".class, "); 296 } 297 298 sb.append(Jsni.buildTypeList(method)); 302 sb.append(','); 303 304 sb.append(Jsni.buildArgList(method)); 308 sb.append(");"); 309 310 sb.append("} catch (java.lang.Throwable __gwt_exception) {"); 312 sb.append("if (__gwt_exception instanceof java.lang.RuntimeException) throw (java.lang.RuntimeException) __gwt_exception;"); 313 JType[] throwTypes = method.getThrows(); 314 for (int i = 0; i < throwTypes.length; ++i) { 315 String typeName = throwTypes[i].getQualifiedSourceName(); 316 sb.append("if (__gwt_exception instanceof " + typeName + ") throw (" + typeName 317 + ") __gwt_exception;"); 318 } 319 sb.append("throw new java.lang.RuntimeException(\"Undeclared checked exception thrown out of JavaScript; web mode behavior may differ.\", __gwt_exception);"); 320 sb.append("}"); 321 322 sb.append("}"); 323 324 for (int i = 0; i < expectedBodyLines; ++i) { 327 sb.append('\n'); 328 } 329 330 return sb.toString(); 331 } 332 333 private String getParamNamesArrayExpr(JParameter[] params) { 334 StringBuffer sb = new StringBuffer (); 335 sb.append("new String[] {"); 336 for (int i = 0, n = params.length; i < n; ++i) { 337 if (i > 0) { 338 sb.append(", "); 339 } 340 341 JParameter param = params[i]; 342 sb.append('\"'); 343 sb.append(param.getName()); 344 sb.append('\"'); 345 } 346 sb.append("}"); 347 return sb.toString(); 348 } 349 350 private boolean isJavaScriptObject(JType type) { 351 JClassType classType = type.isClass(); 352 if (classType == null) { 353 return false; 354 } 355 356 if (classType.isAssignableTo(coreTypes.javaScriptObject)) { 357 return true; 358 } else { 359 return false; 360 } 361 } 362 363 private void rewriteCompilationUnit(TreeLogger logger, char[] source, 364 List changes, CompilationUnitProvider cup) 365 throws UnableToCompleteException { 366 367 JClassType[] types = oracle.getTypesInCompilationUnit(cup); 370 for (int i = 0; i < types.length; i++) { 371 JClassType type = types[i]; 372 rewriteType(logger, source, changes, type); 373 } 374 } 375 376 private void rewriteType(TreeLogger logger, char[] source, List changes, 377 JClassType type) throws UnableToCompleteException { 378 379 String loc = type.getCompilationUnit().getLocation(); 380 381 List patchedMethods = new ArrayList (); 384 JMethod[] methods = type.getMethods(); 385 for (int i = 0; i < methods.length; i++) { 386 JMethod method = methods[i]; 387 if (method.isNative()) { 388 Jsni.Interval interval = Jsni.findJsniSource(method); 389 if (interval != null) { 390 393 String js = new String (source, interval.start, interval.end 396 - interval.start); 397 int startLine = Jsni.countNewlines(source, 0, interval.start) + 1; 398 JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine); 399 400 parsedJsByMethod.put(method, body); 403 404 final int declStart = method.getDeclStart(); 406 final int declEnd = method.getDeclEnd(); 407 408 int expectedHeaderLines = Jsni.countNewlines(source, declStart, 409 interval.start); 410 int expectedBodyLines = Jsni.countNewlines(source, interval.start, 411 interval.end); 412 String newDecl = genNonNativeVersionOfJsniMethod(method, 413 expectedHeaderLines, expectedBodyLines); 414 415 final char[] newSource = newDecl.toCharArray(); 416 changes.add(new Replacement(declStart, declEnd, newSource)); 417 patchedMethods.add(method); 418 } else { 419 String msg = "No JavaScript body found for native method '" + method 421 + "' in type '" + type + "'"; 422 logger.log(TreeLogger.ERROR, msg, null); 423 throw new UnableToCompleteException(); 424 } 425 } 426 } 427 428 if (!patchedMethods.isEmpty()) { 429 JMethod[] patched = new JMethod[patchedMethods.size()]; 430 patched = (JMethod[]) patchedMethods.toArray(patched); 431 432 TreeLogger branch = logger.branch(TreeLogger.SPAM, "Patched methods in '" 433 + type.getQualifiedSourceName() + "'", null); 434 435 for (int i = 0; i < patched.length; i++) { 436 branch.log(TreeLogger.SPAM, patched[i].getReadableDeclaration(), null); 437 } 438 439 char[] block = genInitializerBlock(loc, source, patched); 443 444 while (type.getEnclosingType() != null && !type.isStatic()) { 447 type = type.getEnclosingType(); 448 } 449 450 int bodyStart = type.getBodyStart(); 451 changes.add(new Replacement(bodyStart, bodyStart, block)); 452 } 453 } 454 } 455 | Popular Tags |