• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  [The "BSD licence"]
3  Copyright (c) 2007-2008 Leon Jen-Yuan Su
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions
8  are met:
9  1. Redistributions of source code must retain the above copyright
10     notice, this list of conditions and the following disclaimer.
11  2. Redistributions in binary form must reproduce the above copyright
12     notice, this list of conditions and the following disclaimer in the
13     documentation and/or other materials provided with the distribution.
14  3. The name of the author may not be used to endorse or promote products
15     derived from this software without specific prior written permission.
16 
17  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 package org.antlr.gunit;
29 
30 import org.antlr.stringtemplate.StringTemplate;
31 import org.antlr.stringtemplate.StringTemplateGroup;
32 import org.antlr.stringtemplate.StringTemplateGroupLoader;
33 import org.antlr.stringtemplate.CommonGroupLoader;
34 import org.antlr.stringtemplate.language.AngleBracketTemplateLexer;
35 
36 import java.io.*;
37 import java.lang.reflect.Method;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.logging.ConsoleHandler;
43 import java.util.logging.Handler;
44 import java.util.logging.Level;
45 import java.util.logging.Logger;
46 
47 public class JUnitCodeGen {
48     public GrammarInfo grammarInfo;
49     public Map<String, String> ruleWithReturn;
50     private final String testsuiteDir;
51     private String outputDirectoryPath = ".";
52 
53     private final static Handler console = new ConsoleHandler();
54     private static final Logger logger = Logger.getLogger(JUnitCodeGen.class.getName());
55     static {
56         logger.addHandler(console);
57     }
58 
JUnitCodeGen(GrammarInfo grammarInfo, String testsuiteDir)59     public JUnitCodeGen(GrammarInfo grammarInfo, String testsuiteDir) throws ClassNotFoundException {
60         this( grammarInfo, determineClassLoader(), testsuiteDir);
61     }
62 
determineClassLoader()63     private static ClassLoader determineClassLoader() {
64         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
65         if ( classLoader == null ) {
66             classLoader = JUnitCodeGen.class.getClassLoader();
67         }
68         return classLoader;
69     }
70 
JUnitCodeGen(GrammarInfo grammarInfo, ClassLoader classLoader, String testsuiteDir)71     public JUnitCodeGen(GrammarInfo grammarInfo, ClassLoader classLoader, String testsuiteDir) throws ClassNotFoundException {
72         this.grammarInfo = grammarInfo;
73         this.testsuiteDir = testsuiteDir;
74         /** Map the name of rules having return value to its return type */
75         ruleWithReturn = new HashMap<String, String>();
76         Class<?> parserClass = locateParserClass( grammarInfo, classLoader );
77         Method[] methods = parserClass.getDeclaredMethods();
78         for(Method method : methods) {
79             if ( !method.getReturnType().getName().equals("void") ) {
80                 ruleWithReturn.put(method.getName(), method.getReturnType().getName().replace('$', '.'));
81             }
82         }
83     }
84 
locateParserClass(GrammarInfo grammarInfo, ClassLoader classLoader)85     private Class<?> locateParserClass(GrammarInfo grammarInfo, ClassLoader classLoader) throws ClassNotFoundException {
86         String parserClassName = grammarInfo.getGrammarName() + "Parser";
87         if ( grammarInfo.getGrammarPackage() != null ) {
88             parserClassName = grammarInfo.getGrammarPackage()+ "." + parserClassName;
89         }
90         return classLoader.loadClass( parserClassName );
91     }
92 
getOutputDirectoryPath()93     public String getOutputDirectoryPath() {
94         return outputDirectoryPath;
95     }
96 
setOutputDirectoryPath(String outputDirectoryPath)97     public void setOutputDirectoryPath(String outputDirectoryPath) {
98         this.outputDirectoryPath = outputDirectoryPath;
99     }
100 
compile()101     public void compile() throws IOException{
102         String junitFileName;
103         if ( grammarInfo.getTreeGrammarName()!=null ) {
104             junitFileName = "Test"+grammarInfo.getTreeGrammarName();
105         }
106         else {
107             junitFileName = "Test"+grammarInfo.getGrammarName();
108         }
109         String lexerName = grammarInfo.getGrammarName()+"Lexer";
110         String parserName = grammarInfo.getGrammarName()+"Parser";
111 
112         StringTemplateGroupLoader loader = new CommonGroupLoader("org/antlr/gunit", null);
113         StringTemplateGroup.registerGroupLoader(loader);
114         StringTemplateGroup.registerDefaultLexer(AngleBracketTemplateLexer.class);
115         StringBuffer buf = compileToBuffer(junitFileName, lexerName, parserName);
116         writeTestFile(".", junitFileName+".java", buf.toString());
117     }
118 
compileToBuffer(String className, String lexerName, String parserName)119     public StringBuffer compileToBuffer(String className, String lexerName, String parserName) {
120         StringTemplateGroup group = StringTemplateGroup.loadGroup("junit");
121         StringBuffer buf = new StringBuffer();
122         buf.append(genClassHeader(group, className, lexerName, parserName));
123         buf.append(genTestRuleMethods(group));
124         buf.append("\n\n}");
125         return buf;
126     }
127 
genClassHeader(StringTemplateGroup group, String junitFileName, String lexerName, String parserName)128     protected String genClassHeader(StringTemplateGroup group, String junitFileName, String lexerName, String parserName) {
129         StringTemplate classHeaderST = group.getInstanceOf("classHeader");
130         if ( grammarInfo.getTestPackage()!=null ) {	// Set up class package if there is
131             classHeaderST.setAttribute("header", "package "+grammarInfo.getTestPackage()+";");
132         }
133         classHeaderST.setAttribute("junitFileName", junitFileName);
134 
135         String lexerPath = null;
136         String parserPath = null;
137         String treeParserPath = null;
138         String packagePath = null;
139         boolean isTreeGrammar = false;
140         boolean hasPackage = false;
141         /** Set up appropriate class path for parser/tree parser if using package */
142         if ( grammarInfo.getGrammarPackage()!=null ) {
143             hasPackage = true;
144             packagePath = "./"+grammarInfo.getGrammarPackage().replace('.', '/');
145             lexerPath = grammarInfo.getGrammarPackage()+"."+lexerName;
146             parserPath = grammarInfo.getGrammarPackage()+"."+parserName;
147             if ( grammarInfo.getTreeGrammarName()!=null ) {
148                 treeParserPath = grammarInfo.getGrammarPackage()+"."+grammarInfo.getTreeGrammarName();
149                 isTreeGrammar = true;
150             }
151         }
152         else {
153             lexerPath = lexerName;
154             parserPath = parserName;
155             if ( grammarInfo.getTreeGrammarName()!=null ) {
156                 treeParserPath = grammarInfo.getTreeGrammarName();
157                 isTreeGrammar = true;
158             }
159         }
160         // also set up custom tree adaptor if necessary
161         String treeAdaptorPath = null;
162         boolean hasTreeAdaptor = false;
163         if ( grammarInfo.getAdaptor()!=null ) {
164             hasTreeAdaptor = true;
165             treeAdaptorPath = grammarInfo.getAdaptor();
166         }
167         classHeaderST.setAttribute("hasTreeAdaptor", hasTreeAdaptor);
168         classHeaderST.setAttribute("treeAdaptorPath", treeAdaptorPath);
169         classHeaderST.setAttribute("hasPackage", hasPackage);
170         classHeaderST.setAttribute("packagePath", packagePath);
171         classHeaderST.setAttribute("lexerPath", lexerPath);
172         classHeaderST.setAttribute("parserPath", parserPath);
173         classHeaderST.setAttribute("treeParserPath", treeParserPath);
174         classHeaderST.setAttribute("isTreeGrammar", isTreeGrammar);
175         return classHeaderST.toString();
176     }
177 
genTestRuleMethods(StringTemplateGroup group)178     protected String genTestRuleMethods(StringTemplateGroup group) {
179         StringBuffer buf = new StringBuffer();
180         if ( grammarInfo.getTreeGrammarName()!=null ) {	// Generate junit codes of for tree grammar rule
181             genTreeMethods(group, buf);
182         }
183         else {	// Generate junit codes of for grammar rule
184             genParserMethods(group, buf);
185         }
186         return buf.toString();
187     }
188 
genParserMethods(StringTemplateGroup group, StringBuffer buf)189     private void genParserMethods(StringTemplateGroup group, StringBuffer buf) {
190         for ( gUnitTestSuite ts: grammarInfo.getRuleTestSuites() ) {
191             int i = 0;
192             for ( Map.Entry<gUnitTestInput, AbstractTest> entry : ts.testSuites.entrySet() ) {	// each rule may contain multiple tests
193                 gUnitTestInput input = entry.getKey();
194                 i++;
195                 StringTemplate testRuleMethodST;
196                 /** If rule has multiple return values or ast*/
197                 if ( entry.getValue().getType()== gUnitParser.ACTION && ruleWithReturn.containsKey(ts.getRuleName()) ) {
198                     testRuleMethodST = group.getInstanceOf("testRuleMethod2");
199                     String outputString = entry.getValue().getText();
200                     testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getRuleName())+i);
201                     testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"');
202                     testRuleMethodST.setAttribute("test", input);
203                     testRuleMethodST.setAttribute("returnType", ruleWithReturn.get(ts.getRuleName()));
204                     testRuleMethodST.setAttribute("expecting", outputString);
205                 }
206                 else {
207                     String testRuleName;
208                     // need to determine whether it's a test for parser rule or lexer rule
209                     if ( ts.isLexicalRule() ) testRuleName = ts.getLexicalRuleName();
210                     else testRuleName = ts.getRuleName();
211                     testRuleMethodST = group.getInstanceOf("testRuleMethod");
212                     String outputString = entry.getValue().getText();
213                     testRuleMethodST.setAttribute("isLexicalRule", ts.isLexicalRule());
214                     testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(testRuleName)+i);
215                     testRuleMethodST.setAttribute("testRuleName", '"'+testRuleName+'"');
216                     testRuleMethodST.setAttribute("test", input);
217                     testRuleMethodST.setAttribute("tokenType", getTypeString(entry.getValue().getType()));
218 
219                     // normalize whitespace
220                     outputString = normalizeTreeSpec(outputString);
221 
222                     if ( entry.getValue().getType()==gUnitParser.ACTION ) {	// trim ';' at the end of ACTION if there is...
223                         //testRuleMethodST.setAttribute("expecting", outputString.substring(0, outputString.length()-1));
224                         testRuleMethodST.setAttribute("expecting", outputString);
225                     }
226                     else if ( entry.getValue().getType()==gUnitParser.RETVAL ) {	// Expected: RETVAL
227                         testRuleMethodST.setAttribute("expecting", outputString);
228                     }
229                     else {	// Attach "" to expected STRING or AST
230                         // strip newlines for (...) tree stuff
231                         outputString = outputString.replaceAll("\n", "");
232                         testRuleMethodST.setAttribute("expecting", '"'+escapeForJava(outputString)+'"');
233                     }
234                 }
235                 buf.append(testRuleMethodST.toString());
236             }
237         }
238     }
239 
genTreeMethods(StringTemplateGroup group, StringBuffer buf)240     private void genTreeMethods(StringTemplateGroup group, StringBuffer buf) {
241         for ( gUnitTestSuite ts: grammarInfo.getRuleTestSuites() ) {
242             int i = 0;
243             for ( Map.Entry<gUnitTestInput, AbstractTest> entry : ts.testSuites.entrySet() ) {	// each rule may contain multiple tests
244                 gUnitTestInput input = entry.getKey();
245                 i++;
246                 StringTemplate testRuleMethodST;
247                 /** If rule has multiple return values or ast*/
248                 if ( entry.getValue().getType()== gUnitParser.ACTION && ruleWithReturn.containsKey(ts.getTreeRuleName()) ) {
249                     testRuleMethodST = group.getInstanceOf("testTreeRuleMethod2");
250                     String outputString = entry.getValue().getText();
251                     testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getTreeRuleName())+"_walks_"+
252                                                                 changeFirstCapital(ts.getRuleName())+i);
253                     testRuleMethodST.setAttribute("testTreeRuleName", '"'+ts.getTreeRuleName()+'"');
254                     testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"');
255                     testRuleMethodST.setAttribute("test", input);
256                     testRuleMethodST.setAttribute("returnType", ruleWithReturn.get(ts.getTreeRuleName()));
257                     testRuleMethodST.setAttribute("expecting", outputString);
258                 }
259                 else {
260                     testRuleMethodST = group.getInstanceOf("testTreeRuleMethod");
261                     String outputString = entry.getValue().getText();
262                     testRuleMethodST.setAttribute("methodName", "test"+changeFirstCapital(ts.getTreeRuleName())+"_walks_"+
263                                                                 changeFirstCapital(ts.getRuleName())+i);
264                     testRuleMethodST.setAttribute("testTreeRuleName", '"'+ts.getTreeRuleName()+'"');
265                     testRuleMethodST.setAttribute("testRuleName", '"'+ts.getRuleName()+'"');
266                     testRuleMethodST.setAttribute("test", input);
267                     testRuleMethodST.setAttribute("tokenType", getTypeString(entry.getValue().getType()));
268 
269                     if ( entry.getValue().getType()==gUnitParser.ACTION ) {	// trim ';' at the end of ACTION if there is...
270                         //testRuleMethodST.setAttribute("expecting", outputString.substring(0, outputString.length()-1));
271                         testRuleMethodST.setAttribute("expecting", outputString);
272                     }
273                     else if ( entry.getValue().getType()==gUnitParser.RETVAL ) {	// Expected: RETVAL
274                         testRuleMethodST.setAttribute("expecting", outputString);
275                     }
276                     else {	// Attach "" to expected STRING or AST
277                         testRuleMethodST.setAttribute("expecting", '"'+escapeForJava(outputString)+'"');
278                     }
279                 }
280                 buf.append(testRuleMethodST.toString());
281             }
282         }
283     }
284 
285     // return a meaningful gUnit token type name instead of using the magic number
getTypeString(int type)286     public String getTypeString(int type) {
287         String typeText;
288         switch (type) {
289             case gUnitParser.OK :
290                 typeText = "org.antlr.gunit.gUnitParser.OK";
291                 break;
292             case gUnitParser.FAIL :
293                 typeText = "org.antlr.gunit.gUnitParser.FAIL";
294                 break;
295             case gUnitParser.STRING :
296                 typeText = "org.antlr.gunit.gUnitParser.STRING";
297                 break;
298             case gUnitParser.ML_STRING :
299                 typeText = "org.antlr.gunit.gUnitParser.ML_STRING";
300                 break;
301             case gUnitParser.RETVAL :
302                 typeText = "org.antlr.gunit.gUnitParser.RETVAL";
303                 break;
304             case gUnitParser.AST :
305                 typeText = "org.antlr.gunit.gUnitParser.AST";
306                 break;
307             default :
308                 typeText = "org.antlr.gunit.gUnitParser.EOF";
309                 break;
310         }
311         return typeText;
312     }
313 
writeTestFile(String dir, String fileName, String content)314     protected void writeTestFile(String dir, String fileName, String content) {
315         try {
316             File f = new File(dir, fileName);
317             FileWriter w = new FileWriter(f);
318             BufferedWriter bw = new BufferedWriter(w);
319             bw.write(content);
320             bw.close();
321             w.close();
322         }
323         catch (IOException ioe) {
324             logger.log(Level.SEVERE, "can't write file", ioe);
325         }
326     }
327 
escapeForJava(String inputString)328     public static String escapeForJava(String inputString) {
329         // Gotta escape literal backslash before putting in specials that use escape.
330         inputString = inputString.replace("\\", "\\\\");
331         // Then double quotes need escaping (singles are OK of course).
332         inputString = inputString.replace("\"", "\\\"");
333         // note: replace newline to String ".\n", replace tab to String ".\t"
334         inputString = inputString.replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r").replace("\b", "\\b").replace("\f", "\\f");
335 
336         return inputString;
337     }
338 
changeFirstCapital(String ruleName)339     protected String changeFirstCapital(String ruleName) {
340         String firstChar = String.valueOf(ruleName.charAt(0));
341         return firstChar.toUpperCase()+ruleName.substring(1);
342     }
343 
normalizeTreeSpec(String t)344     public static String normalizeTreeSpec(String t) {
345         List<String> words = new ArrayList<String>();
346         int i = 0;
347         StringBuilder word = new StringBuilder();
348         while ( i<t.length() ) {
349             if ( t.charAt(i)=='(' || t.charAt(i)==')' ) {
350                 if ( word.length()>0 ) {
351                     words.add(word.toString());
352                     word.setLength(0);
353                 }
354                 words.add(String.valueOf(t.charAt(i)));
355                 i++;
356                 continue;
357             }
358             if ( Character.isWhitespace(t.charAt(i)) ) {
359                 // upon WS, save word
360                 if ( word.length()>0 ) {
361                     words.add(word.toString());
362                     word.setLength(0);
363                 }
364                 i++;
365                 continue;
366             }
367 
368             // ... "x" or ...("x"
369             if ( t.charAt(i)=='"' && (i-1)>=0 &&
370                  (t.charAt(i-1)=='(' || Character.isWhitespace(t.charAt(i-1))) )
371             {
372                 i++;
373                 while ( i<t.length() && t.charAt(i)!='"' ) {
374                     if ( t.charAt(i)=='\\' &&
375                          (i+1)<t.length() && t.charAt(i+1)=='"' ) // handle \"
376                     {
377                         word.append('"');
378                         i+=2;
379                         continue;
380                     }
381                     word.append(t.charAt(i));
382                     i++;
383                 }
384                 i++; // skip final "
385                 words.add(word.toString());
386                 word.setLength(0);
387                 continue;
388             }
389             word.append(t.charAt(i));
390             i++;
391         }
392         if ( word.length()>0 ) {
393             words.add(word.toString());
394         }
395         //System.out.println("words="+words);
396         StringBuilder buf = new StringBuilder();
397         for (int j=0; j<words.size(); j++) {
398             if ( j>0 && !words.get(j).equals(")") &&
399                  !words.get(j-1).equals("(") ) {
400                 buf.append(' ');
401             }
402             buf.append(words.get(j));
403         }
404         return buf.toString();
405     }
406 
407 }
408