View Javadoc
1   /**
2    *    Copyright 2006-2016 the original author or authors.
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.mybatis.generator.plugins;
17  
18  import static org.mybatis.generator.internal.util.StringUtility.isTrue;
19  
20  import static org.mybatis.generator.internal.util.JavaBeansUtil.getGetterMethodName;
21  
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Properties;
25  
26  import org.mybatis.generator.api.PluginAdapter;
27  import org.mybatis.generator.api.IntrospectedColumn;
28  import org.mybatis.generator.api.IntrospectedTable;
29  import org.mybatis.generator.api.dom.OutputUtilities;
30  import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
31  import org.mybatis.generator.api.dom.java.JavaVisibility;
32  import org.mybatis.generator.api.dom.java.Method;
33  import org.mybatis.generator.api.dom.java.Parameter;
34  import org.mybatis.generator.api.dom.java.TopLevelClass;
35  
36  /**
37   * This plugin adds equals() and hashCode() methods to the generated model
38   * classes. It demonstrates the process of adding methods to generated classes
39   * <p>
40   * The <tt>equals</tt> method generated by this class is correct in most cases,
41   * but will probably NOT be correct if you have specified a rootClass - because
42   * our equals method only checks the fields it knows about.
43   * <p>
44   * Similarly, the <tt>hashCode</tt> method generated by this class only relies
45   * on fields it knows about. Anything you add, or fields in a super class will
46   * not be factored into the hash code.
47   * 
48   * @author Jeff Butler
49   * 
50   */
51  public class EqualsHashCodePlugin extends PluginAdapter {
52  
53      private boolean useEqualsHashCodeFromRoot;
54  
55      @Override
56      public void setProperties(Properties properties) {
57          super.setProperties(properties);
58          useEqualsHashCodeFromRoot = isTrue(properties.getProperty("useEqualsHashCodeFromRoot"));
59      }
60  
61      /**
62       * This plugin is always valid - no properties are required
63       */
64      public boolean validate(List<String> warnings) {
65          return true;
66      }
67  
68      @Override
69      public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,
70              IntrospectedTable introspectedTable) {
71          List<IntrospectedColumn> columns;
72          if (introspectedTable.getRules().generateRecordWithBLOBsClass()) {
73              columns = introspectedTable.getNonBLOBColumns();
74          } else {
75              columns = introspectedTable.getAllColumns();
76          }
77  
78          generateEquals(topLevelClass, columns, introspectedTable);
79          generateHashCode(topLevelClass, columns, introspectedTable);
80  
81          return true;
82      }
83  
84      @Override
85      public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass,
86              IntrospectedTable introspectedTable) {
87          generateEquals(topLevelClass, introspectedTable.getPrimaryKeyColumns(),
88                  introspectedTable);
89          generateHashCode(topLevelClass, introspectedTable
90                  .getPrimaryKeyColumns(), introspectedTable);
91  
92          return true;
93      }
94  
95      @Override
96      public boolean modelRecordWithBLOBsClassGenerated(
97              TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
98          generateEquals(topLevelClass, introspectedTable.getAllColumns(),
99                  introspectedTable);
100         generateHashCode(topLevelClass, introspectedTable.getAllColumns(),
101                 introspectedTable);
102 
103         return true;
104     }
105 
106     /**
107      * Generates an <tt>equals</tt> method that does a comparison of all fields.
108      * <p>
109      * The generated <tt>equals</tt> method will be correct unless:
110      * <ul>
111      * <li>Other fields have been added to the generated classes</li>
112      * <li>A <tt>rootClass</tt> is specified that holds state</li>
113      * </ul>
114      * 
115      * @param topLevelClass
116      *            the class to which the method will be added
117      * @param introspectedColumns
118      *            column definitions of this class and any superclass of this
119      *            class
120      * @param introspectedTable
121      *            the table corresponding to this class
122      */
123     protected void generateEquals(TopLevelClass topLevelClass,
124             List<IntrospectedColumn> introspectedColumns,
125             IntrospectedTable introspectedTable) {
126         Method method = new Method();
127         method.setVisibility(JavaVisibility.PUBLIC);
128         method.setReturnType(FullyQualifiedJavaType
129                 .getBooleanPrimitiveInstance());
130         method.setName("equals"); //$NON-NLS-1$
131         method.addParameter(new Parameter(FullyQualifiedJavaType
132                 .getObjectInstance(), "that")); //$NON-NLS-1$
133         if (introspectedTable.isJava5Targeted()) {
134             method.addAnnotation("@Override"); //$NON-NLS-1$
135         }
136 
137         context.getCommentGenerator().addGeneralMethodComment(method,
138                 introspectedTable);
139 
140         method.addBodyLine("if (this == that) {"); //$NON-NLS-1$
141         method.addBodyLine("return true;"); //$NON-NLS-1$
142         method.addBodyLine("}"); //$NON-NLS-1$
143 
144         method.addBodyLine("if (that == null) {"); //$NON-NLS-1$
145         method.addBodyLine("return false;"); //$NON-NLS-1$
146         method.addBodyLine("}"); //$NON-NLS-1$
147 
148         method.addBodyLine("if (getClass() != that.getClass()) {"); //$NON-NLS-1$
149         method.addBodyLine("return false;"); //$NON-NLS-1$
150         method.addBodyLine("}"); //$NON-NLS-1$
151 
152         StringBuilder sb = new StringBuilder();
153         sb.append(topLevelClass.getType().getShortName());
154         sb.append(" other = ("); //$NON-NLS-1$
155         sb.append(topLevelClass.getType().getShortName());
156         sb.append(") that;"); //$NON-NLS-1$
157         method.addBodyLine(sb.toString());
158 
159         if (useEqualsHashCodeFromRoot && topLevelClass.getSuperClass() != null) {
160             method.addBodyLine("if (!super.equals(other)) {"); //$NON-NLS-1$
161             method.addBodyLine("return false;"); //$NON-NLS-1$
162             method.addBodyLine("}"); //$NON-NLS-1$
163         }
164 
165         boolean first = true;
166         Iterator<IntrospectedColumn> iter = introspectedColumns.iterator();
167         while (iter.hasNext()) {
168             IntrospectedColumn introspectedColumn = iter.next();
169 
170             sb.setLength(0);
171 
172             if (first) {
173                 sb.append("return ("); //$NON-NLS-1$
174                 first = false;
175             } else {
176                 OutputUtilities.javaIndent(sb, 1);
177                 sb.append("&& ("); //$NON-NLS-1$
178             }
179 
180             String getterMethod = getGetterMethodName(
181                     introspectedColumn.getJavaProperty(), introspectedColumn
182                             .getFullyQualifiedJavaType());
183 
184             if (introspectedColumn.getFullyQualifiedJavaType().isPrimitive()) {
185                 sb.append("this."); //$NON-NLS-1$
186                 sb.append(getterMethod);
187                 sb.append("() == "); //$NON-NLS-1$
188                 sb.append("other."); //$NON-NLS-1$
189                 sb.append(getterMethod);
190                 sb.append("())"); //$NON-NLS-1$
191             } else if (introspectedColumn.getFullyQualifiedJavaType().isArray()) {
192                 topLevelClass.addImportedType("java.util.Arrays"); //$NON-NLS-1$
193                 sb.append("Arrays.equals(this."); //$NON-NLS-1$
194                 sb.append(getterMethod);
195                 sb.append("(), "); //$NON-NLS-1$
196                 sb.append("other."); //$NON-NLS-1$
197                 sb.append(getterMethod);
198                 sb.append("()))"); //$NON-NLS-1$
199             } else {
200                 sb.append("this."); //$NON-NLS-1$
201                 sb.append(getterMethod);
202                 sb.append("() == null ? other."); //$NON-NLS-1$
203                 sb.append(getterMethod);
204                 sb.append("() == null : this."); //$NON-NLS-1$
205                 sb.append(getterMethod);
206                 sb.append("().equals(other."); //$NON-NLS-1$
207                 sb.append(getterMethod);
208                 sb.append("()))"); //$NON-NLS-1$
209             }
210 
211             if (!iter.hasNext()) {
212                 sb.append(';');
213             }
214 
215             method.addBodyLine(sb.toString());
216         }
217 
218         topLevelClass.addMethod(method);
219     }
220 
221     /**
222      * Generates a <tt>hashCode</tt> method that includes all fields.
223      * <p>
224      * Note that this implementation is based on the eclipse foundation hashCode
225      * generator.
226      * 
227      * @param topLevelClass
228      *            the class to which the method will be added
229      * @param introspectedColumns
230      *            column definitions of this class and any superclass of this
231      *            class
232      * @param introspectedTable
233      *            the table corresponding to this class
234      */
235     protected void generateHashCode(TopLevelClass topLevelClass,
236             List<IntrospectedColumn> introspectedColumns,
237             IntrospectedTable introspectedTable) {
238         Method method = new Method();
239         method.setVisibility(JavaVisibility.PUBLIC);
240         method.setReturnType(FullyQualifiedJavaType.getIntInstance());
241         method.setName("hashCode"); //$NON-NLS-1$
242         if (introspectedTable.isJava5Targeted()) {
243             method.addAnnotation("@Override"); //$NON-NLS-1$
244         }
245 
246         context.getCommentGenerator().addGeneralMethodComment(method,
247                 introspectedTable);
248 
249         method.addBodyLine("final int prime = 31;"); //$NON-NLS-1$
250         method.addBodyLine("int result = 1;"); //$NON-NLS-1$
251 
252         if (useEqualsHashCodeFromRoot && topLevelClass.getSuperClass() != null) {
253             method.addBodyLine("result = prime * result + super.hashCode();"); //$NON-NLS-1$
254         }
255 
256         StringBuilder sb = new StringBuilder();
257         boolean hasTemp = false;
258         Iterator<IntrospectedColumn> iter = introspectedColumns.iterator();
259         while (iter.hasNext()) {
260             IntrospectedColumn introspectedColumn = iter.next();
261 
262             FullyQualifiedJavaType fqjt = introspectedColumn
263                     .getFullyQualifiedJavaType();
264 
265             String getterMethod = getGetterMethodName(
266                     introspectedColumn.getJavaProperty(), fqjt);
267 
268             sb.setLength(0);
269             if (fqjt.isPrimitive()) {
270                 if ("boolean".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
271                     sb.append("result = prime * result + ("); //$NON-NLS-1$
272                     sb.append(getterMethod);
273                     sb.append("() ? 1231 : 1237);"); //$NON-NLS-1$
274                     method.addBodyLine(sb.toString());
275                 } else if ("byte".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
276                     sb.append("result = prime * result + "); //$NON-NLS-1$
277                     sb.append(getterMethod);
278                     sb.append("();"); //$NON-NLS-1$
279                     method.addBodyLine(sb.toString());
280                 } else if ("char".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
281                     sb.append("result = prime * result + "); //$NON-NLS-1$
282                     sb.append(getterMethod);
283                     sb.append("();"); //$NON-NLS-1$
284                     method.addBodyLine(sb.toString());
285                 } else if ("double".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
286                     if (!hasTemp) {
287                         method.addBodyLine("long temp;"); //$NON-NLS-1$
288                         hasTemp = true;
289                     }
290                     sb.append("temp = Double.doubleToLongBits("); //$NON-NLS-1$
291                     sb.append(getterMethod);
292                     sb.append("());"); //$NON-NLS-1$
293                     method.addBodyLine(sb.toString());
294                     method
295                             .addBodyLine("result = prime * result + (int) (temp ^ (temp >>> 32));"); //$NON-NLS-1$
296                 } else if ("float".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
297                     sb
298                             .append("result = prime * result + Float.floatToIntBits("); //$NON-NLS-1$
299                     sb.append(getterMethod);
300                     sb.append("());"); //$NON-NLS-1$
301                     method.addBodyLine(sb.toString());
302                 } else if ("int".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
303                     sb.append("result = prime * result + "); //$NON-NLS-1$
304                     sb.append(getterMethod);
305                     sb.append("();"); //$NON-NLS-1$
306                     method.addBodyLine(sb.toString());
307                 } else if ("long".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
308                     sb.append("result = prime * result + (int) ("); //$NON-NLS-1$
309                     sb.append(getterMethod);
310                     sb.append("() ^ ("); //$NON-NLS-1$
311                     sb.append(getterMethod);
312                     sb.append("() >>> 32));"); //$NON-NLS-1$
313                     method.addBodyLine(sb.toString());
314                 } else if ("short".equals(fqjt.getFullyQualifiedName())) { //$NON-NLS-1$
315                     sb.append("result = prime * result + "); //$NON-NLS-1$
316                     sb.append(getterMethod);
317                     sb.append("();"); //$NON-NLS-1$
318                     method.addBodyLine(sb.toString());
319                 } else {
320                     // should never happen
321                     continue;
322                 }
323             } else if (fqjt.isArray()) {
324                 // Arrays is already imported by the generateEquals method, we don't need
325                 // to do it again
326                 sb.append("result = prime * result + (Arrays.hashCode("); //$NON-NLS-1$
327                 sb.append(getterMethod);
328                 sb.append("()));"); //$NON-NLS-1$
329                 method.addBodyLine(sb.toString());
330             } else {
331                 sb.append("result = prime * result + (("); //$NON-NLS-1$
332                 sb.append(getterMethod);
333                 sb.append("() == null) ? 0 : "); //$NON-NLS-1$
334                 sb.append(getterMethod);
335                 sb.append("().hashCode());"); //$NON-NLS-1$
336                 method.addBodyLine(sb.toString());
337             }
338         }
339 
340         method.addBodyLine("return result;"); //$NON-NLS-1$
341 
342         topLevelClass.addMethod(method);
343     }
344 }