001: package net.sf.mockcreator.codegeneration;
002:
003: import java.lang.reflect.Constructor;
004: import java.lang.reflect.Field;
005: import java.lang.reflect.Method;
006: import java.lang.reflect.Modifier;
007: import java.text.MessageFormat;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Set;
011: import java.util.HashSet;
012:
013: import net.sf.mockcreator.utils.SourceUtils;
014:
015: public class DumbyBuilder {
016:
017: private static Set built = new HashSet();
018:
019: private static final String ALERT_FIELD_NAME = "MOCK_CREATOR_ALERT";
020: private static final String DEPLOY_ALERT = "Class {0} is a dumby generated by MockCreator. "
021: + "Ensure you don't deploy it onto a real system instead of the actual class";
022: private static final String DUMBIFY_ALERT = "MockCreator found dumbified (fake) version of {0} "
023: + "instead of original, fully functional one. That means that your "
024: + "classpath is set incorrectly OR you have forgot to clean dumbified "
025: + "classes before running the build.";
026:
027: static IFormatter formatter = new FormatterJava15();
028:
029: public static String createDumby(MockDescriptor md)
030: throws Exception {
031:
032: // avoid duplicate generation of superclasses in the same run
033: String clzName = md.getSourceClass().getName();
034: if (built.contains(clzName))
035: return null;
036: built.add(clzName);
037:
038: formatter.setClass(md.getSourceClass());
039:
040: if (isAlreadyDumbified(md.getSourceClass())) {
041: throw new IllegalStateException(MessageFormat.format(
042: DUMBIFY_ALERT, new Object[] { md.getSourceClass()
043: .getName() }));
044: }
045:
046: StringBuffer sb = new StringBuffer();
047: appendPackageImports(sb, md);
048: appendClassHeader(sb, md);
049: appendFields(sb, md);
050: appendCtors(sb, md);
051:
052: appendMethods(sb, md);
053:
054: appendInners(sb, md);
055: appendClassTail(sb);
056:
057: return sb.toString();
058: }
059:
060: private static boolean isAlreadyDumbified(Class cls) {
061: try {
062: cls.getDeclaredField(ALERT_FIELD_NAME);
063: return true;
064: } catch (Exception e) {
065: return false;
066: }
067: }
068:
069: private static void appendPackageImports(StringBuffer sb,
070: MockDescriptor md) {
071: if (md.isTargetInDefaultPackage())
072: return;
073: if (md.isInner())
074: return;
075:
076: if (md.getSourcePackageName() != null
077: && !"".equals(md.getSourcePackageName())) {
078: sb.append("package ");
079: sb.append(md.getSourcePackageName());
080: sb.append(";\n");
081: sb.append("\n");
082: }
083: }
084:
085: public static void appendClassHeader(StringBuffer sb, Class md) {
086: sb.append("public ");
087:
088: if (!md.isInterface()) {
089: if ((Modifier.isAbstract(md.getModifiers()))) {
090: sb.append("abstract ");
091: }
092: if ((Modifier.isStatic(md.getModifiers()))) {
093: sb.append("static ");
094: }
095: sb.append("class ");
096: } else {
097: sb.append("interface ");
098: }
099:
100: sb.append(md.getName());
101: sb.append(' ');
102:
103: if (md.getSuperclass() != null) {
104: sb.append("extends ");
105: sb.append(md.getSuperclass());
106: sb.append(' ');
107: }
108:
109: Class[] ifaces = md.getInterfaces();
110: if (ifaces.length > 0)
111: sb.append("implements ");
112: for (int it = 0; it < ifaces.length; it++) {
113: if (it != 0)
114: sb.append(", ");
115:
116: String name = ifaces[it].getName();
117: int dollar = name.lastIndexOf('$');
118: if (dollar > 0)
119: name = name.substring(dollar + 1);
120: sb.append(name);
121: }
122:
123: sb.append(" {\n");
124:
125: if (!md.isInterface()) {
126: sb.append("private final static String "
127: + ALERT_FIELD_NAME
128: + " = \""
129: + MessageFormat.format(DEPLOY_ALERT,
130: new Object[] { md.getName() }) + "\";\n");
131: }
132: }
133:
134: private static void appendClassHeader(StringBuffer sb,
135: MockDescriptor md) {
136: sb.append("public ");
137:
138: if (!md.isInterface()) {
139: int modifiers = md.getSourceClass().getModifiers();
140: if (Modifier.isAbstract(modifiers)) {
141: sb.append("abstract ");
142: }
143: if (Modifier.isStatic(modifiers)) {
144: sb.append("static ");
145: }
146: sb.append("class ");
147: } else {
148: sb.append("interface ");
149: }
150:
151: sb.append(formatter.getClassName()); // md.getName());
152: sb.append(' ');
153:
154: if (md.getSuperClassName() != null) {
155: sb.append("extends ");
156: sb.append(md.getSuperClassName());
157: sb.append(' ');
158: }
159:
160: List ifaces = md.getInterfaceNames();
161: if (ifaces.size() > 0) {
162: if (md.isInterface())
163: sb.append("extends ");
164: else
165: sb.append("implements ");
166: }
167: for (Iterator it = ifaces.iterator(); it.hasNext();) {
168: String name = (String) it.next();
169: name = name.replace('$', '.');
170:
171: sb.append(name);
172: if (it.hasNext())
173: sb.append(',');
174: sb.append(' ');
175: }
176:
177: sb.append(" {\n");
178:
179: if (!md.isInterface()) {
180: sb.append("private final static String "
181: + ALERT_FIELD_NAME
182: + " = \""
183: + MessageFormat.format(DEPLOY_ALERT,
184: new Object[] { md.getSourceClass()
185: .getName() }) + "\";\n");
186: }
187: }
188:
189: private static void appendFields(StringBuffer sb, MockDescriptor md) {
190: Field[] fields = md.getSourceClass().getDeclaredFields();
191: for (int i = 0; i < fields.length; i++) {
192: Field field = fields[i];
193: if ((field.getModifiers() & Modifier.PRIVATE) != 0)
194: continue;
195: if (field.getName().indexOf('$') >= 0)
196: continue; // SYNTHETIC
197: appendField(sb, field);
198: }
199: }
200:
201: private static void appendCtors(StringBuffer sb, MockDescriptor md) {
202: if (md.isInterface())
203: return;
204:
205: boolean hasDefault = false;
206:
207: List lst = md.getCtors();
208: for (Iterator it = lst.iterator(); it.hasNext();) {
209: Constructor ctor = (Constructor) it.next();
210: if ((ctor.getModifiers() & Modifier.PRIVATE) != 0)
211: continue;
212: if (ctor.getParameterTypes().length == 0)
213: hasDefault = true;
214: appendCtor(sb, md, ctor);
215: }
216:
217: if (!hasDefault) {
218: appendCtor(sb, md, new Class[] {}, new Class[] {});
219: }
220: }
221:
222: private static void appendInners(StringBuffer sb, MockDescriptor md)
223: throws Exception {
224: Class[] ifaces = md.getSourceClass().getDeclaredClasses();
225: for (int i = 0; i < ifaces.length; ++i) {
226: Class clz = ifaces[i];
227: if (clz.getName().matches(".*[\\.\\$][0-9]+"))
228: continue;
229: appendInnerClass(sb, md, clz);
230: }
231: }
232:
233: private static void appendMethods(StringBuffer sb, MockDescriptor md) {
234: appendGetId(sb, md);
235:
236: Method[] methods = md.getSourceClass().getDeclaredMethods();
237: for (int i = 0; i < methods.length; i++) {
238: Method method = methods[i];
239:
240: if ((method.getModifiers() & Modifier.PRIVATE) != 0)
241: continue;
242: if (method.getName().indexOf('$') >= 0)
243: continue; // SYNTHETIC
244:
245: if (method.getName().equals("toString")
246: && String.class.equals(method.getReturnType())
247: && method.getParameterTypes().length == 0)
248: continue;
249:
250: if (method.getName().equals("hashCode")
251: && int.class.equals(method.getReturnType())
252: && method.getParameterTypes().length == 0)
253: continue;
254:
255: if (method.getName().equals("equals")
256: && boolean.class.equals(method.getReturnType())
257: && method.getParameterTypes().length == 1
258: && Object.class
259: .equals(method.getParameterTypes()[0]))
260: continue;
261:
262: if (md.isInterface()) {
263: MethodGenerator mg = new MethodGenerator(md, method);
264: String ms = mg.getMethodMockDeclaration();
265: sb.append(ms);
266: } else if (SourceUtils.isMockableInFakeSuperClass(method)) {
267: MethodGenerator mg = new MethodGenerator(md, method);
268: String ms = mg.getMethodMockMethods();
269: sb.append(ms);
270: } else {
271: appendMethod(sb, method);
272: }
273: }
274: }
275:
276: private static void appendGetId(StringBuffer sb, MockDescriptor md) {
277: int mods = md.getSourceClass().getModifiers();
278: // System.out.println("INNER:"+md.getSourceClass().getName()+" inner:"+md.isInner()+" static:"+mods+" is static: "+(mods & Modifier.STATIC));
279: if (md.isInterface())
280: return;
281:
282: sb.append("private ");
283: if (!md.isInner() || Modifier.isStatic(mods))
284: sb.append("static ");
285: sb
286: .append("String getClassObjectMethodSignature(String method){ return \""
287: + md.getTargetFullName()
288: + "\"+\"#\"+method; }\n");
289: }
290:
291: private static void appendClassTail(StringBuffer sb) {
292: sb.append("}\n");
293: }
294:
295: private static void appendCtor(StringBuffer sb, MockDescriptor md,
296: Constructor ctor) {
297: Class[] params = ctor.getParameterTypes();
298: if (params.length > 0 && SourceUtils.isSynthetic(params[0]))
299: return;
300: appendCtor(sb, md, params, ctor.getExceptionTypes());
301: }
302:
303: private static void appendCtor(StringBuffer sb, MockDescriptor md,
304: Class[] params, Class[] exceptions) {
305:
306: Class sup = md.getSourceClass().getSuperclass();
307: Constructor super Constructor = SourceUtils.getSameSuperCtor(
308: sup, params);
309: Class[] super Exceptions = super Constructor.getExceptionTypes();
310:
311: sb.append("public "); // enforce public; write a test!
312: sb.append(md.getName());
313: sb.append('(');
314: sb.append(SourceUtils.getParametersSpecification(params));
315: sb.append(") ");
316:
317: if (exceptions.length + super Exceptions.length > 0)
318: sb.append("throws ");
319: for (int i = 0; i < exceptions.length; i++) {
320: if (i > 0)
321: sb.append(", ");
322: Class ex = exceptions[i];
323: sb.append(SourceUtils.getCanonicalTypeSpelling(ex));
324: }
325: for (int i = 0; i < super Exceptions.length; i++) {
326: if (exceptions.length > 0 || i > 0)
327: sb.append(", ");
328: Class ex = super Exceptions[i];
329: sb.append(SourceUtils.getCanonicalTypeSpelling(ex));
330: }
331:
332: sb.append('{');
333:
334: if (sup != null) {
335: sb.append(SourceUtils.getSameSuperCtorCall(sup, params));
336: }
337:
338: sb.append('}');
339: sb.append('\n');
340: }
341:
342: private static void appendInnerClass(StringBuffer sb,
343: MockDescriptor md, Class clz) throws Exception {
344: MockDescriptor innerMd = new MockDescriptor(clz.getName());
345:
346: if (innerMd.getName().matches("[0-9]+")) {
347: return;
348: }
349:
350: IFormatter formatter = new FormatterJava15();
351: formatter.setClass(clz);
352: String en = formatter.getEnum();
353: if (formatter.getEnum() != null) {
354: sb.append('\n');
355: sb.append(en);
356: sb.append('\n');
357: return;
358: }
359:
360: String innerClass = createDumby(innerMd);
361: sb.append('\n');
362: sb.append(innerClass);
363: sb.append('\n');
364: }
365:
366: private static void appendField(StringBuffer sb, Field field) {
367:
368: int modifiers = field.getModifiers();
369: if ((modifiers & Modifier.PROTECTED) != 0)
370: sb.append("protected ");
371: if ((modifiers & Modifier.STATIC) != 0)
372: sb.append("static ");
373: if ((modifiers & Modifier.PUBLIC) != 0)
374: sb.append("public ");
375: if ((modifiers & Modifier.FINAL) != 0)
376: sb.append("final "); // never make them final?
377:
378: sb
379: .append(SourceUtils.getCanonicalTypeSpelling(field
380: .getType()));
381: sb.append(' ');
382: sb.append(field.getName());
383:
384: if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
385: sb.append(" = ");
386:
387: Object val = null;
388: try {
389: field.setAccessible(true);
390: val = field.get(null);
391: } catch (Exception e) {
392: System.err
393: .println("warning: failed to get field value for "
394: + field);
395: }
396:
397: String delim1 = null;
398: String delim2 = null;
399:
400: Class type = field.getType();
401: if (type.equals(String.class)) {
402: delim1 = "\"";
403: delim2 = delim1;
404:
405: if (val != null) {
406: val = escapeString(val);
407: }
408: } else if (type.equals(char.class)) {
409: delim1 = "'";
410: delim2 = delim1;
411:
412: if (val != null) {
413: val = escapeString("" + val);
414: }
415: } else if (type.equals(Character.class)) {
416: delim1 = "new Character('";
417: delim2 = "')";
418:
419: if (val != null) {
420: val = escapeString("" + val);
421: }
422: } else if (type.equals(long.class)) {
423: if (val == null)
424: val = new Long(0);
425: delim2 = "L";
426: } else if (type.equals(int.class)) {
427: if (val == null)
428: val = new Integer(0);
429: delim2 = "";
430: } else if (type.equals(short.class)) {
431: if (val == null)
432: val = new Short((short) 0);
433: delim2 = "";
434: } else if (type.equals(byte.class)) {
435: if (val == null)
436: val = new Byte((byte) 0);
437: delim2 = "";
438: } else if (type.equals(float.class)) {
439: if (val == null)
440: val = new Float(0.0f);
441: delim2 = "f";
442: } else if (type.equals(double.class)) {
443: if (val == null)
444: val = new Double(0.0);
445: delim2 = "";
446: } else if (type.equals(boolean.class)) {
447: if (val == null)
448: val = Boolean.FALSE;
449: delim2 = "";
450: } else if (!type.isPrimitive()) {
451: val = "null";
452: }
453:
454: if (delim1 != null)
455: sb.append(delim1);
456: sb.append(val);
457: if (delim2 != null)
458: sb.append(delim2);
459: }
460:
461: sb.append(";\n");
462: }
463:
464: private static Object escapeString(Object val) {
465: String s = (String) val;
466: char[] cs = s.toCharArray();
467:
468: StringBuffer sb = new StringBuffer();
469: for (int i = 0; i < cs.length; ++i) {
470: if (cs[i] == '\n')
471: sb.append("\\n");
472: else if (cs[i] == '\r')
473: sb.append("\\r");
474: else if (cs[i] == '\t')
475: sb.append("\\t");
476: else if (cs[i] == '\\')
477: sb.append("\\\\");
478: else
479: sb.append(cs[i]);
480: }
481: return sb.toString();
482: }
483:
484: private static void appendMethod(StringBuffer sb, Method method) {
485:
486: formatter.setMethod(method);
487:
488: int modifiers = method.getModifiers();
489: if ((modifiers & Modifier.PROTECTED) != 0)
490: sb.append("protected ");
491: if ((modifiers & Modifier.STATIC) != 0)
492: sb.append("static ");
493: if ((modifiers & Modifier.PUBLIC) != 0)
494: sb.append("public ");
495:
496: sb.append(formatter.getReturnType());
497: // sb.append(SourceUtils.getCanonicalTypeSpelling(method.getReturnType()));
498: sb.append(' ');
499:
500: // sb.append(method.getName());
501: sb.append(formatter.getMethodName());
502: sb.append('(');
503:
504: sb.append(formatter.getParameters());
505: // Class[] params = method.getParameterTypes();
506: // for (int i = 0; i < params.length; i++) {
507: // if( i > 0 ) sb.append(", ");
508: //
509: // Class param = params[i];
510: // sb.append(SourceUtils.getCanonicalTypeSpelling(param));
511: // sb.append(' ');
512: // sb.append("param"+i);
513: // }
514:
515: sb.append(") ");
516:
517: sb.append(formatter.getExceptions());
518: // Class[] exceptions = method.getExceptionTypes();
519: // if( exceptions.length > 0 ) sb.append("throws ");
520: // for (int i = 0; i < exceptions.length; i++) {
521: // if( i > 0 ) sb.append(", ");
522: //
523: // Class ex = exceptions[i];
524: // sb.append(SourceUtils.getCanonicalTypeSpelling(ex));
525: // }
526:
527: sb.append('{');
528:
529: sb
530: .append("throw new net.sf.mockcreator.exceptions.MockException("
531: + ALERT_FIELD_NAME + ");\n");
532:
533: sb.append('}');
534: sb.append('\n');
535: }
536: }
|