001: /**
002: * SpikeTestGen.java
003: * TestGen4J is licensed under Open Software License 2.1
004: * For details, please refer to:
005: * http://www.opensource.org/licenses/osl-2.1.php
006: */package com.spikesource.spiketestgen;
007:
008: import com.sun.javadoc.ClassDoc;
009:
010: import com.sun.javadoc.DocErrorReporter;
011: import com.sun.javadoc.MethodDoc;
012: import com.sun.javadoc.PackageDoc;
013: import com.sun.javadoc.Parameter;
014: import com.sun.javadoc.RootDoc;
015: import java.lang.reflect.Modifier;
016: import java.io.BufferedReader;
017: import java.io.BufferedWriter;
018: import java.io.File;
019: import java.io.FileNotFoundException;
020: import java.io.FileReader;
021: import java.io.FileWriter;
022: import java.io.IOException;
023: import java.lang.reflect.Constructor;
024: import java.lang.Class;
025: import java.util.Properties;
026: import java.util.StringTokenizer;
027:
028: /**
029: * SpikeTestGen is a doclet, which, with the help of class and
030: * method signatures given by Classdoc , * generates unit test
031: * cases requiring just the class files of the package under test.
032: * It decouples the test code and test data using the open source
033: * tool called JTestCase. Unit Test cases are then fed to JUnit
034: * and results are taken.
035: *
036: * @version 0.1.4-alpha
037: * @author Manish Marathe *
038: */
039: public final class SpikeTestGen {
040: /**
041: * The custom option value -d used to specify the directory,
042: * where the unit test cases are generated.
043: */
044:
045: private SpikeTestGen() {
046:
047: }
048:
049: /**
050: * Specifies the output directory.
051: */
052: protected static final String OPTION_OUTPUT_DIR = "-d";
053:
054: /**
055: * String variable which stores the output directory.
056: */
057: private static String outputDIR = null;
058:
059: /**
060: * Copyright and Start-up Notice.
061: */
062: private static String copyRIGHT = "\n*****************************************"
063: + "*****************************************\n"
064: + "*********** Copyright (C) 2005 SpikeSource,"
065: + " Inc. (www.spikesource.com) ***********\n"
066: + "************************************************"
067: + "**********************************\n";
068:
069: /**
070: * Important Information.
071: */
072: private static String note = "Please Note that for classes having no methods,"
073: + " including the main method, no test \n"
074: + "cases are created, and there is no entry for "
075: + "them in the Test Suite.\n\n";
076:
077: /**
078: * Main method of the doclet. Parses the information
079: * given by Classdoc in the form of class and method signatures.
080: *
081: * @param root
082: * Contains parsed information from Classdoc/Javadoc.
083: * @return
084: * Returns true if the operations in the method are valid.
085: */
086: public static boolean start(final RootDoc root) {
087: StringBuffer allParams = new StringBuffer();
088: String className = "", suiteName = "";
089: String dataFile;
090: String packageName;
091: String returnType = null;
092: int flagCreateClassTestSuite = 0;
093: TestDataGeneration td = new TestDataGeneration();
094: TestCodeGeneration cd = new TestCodeGeneration();
095: TestCaseLogger lg = new TestCaseLogger();
096: ClassDoc[] classes = null;
097: PackageDoc[] packageDocs;
098: boolean isTestingClassNecessary = true;
099: boolean isTestingMethodNecessary = true;
100: packageDocs = root.specifiedPackages();
101: Parameter[] pp = null;
102:
103: outputDIR = readOptions(root.options());
104:
105: dataFile = td.createXMLDataFile(outputDIR);
106:
107: printNotice(copyRIGHT);
108:
109: for (int pkgNum = 0; pkgNum < packageDocs.length; pkgNum++) {
110: packageName = packageDocs[pkgNum].toString();
111:
112: classes = packageDocs[pkgNum].ordinaryClasses();
113:
114: printNotice("\nGenerating TestSuites and TestCases for package: \""
115: + packageName + "\"\n");
116:
117: if ((classes.length > 0) && (flagCreateClassTestSuite != 1)) {
118: flagCreateClassTestSuite = 1;
119: suiteName = cd.startClassTestSuite(outputDIR,
120: packageDocs, "PackageTestSuite");
121: }
122:
123: for (int i = 0; i < classes.length; i++) {
124: if ((classes[i].isAbstract())
125: || (classes[i].isInterface())
126: || (!classes[i].isPublic())
127: || (classes[i].toString().indexOf("$") != -1)) {
128: isTestingClassNecessary = false;
129: }
130:
131: if (isTestingClassNecessary) {
132: MethodDoc[] methods = classes[i].methods();
133:
134: if (classes[i].toString().indexOf('.') != -1) {
135: StringTokenizer name = new StringTokenizer(
136: classes[i].toString(), ".");
137: className = getToken(name, name.countTokens());
138: } else if (classes[i].toString().indexOf('.') == -1) {
139: className = classes[i].toString();
140: }
141:
142: if (methods.length > 0) {
143: printNotice("Writing TestCase " + classes[i]
144: + "Test");
145:
146: String[] methodArray = new String[methods.length];
147: int[] methodNumArray = new int[methods.length];
148:
149: for (int a = 0; a < methodArray.length; a++) {
150: methodArray[a] = "10101010";
151: methodNumArray[a] = 0;
152: }
153:
154: td.writeClassDetailsToXMLDataFile(className,
155: outputDIR, dataFile);
156:
157: cd.generateTop(packageName, classes[i],
158: outputDIR);
159:
160: for (int j = 0; j < methods.length; j++) {
161: if (!methods[j].isPublic()) {
162: isTestingMethodNecessary = false;
163: }
164:
165: pp = methods[j].parameters();
166: for (int x = 0; x < pp.length; x++) {
167: if (pp[x].type().qualifiedTypeName()
168: .toString().indexOf("$") != -1) {
169: isTestingMethodNecessary = false;
170: }
171: }
172:
173: returnType = getMethodReturnType(methods[j]);
174:
175: if (returnType.indexOf("$") != -1) {
176: StringTokenizer t = new StringTokenizer(
177: returnType, "$");
178: String mainClass = t.nextToken();
179: String innerClass = t.nextToken();
180: try {
181: Constructor[] c = Class.forName(
182: mainClass)
183: .getConstructors();
184: Class[] d = Class
185: .forName(mainClass)
186: .getDeclaredClasses();
187: for (int dd = 0; dd < d.length; dd++) {
188: if (d[dd].getName().equals(
189: returnType)) {
190: if (!Modifier
191: .isPublic(d[dd]
192: .getModifiers())) {
193: System.out
194: .println("Caught!!, The method: "
195: + methods[j]
196: + " returns: "
197: + returnType
198: + " which has an inner class"
199: + " and it is not public");
200: isTestingMethodNecessary = false;
201: }
202: }
203: }
204: Constructor[] c1 = Class.forName(
205: returnType)
206: .getConstructors();
207: if (c.length == 0) {
208: System.out
209: .println("Caught!!, The class "
210: + Class
211: .forName(
212: mainClass)
213: .getName()
214: + " does not have a public constructor"
215: + " and so it cannot be instantiated.");
216: isTestingMethodNecessary = false;
217: }
218: } catch (java.lang.ClassNotFoundException cnf) {
219: cnf.printStackTrace();
220: }
221: }
222:
223: try {
224: if (isTestingMethodNecessary) {
225: testMethod(allParams, methods[j],
226: methodArray,
227: methodNumArray, j,
228: className, dataFile);
229: }
230: } catch (IOException e) {
231: e.printStackTrace();
232: }
233: isTestingMethodNecessary = true;
234: }
235:
236: cd.endMethodTestSuite(className, outputDIR);
237:
238: endTestClass(className);
239:
240: td.endClassInXMLDataFile(outputDIR, dataFile);
241:
242: if (flagCreateClassTestSuite == 1) {
243: cd.continueClassTestSuite(outputDIR,
244: suiteName, className);
245: }
246: }
247: }
248:
249: isTestingClassNecessary = true;
250: }
251:
252: }
253: if (flagCreateClassTestSuite == 1) {
254: cd.endClassTestSuite(outputDIR, suiteName);
255: }
256:
257: td.endXMLDataFile(outputDIR, dataFile);
258: try {
259: lg.logTestCase(outputDIR);
260: cd.createClassParseFailedDataFile(outputDIR);
261: } catch (IOException e) {
262: e.printStackTrace();
263: }
264: printNotice(note);
265:
266: return true;
267: }
268:
269: /**
270: * Get the method Parameter list.
271: *
272: * @param allParams
273: * All parameters of the method.
274: * @param method
275: * Array of all methods.
276: *
277: * @param methodArray
278: * Method name for overridden methods.
279: *
280: * @param methodNumArray
281: * Number of overridden methods.
282: *
283: * @param currentMethod
284: * Integer value of current method.
285: *
286: * @param className
287: * Name of the original class.
288: *
289: * @param dataFile
290: * Name of the XML data file.
291: * @throws IOException
292: * Throws IOException.
293: */
294: public static void testMethod(final StringBuffer allParams,
295: final MethodDoc method, final String[] methodArray,
296: final int[] methodNumArray, final int currentMethod,
297: final String className, final String dataFile)
298: throws IOException {
299:
300: int l = 0;
301: String methodName, methodReturnType;
302: boolean overloaded = false;
303: TestDataGeneration td = new TestDataGeneration();
304: TestCodeGeneration cd = new TestCodeGeneration();
305:
306: String[] methodParams = new String[method.parameters().length];
307:
308: l = method.parameters().length;
309: StringTokenizer st = new StringTokenizer(method.toString(), "(");
310:
311: methodName = st.nextToken();
312:
313: for (int a = 0; a < methodArray.length; a++) {
314: if (methodArray[a].equals(methodName)) {
315: overloaded = true;
316: methodNumArray[currentMethod]++;
317: }
318: }
319:
320: methodArray[currentMethod] = methodName;
321:
322: String finalToken = st.nextToken();
323:
324: if (!finalToken.equals(")")) {
325: for (int k = 0; k < (finalToken.length() - 1); k++) {
326: allParams.append(finalToken.charAt(k));
327: }
328:
329: methodParams = getParameters(allParams, method);
330: }
331:
332: allParams.setLength(0);
333:
334: StringTokenizer getMethodName = new StringTokenizer(methodName
335: .toString(), ".");
336:
337: String name = getToken(getMethodName, getMethodName
338: .countTokens());
339:
340: if (overloaded) {
341: name = name + methodNumArray[currentMethod];
342: }
343:
344: methodReturnType = getMethodReturnType(method);
345: td.writeMethodDetailsToXMLDataFile(name, methodReturnType,
346: methodParams, l, outputDIR, dataFile);
347: cd.writeTestMethod(name, methodName, methodReturnType, method
348: .isStatic(), methodParams, l, outputDIR);
349: cd.addTestMethodToTestSuite(className, name, outputDIR);
350:
351: }
352:
353: /**
354: * Get the method Parameter list.
355: *
356: * @param allParams
357: * All parameters of the method.
358: * @param method
359: * Array of all methods.
360: * @return
361: * method Parameters
362: */
363: public static String[] getParameters(final StringBuffer allParams,
364: final MethodDoc method) {
365: String scanType;
366: String tempScanType;
367: int l = 0;
368:
369: RulesEngine rules = new RulesEngine();
370:
371: String[] methodParams = new String[method.parameters().length];
372:
373: if (allParams.toString().indexOf(",") != -1) {
374: StringTokenizer singleParam = new StringTokenizer(allParams
375: .toString(), ",");
376:
377: while (singleParam.hasMoreTokens()) {
378: scanType = singleParam.nextToken();
379: tempScanType = scanType;
380:
381: StringTokenizer dType = new StringTokenizer(scanType,
382: ".");
383:
384: String argumentType = getToken(dType, dType
385: .countTokens());
386:
387: argumentType = argumentType.trim();
388:
389: if (rules.getConditions(argumentType) != null) {
390: methodParams[l] = argumentType + ","
391: + rules.getConditions(argumentType);
392: l++;
393: } else {
394: methodParams[l] = tempScanType + "," + "NULL";
395: l++;
396: }
397:
398: scanType = "";
399: tempScanType = "";
400: }
401: } else if (allParams.toString().indexOf(",") == -1) {
402: tempScanType = allParams.toString();
403:
404: StringTokenizer dType = new StringTokenizer(allParams
405: .toString(), ".");
406:
407: String argumentType = getToken(dType, dType.countTokens());
408: argumentType = argumentType.trim();
409:
410: if (rules.getConditions(argumentType) != null) {
411: methodParams[l] = argumentType + ","
412: + rules.getConditions(argumentType);
413: l++;
414: } else {
415: methodParams[l] = tempScanType + "," + "NULL";
416: l++;
417: }
418: }
419: return methodParams;
420: }
421:
422: /**
423: * Returns the return type of original method.
424: *
425: * @param method
426: * Name of the original class.
427: * @return
428: * return type of the method.
429: */
430: public static String getMethodReturnType(final MethodDoc method) {
431: String methodReturnType;
432:
433: methodReturnType = method.returnType().qualifiedTypeName()
434: .toString();
435:
436: if ((methodReturnType.indexOf('.') != -1)
437: && (method.returnType().toString().indexOf('.') == -1)) {
438: methodReturnType = "";
439: StringTokenizer getMethodReturnType = new StringTokenizer(
440: method.returnType().qualifiedTypeName().toString(),
441: ".");
442:
443: int tokens = getMethodReturnType.countTokens();
444:
445: for (int r = 0; r < (tokens - 1); r++) {
446: methodReturnType = methodReturnType
447: + getMethodReturnType.nextToken() + ".";
448: }
449:
450: methodReturnType.trim();
451:
452: methodReturnType = methodReturnType
453: + method.returnType().toString();
454:
455: } else {
456: methodReturnType = "";
457: methodReturnType = method.returnType().toString();
458: }
459: return methodReturnType;
460: }
461:
462: /**
463: * End the testclass file.
464: *
465: * @param className
466: * Name of the original class.
467: */
468: public static void endTestClass(final String className) {
469: BufferedWriter out;
470: String classFileName = className + "Test.java";
471: String tempFileName = className + "Test.temp.java";
472:
473: File handle = new File(outputDIR, tempFileName);
474: File handle1 = new File(outputDIR, classFileName);
475: String copy = "";
476:
477: try {
478: if (handle.exists()) {
479: BufferedReader in = new BufferedReader(new FileReader(
480: handle));
481:
482: out = null;
483:
484: try {
485: out = new BufferedWriter(new FileWriter(handle1,
486: true));
487:
488: while ((copy = in.readLine()) != null) {
489: out.write(copy);
490: out.newLine();
491: }
492:
493: out.newLine();
494: out.write("}");
495: out.newLine();
496: out.newLine();
497: out.newLine();
498: out.flush();
499: out.close();
500: in.close();
501:
502: if (handle.exists()) {
503: handle.delete();
504: }
505: } catch (IOException e2) {
506: e2.printStackTrace();
507: }
508: } else {
509: try {
510: out = new BufferedWriter(new FileWriter(handle1,
511: true));
512: out.newLine();
513: out.write("}");
514: out.flush();
515: out.close();
516: } catch (IOException e3) {
517: e3.printStackTrace();
518: }
519: }
520: } catch (FileNotFoundException e) {
521: e.printStackTrace();
522: }
523: }
524:
525: /**
526: * Retrieves a particular token from the array of tokens,
527: * given the token number to be retrieved.
528: *
529: * @param tokens
530: * An array of tokens, which is an
531: * Object of StringTokenizer.
532: * @param tokenReqNum
533: * Token Number, specifying the index
534: * of the token in request
535: * @return
536: * Returns the requested token.
537: */
538: public static String getToken(final StringTokenizer tokens,
539: final int tokenReqNum) {
540:
541: for (int k = 1; k < tokenReqNum; k++) {
542: tokens.nextToken();
543: }
544:
545: return tokens.nextToken();
546: }
547:
548: /**
549: * Read the complete doclet options.
550: *
551: * @param options
552: * Read doclet options.
553: *
554: * @return
555: * Output directory.
556: */
557: private static String readOptions(final String[][] options) {
558: Properties p = new Properties(System.getProperties());
559: String outputDir = null;
560:
561: for (int i = 0; i < options.length; i++) {
562: String[] opt = options[i];
563: if (opt[0].equals("-d")) {
564: outputDir = opt[1];
565: }
566: }
567:
568: if (outputDir == null) {
569: outputDir = p.getProperty("user.dir");
570: }
571:
572: return outputDir;
573: }
574:
575: /**
576: * Retrieve the option length for a particular
577: * option.
578: *
579: * @param option
580: * Get the option length.
581: * @return
582: * Option length.
583: */
584: public static int optionLength(final String option) {
585: if (option.equals("-d")) {
586: return 2;
587: }
588: return 0;
589: }
590:
591: /**
592: * Verify if the options given are valid.
593: *
594: * @param options
595: * Doclet options
596: * @param reporter
597: * Error reporter
598: * @return
599: * Returns true if the -d option is valid.
600: */
601: public static boolean validOptions(final String[][] options,
602: final DocErrorReporter reporter) {
603: boolean foundDirOption = false;
604: for (int i = 0; i < options.length; i++) {
605: String[] opt = options[i];
606: if (opt[0].equals("-d")) {
607: if (foundDirOption) {
608: reporter.printError("Only one -d option allowed.");
609: return false;
610: } else {
611: foundDirOption = true;
612: }
613: }
614: }
615: if (!foundDirOption) {
616: reporter.printError("Usage: javadoc -d "
617: + "outputDir -doclet "
618: + "com.spikesource.spiketestgen.SpikeTestGen "
619: + "-docletpath myTestGen4JDir/bin/ "
620: + "-sourcepath myPackageSource "
621: + "-package myPackage");
622: }
623: return foundDirOption;
624: }
625:
626: /**
627: * This function prints the important information
628: * passed in form of String to the Standard Output.
629: *
630: * @param msg
631: * String to be printed as a Notice.
632: */
633: public static void printNotice(final String msg) {
634: System.out.println(msg);
635: }
636: }
|