001: package com.avaje.util.codegen;
002:
003: import java.util.logging.Level;
004: import java.util.logging.Logger;
005:
006: import com.avaje.ebean.server.deploy.generatedproperty.GeneratedPropertySettings;
007: import com.avaje.lib.log.LogFactory;
008: import com.avaje.lib.sql.ColumnInfo;
009: import com.avaje.lib.sql.DictionaryInfo;
010: import com.avaje.lib.sql.Fkey;
011: import com.avaje.lib.sql.FkeyColumn;
012: import com.avaje.lib.sql.TableInfo;
013: import com.avaje.lib.util.Dnode;
014:
015: public class BuildBean implements Contants {
016:
017: private static final Logger logger = LogFactory
018: .get(BuildBean.class);
019:
020: String manyFullType = "java.util.List";
021:
022: String manyShortType = "List";
023:
024: boolean includeOneCascadeType;
025:
026: String oneCascadeType = "(cascade=CascadeType.ALL)";
027:
028: boolean includeManyCascadeType;
029:
030: String manyCascadeType = "(cascade=CascadeType.ALL)";
031:
032: int hairyExportThreshold = 2;
033:
034: TableNaming tableNaming;
035:
036: TableType tableType;
037:
038: GeneratedPropertySettings versionSettings;
039:
040: BuildProperty buildProperty;
041:
042: BuildEmbeddedId buildEmbedded;
043:
044: DictionaryInfo dictionaryInfo;
045:
046: GenerateConfiguration config;
047:
048: public BuildBean(GenerateConfiguration config) {
049: this .config = config;
050:
051: tableType = config.getTableType();
052: dictionaryInfo = config.getDictionaryInfo();
053: tableNaming = config.getTableNaming();
054: versionSettings = config.getGeneratedPropertySettings();
055:
056: buildProperty = new BuildProperty(config);
057: buildEmbedded = new BuildEmbeddedId(config);
058: }
059:
060: public void build(GenerateInfo info) {
061:
062: Dnode beanTree = info.getBeanTree();
063:
064: // generate the class Comment
065: String className = info.getClassName();
066:
067: String classComment = buildProperty.getClassComment(className);
068:
069: beanTree.setAttribute("classcomment", classComment);
070:
071: TableInfo tableInfo = info.getTableInfo();
072: // make sure foreign keys are loaded...
073: tableInfo.getImportedFkeys();
074:
075: beanTree.setAttribute("table", tableInfo.getName());
076:
077: info.addImportAnnotation(ENTITY);
078: info.addImportAnnotation(TABLE);
079:
080: beanTree.setAttribute("entityAnnotation", ENTITY);
081:
082: String tableAnn = TABLE + "(name=\"" + tableInfo.getName()
083: + "\")";
084: beanTree.setAttribute("tableAnnotation", tableAnn);
085:
086: // add the Id (perhaps embeddedId)
087: addId(info);
088:
089: // add the non-foreign key properties
090: addBaseProperties(info);
091:
092: addAssocOnes(info);
093: addAssocManys(info);
094:
095: }
096:
097: private void addId(GenerateInfo info) {
098:
099: TableInfo tableInfo = info.getTableInfo();
100: Dnode beanTree = info.getBeanTree();
101:
102: ColumnInfo[] keyColumns = tableInfo.getKeyColumns();
103: if (keyColumns.length == 0) {
104: // No primary key...
105: // Add annotation so IDE shows warning (unused import)
106: info.addImportAnnotation(ID);
107:
108: } else if (keyColumns.length == 1) {
109: // Single column primary key
110: ColumnInfo columnInfo = keyColumns[0];
111:
112: Dnode property = buildProperty.createColumnNode(info,
113: tableInfo, columnInfo);
114:
115: property.setAttribute("idAnnotation", ID);
116: property.setAttribute("id", "true");
117: info.addImportAnnotation(ID);
118:
119: addDerivedInfo(info, property);
120: beanTree.addChild(property);
121:
122: } else {
123: // builds the GenerateInfo for the embedded bean
124: // and adds the id property to this 'parentInfo'
125: // String classComment = buildProperty.getClassComment(className);
126: buildEmbedded.build(info);
127: }
128: }
129:
130: private void addBaseProperties(GenerateInfo info) {
131:
132: TableInfo tableInfo = info.getTableInfo();
133: Dnode beanTree = info.getBeanTree();
134:
135: ColumnInfo[] columns = tableInfo.getColumns();
136: for (int i = 0; i < columns.length; i++) {
137:
138: ColumnInfo columnInfo = columns[i];
139: if (columnInfo.isPrimaryKey() || columnInfo.isForeignKey()) {
140: // exclude primary and foreign key columns
141:
142: } else {
143: Dnode property = buildProperty.createColumnNode(info,
144: tableInfo, columnInfo);
145: addDerivedInfo(info, property);
146: beanTree.addChild(property);
147:
148: String propName = (String) property
149: .getAttribute("name");
150: String propType = (String) property
151: .getAttribute("type");
152: String colName = columnInfo.getName();
153:
154: if (versionSettings.isVersion(propName, propType,
155: colName)) {
156:
157: property.setAttribute("versionAnnotation", VERSION);
158: info.addImportAnnotation(VERSION);
159: }
160: }
161: }
162: }
163:
164: /**
165: * Check to see if this is a 'lookup' table or if its exports more than the
166: * hairyExportThreshold number. If this is the case we do not add the
167: * OneToMany or ManyToMany associations.
168: */
169: private void addAssocManys(GenerateInfo info) {
170:
171: TableInfo tableInfo = info.getTableInfo();
172: Fkey[] exportedKeys = tableInfo.getExportedFkeys();
173:
174: if (tableType.isLookup(tableInfo)) {
175: // seems to be a 'lookup' table so not adding any OneToMany etc
176: String m = " Lookup: Not generating ["
177: + exportedKeys.length + "] AssocMany's";
178:
179: config.print(info.getClassName());
180: config.println(m);
181:
182: info.getLog().addLookup(info);
183: return;
184: }
185:
186: if (tableType.isHairy(tableInfo)) {
187: // Bean too 'Hairy' so not including OneToMany's
188: String m = " Hairy: Not generating ["
189: + exportedKeys.length + "] AssocMany's";
190:
191: config.print(info.getClassName());
192: config.println(m);
193:
194: info.getLog().addHairy(info);
195: return;
196: }
197:
198: // add all the OneToMany or ManyToMany properties
199: for (int i = 0; i < exportedKeys.length; i++) {
200: boolean explicitJoin = needsExplicitJoin(i, exportedKeys);
201:
202: addAssocManysProperty(info, exportedKeys[i], explicitJoin);
203: }
204: }
205:
206: private boolean needsExplicitJoin(int i, Fkey[] exportedKeys) {
207:
208: for (int j = 0; j < exportedKeys.length; j++) {
209: if (j != i) {
210: String ti = exportedKeys[i].getTableName();
211: String tj = exportedKeys[j].getTableName();
212: if (ti.equals(tj)) {
213: return true;
214: }
215: }
216: }
217: return false;
218: }
219:
220: private String getJoinAnnotation(Fkey fkey, GenerateInfo info) {
221: FkeyColumn[] cols = fkey.columns();
222: if (cols.length > 1) {
223: // TODO: JOINCOLUMNS... annotation
224: return null;
225:
226: } else {
227: // propName = getManyPropertyName(className, cols[0],
228: // info.getClassName());
229:
230: String joinColumn = cols[0].getFkColumnName();
231:
232: info.addImportAnnotation(JOINCOLUMN);
233: return JOINCOLUMN + "(name=\"" + joinColumn + "\")";
234: }
235: }
236:
237: /**
238: * Add OneToMany or ManyToMany associations.
239: */
240: private void addAssocManysProperty(GenerateInfo info, Fkey fkey,
241: boolean needExplicitJoin) {
242:
243: String fkTable = fkey.getTableName();
244: TableInfo foreignTable = dictionaryInfo.getTableInfo(fkTable);
245:
246: boolean oneToOne = false;
247:
248: // the foreign table is a 'pure' intersection table
249: // with no columns other than the primary key
250: boolean manyToMany = tableType.isIntersection(foreignTable);
251: if (manyToMany) {
252: // search for other side of the ManyToMany
253: String localTable = info.getTableInfo().getName();
254: fkTable = getManyToManyOtherTable(localTable, foreignTable);
255: if (fkTable == null) {
256: // an error occured... just carry on.
257: return;
258: }
259:
260: config.print(info.getClassName());
261: String msg = " ManyToMany tables " + localTable + " to "
262: + fkTable + " via " + foreignTable;
263: config.println(msg);
264:
265: } else {
266: // check for one to one...
267: Fkey inverseFkey = dictionaryInfo.findInverse(fkey);
268: if (inverseFkey.isImportUnique()) {
269: // this is a OneToOne
270: oneToOne = true;
271: }
272: }
273:
274: // the type and package of the foreign key
275: String className = tableNaming.toBeanClassName(fkTable);
276: String pkgName = tableNaming.toPackage(fkTable);
277:
278: String propName;
279: if (oneToOne) {
280: propName = getOnePropertyName(className);
281: } else {
282: propName = getManyPropertyName(className);
283: }
284:
285: String joinAnn = null;
286: if (needExplicitJoin) {
287: // explicit join annotation required
288: joinAnn = getJoinAnnotation(fkey, info);
289: propName = getManyPropertyName(className, fkey, info
290: .getClassName());
291: }
292:
293: String importType = pkgName + "." + className;
294: info.addImport(importType);
295:
296: // create the property node
297: Dnode property = new Dnode();
298: property.setNodeName("property");
299: property.setAttribute("name", propName);
300:
301: if (joinAnn != null) {
302: property.setAttribute("joinAnnotation", joinAnn);
303: }
304:
305: if (oneToOne) {
306: property.setAttribute("type", className);
307: } else {
308: property.setAttribute("type", manyFullType);
309: }
310:
311: addDerivedInfo(info, property);
312:
313: if (!oneToOne) {
314: // TODO: List Set Map many property types
315: String extClassName = manyShortType + "<" + className + ">";
316: property.setAttribute("shorttype", extClassName);
317:
318: }
319:
320: if (oneToOne) {
321: config.print(info.getClassName());
322: String msg = " OneToOne " + className + " "
323: + property.getAttribute("name");
324: config.println(msg);
325: }
326: if (manyToMany) {
327: config.print(info.getClassName());
328: String msg = " ManyToMany " + className + " "
329: + property.getAttribute("name");
330: config.println(msg);
331: }
332:
333: String ann;
334: if (manyToMany) {
335: ann = MANYTOMANY;
336: info.addImportAnnotation(MANYTOMANY);
337:
338: } else if (oneToOne) {
339: ann = ONETOONE;
340: info.addImportAnnotation(ONETOONE);
341:
342: } else {
343: ann = ONETOMANY;
344: info.addImportAnnotation(ONETOMANY);
345: }
346: if (includeManyCascadeType) {
347: ann += manyCascadeType;
348: info.addImportAnnotation(CASCADETYPE);
349: }
350:
351: property.setAttribute("assocAnnotation", ann);
352:
353: Dnode beanTree = info.getBeanTree();
354: beanTree.addChild(property);
355: }
356:
357: /**
358: * Find the other table in a ManyToMany association.
359: */
360: private String getManyToManyOtherTable(String localTable,
361: TableInfo intersectionTable) {
362:
363: Fkey[] fkeys = intersectionTable.getImportedFkeys();
364: // String localTable = info.getTableInfo().getName();
365: localTable = localTable.toLowerCase();
366:
367: String otherManyTable = null;
368:
369: for (int i = 0; i < fkeys.length; i++) {
370: String destTable = fkeys[i].getTableName().toLowerCase();
371: if (!destTable.equals(localTable)) {
372: if (otherManyTable != null) {
373: String m = "Error: multiple possible destinations";
374: m += " on ManyToMany from [" + localTable + "] to ";
375: m += "[" + otherManyTable + "] and [" + destTable
376: + "]?";
377: logger.log(Level.SEVERE, m);
378: }
379: otherManyTable = destTable;
380: }
381: }
382: if (otherManyTable == null) {
383: String m = "Error: Could not find other side of ManyToMany";
384: m += " starting from [" + localTable + "] via ["
385: + intersectionTable + "]";
386: logger.log(Level.SEVERE, m);
387: return null;
388: }
389:
390: return otherManyTable;
391: }
392:
393: /**
394: * Add ManyToOne type associations from foreign keys.
395: */
396: private void addAssocOnes(GenerateInfo info) {
397:
398: TableInfo tableInfo = info.getTableInfo();
399: Dnode beanTree = info.getBeanTree();
400:
401: Fkey[] fkeys = tableInfo.getImportedFkeys();
402: for (int i = 0; i < fkeys.length; i++) {
403:
404: Fkey fkey = fkeys[i];
405: String fkTable = fkey.getTableName();
406:
407: // the type and package of the foreign key
408: String className = tableNaming.toBeanClassName(fkTable);
409: String pkgName = tableNaming.toPackage(fkTable);
410:
411: String fullClassName = pkgName + "." + className;
412:
413: Dnode property = null;
414: FkeyColumn[] fkColumns = fkey.columns();
415: if (fkColumns.length == 1) {
416: property = buildProperty.createForeignKey(tableInfo,
417: fkColumns[0].getFkColumnName());
418: } else {
419: property = buildProperty.createForeignKey(tableInfo,
420: fkey.getFkName());
421: }
422:
423: property.setAttribute("type", fullClassName);
424: addDerivedInfo(info, property);
425:
426: String ann = MANYTOONE;
427: if (fkey.isImportUnique()) {
428: // there is a unique constraint on the matching
429: // foreign key column making this a OneToOne
430: ann = ONETOONE;
431: config.print(info.getClassName());
432: String msg = " OneToOne " + className + " "
433: + property.getAttribute("name");
434: config.println(msg);
435: }
436: if (includeOneCascadeType) {
437: ann += oneCascadeType;
438: info.addImportAnnotation(CASCADETYPE);
439: }
440: property.setAttribute("assocAnnotation", ann);
441: info.addImportAnnotation(ann);
442:
443: beanTree.addChild(property);
444: }
445: }
446:
447: private void addDerivedInfo(GenerateInfo info, Dnode beanProp) {
448: buildProperty.addDerivedInfo(info, beanProp);
449: }
450:
451: private String joinParts(String[] parts, String exclude) {
452:
453: exclude = exclude.toLowerCase();
454:
455: int count = 0;
456:
457: StringBuffer sb = new StringBuffer();
458: for (int i = 0; i < parts.length; i++) {
459: if (parts[i].length() > 2) {
460: String s = parts[i].toLowerCase();
461: if (!s.equals("id") && !s.equals(exclude)) {
462: if (count > 0) {
463: sb.append(Character.toUpperCase(s.charAt(0)));
464: sb.append(s.substring(1));
465: } else {
466: sb.append(s);
467: }
468: count++;
469: }
470: }
471: }
472:
473: return sb.toString();
474: }
475:
476: private String getManyPropertyName(String type, Fkey fkey,
477: String this ClassName) {
478:
479: // TODO: this doesn't work for concatinated keys...
480: FkeyColumn[] fkColumns = fkey.columns();
481: String prop = type + "s";
482:
483: String fkColumnName = fkColumns[0].getFkColumnName();
484: String[] parts = fkColumnName.split("[_]");
485:
486: prop = joinParts(parts, this ClassName) + prop;
487:
488: return prop;
489: }
490:
491: private String getOnePropertyName(String type) {
492: return Character.toLowerCase(type.charAt(0))
493: + type.substring(1);
494: }
495:
496: private String getManyPropertyName(String type) {
497: int lastUpperPos = 0;
498: for (int i = 0; i < type.length(); i++) {
499: char c = type.charAt(i);
500: if (Character.isUpperCase(c)) {
501: lastUpperPos = i;
502: }
503: }
504:
505: // get a plural name to use for the many property
506: // This just bangs on a 's' so it will create incorrect
507: // english words, near enough for now...
508:
509: // TODO: plural many property name conversion for correct english
510: String propName = Character.toLowerCase(type
511: .charAt(lastUpperPos))
512: + type.substring(lastUpperPos + 1) + "s";
513:
514: return propName;
515:
516: }
517: }
|