KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > util > Jsni


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.util;
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.JMethod;
21 import com.google.gwt.core.ext.typeinfo.JParameter;
22 import com.google.gwt.core.ext.typeinfo.JType;
23 import com.google.gwt.dev.js.JsParser;
24 import com.google.gwt.dev.js.JsParserException;
25 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
26 import com.google.gwt.dev.js.JsParserException.SourceDetail;
27 import com.google.gwt.dev.js.ast.JsBlock;
28 import com.google.gwt.dev.js.ast.JsContext;
29 import com.google.gwt.dev.js.ast.JsExprStmt;
30 import com.google.gwt.dev.js.ast.JsExpression;
31 import com.google.gwt.dev.js.ast.JsFunction;
32 import com.google.gwt.dev.js.ast.JsNameRef;
33 import com.google.gwt.dev.js.ast.JsNode;
34 import com.google.gwt.dev.js.ast.JsProgram;
35 import com.google.gwt.dev.js.ast.JsStatements;
36 import com.google.gwt.dev.shell.JavaScriptHost;
37
38 import java.io.IOException JavaDoc;
39 import java.io.StringReader JavaDoc;
40
41 /**
42  * Helper methods working with JSNI.
43  */

44 public class Jsni {
45
46   /**
47    * Represents a logical interval of text.
48    */

49   public static class Interval {
50
51     public final int end;
52
53     public final int start;
54
55     public Interval(int start, int end) {
56       this.start = start;
57       this.end = end;
58     }
59   }
60
61   /**
62    * Generate source code, fixing up any JSNI references for hosted mode.
63    *
64    * <p/><table>
65    * <tr>
66    * <td>Original</td>
67    * <td>Becomes</td>
68    * </tr>
69    * <tr>
70    * <td><code>.@class::method(params)(args)</code></td>
71    *
72    * <td><code>["@class::method(params)"](args)</code></td>
73    * </tr>
74    * <tr>
75    * <td><code>@class::method(params)(args)</code></td>
76    *
77    * <td><code>__static["@class::method(params)"](args)</code></td>
78    * </tr>
79    * <tr>
80    * <td><code>.@class::field</code></td>
81    *
82    * <td><code>["@class::field"]</code></td>
83    * </tr>
84    * <tr>
85    * <td><code>@class::field</code></td>
86    *
87    * <td><code>__static["@class::field"]</code></td>
88    * </tr>
89    * </table>
90    */

91   private static class JsSourceGenWithJsniIdentFixup extends
92       JsSourceGenerationVisitor {
93     private final TextOutput out;
94
95     public JsSourceGenWithJsniIdentFixup(TextOutput out) {
96       super(out);
97       this.out = out;
98     }
99
100     public boolean visit(JsNameRef x, JsContext ctx) {
101       String JavaDoc ident = x.getIdent();
102       if (ident.startsWith("@")) {
103         JsExpression q = x.getQualifier();
104         if (q != null) {
105           accept(q);
106           out.print("[\"");
107           out.print(ident);
108           out.print("\"]");
109         } else {
110           out.print("__static[\"");
111           out.print(ident);
112           out.print("\"]");
113         }
114         return false;
115       } else {
116         return super.visit(x, ctx);
117       }
118     }
119   }
120
121   public static final String JavaDoc JAVASCRIPTHOST_NAME = JavaScriptHost.class.getName();
122
123   public static final String JavaDoc JSNI_BLOCK_END = "}-*/";
124
125   public static final String JavaDoc JSNI_BLOCK_START = "/*-{";
126
127   /**
128    * Generates the code to wrap a set of parameters as an object array.
129    */

130   public static String JavaDoc buildArgList(JMethod method) {
131     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
132     sb.append("new Object[]{");
133
134     JParameter[] params = method.getParameters();
135     for (int i = 0; i < params.length; ++i) {
136       if (i > 0) {
137         sb.append(", ");
138       }
139
140       JType type = params[i].getType();
141       String JavaDoc typeName = type.getQualifiedSourceName();
142
143       if ((type.isArray() == null) && (type.isPrimitive() != null)) {
144         // Primitive types have to be wrapped for reflection invoke().
145
//
146
if (typeName.equals("boolean")) {
147           sb.append("new Boolean(" + params[i].getName() + ")");
148         } else if (typeName.equals("byte")) {
149           sb.append("new Byte(" + params[i].getName() + ")");
150         } else if (typeName.equals("char")) {
151           sb.append("new Character(" + params[i].getName() + ")");
152         } else if (typeName.equals("short")) {
153           sb.append("new Short(" + params[i].getName() + ")");
154         } else if (typeName.equals("int")) {
155           sb.append("new Integer(" + params[i].getName() + ")");
156         } else if (typeName.equals("float")) {
157           sb.append("new Float(" + params[i].getName() + ")");
158         } else if (typeName.equals("double")) {
159           sb.append("new Double(" + params[i].getName() + ")");
160         } else if (typeName.equals("long")) {
161           sb.append("new Long(" + params[i].getName() + ")");
162         } else {
163           throw new RuntimeException JavaDoc("Unexpected primitive parameter type");
164         }
165       } else {
166         // Reference types pass through as themselves.
167
//
168
sb.append(params[i].getName());
169       }
170     }
171
172     sb.append("}");
173     String JavaDoc args = sb.toString();
174     return args;
175   }
176
177   /**
178    * Generates the code to pass the exact types associated with each argument of
179    * this method.
180    */

181   public static String JavaDoc buildTypeList(JMethod method) {
182     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
183     sb.append("new Class[]{");
184
185     JParameter[] params = method.getParameters();
186     for (int i = 0; i < params.length; ++i) {
187       if (i > 0) {
188         sb.append(", ");
189       }
190
191       JType type = params[i].getType();
192       String JavaDoc typeName = type.getQualifiedSourceName();
193       sb.append(typeName);
194       sb.append(".class");
195     }
196
197     sb.append("}");
198     String JavaDoc classes = sb.toString();
199     return classes;
200   }
201
202   public static int countNewlines(char[] buf, int start, int end) {
203     int total = 0;
204     while (start < end) {
205       switch (buf[start]) {
206         case '\r':
207           ++total;
208           // if the next character is a linefeed, eat it too
209
if (start + 1 < end && buf[start + 1] == '\n') {
210             ++start;
211           }
212           break;
213         case '\n':
214           ++total;
215           break;
216       }
217       ++start;
218     }
219     return total;
220   }
221
222   public static int countNewlines(String JavaDoc src, int start, int end) {
223     return countNewlines(src.toCharArray(), start, end);
224   }
225
226   /**
227    * Replaces double-quotes and backslashes in native JS code with their
228    * appropriate escaped form (so they can be encoded in a java string).
229    */

230   public static String JavaDoc escapeQuotesAndSlashes(String JavaDoc str) {
231     StringBuffer JavaDoc buf = new StringBuffer JavaDoc(str);
232     escapeQuotesAndSlashes(buf);
233     return buf.toString();
234   }
235
236   public static Interval findJsniSource(JMethod method)
237       throws UnableToCompleteException {
238     assert (method.isNative());
239     int bodyStart = method.getBodyStart();
240     int bodyEnd = method.getBodyEnd();
241     int bodyLen = bodyEnd - bodyStart + 1;
242     char[] source = method.getEnclosingType().getCompilationUnit().getSource();
243     String JavaDoc js = String.valueOf(source, bodyStart, bodyLen);
244
245     int jsniStart = js.indexOf(JSNI_BLOCK_START);
246     if (jsniStart == -1) {
247       return null;
248     }
249
250     int jsniEnd = js.indexOf(JSNI_BLOCK_END, jsniStart);
251     if (jsniEnd == -1) {
252       // Suspicious, but maybe this is just a weird comment, so let it slide.
253
//
254
return null;
255     }
256
257     int srcStart = bodyStart + jsniStart + JSNI_BLOCK_START.length();
258     int srcEnd = bodyStart + jsniEnd;
259     return new Interval(srcStart, srcEnd);
260   }
261
262   /**
263    * Returns a string representing the source output of the JsNode, where all
264    * JSNI idents have been replaced with legal JavaScript for hosted mode.
265    *
266    * The output has quotes and slashes escaped so that the result can be part of
267    * a legal Java source code string literal.
268    */

269   public static String JavaDoc generateEscapedJavaScriptForHostedMode(JsNode node) {
270     String JavaDoc source = generateJavaScriptForHostedMode(node);
271     StringBuffer JavaDoc body = new StringBuffer JavaDoc(source.length());
272     body.append(source);
273     escapeQuotesAndSlashes(body);
274     fixupLinebreaks(body);
275     return body.toString();
276   }
277
278   /**
279    * Gets a unique name for this method and its signature (this is used to
280    * determine whether one method overrides another).
281    */

282   public static String JavaDoc getJsniSignature(JMethod method) {
283     String JavaDoc name = method.getName();
284     String JavaDoc className = method.getEnclosingType().getQualifiedSourceName();
285
286     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
287     sb.append(className);
288     sb.append("::");
289     sb.append(name);
290     sb.append("(");
291     JParameter[] params = method.getParameters();
292     for (int i = 0; i < params.length; ++i) {
293       JParameter param = params[i];
294       String JavaDoc typeSig = param.getType().getJNISignature();
295       sb.append(typeSig);
296     }
297     sb.append(")");
298     String JavaDoc fullName = sb.toString();
299     return fullName;
300   }
301
302   /**
303    * In other words, it can have <code>return</code> statements.
304    */

305   public static JsBlock parseAsFunctionBody(TreeLogger logger, String JavaDoc js,
306       String JavaDoc location, int startLine) throws UnableToCompleteException {
307     // Wrap it in fake function and parse it.
308
js = "function(){ " + js + " }";
309
310     JsParser jsParser = new JsParser();
311     JsProgram jsPgm = new JsProgram();
312     StringReader JavaDoc r = new StringReader JavaDoc(js);
313
314     try {
315       JsStatements stmts = jsParser.parse(jsPgm.getScope(), r, startLine);
316
317       // Rip the body out of the parsed function and attach the JavaScript
318
// AST to the method.
319
//
320
JsFunction fn = (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
321       return fn.getBody();
322     } catch (IOException JavaDoc e) {
323       logger.log(TreeLogger.ERROR, "Error reading JavaScript source", e);
324       throw new UnableToCompleteException();
325     } catch (JsParserException e) {
326       SourceDetail dtl = e.getSourceDetail();
327       if (dtl != null) {
328         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
329         sb.append(location);
330         sb.append("(");
331         sb.append(dtl.getLine());
332         sb.append(", ");
333         sb.append(dtl.getLineOffset());
334         sb.append("): ");
335         sb.append(e.getMessage());
336         logger.log(TreeLogger.ERROR, sb.toString(), e);
337         throw new UnableToCompleteException();
338       } else {
339         logger.log(TreeLogger.ERROR, "Error parsing JSNI source", e);
340         throw new UnableToCompleteException();
341       }
342     }
343   }
344
345   /**
346    * Replaces double-quotes and backslashes in native JS code with their
347    * appropriate escaped form (so they can be encoded in a java string).
348    */

349   private static void escapeQuotesAndSlashes(StringBuffer JavaDoc buf) {
350     for (int i = 0; i < buf.length(); ++i) {
351       char c = buf.charAt(i);
352       if (c == '\"' || c == '\\') {
353         buf.insert(i, '\\');
354         i += 1;
355       }
356     }
357   }
358
359   /**
360    * Replaces any actual carriage returns and linebreaks we put in earlier with
361    * an escaped form (so they can be encoded in a java string).
362    */

363   private static void fixupLinebreaks(StringBuffer JavaDoc body) {
364     for (int i = 0; i < body.length(); ++i) {
365       char c = body.charAt(i);
366       if (c == '\r') {
367         body.setCharAt(i, 'r');
368         body.insert(i, '\\');
369         i += 1;
370       } else if (c == '\n') {
371         body.setCharAt(i, 'n');
372         body.insert(i, '\\');
373         i += 1;
374       }
375     }
376   }
377
378   /**
379    * Returns a string representing the source output of the JsNode, where all
380    * JSNI idents have been replaced with legal JavaScript for hosted mode.
381    */

382   private static String JavaDoc generateJavaScriptForHostedMode(JsNode node) {
383     DefaultTextOutput out = new DefaultTextOutput(false);
384     JsSourceGenWithJsniIdentFixup vi = new JsSourceGenWithJsniIdentFixup(out);
385     vi.accept(node);
386     return out.toString();
387   }
388
389 }
390
Popular Tags