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