1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package util.build;
18 
19 import com.android.dex.util.FileUtils;
20 
21 import dot.junit.AllTests;
22 import util.build.BuildStep.BuildFile;
23 
24 import junit.framework.TestCase;
25 import junit.framework.TestResult;
26 import junit.framework.TestSuite;
27 import junit.textui.TestRunner;
28 
29 import java.io.BufferedWriter;
30 import java.io.File;
31 import java.io.FileNotFoundException;
32 import java.io.FileOutputStream;
33 import java.io.FileReader;
34 import java.io.IOException;
35 import java.io.OutputStreamWriter;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.Comparator;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.LinkedHashMap;
43 import java.util.List;
44 import java.util.Scanner;
45 import java.util.Set;
46 import java.util.TreeSet;
47 import java.util.Map.Entry;
48 import java.util.regex.MatchResult;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51 
52 /**
53  * Main class to generate data from the test suite to later run from a shell
54  * script. the project's home folder.<br>
55  * <project-home>/src must contain the java sources<br>
56  * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
57  * (one Main class for each test method in the Test_... class
58  */
59 public class BuildDalvikSuite {
60 
61     public static final String TARGET_MAIN_FILE = "mains.jar";
62 
63     public static boolean DEBUG = true;
64 
65     private static String JAVASRC_FOLDER = "";
66     private static String MAIN_SRC_OUTPUT_FOLDER = "";
67 
68     // the folder for the generated junit-files for the cts host (which in turn
69     // execute the real vm tests using adb push/shell etc)
70     private static String HOSTJUNIT_SRC_OUTPUT_FOLDER = "";
71     private static String OUTPUT_FOLDER = "";
72     private static String COMPILED_CLASSES_FOLDER = "";
73 
74     private static String CLASSES_OUTPUT_FOLDER = "";
75     private static String HOSTJUNIT_CLASSES_OUTPUT_FOLDER = "";
76 
77     private static String CLASS_PATH = "";
78 
79     private static String restrictTo = null; // e.g. restrict to "opcodes.add_double"
80 
81     private static final String TARGET_JAR_ROOT_PATH = "/data/local/tmp/vm-tests";
82 
83     private int testClassCnt = 0;
84     private int testMethodsCnt = 0;
85 
86     /*
87      * using a linked hashmap to keep the insertion order for iterators.
88      * the junit suite/tests adding order is used to generate the order of the
89      * report.
90      * a map. key: fully qualified class name, value: a list of test methods for
91      * the given class
92      */
93     private LinkedHashMap<String, List<String>> map = new LinkedHashMap<String,
94     List<String>>();
95 
96     private class MethodData {
97         String methodBody, constraint, title;
98     }
99 
100     /**
101      * @param args
102      *            args 0 must be the project root folder (where src, lib etc.
103      *            resides)
104      * @throws IOException
105      */
main(String[] args)106     public static void main(String[] args) throws IOException {
107 
108         if (!parseArgs(args)) {
109           printUsage();
110           System.exit(-1);
111         }
112 
113         long start = System.currentTimeMillis();
114         BuildDalvikSuite cat = new BuildDalvikSuite();
115         cat.compose();
116         long end = System.currentTimeMillis();
117 
118         System.out.println("elapsed seconds: " + (end - start) / 1000);
119     }
120 
parseArgs(String[] args)121     public static boolean parseArgs(String[] args) {
122       if (args.length > 5) {
123           JAVASRC_FOLDER = args[0];
124           OUTPUT_FOLDER = args[1];
125           CLASS_PATH = args[2];
126           MAIN_SRC_OUTPUT_FOLDER = args[3];
127           CLASSES_OUTPUT_FOLDER = MAIN_SRC_OUTPUT_FOLDER + "/classes";
128 
129           COMPILED_CLASSES_FOLDER = args[4];
130 
131           HOSTJUNIT_SRC_OUTPUT_FOLDER = args[5];
132           HOSTJUNIT_CLASSES_OUTPUT_FOLDER = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/classes";
133 
134           if (args.length > 6) {
135               // optional: restrict to e.g. "opcodes.add_double"
136               restrictTo = args[6];
137               System.out.println("restricting build to: " + restrictTo);
138           }
139           return true;
140       } else {
141           return false;
142       }
143     }
144 
printUsage()145     private static void printUsage() {
146         System.out.println("usage: java-src-folder output-folder classpath " +
147                            "generated-main-files compiled_output generated-main-files " +
148                            "[restrict-to-opcode]");
149     }
150 
compose()151     public void compose() throws IOException {
152         System.out.println("Collecting all junit tests...");
153         new TestRunner() {
154             @Override
155             protected TestResult createTestResult() {
156                 return new TestResult() {
157                     @Override
158                     protected void run(TestCase test) {
159                         addToTests(test);
160                     }
161 
162                 };
163             }
164         }.doRun(AllTests.suite());
165 
166         // for each combination of TestClass and method, generate a Main_testN1
167         // class in the respective package.
168         // for the report make sure all N... tests are called first, then B,
169         // then E, then VFE test methods.
170         // e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() ->
171         // File Main_testN1.java in package dxc.junit.opcodes.aaload.
172         //
173         handleTests();
174     }
175 
176     private void addToTests(TestCase test) {
177 
178         String packageName = test.getClass().getPackage().getName();
179         packageName = packageName.substring(packageName.lastIndexOf('.'));
180 
181 
182         String method = test.getName(); // e.g. testVFE2
183         String fqcn = test.getClass().getName(); // e.g.
184         // dxc.junit.opcodes.iload_3.Test_iload_3
185 
186         // ignore all tests not belonging to the given restriction
187         if (restrictTo != null && !fqcn.contains(restrictTo)) return;
188 
189         testMethodsCnt++;
190         List<String> li = map.get(fqcn);
191         if (li == null) {
192             testClassCnt++;
193             li = new ArrayList<String>();
194             map.put(fqcn, li);
195         }
196         li.add(method);
197     }
198     private String curJunitFileName = null;
199     private String curJunitName = null;
200     private String curJunitFileData = "";
201 
202     private SourceBuildStep hostJunitBuildStep;
203 
204     private void flushHostJunitFile() {
205         if (curJunitFileName != null) {
206             File toWrite = new File(curJunitFileName);
207             String absPath = toWrite.getAbsolutePath();
208             // add to java source files for later compilation
209             hostJunitBuildStep.addSourceFile(absPath);
210             // write file
211             curJunitFileData += "\n}\n";
212             writeToFileMkdir(toWrite, curJunitFileData);
213 
214             curJunitFileName = null;
215             curJunitFileData = "";
216         }
217     }
218 
219     private void openCTSHostFileFor(String pName, String classOnlyName) {
220         // flush previous JunitFile
221         flushHostJunitFile();
222         String sourceName = "JUnit_" + classOnlyName;
223 
224         // prepare current testcase-file
225         curJunitFileName = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/" + pName.replaceAll("\\.","/") + "/" +
226         sourceName + ".java";
227         curJunitFileData = getWarningMessage() +
228         "package " + pName + ";\n" +
229         "import java.io.IOException;\n" +
230         "import java.util.concurrent.TimeUnit;\n\n" +
231         "import com.android.tradefed.device.CollectingOutputReceiver;\n" +
232         "import com.android.tradefed.testtype.IAbi;\n" +
233         "import com.android.tradefed.testtype.IAbiReceiver;\n" +
234         "import com.android.tradefed.testtype.DeviceTestCase;\n" +
235         "import com.android.tradefed.util.AbiFormatter;\n" +
236         "\n" +
237         "public class " + sourceName + " extends DeviceTestCase implements IAbiReceiver {\n";
238     }
239 
240     private String getShellExecJavaLine(String classpath, String mainclass) {
241       String cmd = String.format("ANDROID_DATA=%s dalvikvm|#ABI#| -Xmx512M -Xss32K -Xnodex2oat " +
242               "-Djava.io.tmpdir=%s -classpath %s %s", TARGET_JAR_ROOT_PATH, TARGET_JAR_ROOT_PATH,
243               classpath, mainclass);
244       StringBuilder code = new StringBuilder();
245       code.append("    String cmd = AbiFormatter.formatCmdForAbi(\"")
246           .append(cmd)
247           .append("\", mAbi.getBitness());\n")
248           .append("    CollectingOutputReceiver receiver = new CollectingOutputReceiver();\n")
249           .append("    getDevice().executeShellCommand(cmd, receiver, 6, TimeUnit.MINUTES, 1);\n")
250           .append("    // A sucessful adb shell command returns an empty string.\n")
251           .append("    assertEquals(cmd, \"\", receiver.getOutput());");
252       return code.toString();
253     }
254 
255     private String getWarningMessage() {
256         return "//Autogenerated code by " + this.getClass().getName() + "; do not edit.\n";
257     }
258 
259     private void addCTSHostMethod(String pName, String method, MethodData md,
260             Collection<String> dependentTestClassNames) {
261         curJunitFileData += "public void " + method + "() throws Exception {\n";
262         final String targetCoreJarPath = String.format("%s/dot/junit/dexcore.jar",
263                 TARGET_JAR_ROOT_PATH);
264 
265         String mainsJar = String.format("%s/%s", TARGET_JAR_ROOT_PATH, TARGET_MAIN_FILE);
266 
267         String cp = String.format("%s:%s", targetCoreJarPath, mainsJar);
268         for (String depFqcn : dependentTestClassNames) {
269             String sourceName = depFqcn.replaceAll("\\.", "/") + ".jar";
270             String targetName= String.format("%s/%s", TARGET_JAR_ROOT_PATH,
271                     sourceName);
272             cp += ":" + targetName;
273             // dot.junit.opcodes.invoke_interface_range.ITest
274             // -> dot/junit/opcodes/invoke_interface_range/ITest.jar
275         }
276 
277         //"dot.junit.opcodes.add_double_2addr.Main_testN2";
278         String mainclass = pName + ".Main_" + method;
279         curJunitFileData += getShellExecJavaLine(cp, mainclass);
280         curJunitFileData += "\n}\n\n";
281     }
282 
283     private void handleTests() throws IOException {
284         System.out.println("collected " + testMethodsCnt + " test methods in " +
285                 testClassCnt + " junit test classes");
286         String datafileContent = "";
287         Set<BuildStep> targets = new TreeSet<BuildStep>();
288 
289         SourceBuildStep srcBuildStep;
290         hostJunitBuildStep = new JavacBuildStep(
291             HOSTJUNIT_CLASSES_OUTPUT_FOLDER, CLASS_PATH);
292 
293         String mainsJar = OUTPUT_FOLDER + File.separator + TARGET_MAIN_FILE;
294         srcBuildStep = new JavacBuildStep(CLASSES_OUTPUT_FOLDER, CLASS_PATH);
295 
296         for (Entry<String, List<String>> entry : map.entrySet()) {
297 
298             String fqcn = entry.getKey();
299             int lastDotPos = fqcn.lastIndexOf('.');
300             String pName = fqcn.substring(0, lastDotPos);
301             String classOnlyName = fqcn.substring(lastDotPos + 1);
302             String instPrefix = "new " + classOnlyName + "()";
303 
304             openCTSHostFileFor(pName, classOnlyName);
305 
306             curJunitFileData += "\n" +
307                     "protected IAbi mAbi;\n" +
308                     "@Override\n" +
309                     "public void setAbi(IAbi abi) {\n" +
310                     "    mAbi = abi;\n" +
311                     "}\n\n";
312 
313             List<String> methods = entry.getValue();
314             Collections.sort(methods, new Comparator<String>() {
315                 public int compare(String s1, String s2) {
316                     // TODO sort according: test ... N, B, E, VFE
317                     return s1.compareTo(s2);
318                 }
319             });
320             for (String method : methods) {
321                 // e.g. testN1
322                 if (!method.startsWith("test")) {
323                     throw new RuntimeException("no test method: " + method);
324                 }
325 
326                 // generate the Main_xx java class
327 
328                 // a Main_testXXX.java contains:
329                 // package <packagenamehere>;
330                 // public class Main_testxxx {
331                 // public static void main(String[] args) {
332                 // new dxc.junit.opcodes.aaload.Test_aaload().testN1();
333                 // }
334                 // }
335                 MethodData md = parseTestMethod(pName, classOnlyName, method);
336                 String methodContent = md.methodBody;
337 
338                 List<String> dependentTestClassNames = parseTestClassName(pName,
339                         classOnlyName, methodContent);
340 
341                 addCTSHostMethod(pName, method, md, dependentTestClassNames);
342 
343 
344                 if (dependentTestClassNames.isEmpty()) {
345                     continue;
346                 }
347 
348 
349                 String content = getWarningMessage() +
350                 "package " + pName + ";\n" +
351                 "import " + pName + ".d.*;\n" +
352                 "import dot.junit.*;\n" +
353                 "public class Main_" + method + " extends DxAbstractMain {\n" +
354                 "    public static void main(String[] args) throws Exception {" +
355                 methodContent + "\n}\n";
356 
357                 File sourceFile = getFileFromPackage(pName, method);
358 
359                 writeToFile(sourceFile, content);
360                 srcBuildStep.addSourceFile(sourceFile.getAbsolutePath());
361 
362                 // prepare the entry in the data file for the bash script.
363                 // e.g.
364                 // main class to execute; opcode/constraint; test purpose
365                 // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
366                 // (#1)
367 
368                 char ca = method.charAt("test".length()); // either N,B,E,
369                 // or V (VFE)
370                 String comment;
371                 switch (ca) {
372                 case 'N':
373                     comment = "Normal #" + method.substring(5);
374                     break;
375                 case 'B':
376                     comment = "Boundary #" + method.substring(5);
377                     break;
378                 case 'E':
379                     comment = "Exception #" + method.substring(5);
380                     break;
381                 case 'V':
382                     comment = "Verifier #" + method.substring(7);
383                     break;
384                 default:
385                     throw new RuntimeException("unknown test abbreviation:"
386                             + method + " for " + fqcn);
387                 }
388 
389                 String line = pName + ".Main_" + method + ";";
390                 for (String className : dependentTestClassNames) {
391                     line += className + " ";
392                 }
393 
394 
395                 // test description
396                 String[] pparts = pName.split("\\.");
397                 // detail e.g. add_double
398                 String detail = pparts[pparts.length-1];
399                 // type := opcode | verify
400                 String type = pparts[pparts.length-2];
401 
402                 String description;
403                 if ("format".equals(type)) {
404                     description = "format";
405                 } else if ("opcodes".equals(type)) {
406                     // Beautify name, so it matches the actual mnemonic
407                     detail = detail.replaceAll("_", "-");
408                     detail = detail.replace("-from16", "/from16");
409                     detail = detail.replace("-high16", "/high16");
410                     detail = detail.replace("-lit8", "/lit8");
411                     detail = detail.replace("-lit16", "/lit16");
412                     detail = detail.replace("-4", "/4");
413                     detail = detail.replace("-16", "/16");
414                     detail = detail.replace("-32", "/32");
415                     detail = detail.replace("-jumbo", "/jumbo");
416                     detail = detail.replace("-range", "/range");
417                     detail = detail.replace("-2addr", "/2addr");
418 
419                     // Unescape reserved words
420                     detail = detail.replace("opc-", "");
421 
422                     description = detail;
423                 } else if ("verify".equals(type)) {
424                     description = "verifier";
425                 } else {
426                     description = type + " " + detail;
427                 }
428 
429                 String details = (md.title != null ? md.title : "");
430                 if (md.constraint != null) {
431                     details = " Constraint " + md.constraint + ", " + details;
432                 }
433                 if (details.length() != 0) {
434                     details = details.substring(0, 1).toUpperCase()
435                             + details.substring(1);
436                 }
437 
438                 line += ";" + description + ";" + comment + ";" + details;
439 
440                 datafileContent += line + "\n";
441                 generateBuildStepFor(pName, method, dependentTestClassNames,
442                         targets);
443             }
444 
445 
446         }
447 
448         D8BuildStep dexBuildStep = new D8BuildStep(
449             new BuildStep.BuildFile(new File(CLASSES_OUTPUT_FOLDER)),
450             new BuildStep.BuildFile(new File(mainsJar)),
451             false);
452 
453         targets.add(dexBuildStep);
454 
455         // write latest HOSTJUNIT generated file.
456         flushHostJunitFile();
457 
458         File scriptDataDir = new File(OUTPUT_FOLDER + "/data/");
459         scriptDataDir.mkdirs();
460         writeToFile(new File(scriptDataDir, "scriptdata"), datafileContent);
461 
462         if (!hostJunitBuildStep.build()) {
463             System.out.println("main javac cts-host-hostjunit-classes build step failed");
464             System.exit(1);
465         }
466 
467         if (!srcBuildStep.build()) {
468             System.out.println("main src dalvik-cts-buildutil build step failed");
469             System.exit(1);
470         }
471 
472         for (BuildStep buildStep : targets) {
473             if (!buildStep.build()) {
474                 System.out.println("building failed. buildStep: " +
475                         buildStep.getClass().getName() + ", " + buildStep);
476                 System.exit(1);
477             }
478         }
479     }
480 
481     private void generateBuildStepFor(String pName, String method,
482             Collection<String> dependentTestClassNames, Set<BuildStep> targets) {
483 
484 
485         for (String dependentTestClassName : dependentTestClassNames) {
486             generateBuildStepForDependant(dependentTestClassName, targets);
487         }
488     }
489 
490     private void generateBuildStepForDependant(String dependentTestClassName,
491             Set<BuildStep> targets) {
492 
493         File sourceFolder = new File(JAVASRC_FOLDER);
494         String fileName = dependentTestClassName.replace('.', '/').trim();
495 
496         if (new File(sourceFolder, fileName + ".dfh").exists()) {
497 
498             BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
499                     JAVASRC_FOLDER, fileName + ".dfh");
500             BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
501                     OUTPUT_FOLDER, fileName + ".dex");
502 
503             DFHBuildStep buildStep = new DFHBuildStep(inputFile, dexFile);
504 
505             BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
506                     OUTPUT_FOLDER, fileName + ".jar");
507             JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
508                     "classes.dex", jarFile, true);
509             jarBuildStep.addChild(buildStep);
510 
511             targets.add(jarBuildStep);
512             return;
513         }
514 
515         if (new File(sourceFolder, fileName + ".d").exists()) {
516 
517             BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
518                     JAVASRC_FOLDER, fileName + ".d");
519             BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
520                     OUTPUT_FOLDER, fileName + ".dex");
521 
522             DasmBuildStep buildStep = new DasmBuildStep(inputFile, dexFile);
523 
524             BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
525                     OUTPUT_FOLDER, fileName + ".jar");
526 
527             JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
528                     "classes.dex", jarFile, true);
529             jarBuildStep.addChild(buildStep);
530             targets.add(jarBuildStep);
531             return;
532         }
533 
534         File srcFile = new File(sourceFolder, fileName + ".java");
535         if (srcFile.exists()) {
536             BuildStep dexBuildStep;
537             dexBuildStep = generateDexBuildStep(
538               COMPILED_CLASSES_FOLDER, fileName);
539             targets.add(dexBuildStep);
540             return;
541         }
542 
543         try {
544             if (Class.forName(dependentTestClassName) != null) {
545                 BuildStep dexBuildStep = generateDexBuildStep(
546                     COMPILED_CLASSES_FOLDER, fileName);
547                 targets.add(dexBuildStep);
548                 return;
549             }
550         } catch (ClassNotFoundException e) {
551             // do nothing
552         }
553 
554         throw new RuntimeException("neither .dfh,.d,.java file of dependant test class found : " +
555                 dependentTestClassName + ";" + fileName);
556     }
557 
558     private BuildStep generateDexBuildStep(String classFileFolder,
559             String classFileName) {
560         BuildStep.BuildFile classFile = new BuildStep.BuildFile(
561                 classFileFolder, classFileName + ".class");
562 
563         BuildStep.BuildFile tmpJarFile = new BuildStep.BuildFile(
564                 OUTPUT_FOLDER,
565                 classFileName + "_tmp.jar");
566 
567         JarBuildStep jarBuildStep = new JarBuildStep(classFile,
568                 classFileName + ".class", tmpJarFile, false);
569 
570         BuildStep.BuildFile outputFile = new BuildStep.BuildFile(
571                 OUTPUT_FOLDER,
572                 classFileName + ".jar");
573 
574         D8BuildStep dexBuildStep = new D8BuildStep(tmpJarFile,
575                 outputFile,
576                 true);
577 
578         dexBuildStep.addChild(jarBuildStep);
579         return dexBuildStep;
580     }
581 
582     /**
583      * @param pName
584      * @param classOnlyName
585      * @param methodSource
586      * @return testclass names
587      */
588     private List<String> parseTestClassName(String pName, String classOnlyName,
589             String methodSource) {
590         List<String> entries = new ArrayList<String>(2);
591         String opcodeName = classOnlyName.substring(5);
592 
593         Scanner scanner = new Scanner(methodSource);
594 
595         String[] patterns = new String[] {"new\\s(T_" + opcodeName + "\\w*)",
596                 "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"};
597 
598         String token = null;
599         for (String pattern : patterns) {
600             token = scanner.findWithinHorizon(pattern, methodSource.length());
601             if (token != null) {
602                 break;
603             }
604         }
605 
606         if (token == null) {
607             System.err.println("warning: failed to find dependent test class name: " + pName +
608                     ", " + classOnlyName + " in methodSource:\n" + methodSource);
609             return entries;
610         }
611 
612         MatchResult result = scanner.match();
613 
614         entries.add((pName + ".d." + result.group(1)).trim());
615 
616         // search additional @uses directives
617         Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
618         Matcher m = p.matcher(methodSource);
619         while (m.find()) {
620             String res = m.group(1);
621             entries.add(0, res.trim());
622         }
623 
624         // search for " load(\"...\" " and add as dependency
625         Pattern loadPattern = Pattern.compile("load\\(\"([^\"]*)\"", Pattern.MULTILINE);
626         Matcher loadMatcher = loadPattern.matcher(methodSource);
627         while (loadMatcher.find()) {
628             String res = loadMatcher.group(1);
629             entries.add(res.trim());
630         }
631 
632         // search for " loadAndRun(\"...\" " and add as dependency
633         Pattern loadAndRunPattern = Pattern.compile("loadAndRun\\(\"([^\"]*)\"", Pattern.MULTILINE);
634         Matcher loadAndRunMatcher = loadAndRunPattern.matcher(methodSource);
635         while (loadAndRunMatcher.find()) {
636             String res = loadAndRunMatcher.group(1);
637             entries.add(res.trim());
638         }
639 
640         // lines with the form @uses
641         // dot.junit.opcodes.add_double.jm.T_add_double_2
642         // one dependency per one @uses
643         // TODO
644 
645         return entries;
646     }
647 
648     private MethodData parseTestMethod(String pname, String classOnlyName,
649             String method) {
650 
651         String path = pname.replaceAll("\\.", "/");
652         String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName + ".java";
653         File f = new File(absPath);
654 
655         Scanner scanner;
656         try {
657             scanner = new Scanner(f);
658         } catch (FileNotFoundException e) {
659             throw new RuntimeException("error while reading to file: " + e.getClass().getName() +
660                     ", msg:" + e.getMessage());
661         }
662 
663         String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";
664 
665         String token = scanner.findWithinHorizon(methodPattern, (int) f.length());
666         if (token == null) {
667             throw new RuntimeException("cannot find method source of 'public void " + method +
668                     "' in file '" + absPath + "'");
669         }
670 
671         MatchResult result = scanner.match();
672         result.start();
673         result.end();
674 
675         StringBuilder builder = new StringBuilder();
676         //builder.append(token);
677 
678         try {
679             FileReader reader = new FileReader(f);
680             reader.skip(result.end());
681 
682             int readResult;
683             int blocks = 1;
684             while ((readResult = reader.read()) != -1 && blocks > 0) {
685                 char currentChar = (char) readResult;
686                 switch (currentChar) {
687                     case '}': {
688                         blocks--;
689                         builder.append(currentChar);
690                         break;
691                     }
692                     case '{': {
693                         blocks++;
694                         builder.append(currentChar);
695                         break;
696                     }
697                     default: {
698                         builder.append(currentChar);
699                         break;
700                     }
701                 }
702             }
703             if (reader != null) {
704                 reader.close();
705             }
706         } catch (Exception e) {
707             throw new RuntimeException("failed to parse", e);
708         }
709 
710         // find the @title/@constraint in javadoc comment for this method
711         // using platform's default charset
712         String all = new String(FileUtils.readFile(f));
713         // System.out.println("grepping javadoc found for method " + method +
714         // " in " + pname + "," + classOnlyName);
715         String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
716         Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
717         Matcher m = p.matcher(all);
718         String title = null, constraint = null;
719         if (m.find()) {
720             String res = m.group(1);
721             // System.out.println("res: " + res);
722             // now grep @title and @constraint
723             Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
724             .matcher(res);
725             if (titleM.find()) {
726                 title = titleM.group(1).replaceAll("\\n     \\*", "");
727                 title = title.replaceAll("\\n", " ");
728                 title = title.trim();
729                 // System.out.println("title: " + title);
730             } else {
731                 System.err.println("warning: no @title found for method " + method + " in " + pname +
732                         "," + classOnlyName);
733             }
734             // constraint can be one line only
735             Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
736                     res);
737             if (constraintM.find()) {
738                 constraint = constraintM.group(1);
739                 constraint = constraint.trim();
740                 // System.out.println("constraint: " + constraint);
741             } else if (method.contains("VFE")) {
742                 System.err
743                 .println("warning: no @constraint for for a VFE method:" + method + " in " +
744                         pname + "," + classOnlyName);
745             }
746         } else {
747             System.err.println("warning: no javadoc found for method " + method + " in " + pname +
748                     "," + classOnlyName);
749         }
750         MethodData md = new MethodData();
751         md.methodBody = builder.toString();
752         md.constraint = constraint;
753         md.title = title;
754         if (scanner != null) {
755             scanner.close();
756         }
757         return md;
758     }
759 
760     private void writeToFileMkdir(File file, String content) {
761         File parent = file.getParentFile();
762         if (!parent.exists() && !parent.mkdirs()) {
763             throw new RuntimeException("failed to create directory: " + parent.getAbsolutePath());
764         }
765         writeToFile(file, content);
766     }
767 
768     private void writeToFile(File file, String content) {
769         try {
770             if (file.exists() && file.length() == content.length()) {
771                 FileReader reader = new FileReader(file);
772                 char[] charContents = new char[(int) file.length()];
773                 reader.read(charContents);
774                 reader.close();
775                 String contents = new String(charContents);
776                 if (contents.equals(content)) {
777                     // System.out.println("skipping identical: "
778                     // + file.getAbsolutePath());
779                     return;
780                 }
781             }
782 
783             //System.out.println("writing file " + file.getAbsolutePath());
784 
785             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
786                     new FileOutputStream(file), "utf-8"));
787             bw.write(content);
788             bw.close();
789         } catch (Exception e) {
790             throw new RuntimeException("error while writing to file: " + e.getClass().getName() +
791                     ", msg:" + e.getMessage());
792         }
793     }
794 
795     private File getFileFromPackage(String pname, String methodName)
796     throws IOException {
797         // e.g. dxc.junit.argsreturns.pargsreturn
798         String path = getFileName(pname, methodName, ".java");
799         String absPath = MAIN_SRC_OUTPUT_FOLDER + "/" + path;
800         File dirPath = new File(absPath);
801         File parent = dirPath.getParentFile();
802         if (!parent.exists() && !parent.mkdirs()) {
803             throw new IOException("failed to create directory: " + absPath);
804         }
805         return dirPath;
806     }
807 
808     private String getFileName(String pname, String methodName,
809             String extension) {
810         String path = pname.replaceAll("\\.", "/");
811         return new File(path, "Main_" + methodName + extension).getPath();
812     }
813 }
814