1 package host.exp.exponent.tools;
2 
3 import com.github.javaparser.JavaParser;
4 import com.github.javaparser.ParseException;
5 import com.github.javaparser.ast.CompilationUnit;
6 import com.github.javaparser.ast.Modifier;
7 import com.github.javaparser.ast.Node;
8 import com.github.javaparser.ast.NodeList;
9 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
10 import com.github.javaparser.ast.body.ConstructorDeclaration;
11 import com.github.javaparser.ast.body.FieldDeclaration;
12 import com.github.javaparser.ast.body.MethodDeclaration;
13 import com.github.javaparser.ast.body.Parameter;
14 import com.github.javaparser.ast.comments.LineComment;
15 import com.github.javaparser.ast.expr.Expression;
16 import com.github.javaparser.ast.expr.MethodCallExpr;
17 import com.github.javaparser.ast.expr.SimpleName;
18 import com.github.javaparser.ast.stmt.BlockStmt;
19 import com.github.javaparser.ast.stmt.CatchClause;
20 import com.github.javaparser.ast.stmt.EmptyStmt;
21 import com.github.javaparser.ast.stmt.LabeledStmt;
22 import com.github.javaparser.ast.stmt.Statement;
23 import com.github.javaparser.ast.stmt.TryStmt;
24 import com.github.javaparser.ast.type.ReferenceType;
25 import com.github.javaparser.ast.type.UnionType;
26 import com.github.javaparser.ast.visitor.GenericVisitor;
27 import com.github.javaparser.ast.visitor.ModifierVisitor;
28 import com.github.javaparser.ast.visitor.VoidVisitor;
29 
30 import org.apache.commons.io.FileUtils;
31 import org.json.JSONObject;
32 
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.OutputStream;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.EnumSet;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 
46 public class ReactAndroidCodeTransformer {
47 
48   private static final String REACT_ANDROID_DEST_ROOT = "react-native-lab/react-native/packages/react-native/ReactAndroid";
49   private static final String SOURCE_PATH = "src/main/java/com/facebook/react/";
50 
51   private static abstract class MethodVisitor {
visit(final String name, final MethodDeclaration n)52     abstract Node visit(final String name, final MethodDeclaration n);
modifySource(final String source)53     String modifySource(final String source) {
54       return source;
55     };
56   }
57 
58   private static final Map<String, MethodVisitor> FILES_TO_MODIFY = new HashMap<>();
59 
getCallMethodReflectionBlock(String className, String methodNameAndTypes, String targetAndValues)60   private static String getCallMethodReflectionBlock(String className, String methodNameAndTypes, String targetAndValues) {
61     return getCallMethodReflectionBlock(className, methodNameAndTypes, targetAndValues, "", "");
62   }
63 
getCallMethodReflectionBlock(String className, String methodNameAndTypes, String targetAndValues, String returnValue, String defaultReturnValue)64   private static String getCallMethodReflectionBlock(String className, String methodNameAndTypes, String targetAndValues, String returnValue, String defaultReturnValue) {
65     return "{\n" +
66         "  try {\n" +
67         "    " + returnValue + "Class.forName(\"" + className + "\").getMethod(" + methodNameAndTypes + ").invoke(" + targetAndValues + ");\n" +
68         "  } catch (Exception expoHandleErrorException) {\n" +
69         "    expoHandleErrorException.printStackTrace();\n" + defaultReturnValue +
70         "  }\n" +
71         "}";
72   }
73 
getHandleErrorBlockString(String title, String details, String exceptionId, String isFatal)74   private static String getHandleErrorBlockString(String title, String details, String exceptionId, String isFatal) {
75     return getCallMethodReflectionBlock("host.exp.exponent.ReactNativeStaticHelpers", "\"handleReactNativeError\", String.class, Object.class, Integer.class, Boolean.class", "null, " + title + ", " + details + ", " + exceptionId + ", " + isFatal);
76   }
77 
getHandleErrorBlock(String title, String details, String exceptionId, String isFatal)78   private static BlockStmt getHandleErrorBlock(String title, String details, String exceptionId, String isFatal) {
79     return JavaParser.parseBlock(getHandleErrorBlockString(title, details, exceptionId, isFatal));
80   }
81 
getCatchClause(String title, String details, String exceptionId, String isFatal)82   private static CatchClause getCatchClause(String title, String details, String exceptionId, String isFatal) {
83     ReferenceType t = JavaParser.parseClassOrInterfaceType("RuntimeException");
84     SimpleName v = new SimpleName("expoException");
85     BlockStmt catchBlock = getHandleErrorBlock(title, details, exceptionId, isFatal);
86     return getCatchClause(Arrays.asList(t), v, catchBlock);
87   }
88 
getCatchClause()89   private static CatchClause getCatchClause() {
90     ReferenceType t = JavaParser.parseClassOrInterfaceType("Throwable");
91     SimpleName v = new SimpleName("expoException");
92     return getCatchClause(Arrays.asList(t), v, new BlockStmt());
93   }
94 
getCatchClause( List<ReferenceType> exceptionTypes, SimpleName exceptionId, BlockStmt catchBlock)95   private static CatchClause getCatchClause(
96       List<ReferenceType> exceptionTypes,
97       SimpleName exceptionId,
98       BlockStmt catchBlock) {
99     UnionType type = new UnionType(NodeList.nodeList(exceptionTypes));
100     Parameter exceptionParam = new Parameter(type, exceptionId);
101     return new CatchClause(exceptionParam, catchBlock);
102   }
103 
getTryCatch(Statement statement, String title, String details, String exceptionId, String isFatal)104   private static TryStmt getTryCatch(Statement statement, String title, String details, String exceptionId, String isFatal) {
105     TryStmt tryStatement = new TryStmt();
106     BlockStmt tryBlockStatement = new BlockStmt(NodeList.nodeList(statement));
107     tryStatement.setTryBlock(tryBlockStatement);
108     tryStatement.setCatchClauses(NodeList.nodeList(getCatchClause(title, details, exceptionId, isFatal)));
109     return tryStatement;
110   }
111 
getTryCatch(Statement statement)112   private static TryStmt getTryCatch(Statement statement) {
113     TryStmt tryStatement = new TryStmt();
114     BlockStmt tryBlockStatement = new BlockStmt(NodeList.nodeList(statement));
115     tryStatement.setTryBlock(tryBlockStatement);
116     tryStatement.setCatchClauses(NodeList.nodeList(getCatchClause()));
117     return tryStatement;
118   }
119 
addBeforeEndOfClass(final String source, final String add)120   private static String addBeforeEndOfClass(final String source, final String add) {
121     int endOfClass = source.lastIndexOf("}");
122     return source.substring(0, endOfClass) + "\n" + add + "\n" + source.substring(endOfClass);
123   }
124 
125   static {
126     FILES_TO_MODIFY.put("devsupport/DevServerHelper.java", new MethodVisitor() {
127 
128       @Override
129       public Node visit(String methodName, MethodDeclaration n) {
130         switch (methodName) {
131           case "createBundleURL":
132             // In RN 0.54 this method is overloaded; skip the convenience version
133             NodeList<Parameter> params = n.getParameters();
134             if (params.size() == 2 && params.get(0).getNameAsString().equals("mainModuleID") &&
135                 params.get(1).getNameAsString().equals("type")) {
136               return n;
137             }
138 
139             BlockStmt stmt = JavaParser.parseBlock(getCallMethodReflectionBlock(
140                 "host.exp.exponent.ReactNativeStaticHelpers",
141                 "\"getBundleUrlForActivityId\", int.class, String.class, String.class, String.class, boolean.class, boolean.class",
142                 "null, mSettings.exponentActivityId, host, mainModuleID, type.typeID(), getDevMode(), getJSMinifyMode()",
143                 "return (String) ",
144                 "return null;"));
145             n.setBody(stmt);
146             n.getModifiers().remove(Modifier.STATIC);
147             return n;
148         }
149 
150         return n;
151       }
152     });
153     FILES_TO_MODIFY.put("modules/network/OkHttpClientProvider.java", new MethodVisitor() {
154 
155       @Override
156       public Node visit(String methodName, MethodDeclaration n) {
157         switch (methodName) {
158           case "createClient":
159             BlockStmt stmt = JavaParser.parseBlock(getCallMethodReflectionBlock(
160                 "host.exp.exponent.ReactNativeStaticHelpers",
161                 "\"getOkHttpClient\", Class.class",
162                 "null, OkHttpClientProvider.class",
163                 "return (OkHttpClient) ",
164                 "return null;"));
165             n.setBody(stmt);
166             return n;
167         }
168 
169         return n;
170       }
171     });
172     FILES_TO_MODIFY.put("devsupport/DevSupportManagerBase.java", new MethodVisitor() {
173 
174       @Override
175       public Node visit(String methodName, MethodDeclaration n) {
176         switch (methodName) {
177           case "handleReloadJS":
178             // Catch error if "draw over other apps" not enabled
179             return handleReloadJS(n);
180           case "handleException":
181             // Handle any uncaught error in original method
182             return handleException(n);
183           case "hasUpToDateJSBundleInCache":
184             // Use this to always force a refresh in debug mode.
185             return hasUpToDateJSBundleInCache(n);
186           case "showDevOptionsDialog":
187             return showDevOptionsDialog(n);
188           case "getExponentActivityId":
189             n.setBody(JavaParser.parseBlock("{return mDevServerHelper.mSettings.exponentActivityId;}"));
190             return n;
191         }
192 
193         return n;
194       }
195     });
196     FILES_TO_MODIFY.put("devsupport/BridgeDevSupportManager.java", null);
197 
198     FILES_TO_MODIFY.put("modules/core/ExceptionsManagerModule.java", new MethodVisitor() {
199 
200       @Override
201       public Node visit(String methodName, MethodDeclaration n) {
202         // In dev mode call the original methods. Otherwise open Expo error screen
203         switch (methodName) {
204           case "reportFatalException":
205             return exceptionsManagerModuleHandleException(n, "message", "stack", "(int) idDouble", "true");
206           case "reportSoftException":
207             return exceptionsManagerModuleHandleException(n, "message", "stack", "(int) idDouble", "false");
208           case "updateExceptionMessage":
209             return exceptionsManagerModuleHandleException(n, "title", "details", "(int) exceptionIdDouble", "false");
210         }
211 
212         return n;
213       }
214     });
215     FILES_TO_MODIFY.put("modules/dialog/DialogModule.java", new MethodVisitor() {
216 
217       @Override
218       public Node visit(String methodName, MethodDeclaration n) {
219         switch (methodName) {
220           case "onHostResume":
221             return wrapInTryCatch(n);
222         }
223 
224         return n;
225       }
226     });
227     FILES_TO_MODIFY.put("modules/network/NetworkingModule.java", null);
228     FILES_TO_MODIFY.put("modules/systeminfo/AndroidInfoHelpers.java", null);
229     FILES_TO_MODIFY.put("uimanager/NativeViewHierarchyManager.java", new MethodVisitor() {
230 
231       @Override
232       public Node visit(String methodName, MethodDeclaration n) {
233         switch (methodName) {
234           case "updateProperties":
235             return wrapInTryCatch(n);
236         }
237 
238         return n;
239       }
240     });
241     FILES_TO_MODIFY.put("bridge/DefaultJSExceptionHandler.java", new MethodVisitor() {
242 
243       @Override
244       public Node visit(String methodName, MethodDeclaration n) {
245         switch (methodName) {
246           case "handleException":
247             // Catch any uncaught exceptions
248             return wrapInTryCatchAndHandleError(n);
249         }
250 
251         return n;
252       }
253     });
254     FILES_TO_MODIFY.put("devsupport/DevInternalSettings.java", new MethodVisitor() {
255 
256       @Override
257       public Node visit(String methodName, MethodDeclaration n) {
258         switch (methodName) {
259           case "isReloadOnJSChangeEnabled":
260             BlockStmt blockStmt = JavaParser.parseBlock("{return false;}");
261             blockStmt.addOrphanComment(new LineComment(" NOTE(brentvatne): This is not possible to enable/disable so we should always disable it for"));
262             blockStmt.addOrphanComment(new LineComment(" now. I managed to get into a state where fast refresh wouldn't work because live reload"));
263             blockStmt.addOrphanComment(new LineComment(" would kick in every time and there was no way to turn it off from the dev menu."));
264             blockStmt.addOrphanComment(new LineComment(" return mPreferences.getBoolean(PREFS_RELOAD_ON_JS_CHANGE_KEY, false);"));
265             n.setBody(blockStmt);
266             return n;
267           case "setReloadOnJSChangeEnabled":
268             BlockStmt emptyBlockStmt = JavaParser.parseBlock("{}");
269             emptyBlockStmt.addOrphanComment(new LineComment(" NOTE(brentvatne): We don't need to do anything here because this option is always false"));
270             emptyBlockStmt.addOrphanComment(new LineComment(" mPreferences.edit().putBoolean(PREFS_RELOAD_ON_JS_CHANGE_KEY, enabled).apply();"));
271             n.setBody(emptyBlockStmt);
272             return n;
273         }
274 
275         return n;
276       }
277 
278       @Override
279       String modifySource(String source) {
280         return addBeforeEndOfClass(source, "public int exponentActivityId = -1;");
281       }
282     });
283   }
284 
main(final String[] args)285   public static void main(final String[] args) throws IOException {
286     String executionPath = ReactAndroidCodeTransformer.class.getProtectionDomain().getCodeSource().getLocation().getPath();
287     String projectRoot = new File(executionPath + "../../../../../../").getCanonicalPath() + '/';
288 
289     String sdkVersion;
290     try {
291       sdkVersion = args[0];
292     } catch (Exception e) {
293       throw new IllegalArgumentException("Invalid args passed in, expected one argument -- SDK version.");
294     }
295 
296     // Update maven publish information
297     replaceInFile(new File(projectRoot + REACT_ANDROID_DEST_ROOT + "/build.gradle"),
298         "def AAR_OUTPUT_URL = \"file://${projectDir}/../android\"",
299         "def AAR_OUTPUT_URL = \"file:${System.env.HOME}/.m2/repository\"");
300 
301     replaceInFile(new File(projectRoot + REACT_ANDROID_DEST_ROOT + "/build.gradle"),
302         "group = GROUP",
303         "group = 'com.facebook.react'");
304 
305     // This version also gets updated in android-tasks.js
306     replaceInFile(new File(projectRoot + REACT_ANDROID_DEST_ROOT + "/build.gradle"),
307         "version = VERSION_NAME",
308         "version = '" + sdkVersion + "'");
309 
310     // RN uses a weird directory structure for soloader to build with Buck. Change this so that Android Studio doesn't complain.
311     replaceInFile(new File(projectRoot + REACT_ANDROID_DEST_ROOT + "/build.gradle"),
312         "'src/main/libraries/soloader'",
313         "'src/main/libraries/soloader/java'");
314 
315     // Actually modify the files
316     String path = projectRoot + REACT_ANDROID_DEST_ROOT + '/' + SOURCE_PATH;
317     for (String fileName : FILES_TO_MODIFY.keySet()) {
318       try {
319         updateFile(path + fileName, FILES_TO_MODIFY.get(fileName));
320       } catch (ParseException e) {
321         e.printStackTrace();
322       }
323     }
324   }
325 
replaceInFile(final File file, final String searchString, final String replaceString)326   private static void replaceInFile(final File file, final String searchString, final String replaceString) {
327     try {
328       String content = FileUtils.readFileToString(file, "UTF-8");
329       content = content.replace(searchString, replaceString);
330       FileUtils.writeStringToFile(file, content, "UTF-8");
331     } catch (IOException e) {
332       throw new RuntimeException("Generating file failed", e);
333     }
334   }
335 
updateFile(final String path, final MethodVisitor methodVisitor)336   private static void updateFile(final String path, final MethodVisitor methodVisitor) throws IOException, ParseException {
337     FileInputStream in = new FileInputStream(path);
338     CompilationUnit cu = JavaParser.parse(in);
339     in.close();
340 
341     new ChangerVisitor(methodVisitor).visit(cu, null);
342 
343     try (OutputStream out = new FileOutputStream(path)) {
344       if (methodVisitor != null) {
345         out.write(methodVisitor.modifySource(cu.toString()).getBytes());
346       } else {
347         out.write(cu.toString().getBytes());
348       }
349     }
350   }
351 
352   private static class ChangerVisitor extends ModifierVisitor<Void> {
353 
354     MethodVisitor mMethodVisitor;
355 
ChangerVisitor(MethodVisitor methodVisitor)356     ChangerVisitor(MethodVisitor methodVisitor) {
357       mMethodVisitor = methodVisitor;
358     }
359 
360     @Override
visit(final ClassOrInterfaceDeclaration n, final Void arg)361     public Node visit(final ClassOrInterfaceDeclaration n, final Void arg) {
362       super.visit(n, arg);
363 
364       // Remove all final modifiers
365       n.getModifiers().remove(Modifier.FINAL);
366 
367       return n;
368     }
369 
370     @Override
visit(final ConstructorDeclaration n, final Void arg)371     public Node visit(final ConstructorDeclaration n, final Void arg) {
372       String name = n.getName().toString();
373       switch (name) {
374         case "NetworkingModule":
375           return networkingModuleConstructor(n);
376         case "BridgeDevSupportManager":
377           return bridgeDevSupportManagerConstructor(n);
378       }
379 
380       return n;
381     }
382 
383     @Override
visit(final FieldDeclaration n, final Void arg)384     public Node visit(final FieldDeclaration n, final Void arg) {
385       super.visit(n, arg);
386 
387       // Remove all final modifiers from static fields
388       EnumSet<Modifier> modifiers = n.getModifiers();
389       if (modifiers.contains(Modifier.STATIC) && !n.toString().contains("String NAME")) {
390         modifiers.remove(Modifier.FINAL);
391       }
392 
393       modifiers.remove(Modifier.PRIVATE);
394       modifiers.remove(Modifier.PROTECTED);
395       modifiers.add(Modifier.PUBLIC);
396 
397       n.setModifiers(modifiers);
398       return n;
399     }
400 
401     @Override
visit(final MethodDeclaration n, final Void arg)402     public Node visit(final MethodDeclaration n, final Void arg) {
403       super.visit(n, arg);
404 
405       String methodName = n.getName().toString();
406       if (mMethodVisitor != null) {
407         return mMethodVisitor.visit(methodName, n);
408       }
409 
410       return n;
411     }
412   }
413 
414   private interface StatementMapper {
map(Statement statement)415     Statement map(Statement statement);
416   }
417 
mapNode(final Node node, final StatementMapper mapper)418   private static Node mapNode(final Node node, final StatementMapper mapper) {
419     if (node instanceof BlockStmt) {
420       return mapBlockStatement((BlockStmt) node, mapper);
421     } else if (node.getChildNodes().size() > 0) {
422       List<Node> childNodes = new ArrayList<>(node.getChildNodes());
423       for (Node child : childNodes) {
424         child.setParentNode(null);
425         mapNode(child, mapper).setParentNode(node);
426       }
427 
428       if (node instanceof Statement) {
429         return mapper.map((Statement) node);
430       } else {
431         return node;
432       }
433     } else if (node instanceof Statement) {
434       return mapper.map((Statement) node);
435     } else {
436       return node;
437     }
438   }
439 
mapBlockStatement(final BlockStmt body, final StatementMapper mapper)440   private static BlockStmt mapBlockStatement(final BlockStmt body, final StatementMapper mapper) {
441     NodeList<Statement> newStatements = new NodeList<>();
442     for (Statement statement : body.getStatements()) {
443       newStatements.add((Statement) mapNode(statement, mapper));
444     }
445     body.setStatements(newStatements);
446     return body;
447   }
448 
mapBlockStatement(final MethodDeclaration n, final StatementMapper mapper)449   private static Node mapBlockStatement(final MethodDeclaration n, final StatementMapper mapper) {
450     n.getBody().ifPresent(body -> {
451       body = mapBlockStatement(body, mapper);
452       n.setBody(body);
453     });
454 
455     return n;
456   }
457 
mapBlockStatement(final ConstructorDeclaration n, final StatementMapper mapper)458   private static Node mapBlockStatement(final ConstructorDeclaration n, final StatementMapper mapper) {
459     BlockStmt body = n.getBody();
460     body = mapBlockStatement(body, mapper);
461     n.setBody(body);
462 
463     return n;
464   }
465 
handleReloadJS(final MethodDeclaration n)466   private static Node handleReloadJS(final MethodDeclaration n) {
467     return mapBlockStatement(n, new StatementMapper() {
468       @Override
469       public Statement map(Statement statement) {
470         if (!statement.toString().contains("progressDialog.show();")) {
471           return statement;
472         }
473 
474         return getTryCatch(statement, "\"Must allow Expo to draw over other apps in dev mode.\"", "null", "-1", "true");
475       }
476     });
477   }
478 
479   private static Node handleException(final MethodDeclaration n) {
480     return mapBlockStatement(n, new StatementMapper() {
481       @Override
482       public Statement map(Statement statement) {
483         if (!statement.toString().startsWith("if (mIsDevSupportEnabled) {")) {
484           return statement;
485         }
486 
487         return getTryCatch(statement, "expoException.getMessage()", "null", "-1", "true");
488       }
489     });
490   }
491 
492   private static Node exceptionsManagerModuleHandleException(final MethodDeclaration n, final String errorMessageName, final String errorDetailsName, final String errorIdName, final String isFatal) {
493     String source =
494         "{\n" +
495             "if (mDevSupportManager.getDevSupportEnabled()) {\n" +
496                 n.getBody().get().toString() + "\n" +
497             "} else {\n" +
498                 getHandleErrorBlockString(errorMessageName, errorDetailsName, errorIdName, isFatal) + "\n" +
499             "}\n" +
500         "}\n";
501 
502     BlockStmt blockStmt = JavaParser.parseBlock(source);
503     n.setBody(blockStmt);
504     return n;
505   }
506 
507   private static Node hasUpToDateJSBundleInCache(final MethodDeclaration n) {
508     BlockStmt blockStmt = JavaParser.parseBlock("{\nreturn false;\n}");
509     n.setBody(blockStmt);
510     return n;
511   }
512 
513   private static Node showDevOptionsDialog(final MethodDeclaration n) {
514     return mapBlockStatement(n, new StatementMapper() {
515       @Override
516       public Statement map(Statement statement) {
517         if (statement instanceof LabeledStmt) {
518           LabeledStmt labeledStmt = (LabeledStmt) statement;
519           if ("expo_transformer_remove".equals(labeledStmt.getLabel().getIdentifier())) {
520             Statement emptyStatement = new EmptyStmt();
521             emptyStatement.setLineComment(" code removed by ReactAndroidCodeTransformer");
522             return emptyStatement;
523           }
524         }
525 
526         return statement;
527       }
528     });
529   }
530 
531   // Remove stetho. Otherwise a stetho interceptor gets added each time a new NetworkingModule
532   // is created.
533   private static Node networkingModuleConstructor(final ConstructorDeclaration n) {
534     return mapBlockStatement(n, new StatementMapper() {
535       @Override
536       public Statement map(Statement statement) {
537         if (!statement.toString().equals("mClient.networkInterceptors().add(new StethoInterceptor());")) {
538           return statement;
539         }
540 
541         return new EmptyStmt();
542       }
543     });
544   }
545 
546   // Remove some custom dev options. unlike `showDevOptionsDialog`, this happens in constructor.
547   private static Node bridgeDevSupportManagerConstructor(final ConstructorDeclaration n) {
548     return mapBlockStatement(n, new StatementMapper() {
549       @Override
550       public Statement map(Statement statement) {
551         if (statement instanceof LabeledStmt) {
552           LabeledStmt labeledStmt = (LabeledStmt) statement;
553           if ("expo_transformer_remove".equals(labeledStmt.getLabel().getIdentifier())) {
554             Statement emptyStatement = new EmptyStmt();
555             emptyStatement.setLineComment(" code removed by ReactAndroidCodeTransformer");
556             return emptyStatement;
557           }
558         }
559 
560         return statement;
561       }
562     });
563   }
564 
565   private static Node wrapInTryCatch(final MethodDeclaration n) {
566     n.getBody().ifPresent(body -> {
567       Statement tryCatch = getTryCatch(body);
568       NodeList<Statement> statements = NodeList.nodeList(tryCatch);
569       body = new BlockStmt(statements);
570       n.setBody(body);
571     });
572 
573     return n;
574   }
575 
576   private static Node wrapInTryCatchAndHandleError(final MethodDeclaration n) {
577     n.getBody().ifPresent(body -> {
578       Statement tryCatch = getTryCatch(body, "expoException.getMessage()", "null", "-1", "true");
579       NodeList<Statement> statements = NodeList.nodeList(tryCatch);
580       body = new BlockStmt(statements);
581       n.setBody(body);
582     });
583 
584     return n;
585   }
586 }
587