View Javadoc

1   /*
2    * Copyright 2006 Outsource Cafe, Inc.
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  package org.javagen.revgen.mapping;
17  
18  import org.javagen.agile.core.util.RegexRenamer;
19  import org.javagen.agile.core.util.StringUtil;
20  import org.javagen.agile.db.model.Cardinality;
21  import org.javagen.agile.db.model.Column;
22  import org.javagen.agile.db.model.FkConstraint;
23  import org.javagen.agile.db.model.FkEnum;
24  import org.javagen.agile.db.model.Table;
25  import org.javagen.agile.oo.model.Class;
26  import org.javagen.agile.oo.naming.Collections;
27  import org.javagen.agile.oo.naming.JavaNamingService;
28  import org.javagen.agile.oo.naming.OONamingService;
29  import org.javagen.agile.oo.util.OOUtil;
30  
31  /***
32   * Maps DB elements to Java types and names.
33   * <p>
34   * TODO with a little more refactoring this class could be non-Java specific and renamed DefaultDb2OOMapper.
35   * 
36   * @author Richard Easterling
37   */
38  public class Db2JavaMapper implements Db2OOMapper {
39  	
40      public static final String DEFAULT_TYPE = "String";
41      
42  	protected OONamingService ooNamingService;
43      private Collections collections;
44      private String defaultType = DEFAULT_TYPE;
45      private ReferenceNamingStrategy referenceNamingStrategy = ReferenceNamingStrategy.useTargetClass;
46      private RegexRenamer regexRenamer;
47      private boolean pluralTableNames = false;
48      
49  	public Db2JavaMapper() {}
50  	
51      ///////////////////////////////////////////////////////////////////////////
52      // Db2JavaMapper interface methods:
53      ///////////////////////////////////////////////////////////////////////////
54  
55      /***
56       * Uses camel-back naming conventions to convert table name to class name converting
57       * plural to singular names as specified in the <code>OONamingService</code>.
58       * <p>Applies regular expression renaming if a RegexRenamer is set.
59       */
60      public String classNameSingular(Table table) {
61          String name = OOUtil.camelBackJavaClass(table.getName());
62          if (pluralTableNames)
63              name = getOONamingService().toSingular(name);
64          return regexRename(name);
65      }
66  
67      /***
68       * Uses camel-back naming conventions to convert column name to property name.
69       * <p>Applies regular expression renaming if a RegexRenamer is set.
70       */
71      public String propertyNameSingular(Column column) {
72          String propeName = OOUtil.camelBackJavaClass(column.getName());
73          if (pluralTableNames)
74              propeName = getOONamingService().toSingular(propeName);
75          String varName = OOUtil.varFromClassName(propeName);
76          return regexRename(varName);
77      }
78  
79      /***
80       * Tries to find best Java type for column type.  The following rules are applied in order:
81       * <ol>
82       * <li>if column size is 1 char and default is 'Y', 'N', 'T' or 'F' then convert to a boolean.</li>
83       * <li>If NUMERIC OR DECIMAL use the smallest possible Java type for the specified precision.</li>
84       * <li>If column is primary or foreign key, use wrapper types for numbers.</li>
85       * <li>For non-null simple types use primitives (byte, int, float), otherwise use the wrapper types (Byte, Integer, Float).</li>
86       * <li>Otherwise use default JDBC mapping types.</li>
87       * <li>If still no match is found use the <code>defaultType</code>, set by default to a String.</li>
88       * </ol>
89       */
90      public String propertyType(Column column) {
91          java.lang.Class<?> booleanType = Db2JavaUtil.booleanTypeCanidate(column);
92          if (booleanType!=null) {
93              return booleanType.getName();
94          } else {
95              java.lang.Class<?> minimalNumericType = Db2JavaUtil.minimalJavaType(column);
96              if (minimalNumericType!=null) {
97                  java.lang.Class<?> primitiveType = Db2JavaUtil.primitiveMappingCanidate(minimalNumericType, column);
98                  if (primitiveType!=null) {
99                      return primitiveType.getName();
100                 } else {
101                     return minimalNumericType.getName();
102                 }
103             } 
104         }
105         java.lang.Class<?> type = Db2JavaUtil.javaType(column.getDbType());
106         if (type==null) {
107             return defaultType;
108         } else {
109             return type.getName();
110         }
111 	}
112  
113     /***
114      * Given a foreign key constraint assign the property type based on the target class,
115      * including special handling needed for relations containing link tables.
116      */
117 	public Class referenceType(FkConstraint fkConstraint) {
118         Class type = null;
119         Table targetTable = fkConstraint.getTargetTable();
120         if (Boolean.TRUE.equals(targetTable.isLinkTable())) {
121             for(FkConstraint linkFkColumn : fkConstraint.getTargetTable().getFkColumns()) {
122                 if (fkConstraint.getParentTable() != linkFkColumn.getTargetTable())
123                     targetTable = linkFkColumn.getTargetTable();
124             }
125         }
126         type = Db2JavaUtil.getClassFromTable(targetTable);
127 		return type;
128 	}
129 
130 	/***
131 	 * Name reference based on ReferenceNamingStrategy.
132 	 * Name will be plural or singular based on cardinality.
133      * <p>Applies regular expression renaming if a RegexRenamer is set.
134 	 * @param fkConstraint
135 	 * @return reference name for resulting Java property.
136 	 */
137 	public String referenceName(FkConstraint fkConstraint) {
138         String referencePropName = null;
139         ReferenceNamingStrategy strategy = referenceNamingStrategy;
140         if (strategy == ReferenceNamingStrategy.useFkColumn && (fkConstraint.columnReferencesSize()>1 || FkEnum.EXPORTED == fkConstraint.getFkType())) {
141             //useFkColumn naming has limited applicability, fallback when it won't work
142             strategy = ReferenceNamingStrategy.useTargetClass;
143         }
144         switch (strategy) {
145             case useTargetClass:
146                 Class targetClass = referenceType(fkConstraint);
147                 boolean toMany = isManyCardinality(fkConstraint);
148                 referencePropName = toMany 
149                     ? getOONamingService().toPlural(targetClass.getName()) 
150                     : getOONamingService().toSingular(targetClass.getName());
151                 break;
152             case useFkColumn:
153                 //these are IMPORTED constraints holding the fkColumn
154                 Column fkColumn = fkConstraint.getColumnReferences().get(0).getLocalColumn();
155                 referencePropName = propertyNameSingular(fkColumn);
156                 return referencePropName; //already regexRenamed and case-correct
157             case useFkConstraint:
158                 referencePropName = OOUtil.camelBackJavaClass(fkConstraint.getName());
159         }
160 		String result = StringUtil.makeFirstLetterLowerCase( referencePropName );
161         return this.regexRename(result);
162 	}
163 
164     /***
165      * This method calls <code>determineCardinality</code> as a side effect
166      * @return true if target table cardinality is to-many.
167      */
168     public boolean isManyCardinality(FkConstraint fkConstraint) {
169         Cardinality cardinality = determineCardinality(fkConstraint);
170         return Cardinality.Side.MANY.equals( Cardinality.otherSide(cardinality) );
171     }
172     
173     /***
174      * Determines the cardinality based on foreign key constraints as follows:
175      * <ol>
176      * <li>If cardinality is already set just return it.</li>
177      * <li>if a link table is referenced, return <code>MANY_TO_MANY</code>.</li>
178      * <li>If foreign key is unique, return <code>ONE_TO_ONE</code>.</li>
179      * <li>Otherwise, return <code>MANY_TO_ONE</code> for the side that contains the foreign 
180      * key and <code>ONE_TO_MANY</code> for the opposite side.</li>
181      * </ol>
182      */
183     public Cardinality determineCardinality(FkConstraint fkConstraint) {
184         Cardinality cardinality = fkConstraint.getCardinality();
185         if (cardinality==null) {
186             boolean isLinkTable = Boolean.TRUE.equals(fkConstraint.getTargetTable().isLinkTable());
187             if (isLinkTable) {
188                 cardinality = Cardinality.MANY_TO_MANY;
189             } else if (fkConstraint.unique()) {
190                 cardinality = Cardinality.ONE_TO_ONE;
191             } else {
192                 cardinality = FkEnum.IMPORTED.equals(fkConstraint.getFkType()) ? Cardinality.MANY_TO_ONE : Cardinality.ONE_TO_MANY;
193             }
194             fkConstraint.setCardinality(cardinality);
195         }
196         return cardinality;
197     }
198     
199     /***
200      * Given a foreign key constraint with a many-sided mapping, return container interface.
201      * @return name of type (LIST, SET, MAP, BAG) or null if to-one relationship
202      */
203 	public String containerType(FkConstraint fkConstraint) {      
204 		return isManyCardinality(fkConstraint) ? getCollections().getDefaultCollectionKey() : null;
205 	}
206 
207     public void setReferenceNamingStrategy(ReferenceNamingStrategy referenceNamingStrategy) {
208         this.referenceNamingStrategy = referenceNamingStrategy;
209     }
210 
211     ///////////////////////////////////////////////////////////////////////////
212     // support methods:
213     ///////////////////////////////////////////////////////////////////////////
214 
215     public void setReferenceNamingStrategyString(String referenceNamingStrategy) {
216         this.setReferenceNamingStrategy( ReferenceNamingStrategy.valueOf(referenceNamingStrategy) );
217     }
218 
219 //    public ReferenceNamingStrategy getReferenceNamingStrategy() {
220 //        return referenceNamingStrategy;
221 //    }
222 //
223     public OONamingService getOONamingService() {
224         if (ooNamingService==null)
225             ooNamingService = new JavaNamingService();
226         return ooNamingService;
227     }
228 
229     public void setOONamingService(OONamingService ooNamingService) {
230         this.ooNamingService = ooNamingService;
231     }
232 	
233     public Collections getCollections() {
234         if (collections==null)
235             collections = new Collections();
236         return collections;
237     }
238 
239     public void setCollections(Collections collections) {
240         this.collections = collections;
241     }
242 
243     public String getDefaultType() {
244         return defaultType;
245     }
246 
247     public void setDefaultType(String defaultType) {
248         this.defaultType = defaultType;
249     }
250 
251     public RegexRenamer getRegexRenamer() {
252         return regexRenamer;
253     }
254 
255     public void setRegexRenamer(RegexRenamer regexRenamer) {
256         this.regexRenamer = regexRenamer;
257     }
258     
259     /***
260      * Property file friendly means of adding regex-replacePattern pairs as comma-delineated list.
261      * @see RegexRenamer#setRegexReplacePairs(String)
262      */
263     public void setRegexReplacePairs(String replacePatterns) {
264         if (regexRenamer==null)
265             regexRenamer = new RegexRenamer();
266         regexRenamer.setRegexReplacePairs(replacePatterns);
267     }
268 
269     private String regexRename(String name) {
270         if (regexRenamer==null || regexRenamer.isEmpty()) {
271           return name;  
272         } else {
273             String newName = regexRenamer.rename(name);
274             return newName==null ? name : newName;
275         }
276     }
277 
278     public boolean getPluralTableNames() {
279         return pluralTableNames;
280     }
281 
282 
283     public void setPluralTableNames(boolean pluralTableNames) {
284         this.pluralTableNames = pluralTableNames;
285     }
286 
287 }