/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package util.build;

import com.android.dex.util.FileUtils;

import dot.junit.AllTests;
import util.build.BuildStep.BuildFile;

import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import junit.textui.TestRunner;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Main class to generate data from the test suite to later run from a shell
 * script. the project's home folder.<br>
 * <project-home>/src must contain the java sources<br>
 * <project-home>/src/<for-each-package>/Main_testN1.java will be generated<br>
 * (one Main class for each test method in the Test_... class
 */
public class BuildDalvikSuite {

    public static final String TARGET_MAIN_FILE = "mains.jar";

    public static boolean DEBUG = true;

    private static String JAVASRC_FOLDER = "";
    private static String MAIN_SRC_OUTPUT_FOLDER = "";

    // the folder for the generated junit-files for the cts host (which in turn
    // execute the real vm tests using adb push/shell etc)
    private static String HOSTJUNIT_SRC_OUTPUT_FOLDER = "";
    private static String OUTPUT_FOLDER = "";
    private static String COMPILED_CLASSES_FOLDER = "";

    private static String CLASSES_OUTPUT_FOLDER = "";
    private static String HOSTJUNIT_CLASSES_OUTPUT_FOLDER = "";

    private static String CLASS_PATH = "";

    private static String restrictTo = null; // e.g. restrict to "opcodes.add_double"

    private static final String TARGET_JAR_ROOT_PATH = "/data/local/tmp/vm-tests";

    private int testClassCnt = 0;
    private int testMethodsCnt = 0;

    /*
     * using a linked hashmap to keep the insertion order for iterators.
     * the junit suite/tests adding order is used to generate the order of the
     * report.
     * a map. key: fully qualified class name, value: a list of test methods for
     * the given class
     */
    private LinkedHashMap<String, List<String>> map = new LinkedHashMap<String,
    List<String>>();

    private class MethodData {
        String methodBody, constraint, title;
    }

    /**
     * @param args
     *            args 0 must be the project root folder (where src, lib etc.
     *            resides)
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        if (!parseArgs(args)) {
          printUsage();
          System.exit(-1);
        }

        long start = System.currentTimeMillis();
        BuildDalvikSuite cat = new BuildDalvikSuite();
        cat.compose();
        long end = System.currentTimeMillis();

        System.out.println("elapsed seconds: " + (end - start) / 1000);
    }

    public static boolean parseArgs(String[] args) {
      if (args.length > 5) {
          JAVASRC_FOLDER = args[0];
          OUTPUT_FOLDER = args[1];
          CLASS_PATH = args[2];
          MAIN_SRC_OUTPUT_FOLDER = args[3];
          CLASSES_OUTPUT_FOLDER = MAIN_SRC_OUTPUT_FOLDER + "/classes";

          COMPILED_CLASSES_FOLDER = args[4];

          HOSTJUNIT_SRC_OUTPUT_FOLDER = args[5];
          HOSTJUNIT_CLASSES_OUTPUT_FOLDER = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/classes";

          if (args.length > 6) {
              // optional: restrict to e.g. "opcodes.add_double"
              restrictTo = args[6];
              System.out.println("restricting build to: " + restrictTo);
          }
          return true;
      } else {
          return false;
      }
    }

    private static void printUsage() {
        System.out.println("usage: java-src-folder output-folder classpath " +
                           "generated-main-files compiled_output generated-main-files " +
                           "[restrict-to-opcode]");
    }

    public void compose() throws IOException {
        System.out.println("Collecting all junit tests...");
        new TestRunner() {
            @Override
            protected TestResult createTestResult() {
                return new TestResult() {
                    @Override
                    protected void run(TestCase test) {
                        addToTests(test);
                    }

                };
            }
        }.doRun(AllTests.suite());

        // for each combination of TestClass and method, generate a Main_testN1
        // class in the respective package.
        // for the report make sure all N... tests are called first, then B,
        // then E, then VFE test methods.
        // e.g. dxc.junit.opcodes.aaload.Test_aaload - testN1() ->
        // File Main_testN1.java in package dxc.junit.opcodes.aaload.
        //
        handleTests();
    }

    private void addToTests(TestCase test) {

        String packageName = test.getClass().getPackage().getName();
        packageName = packageName.substring(packageName.lastIndexOf('.'));


        String method = test.getName(); // e.g. testVFE2
        String fqcn = test.getClass().getName(); // e.g.
        // dxc.junit.opcodes.iload_3.Test_iload_3

        // ignore all tests not belonging to the given restriction
        if (restrictTo != null && !fqcn.contains(restrictTo)) return;

        testMethodsCnt++;
        List<String> li = map.get(fqcn);
        if (li == null) {
            testClassCnt++;
            li = new ArrayList<String>();
            map.put(fqcn, li);
        }
        li.add(method);
    }
    private String curJunitFileName = null;
    private String curJunitName = null;
    private String curJunitFileData = "";

    private SourceBuildStep hostJunitBuildStep;

    private void flushHostJunitFile() {
        if (curJunitFileName != null) {
            File toWrite = new File(curJunitFileName);
            String absPath = toWrite.getAbsolutePath();
            // add to java source files for later compilation
            hostJunitBuildStep.addSourceFile(absPath);
            // write file
            curJunitFileData += "\n}\n";
            writeToFileMkdir(toWrite, curJunitFileData);

            curJunitFileName = null;
            curJunitFileData = "";
        }
    }

    private void openCTSHostFileFor(String pName, String classOnlyName) {
        // flush previous JunitFile
        flushHostJunitFile();
        String sourceName = "JUnit_" + classOnlyName;

        // prepare current testcase-file
        curJunitFileName = HOSTJUNIT_SRC_OUTPUT_FOLDER + "/" + pName.replaceAll("\\.","/") + "/" +
        sourceName + ".java";
        curJunitFileData = getWarningMessage() +
        "package " + pName + ";\n" +
        "import java.io.IOException;\n" +
        "import java.util.concurrent.TimeUnit;\n\n" +
        "import com.android.tradefed.device.CollectingOutputReceiver;\n" +
        "import com.android.tradefed.testtype.IAbi;\n" +
        "import com.android.tradefed.testtype.IAbiReceiver;\n" +
        "import com.android.tradefed.testtype.DeviceTestCase;\n" +
        "import com.android.tradefed.util.AbiFormatter;\n" +
        "\n" +
        "public class " + sourceName + " extends DeviceTestCase implements IAbiReceiver {\n";
    }

    private String getShellExecJavaLine(String classpath, String mainclass) {
      String cmd = String.format("ANDROID_DATA=%s dalvikvm|#ABI#| -Xmx512M -Xss32K -Xnodex2oat " +
              "-Djava.io.tmpdir=%s -classpath %s %s", TARGET_JAR_ROOT_PATH, TARGET_JAR_ROOT_PATH,
              classpath, mainclass);
      StringBuilder code = new StringBuilder();
      code.append("    String cmd = AbiFormatter.formatCmdForAbi(\"")
          .append(cmd)
          .append("\", mAbi.getBitness());\n")
          .append("    CollectingOutputReceiver receiver = new CollectingOutputReceiver();\n")
          .append("    getDevice().executeShellCommand(cmd, receiver, 6, TimeUnit.MINUTES, 1);\n")
          .append("    // A sucessful adb shell command returns an empty string.\n")
          .append("    assertEquals(cmd, \"\", receiver.getOutput());");
      return code.toString();
    }

    private String getWarningMessage() {
        return "//Autogenerated code by " + this.getClass().getName() + "; do not edit.\n";
    }

    private void addCTSHostMethod(String pName, String method, MethodData md,
            Collection<String> dependentTestClassNames) {
        curJunitFileData += "public void " + method + "() throws Exception {\n";
        final String targetCoreJarPath = String.format("%s/dot/junit/dexcore.jar",
                TARGET_JAR_ROOT_PATH);

        String mainsJar = String.format("%s/%s", TARGET_JAR_ROOT_PATH, TARGET_MAIN_FILE);

        String cp = String.format("%s:%s", targetCoreJarPath, mainsJar);
        for (String depFqcn : dependentTestClassNames) {
            String sourceName = depFqcn.replaceAll("\\.", "/") + ".jar";
            String targetName= String.format("%s/%s", TARGET_JAR_ROOT_PATH,
                    sourceName);
            cp += ":" + targetName;
            // dot.junit.opcodes.invoke_interface_range.ITest
            // -> dot/junit/opcodes/invoke_interface_range/ITest.jar
        }

        //"dot.junit.opcodes.add_double_2addr.Main_testN2";
        String mainclass = pName + ".Main_" + method;
        curJunitFileData += getShellExecJavaLine(cp, mainclass);
        curJunitFileData += "\n}\n\n";
    }

    private void handleTests() throws IOException {
        System.out.println("collected " + testMethodsCnt + " test methods in " +
                testClassCnt + " junit test classes");
        String datafileContent = "";
        Set<BuildStep> targets = new TreeSet<BuildStep>();

        SourceBuildStep srcBuildStep;
        hostJunitBuildStep = new JavacBuildStep(
            HOSTJUNIT_CLASSES_OUTPUT_FOLDER, CLASS_PATH);

        String mainsJar = OUTPUT_FOLDER + File.separator + TARGET_MAIN_FILE;
        srcBuildStep = new JavacBuildStep(CLASSES_OUTPUT_FOLDER, CLASS_PATH);

        for (Entry<String, List<String>> entry : map.entrySet()) {

            String fqcn = entry.getKey();
            int lastDotPos = fqcn.lastIndexOf('.');
            String pName = fqcn.substring(0, lastDotPos);
            String classOnlyName = fqcn.substring(lastDotPos + 1);
            String instPrefix = "new " + classOnlyName + "()";

            openCTSHostFileFor(pName, classOnlyName);

            curJunitFileData += "\n" +
                    "protected IAbi mAbi;\n" +
                    "@Override\n" +
                    "public void setAbi(IAbi abi) {\n" +
                    "    mAbi = abi;\n" +
                    "}\n\n";

            List<String> methods = entry.getValue();
            Collections.sort(methods, new Comparator<String>() {
                public int compare(String s1, String s2) {
                    // TODO sort according: test ... N, B, E, VFE
                    return s1.compareTo(s2);
                }
            });
            for (String method : methods) {
                // e.g. testN1
                if (!method.startsWith("test")) {
                    throw new RuntimeException("no test method: " + method);
                }

                // generate the Main_xx java class

                // a Main_testXXX.java contains:
                // package <packagenamehere>;
                // public class Main_testxxx {
                // public static void main(String[] args) {
                // new dxc.junit.opcodes.aaload.Test_aaload().testN1();
                // }
                // }
                MethodData md = parseTestMethod(pName, classOnlyName, method);
                String methodContent = md.methodBody;

                List<String> dependentTestClassNames = parseTestClassName(pName,
                        classOnlyName, methodContent);

                addCTSHostMethod(pName, method, md, dependentTestClassNames);


                if (dependentTestClassNames.isEmpty()) {
                    continue;
                }


                String content = getWarningMessage() +
                "package " + pName + ";\n" +
                "import " + pName + ".d.*;\n" +
                "import dot.junit.*;\n" +
                "public class Main_" + method + " extends DxAbstractMain {\n" +
                "    public static void main(String[] args) throws Exception {" +
                methodContent + "\n}\n";

                File sourceFile = getFileFromPackage(pName, method);

                writeToFile(sourceFile, content);
                srcBuildStep.addSourceFile(sourceFile.getAbsolutePath());

                // prepare the entry in the data file for the bash script.
                // e.g.
                // main class to execute; opcode/constraint; test purpose
                // dxc.junit.opcodes.aaload.Main_testN1;aaload;normal case test
                // (#1)

                char ca = method.charAt("test".length()); // either N,B,E,
                // or V (VFE)
                String comment;
                switch (ca) {
                case 'N':
                    comment = "Normal #" + method.substring(5);
                    break;
                case 'B':
                    comment = "Boundary #" + method.substring(5);
                    break;
                case 'E':
                    comment = "Exception #" + method.substring(5);
                    break;
                case 'V':
                    comment = "Verifier #" + method.substring(7);
                    break;
                default:
                    throw new RuntimeException("unknown test abbreviation:"
                            + method + " for " + fqcn);
                }

                String line = pName + ".Main_" + method + ";";
                for (String className : dependentTestClassNames) {
                    line += className + " ";
                }


                // test description
                String[] pparts = pName.split("\\.");
                // detail e.g. add_double
                String detail = pparts[pparts.length-1];
                // type := opcode | verify
                String type = pparts[pparts.length-2];

                String description;
                if ("format".equals(type)) {
                    description = "format";
                } else if ("opcodes".equals(type)) {
                    // Beautify name, so it matches the actual mnemonic
                    detail = detail.replaceAll("_", "-");
                    detail = detail.replace("-from16", "/from16");
                    detail = detail.replace("-high16", "/high16");
                    detail = detail.replace("-lit8", "/lit8");
                    detail = detail.replace("-lit16", "/lit16");
                    detail = detail.replace("-4", "/4");
                    detail = detail.replace("-16", "/16");
                    detail = detail.replace("-32", "/32");
                    detail = detail.replace("-jumbo", "/jumbo");
                    detail = detail.replace("-range", "/range");
                    detail = detail.replace("-2addr", "/2addr");

                    // Unescape reserved words
                    detail = detail.replace("opc-", "");

                    description = detail;
                } else if ("verify".equals(type)) {
                    description = "verifier";
                } else {
                    description = type + " " + detail;
                }

                String details = (md.title != null ? md.title : "");
                if (md.constraint != null) {
                    details = " Constraint " + md.constraint + ", " + details;
                }
                if (details.length() != 0) {
                    details = details.substring(0, 1).toUpperCase()
                            + details.substring(1);
                }

                line += ";" + description + ";" + comment + ";" + details;

                datafileContent += line + "\n";
                generateBuildStepFor(pName, method, dependentTestClassNames,
                        targets);
            }


        }

        D8BuildStep dexBuildStep = new D8BuildStep(
            new BuildStep.BuildFile(new File(CLASSES_OUTPUT_FOLDER)),
            new BuildStep.BuildFile(new File(mainsJar)),
            false);

        targets.add(dexBuildStep);

        // write latest HOSTJUNIT generated file.
        flushHostJunitFile();

        File scriptDataDir = new File(OUTPUT_FOLDER + "/data/");
        scriptDataDir.mkdirs();
        writeToFile(new File(scriptDataDir, "scriptdata"), datafileContent);

        if (!hostJunitBuildStep.build()) {
            System.out.println("main javac cts-host-hostjunit-classes build step failed");
            System.exit(1);
        }

        if (!srcBuildStep.build()) {
            System.out.println("main src dalvik-cts-buildutil build step failed");
            System.exit(1);
        }

        for (BuildStep buildStep : targets) {
            if (!buildStep.build()) {
                System.out.println("building failed. buildStep: " +
                        buildStep.getClass().getName() + ", " + buildStep);
                System.exit(1);
            }
        }
    }

    private void generateBuildStepFor(String pName, String method,
            Collection<String> dependentTestClassNames, Set<BuildStep> targets) {


        for (String dependentTestClassName : dependentTestClassNames) {
            generateBuildStepForDependant(dependentTestClassName, targets);
        }
    }

    private void generateBuildStepForDependant(String dependentTestClassName,
            Set<BuildStep> targets) {

        File sourceFolder = new File(JAVASRC_FOLDER);
        String fileName = dependentTestClassName.replace('.', '/').trim();

        if (new File(sourceFolder, fileName + ".dfh").exists()) {

            BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
                    JAVASRC_FOLDER, fileName + ".dfh");
            BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
                    OUTPUT_FOLDER, fileName + ".dex");

            DFHBuildStep buildStep = new DFHBuildStep(inputFile, dexFile);

            BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
                    OUTPUT_FOLDER, fileName + ".jar");
            JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
                    "classes.dex", jarFile, true);
            jarBuildStep.addChild(buildStep);

            targets.add(jarBuildStep);
            return;
        }

        if (new File(sourceFolder, fileName + ".d").exists()) {

            BuildStep.BuildFile inputFile = new BuildStep.BuildFile(
                    JAVASRC_FOLDER, fileName + ".d");
            BuildStep.BuildFile dexFile = new BuildStep.BuildFile(
                    OUTPUT_FOLDER, fileName + ".dex");

            DasmBuildStep buildStep = new DasmBuildStep(inputFile, dexFile);

            BuildStep.BuildFile jarFile = new BuildStep.BuildFile(
                    OUTPUT_FOLDER, fileName + ".jar");

            JarBuildStep jarBuildStep = new JarBuildStep(dexFile,
                    "classes.dex", jarFile, true);
            jarBuildStep.addChild(buildStep);
            targets.add(jarBuildStep);
            return;
        }

        File srcFile = new File(sourceFolder, fileName + ".java");
        if (srcFile.exists()) {
            BuildStep dexBuildStep;
            dexBuildStep = generateDexBuildStep(
              COMPILED_CLASSES_FOLDER, fileName);
            targets.add(dexBuildStep);
            return;
        }

        try {
            if (Class.forName(dependentTestClassName) != null) {
                BuildStep dexBuildStep = generateDexBuildStep(
                    COMPILED_CLASSES_FOLDER, fileName);
                targets.add(dexBuildStep);
                return;
            }
        } catch (ClassNotFoundException e) {
            // do nothing
        }

        throw new RuntimeException("neither .dfh,.d,.java file of dependant test class found : " +
                dependentTestClassName + ";" + fileName);
    }

    private BuildStep generateDexBuildStep(String classFileFolder,
            String classFileName) {
        BuildStep.BuildFile classFile = new BuildStep.BuildFile(
                classFileFolder, classFileName + ".class");

        BuildStep.BuildFile tmpJarFile = new BuildStep.BuildFile(
                OUTPUT_FOLDER,
                classFileName + "_tmp.jar");

        JarBuildStep jarBuildStep = new JarBuildStep(classFile,
                classFileName + ".class", tmpJarFile, false);

        BuildStep.BuildFile outputFile = new BuildStep.BuildFile(
                OUTPUT_FOLDER,
                classFileName + ".jar");

        D8BuildStep dexBuildStep = new D8BuildStep(tmpJarFile,
                outputFile,
                true);

        dexBuildStep.addChild(jarBuildStep);
        return dexBuildStep;
    }

    /**
     * @param pName
     * @param classOnlyName
     * @param methodSource
     * @return testclass names
     */
    private List<String> parseTestClassName(String pName, String classOnlyName,
            String methodSource) {
        List<String> entries = new ArrayList<String>(2);
        String opcodeName = classOnlyName.substring(5);

        Scanner scanner = new Scanner(methodSource);

        String[] patterns = new String[] {"new\\s(T_" + opcodeName + "\\w*)",
                "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)"};

        String token = null;
        for (String pattern : patterns) {
            token = scanner.findWithinHorizon(pattern, methodSource.length());
            if (token != null) {
                break;
            }
        }

        if (token == null) {
            System.err.println("warning: failed to find dependent test class name: " + pName +
                    ", " + classOnlyName + " in methodSource:\n" + methodSource);
            return entries;
        }

        MatchResult result = scanner.match();

        entries.add((pName + ".d." + result.group(1)).trim());

        // search additional @uses directives
        Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
        Matcher m = p.matcher(methodSource);
        while (m.find()) {
            String res = m.group(1);
            entries.add(0, res.trim());
        }

        // search for " load(\"...\" " and add as dependency
        Pattern loadPattern = Pattern.compile("load\\(\"([^\"]*)\"", Pattern.MULTILINE);
        Matcher loadMatcher = loadPattern.matcher(methodSource);
        while (loadMatcher.find()) {
            String res = loadMatcher.group(1);
            entries.add(res.trim());
        }

        // search for " loadAndRun(\"...\" " and add as dependency
        Pattern loadAndRunPattern = Pattern.compile("loadAndRun\\(\"([^\"]*)\"", Pattern.MULTILINE);
        Matcher loadAndRunMatcher = loadAndRunPattern.matcher(methodSource);
        while (loadAndRunMatcher.find()) {
            String res = loadAndRunMatcher.group(1);
            entries.add(res.trim());
        }

        // lines with the form @uses
        // dot.junit.opcodes.add_double.jm.T_add_double_2
        // one dependency per one @uses
        // TODO

        return entries;
    }

    private MethodData parseTestMethod(String pname, String classOnlyName,
            String method) {

        String path = pname.replaceAll("\\.", "/");
        String absPath = JAVASRC_FOLDER + "/" + path + "/" + classOnlyName + ".java";
        File f = new File(absPath);

        Scanner scanner;
        try {
            scanner = new Scanner(f);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("error while reading to file: " + e.getClass().getName() +
                    ", msg:" + e.getMessage());
        }

        String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";

        String token = scanner.findWithinHorizon(methodPattern, (int) f.length());
        if (token == null) {
            throw new RuntimeException("cannot find method source of 'public void " + method +
                    "' in file '" + absPath + "'");
        }

        MatchResult result = scanner.match();
        result.start();
        result.end();

        StringBuilder builder = new StringBuilder();
        //builder.append(token);

        try {
            FileReader reader = new FileReader(f);
            reader.skip(result.end());

            int readResult;
            int blocks = 1;
            while ((readResult = reader.read()) != -1 && blocks > 0) {
                char currentChar = (char) readResult;
                switch (currentChar) {
                    case '}': {
                        blocks--;
                        builder.append(currentChar);
                        break;
                    }
                    case '{': {
                        blocks++;
                        builder.append(currentChar);
                        break;
                    }
                    default: {
                        builder.append(currentChar);
                        break;
                    }
                }
            }
            if (reader != null) {
                reader.close();
            }
        } catch (Exception e) {
            throw new RuntimeException("failed to parse", e);
        }

        // find the @title/@constraint in javadoc comment for this method
        // using platform's default charset
        String all = new String(FileUtils.readFile(f));
        // System.out.println("grepping javadoc found for method " + method +
        // " in " + pname + "," + classOnlyName);
        String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
        Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
        Matcher m = p.matcher(all);
        String title = null, constraint = null;
        if (m.find()) {
            String res = m.group(1);
            // System.out.println("res: " + res);
            // now grep @title and @constraint
            Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
            .matcher(res);
            if (titleM.find()) {
                title = titleM.group(1).replaceAll("\\n     \\*", "");
                title = title.replaceAll("\\n", " ");
                title = title.trim();
                // System.out.println("title: " + title);
            } else {
                System.err.println("warning: no @title found for method " + method + " in " + pname +
                        "," + classOnlyName);
            }
            // constraint can be one line only
            Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
                    res);
            if (constraintM.find()) {
                constraint = constraintM.group(1);
                constraint = constraint.trim();
                // System.out.println("constraint: " + constraint);
            } else if (method.contains("VFE")) {
                System.err
                .println("warning: no @constraint for for a VFE method:" + method + " in " +
                        pname + "," + classOnlyName);
            }
        } else {
            System.err.println("warning: no javadoc found for method " + method + " in " + pname +
                    "," + classOnlyName);
        }
        MethodData md = new MethodData();
        md.methodBody = builder.toString();
        md.constraint = constraint;
        md.title = title;
        if (scanner != null) {
            scanner.close();
        }
        return md;
    }

    private void writeToFileMkdir(File file, String content) {
        File parent = file.getParentFile();
        if (!parent.exists() && !parent.mkdirs()) {
            throw new RuntimeException("failed to create directory: " + parent.getAbsolutePath());
        }
        writeToFile(file, content);
    }

    private void writeToFile(File file, String content) {
        try {
            if (file.exists() && file.length() == content.length()) {
                FileReader reader = new FileReader(file);
                char[] charContents = new char[(int) file.length()];
                reader.read(charContents);
                reader.close();
                String contents = new String(charContents);
                if (contents.equals(content)) {
                    // System.out.println("skipping identical: "
                    // + file.getAbsolutePath());
                    return;
                }
            }

            //System.out.println("writing file " + file.getAbsolutePath());

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(file), "utf-8"));
            bw.write(content);
            bw.close();
        } catch (Exception e) {
            throw new RuntimeException("error while writing to file: " + e.getClass().getName() +
                    ", msg:" + e.getMessage());
        }
    }

    private File getFileFromPackage(String pname, String methodName)
    throws IOException {
        // e.g. dxc.junit.argsreturns.pargsreturn
        String path = getFileName(pname, methodName, ".java");
        String absPath = MAIN_SRC_OUTPUT_FOLDER + "/" + path;
        File dirPath = new File(absPath);
        File parent = dirPath.getParentFile();
        if (!parent.exists() && !parent.mkdirs()) {
            throw new IOException("failed to create directory: " + absPath);
        }
        return dirPath;
    }

    private String getFileName(String pname, String methodName,
            String extension) {
        String path = pname.replaceAll("\\.", "/");
        return new File(path, "Main_" + methodName + extension).getPath();
    }
}