View Javadoc
1   /*
2    *  Copyright 2001-2013 Stephen Colebourne
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.joda.beans.gen;
17  
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.ListIterator;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.joda.beans.BeanBuilder;
28  import org.joda.beans.JodaBeanUtils;
29  import org.joda.beans.MetaProperty;
30  import org.joda.beans.impl.direct.DirectBean;
31  import org.joda.beans.impl.direct.DirectBeanBuilder;
32  import org.joda.beans.impl.direct.DirectMetaBean;
33  import org.joda.beans.impl.direct.DirectMetaPropertyMap;
34  
35  /**
36   * Code generator for a bean.
37   * 
38   * @author Stephen Colebourne
39   */
40  class BeanGen {
41  
42      /** Start marker. */
43      private static final String AUTOGENERATED_START = "\t//------------------------- AUTOGENERATED START -------------------------";
44      /** End marker. */
45      private static final String AUTOGENERATED_END = "\t//-------------------------- AUTOGENERATED END --------------------------";
46      /** Pattern to find bean type. */
47      private static final Pattern BEAN_TYPE = Pattern.compile(".*class (([A-Z][A-Za-z0-9_]+)(?:<([A-Z])( extends [A-Za-z0-9_]+)?>)?) .*");
48      /** Pattern to find super type. */
49      private static final Pattern SUPER_TYPE = Pattern.compile(".* extends (([A-Z][A-Za-z0-9_]+)(?:<([A-Z][A-Za-z0-9_<> ]*)>)?).*");
50  
51      /** The content to process. */
52      private final List<String> content;
53      /** The indent. */
54      private final String indent;
55      /** The prefix. */
56      private final String prefix;
57      /** The start position of auto-generation. */
58      private final int autoStartIndex;
59      /** The end position of auto-generation. */
60      private final int autoEndIndex;
61      /** The region to insert into. */
62      private final List<String> insertRegion;
63      /** The list of property generators. */
64      private final List<PropertyGen> properties;
65      /** The data model of the bean. */
66      private final GeneratableBean data;
67  
68      /**
69       * Constructor.
70       * @param content  the content to process, not null
71       * @param indent  the indent to use, not null
72       * @param prefix  the prefix to use, not null
73       */
74      BeanGen(List<String> content, String indent, String prefix) {
75          this.content = content;
76          this.indent = indent;
77          this.prefix = prefix;
78          int beanDefIndex = parseBeanDefinition();
79          if (beanDefIndex >= 0) {
80              this.data = new GeneratableBean();
81              this.data.getCurrentImports().addAll(parseImports(beanDefIndex));
82              this.data.setImportInsertLocation(parseImportLocation(beanDefIndex));
83              this.data.setConstructable(parseConstructable(beanDefIndex));
84              this.data.setTypeParts(parseBeanType(beanDefIndex));
85              this.data.setSuperTypeParts(parseBeanSuperType(beanDefIndex));
86              this.properties = parseProperties(data);
87              this.autoStartIndex = parseStartAutogen();
88              this.autoEndIndex = parseEndAutogen();
89              this.insertRegion = content.subList(autoStartIndex + 1, autoEndIndex);
90              this.data.setManualEqualsHashCode(parseManualEqualsHashCode(beanDefIndex));
91          } else {
92              this.autoStartIndex = -1;
93              this.autoEndIndex = -1;
94              this.insertRegion = null;
95              this.data = null;
96              this.properties = null;
97          }
98      }
99  
100     //-----------------------------------------------------------------------
101     void process() {
102         if (insertRegion != null) {
103             removeOld();
104             if (data.isSubclass() == false) {
105                 data.ensureImport(DirectBean.class);
106             }
107             insertRegion.add("\t///CLOVER:OFF");
108             generateMeta();
109             generateMetaBean();
110             generatePropertyGet();
111             generatePropertySet();
112             if (data.isValidated()) {
113                 generateValidate();
114             }
115             if (data.isManualEqualsHashCode() == false) {
116                 generateEquals();
117                 generateHashCode();
118             }
119             generateGettersSetters();
120             generateMetaClass();
121             insertRegion.add("\t///CLOVER:ON");
122             resolveImports();
123             resolveIndents();
124         }
125     }
126 
127     private void resolveImports() {
128         if (data.getNewImports().size() > 0) {
129             int pos = data.getImportInsertLocation() + 1;
130             for (String imp : data.getNewImports()) {
131                 content.add(pos++, "import " + imp + ";");
132             }
133         }
134     }
135 
136     private void resolveIndents() {
137         for (ListIterator<String> it = content.listIterator(); it.hasNext(); ) {
138             it.set(it.next().replace("\t", indent));
139         }
140     }
141 
142     //-----------------------------------------------------------------------
143     private int parseBeanDefinition() {
144         for (int index = 0; index < content.size(); index++) {
145             String line = content.get(index).trim();
146             if (line.startsWith("@BeanDefinition")) {
147                 return index;
148             }
149         }
150         return -1;
151     }
152 
153     private Set<String> parseImports(int defLine) {
154         Set<String> imports = new HashSet<String>();
155         for (int index = 0; index < defLine; index++) {
156             if (content.get(index).startsWith("import ")) {
157                 String imp = content.get(index).substring(7).trim();
158                 imp = imp.substring(0, imp.indexOf(';'));
159                 if (imp.endsWith(".*") == false) {
160                     imports.add(imp);
161                 }
162             }
163         }
164         return imports;
165     }
166 
167     private int parseImportLocation(int defLine) {
168         int location = 0;
169         for (int index = 0; index < defLine; index++) {
170             if (content.get(index).startsWith("import ") || content.get(index).startsWith("package ")) {
171                 location = index;
172             }
173         }
174         return location;
175     }
176 
177     private boolean parseConstructable(int defLine) {
178         for (int index = defLine; index < content.size(); index++) {
179             if (content.get(index).contains(" abstract class ")) {
180                 return false;
181             }
182         }
183         return true;
184     }
185 
186     private String[] parseBeanType(int defLine) {
187         Matcher matcher = BEAN_TYPE.matcher("");
188         for (int index = defLine; index < content.size(); index++) {
189             matcher.reset(content.get(index));
190             if (matcher.matches()) {
191                 return new String[] {matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)};
192             }
193         }
194         throw new RuntimeException("Unable to locate bean class name");
195     }
196 
197     private String[] parseBeanSuperType(int defLine) {
198         Matcher matcher = SUPER_TYPE.matcher("");
199         for (int index = defLine; index < content.size(); index++) {
200             matcher.reset(content.get(index));
201             if (matcher.matches()) {
202                 return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)};
203             }
204         }
205         throw new RuntimeException("Unable to locate bean superclass");
206     }
207 
208     private List<PropertyGen> parseProperties(GeneratableBean data) {
209         List<PropertyGen> props = new ArrayList<PropertyGen>();
210         for (int index = 0; index < content.size(); index++) {
211             String line = content.get(index).trim();
212             if (line.startsWith("@PropertyDefinition")) {
213                 PropertyGen prop = new PropertyGen(this, content, index, false);
214                 props.add(prop);
215                 data.getProperties().add(prop.getData());
216             } else if (line.startsWith("@DerivedProperty")) {
217                 PropertyGen prop = new PropertyGen(this, content, index, true);
218                 props.add(prop);
219                 data.getProperties().add(prop.getData());
220             }
221         }
222         return props;
223     }
224 
225     private int parseStartAutogen() {
226         for (int index = 0; index < content.size(); index++) {
227             String line = content.get(index).trim();
228             if (line.contains(" AUTOGENERATED START ")) {
229                 content.set(index, AUTOGENERATED_START);
230                 return index;
231             }
232         }
233         for (int index = content.size() - 1; index >= 0; index--) {
234             String line = content.get(index).trim();
235             if (line.equals("}")) {
236                 content.add(index, AUTOGENERATED_START);
237                 return index;
238             }
239             if (line.length() > 0) {
240                 break;
241             }
242         }
243         throw new RuntimeException("Unable to locate start autogeneration point");
244     }
245 
246     private int parseEndAutogen() {
247         for (int index = autoStartIndex; index < content.size(); index++) {
248             String line = content.get(index).trim();
249             if (line.contains(" AUTOGENERATED END ")) {
250                 content.set(index, AUTOGENERATED_END);
251                 return index;
252             }
253         }
254         content.add(autoStartIndex + 1, AUTOGENERATED_END);
255         return autoStartIndex + 1;
256     }
257 
258     private void removeOld() {
259         insertRegion.clear();
260     }
261 
262     private boolean parseManualEqualsHashCode(int defLine) {
263         for (int index = defLine; index < autoStartIndex; index++) {
264             String line = content.get(index).trim();
265             if (line.equals("public int hashCode() {") || (line.startsWith("public boolean equals(") && line.endsWith(") {"))) {
266                 return true;
267             }
268         }
269         for (int index = autoEndIndex; index < content.size(); index++) {
270             String line = content.get(index).trim();
271             if (line.equals("public int hashCode() {") || (line.startsWith("public boolean equals(") && line.endsWith(") {"))) {
272                 return true;
273             }
274         }
275         return false;
276     }
277 
278     //-----------------------------------------------------------------------
279     private void generateSeparator() {
280         insertRegion.add("\t//-----------------------------------------------------------------------");
281     }
282 
283     private void generateMeta() {
284         data.ensureImport(JodaBeanUtils.class);
285         // this cannot be generified without either Eclipse or javac complaining
286         // raw types forever
287         insertRegion.add("\t/**");
288         insertRegion.add("\t * The meta-bean for {@code " + data.getTypeRaw() + "}.");
289         if (data.isTypeGeneric()) {
290             insertRegion.add("\t * @return the meta-bean, not null");
291             insertRegion.add("\t */");
292             insertRegion.add("\t@SuppressWarnings(\"rawtypes\")");
293             insertRegion.add("\tpublic static " + data.getTypeRaw() + ".Meta meta() {");
294         } else {
295             insertRegion.add("\t * @return the meta-bean, not null");
296             insertRegion.add("\t */");
297             insertRegion.add("\tpublic static " + data.getTypeRaw() + ".Meta meta() {");
298         }
299         insertRegion.add("\t\treturn " + data.getTypeRaw() + ".Meta.INSTANCE;");
300         insertRegion.add("\t}");
301         
302         if (data.isTypeGeneric()) {
303             // this works around an Eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=397462
304             // long name needed for uniqueness as static overriding is borked
305             insertRegion.add("");
306             insertRegion.add("\t/**");
307             insertRegion.add("\t * The meta-bean for {@code " + data.getTypeRaw() + "}.");
308             insertRegion.add("\t * @param <R>  the bean's generic type");
309             insertRegion.add("\t * @param cls  the bean's generic type");
310             insertRegion.add("\t * @return the meta-bean, not null");
311             insertRegion.add("\t */");
312             insertRegion.add("\t@SuppressWarnings(\"unchecked\")");
313             insertRegion.add("\tpublic static <R" + data.getTypeGenericExtends() + "> " + data.getTypeRaw() +
314                     ".Meta<R> meta" + data.getTypeRaw() + "(Class<R> cls) {");
315             insertRegion.add("\t\treturn " + data.getTypeRaw() + ".Meta.INSTANCE;");
316             insertRegion.add("\t}");
317         }
318         
319         insertRegion.add("");
320         insertRegion.add("\tstatic {");
321         insertRegion.add("\t\tJodaBeanUtils.registerMetaBean(" + data.getTypeRaw() + ".Meta.INSTANCE);");
322         insertRegion.add("\t}");
323         insertRegion.add("");
324     }
325 
326     private void generateMetaBean() {
327         if (data.isTypeGeneric()) {
328             insertRegion.add("\t@SuppressWarnings(\"unchecked\")");
329         }
330         insertRegion.add("\t@Override");
331         insertRegion.add("\tpublic " + data.getTypeRaw() + ".Meta" + data.getTypeGenericName(true) + " metaBean() {");
332         insertRegion.add("\t\treturn " + data.getTypeRaw() + ".Meta.INSTANCE;");
333         insertRegion.add("\t}");
334         insertRegion.add("");
335     }
336 
337     private void generateGettersSetters() {
338         for (PropertyGen prop : properties) {
339             generateSeparator();
340             insertRegion.addAll(prop.generateGetter());
341             insertRegion.addAll(prop.generateSetter());
342             insertRegion.addAll(prop.generateProperty());
343         }
344     }
345 
346     private void generatePropertyGet() {
347         insertRegion.add("\t@Override");
348         insertRegion.add("\tprotected Object propertyGet(String propertyName, boolean quiet) {");
349         if (properties.size() > 0) {
350             insertRegion.add("\t\tswitch (propertyName.hashCode()) {");
351             for (PropertyGen prop : properties) {
352                 insertRegion.addAll(prop.generatePropertyGetCase());
353             }
354             insertRegion.add("\t\t}");
355         }
356         insertRegion.add("\t\treturn super.propertyGet(propertyName, quiet);");
357         insertRegion.add("\t}");
358         insertRegion.add("");
359     }
360 
361     private void generatePropertySet() {
362         boolean generics = false;
363         for (GeneratableProperty prop : data.getProperties()) {
364             generics |= (prop.getReadWrite().isWritable() && prop.isGeneric() && prop.isGenericWildcardParamType() == false);
365         }
366         if (generics) {
367             insertRegion.add("\t@SuppressWarnings(\"unchecked\")");
368         }
369         insertRegion.add("\t@Override");
370         insertRegion.add("\tprotected void propertySet(String propertyName, Object newValue, boolean quiet) {");
371         if (properties.size() > 0) {
372             insertRegion.add("\t\tswitch (propertyName.hashCode()) {");
373             for (PropertyGen prop : properties) {
374                 insertRegion.addAll(prop.generatePropertySetCase());
375             }
376             insertRegion.add("\t\t}");
377         }
378         insertRegion.add("\t\tsuper.propertySet(propertyName, newValue, quiet);");
379         insertRegion.add("\t}");
380         insertRegion.add("");
381     }
382 
383     private void generateValidate() {
384         insertRegion.add("\t@Override");
385         insertRegion.add("\tprotected void validate() {");
386         for (PropertyGen prop : properties) {
387             if (prop.getData().isValidated()) {
388                 insertRegion.add("\t\t" + prop.getData().getValidationMethodName() + "(" + prop.getData().getFieldName() + ", \"" + prop.getData().getPropertyName() + "\");");
389             }
390         }
391         insertRegion.add("\t\tsuper.validate();");
392         insertRegion.add("\t}");
393         insertRegion.add("");
394     }
395 
396     private void generateEquals() {
397         data.ensureImport(JodaBeanUtils.class);
398         insertRegion.add("\t@Override");
399         insertRegion.add("\tpublic boolean equals(Object obj) {");
400         insertRegion.add("\t\tif (obj == this) {");
401         insertRegion.add("\t\t\treturn true;");
402         insertRegion.add("\t\t}");
403         insertRegion.add("\t\tif (obj != null && obj.getClass() == this.getClass()) {");
404         if (properties.size() == 0) {
405             if (data.isSubclass()) {
406                 insertRegion.add("\t\t\treturn super.equals(obj);");
407             } else {
408                 insertRegion.add("\t\t\treturn true;");
409             }
410         } else {
411             insertRegion.add("\t\t\t" + data.getTypeWildcard() + " other = (" + data.getTypeWildcard() + ") obj;");
412             for (int i = 0; i < properties.size(); i++) {
413                 PropertyGen prop = properties.get(i);
414                 String getter = GetterGen.of(prop.getData()).generateGetInvoke(prop.getData());
415                 insertRegion.add(
416                         (i == 0 ? "\t\t\treturn " : "\t\t\t\t\t") +
417                         "JodaBeanUtils.equal(" + getter + ", other." + getter + ")" +
418                         (data.isSubclass() || i < properties.size() - 1 ? " &&" : ";"));
419             }
420             if (data.isSubclass()) {
421                 insertRegion.add("\t\t\t\t\tsuper.equals(obj);");
422             }
423         }
424         insertRegion.add("\t\t}");
425         insertRegion.add("\t\treturn false;");
426         insertRegion.add("\t}");
427         insertRegion.add("");
428     }
429 
430     private void generateHashCode() {
431         data.ensureImport(JodaBeanUtils.class);
432         insertRegion.add("\t@Override");
433         insertRegion.add("\tpublic int hashCode() {");
434         if (data.isSubclass()) {
435             insertRegion.add("\t\tint hash = 7;");
436         } else {
437             insertRegion.add("\t\tint hash = getClass().hashCode();");
438         }
439         for (int i = 0; i < properties.size(); i++) {
440             PropertyGen prop = properties.get(i);
441             String getter = GetterGen.of(prop.getData()).generateGetInvoke(prop.getData());
442             insertRegion.add("\t\thash += hash * 31 + JodaBeanUtils.hashCode(" + getter + ");");
443         }
444         if (data.isSubclass()) {
445             insertRegion.add("\t\treturn hash ^ super.hashCode();");
446         } else {
447             insertRegion.add("\t\treturn hash;");
448         }
449         insertRegion.add("\t}");
450         insertRegion.add("");
451     }
452 
453     private void generateMetaClass() {
454         generateSeparator();
455         insertRegion.add("\t/**");
456         insertRegion.add("\t * The meta-bean for {@code " + data.getTypeRaw() + "}.");
457         insertRegion.add("\t */");
458         String superMeta;
459         if (data.isSubclass()) {
460             superMeta = data.getSuperTypeRaw() + ".Meta" + data.getSuperTypeGeneric(true);
461         } else {
462             data.ensureImport(DirectMetaBean.class);
463             superMeta = "DirectMetaBean";
464         }
465         if (data.isTypeGeneric()) {
466             insertRegion.add("\tpublic static class Meta" + data.getTypeGeneric(true) + " extends " + superMeta + " {");
467         } else {
468             insertRegion.add("\tpublic static class Meta extends " + superMeta + " {");
469         }
470         insertRegion.add("\t\t/**");
471         insertRegion.add("\t\t * The singleton instance of the meta-bean.");
472         insertRegion.add("\t\t */");
473         if (data.isTypeGeneric()) {
474             insertRegion.add("\t\t@SuppressWarnings(\"rawtypes\")");
475         }
476         insertRegion.add("\t\tstatic final Meta INSTANCE = new Meta();");
477         insertRegion.add("");
478         generateMetaPropertyConstants();
479         generateMetaPropertyMapSetup();
480         insertRegion.add("\t\t/**");
481         insertRegion.add("\t\t * Restricted constructor.");
482         insertRegion.add("\t\t */");
483         insertRegion.add("\t\tprotected Meta() {");
484         insertRegion.add("\t\t}");
485         insertRegion.add("");
486         generateMetaPropertyGet();
487         generateMetaBuilder();
488         generateMetaBeanType();
489         generateMetaPropertyMap();
490         insertRegion.add("\t\t//-----------------------------------------------------------------------");
491         generateMetaPropertyMethods();
492         insertRegion.add("\t}");
493         insertRegion.add("");
494     }
495 
496     private void generateMetaPropertyConstants() {
497         for (PropertyGen prop : properties) {
498             insertRegion.addAll(prop.generateMetaPropertyConstant());
499         }
500     }
501 
502     private void generateMetaPropertyMapSetup() {
503         data.ensureImport(MetaProperty.class);
504         data.ensureImport(DirectMetaPropertyMap.class);
505         insertRegion.add("\t\t/**");
506         insertRegion.add("\t\t * The meta-properties.");
507         insertRegion.add("\t\t */");
508         insertRegion.add("\t\tprivate final Map<String, MetaProperty<?>> " + prefix + "metaPropertyMap$ = new DirectMetaPropertyMap(");
509         if (data.isSubclass()) {
510             insertRegion.add("\t\t\t\tthis, (DirectMetaPropertyMap) super.metaPropertyMap()" + (properties.size() == 0 ? ");" : ","));
511         } else {
512             insertRegion.add("\t\t\t\tthis, null" + (properties.size() == 0 ? ");" : ","));
513         }
514         for (int i = 0; i < properties.size(); i++) {
515             String line = "\t\t\t\t\"" + properties.get(i).getData().getPropertyName() + "\"";
516             line += (i + 1 == properties.size() ? ");" : ",");
517             insertRegion.add(line);
518         }
519         insertRegion.add("");
520     }
521 
522     private void generateMetaBuilder() {
523         data.ensureImport(BeanBuilder.class);
524         insertRegion.add("\t\t@Override");
525         insertRegion.add("\t\tpublic BeanBuilder<? extends " + data.getTypeNoExtends() + "> builder() {");
526         if (data.isConstructable()) {
527             data.ensureImport(DirectBeanBuilder.class);
528             insertRegion.add("\t\t\treturn new DirectBeanBuilder<" + data.getTypeNoExtends() + ">(new " + data.getTypeNoExtends() + "());");
529         } else {
530             insertRegion.add("\t\t\tthrow new UnsupportedOperationException(\"" + data.getTypeRaw() + " is an abstract class\");");
531         }
532         insertRegion.add("\t\t}");
533         insertRegion.add("");
534     }
535 
536     private void generateMetaBeanType() {
537         if (data.isTypeGeneric()) {
538             insertRegion.add("\t\t@SuppressWarnings({\"unchecked\", \"rawtypes\" })");
539         }
540         insertRegion.add("\t\t@Override");
541         insertRegion.add("\t\tpublic Class<? extends " + data.getTypeNoExtends() + "> beanType() {");
542         if (data.isTypeGeneric()) {
543             insertRegion.add("\t\t\treturn (Class) " + data.getTypeRaw() + ".class;");
544         } else {
545             insertRegion.add("\t\t\treturn " + data.getTypeNoExtends() + ".class;");
546         }
547         insertRegion.add("\t\t}");
548         insertRegion.add("");
549     }
550 
551     private void generateMetaPropertyGet() {
552         if (properties.size() > 0) {
553             data.ensureImport(MetaProperty.class);
554             insertRegion.add("\t\t@Override");
555             insertRegion.add("\t\tprotected MetaProperty<?> metaPropertyGet(String propertyName) {");
556             insertRegion.add("\t\t\tswitch (propertyName.hashCode()) {");
557             for (PropertyGen prop : properties) {
558                 insertRegion.addAll(prop.generateMetaPropertyGetCase());
559             }
560             insertRegion.add("\t\t\t}");
561             insertRegion.add("\t\t\treturn super.metaPropertyGet(propertyName);");
562             insertRegion.add("\t\t}");
563             insertRegion.add("");
564         }
565     }
566 
567     private void generateMetaPropertyMap() {
568         data.ensureImport(Map.class);
569         insertRegion.add("\t\t@Override");
570         insertRegion.add("\t\tpublic Map<String, MetaProperty<?>> metaPropertyMap() {");
571         insertRegion.add("\t\t\treturn " + prefix + "metaPropertyMap$;");
572         insertRegion.add("\t\t}");
573         insertRegion.add("");
574     }
575 
576     private void generateMetaPropertyMethods() {
577         for (PropertyGen prop : properties) {
578             insertRegion.addAll(prop.generateMetaProperty());
579         }
580     }
581 
582     //-----------------------------------------------------------------------
583     boolean isBean() {
584         return data != null;
585     }
586 
587     GeneratableBean getData() {
588         return data;
589     }
590 
591     String getFieldPrefix() {
592         return prefix;
593     }
594 
595 }