1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
37
38
39
40 class BeanGen {
41
42
43 private static final String AUTOGENERATED_START = "\t//------------------------- AUTOGENERATED START -------------------------";
44
45 private static final String AUTOGENERATED_END = "\t//-------------------------- AUTOGENERATED END --------------------------";
46
47 private static final Pattern BEAN_TYPE = Pattern.compile(".*class (([A-Z][A-Za-z0-9_]+)(?:<([A-Z])( extends [A-Za-z0-9_]+)?>)?) .*");
48
49 private static final Pattern SUPER_TYPE = Pattern.compile(".* extends (([A-Z][A-Za-z0-9_]+)(?:<([A-Z][A-Za-z0-9_<> ]*)>)?).*");
50
51
52 private final List<String> content;
53
54 private final String indent;
55
56 private final String prefix;
57
58 private final int autoStartIndex;
59
60 private final int autoEndIndex;
61
62 private final List<String> insertRegion;
63
64 private final List<PropertyGen> properties;
65
66 private final GeneratableBean data;
67
68
69
70
71
72
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
286
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
304
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 }