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 java.io.BufferedWriter;
20 import java.io.File;
21 import java.io.FileOutputStream;
22 import java.io.FileReader;
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.io.StringReader;
26 import java.net.URL;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map.Entry;
31 import java.util.Scanner;
32 import java.util.regex.MatchResult;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 
36 /**
37  * Helper base class for code generators.
38  */
39 public abstract class BuildUtilBase {
40 
41     public static boolean DEBUG = true;
42 
43     public static class MethodData {
44         String methodBody, constraint, title;
45     }
46 
47     public interface TestHandler {
handleTest(String fqcn, List<String> methods)48         public void handleTest(String fqcn, List<String> methods);
49     }
50 
run(TestHandler handler)51     public void run(TestHandler handler) {
52         System.out.println("Collecting all junit tests...");
53         JUnitTestCollector tests = new JUnitTestCollector(getClass().getClassLoader());
54 
55         handleTests(tests, handler);
56     }
57 
handleTests(JUnitTestCollector tests, TestHandler handler)58     protected void handleTests(JUnitTestCollector tests, TestHandler handler) {
59         System.out.println("collected " + tests.testMethodsCnt + " test methods in " +
60                 tests.testClassCnt + " junit test classes");
61 
62         for (Entry<String, List<String>> entry : tests.map.entrySet()) {
63             handler.handleTest(entry.getKey(), entry.getValue());
64         }
65     }
66 
readURL(URL in)67     private static String readURL(URL in) {
68         // Use common scanner idiom to read a complete InputStream into a string.
69         try (Scanner scanner = new Scanner(in.openStream(), StandardCharsets.UTF_8.toString())) {
70             scanner.useDelimiter("\\A");  // This delimits by "start of content," of which there is
71                                           // only one.
72             return scanner.hasNext() ? scanner.next() : null;
73         } catch (IOException e) {
74             throw new RuntimeException(e);
75         }
76     }
77 
parseTestMethod(String pname, String classOnlyName, String method)78     protected MethodData parseTestMethod(String pname, String classOnlyName,
79             String method) {
80         String searchPath = "src/" + pname.replaceAll("\\.", "/") + "/" + classOnlyName + ".java";
81         String content;
82         {
83             URL resource = getClass().getClassLoader().getResource(searchPath);
84             if (resource == null) {
85                 throw new RuntimeException("Could not find " + searchPath);
86             }
87             content = readURL(resource);
88             if (content == null) {
89                 throw new RuntimeException("Could not retrieve content for " + searchPath);
90             }
91         }
92 
93         final String methodPattern = "public\\s+void\\s+" + method + "[^\\{]+\\{";
94 
95         int methodSkip;
96         try (Scanner scanner = new Scanner(content)) {
97             String token = scanner.findWithinHorizon(methodPattern, content.length());
98             if (token == null) {
99                 throw new RuntimeException("cannot find method source of 'public void " + method +
100                         "' in file '" + searchPath + "'");
101             }
102 
103             MatchResult result = scanner.match();
104             result.start();
105             methodSkip = result.end();
106         }
107 
108         StringBuilder builder = new StringBuilder();
109 
110         try {
111             StringReader reader = new StringReader(content);
112             reader.skip(methodSkip);
113 
114             int readResult;
115             int blocks = 1;
116             while ((readResult = reader.read()) != -1 && blocks > 0) {
117                 char currentChar = (char) readResult;
118                 switch (currentChar) {
119                     case '}': {
120                         blocks--;
121                         builder.append(currentChar);
122                         break;
123                     }
124                     case '{': {
125                         blocks++;
126                         builder.append(currentChar);
127                         break;
128                     }
129                     default: {
130                         builder.append(currentChar);
131                         break;
132                     }
133                 }
134             }
135             if (reader != null) {
136                 reader.close();
137             }
138         } catch (Exception e) {
139             throw new RuntimeException("failed to parse", e);
140         }
141 
142         // find the @title/@constraint in javadoc comment for this method
143         // using platform's default charset
144 
145         // System.out.println("grepping javadoc found for method " + method +
146         // " in " + pname + "," + classOnlyName);
147         String commentPattern = "/\\*\\*([^{]*)\\*/\\s*" + methodPattern;
148         Pattern p = Pattern.compile(commentPattern, Pattern.DOTALL);
149         Matcher m = p.matcher(content);
150         String title = null, constraint = null;
151         if (m.find()) {
152             String res = m.group(1);
153             // System.out.println("res: " + res);
154             // now grep @title and @constraint
155             Matcher titleM = Pattern.compile("@title (.*)", Pattern.DOTALL)
156             .matcher(res);
157             if (titleM.find()) {
158                 title = titleM.group(1).replaceAll("\\n     \\*", "");
159                 title = title.replaceAll("\\n", " ");
160                 title = title.trim();
161                 // System.out.println("title: " + title);
162             } else {
163                 System.err.println("warning: no @title found for method " + method + " in " + pname +
164                         "," + classOnlyName);
165             }
166             // constraint can be one line only
167             Matcher constraintM = Pattern.compile("@constraint (.*)").matcher(
168                     res);
169             if (constraintM.find()) {
170                 constraint = constraintM.group(1);
171                 constraint = constraint.trim();
172                 // System.out.println("constraint: " + constraint);
173             } else if (method.contains("VFE")) {
174                 System.err
175                 .println("warning: no @constraint for for a VFE method:" + method + " in " +
176                         pname + "," + classOnlyName);
177             }
178         } else {
179             System.err.println("warning: no javadoc found for method " + method + " in " + pname +
180                     "," + classOnlyName);
181         }
182         MethodData md = new MethodData();
183         md.methodBody = builder.toString();
184         md.constraint = constraint;
185         md.title = title;
186         return md;
187     }
188 
189     /**
190      * @param pName
191      * @param classOnlyName
192      * @param methodSource
193      * @return testclass names
194      */
parseTestClassName(String pName, String classOnlyName, String methodSource)195     protected static List<String> parseTestClassName(String pName, String classOnlyName,
196             String methodSource) {
197         List<String> entries = new ArrayList<String>(2);
198         String opcodeName = classOnlyName.substring(5);
199 
200         try (Scanner scanner = new Scanner(methodSource)) {
201             String[] patterns = new String[] { "new\\s(T_" + opcodeName + "\\w*)",
202                     "(T_" + opcodeName + "\\w*)", "new\\s(T\\w*)" };
203 
204             String token = null;
205             for (String pattern : patterns) {
206                 token = scanner.findWithinHorizon(pattern, methodSource.length());
207                 if (token != null) {
208                     break;
209                 }
210             }
211 
212             if (token == null) {
213                 System.err.println("warning: failed to find dependent test class name: " + pName
214                         + ", " + classOnlyName + " in methodSource:\n" + methodSource);
215                 return entries;
216             }
217 
218             MatchResult result = scanner.match();
219 
220             entries.add((pName + ".d." + result.group(1)).trim());
221 
222             // search additional @uses directives
223             Pattern p = Pattern.compile("@uses\\s+(.*)\\s+", Pattern.MULTILINE);
224             Matcher m = p.matcher(methodSource);
225             while (m.find()) {
226                 String res = m.group(1);
227                 entries.add(0, res.trim());
228             }
229 
230             // search for " load(\"...\" " and add as dependency
231             Pattern loadPattern = Pattern.compile("load\\(\"([^\"]*)\"", Pattern.MULTILINE);
232             Matcher loadMatcher = loadPattern.matcher(methodSource);
233             while (loadMatcher.find()) {
234                 String res = loadMatcher.group(1);
235                 entries.add(res.trim());
236             }
237 
238             // search for " loadAndRun(\"...\" " and add as dependency
239             Pattern loadAndRunPattern = Pattern.compile("loadAndRun\\(\"([^\"]*)\"",
240                     Pattern.MULTILINE);
241             Matcher loadAndRunMatcher = loadAndRunPattern.matcher(methodSource);
242             while (loadAndRunMatcher.find()) {
243                 String res = loadAndRunMatcher.group(1);
244                 entries.add(res.trim());
245             }
246 
247             // lines with the form @uses
248             // dot.junit.opcodes.add_double.jm.T_add_double_2
249             // one dependency per one @uses
250             // TODO
251 
252             return entries;
253         }
254     }
255 
writeToFileMkdir(File file, String content)256     public static void writeToFileMkdir(File file, String content) {
257         File parent = file.getParentFile();
258         if (!parent.exists() && !parent.mkdirs()) {
259             throw new RuntimeException("failed to create directory: " + parent.getAbsolutePath());
260         }
261         writeToFile(file, content);
262     }
263 
writeToFile(File file, String content)264     public static void writeToFile(File file, String content) {
265         try {
266             if (file.exists() && file.length() == content.length()) {
267                 FileReader reader = new FileReader(file);
268                 char[] charContents = new char[(int) file.length()];
269                 reader.read(charContents);
270                 reader.close();
271                 String contents = new String(charContents);
272                 if (contents.equals(content)) {
273                     // System.out.println("skipping identical: "
274                     // + file.getAbsolutePath());
275                     return;
276                 }
277             }
278 
279             //System.out.println("writing file " + file.getAbsolutePath());
280 
281             BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
282                     new FileOutputStream(file), "utf-8"));
283             bw.write(content);
284             bw.close();
285         } catch (Exception e) {
286             throw new RuntimeException("error while writing to file: " + e.getClass().getName() +
287                     ", msg:" + e.getMessage());
288         }
289     }
290 
291 }
292