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.List;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 import org.joda.beans.MetaProperty;
24 import org.joda.beans.Property;
25 import org.joda.beans.impl.direct.DirectMetaProperty;
26
27
28
29
30
31
32 class PropertyGen {
33
34
35 private static final Pattern GETTER_PATTERN = Pattern.compile(".*[ ,(]get[ ]*[=][ ]*[\"]([a-zA-Z-]*)[\"].*");
36
37 private static final Pattern SETTER_PATTERN = Pattern.compile(".*[ ,(]set[ ]*[=][ ]*[\"]([a-zA-Z-]*)[\"].*");
38
39 private static final Pattern VALIDATION_PATTERN = Pattern.compile(".*[ ,(]validate[ ]*[=][ ]*[\"]([a-zA-Z_.]*)[\"].*");
40
41
42 private final int annotationIndex;
43
44 private final int fieldIndex;
45
46 private final BeanGen bean;
47
48 private final GeneratableProperty data;
49
50
51
52
53
54
55
56
57 public PropertyGen(BeanGen bean, List<String> content, int lineIndex, boolean derived) {
58 this.bean = bean;
59 this.annotationIndex = lineIndex;
60 this.fieldIndex = parseCodeIndex(content);
61 GeneratableProperty prop = new GeneratableProperty(bean.getData());
62 if (derived) {
63 prop.setGetStyle("manual");
64 prop.setSetStyle("");
65 prop.setDeprecated(parseDeprecated(content));
66 prop.setPropertyName(parseMethodNameAsPropertyName(content));
67 prop.setUpperName(makeUpperName(prop.getPropertyName()));
68 prop.setType(parseMethodType(content));
69 } else {
70 prop.setGetStyle(parseGetStyle(content));
71 prop.setSetStyle(parseSetStyle(content));
72 prop.setValidation(parseValidation(content));
73 prop.setDeprecated(parseDeprecated(content));
74 prop.setFieldName(parseFieldName(content));
75 prop.setPropertyName(makePropertyName(bean, prop.getFieldName()));
76 prop.setUpperName(makeUpperName(prop.getPropertyName()));
77 prop.setFinal(parseFinal(content));
78 prop.setType(parseFieldType(content));
79 }
80 List<String> comments = parseComment(content, prop.getPropertyName());
81 prop.setFirstComment(comments.get(0));
82 prop.getComments().addAll(comments.subList(1, comments.size()));
83 this.data = prop;
84 }
85
86 private String makePropertyName(BeanGen bean, String name) {
87 if (name.startsWith(bean.getFieldPrefix())) {
88 name = name.substring(bean.getFieldPrefix().length());
89 }
90 return name;
91 }
92
93 private String makeUpperName(String name) {
94 return name.substring(0, 1).toUpperCase() + name.substring(1);
95 }
96
97
98 private int parseCodeIndex(List<String> content) {
99 for (int index = annotationIndex; index < content.size(); index++) {
100 if (content.get(index).trim().startsWith("@") == false) {
101 if (content.get(index).trim().length() == 0) {
102 throw new RuntimeException("Unable to locate field for property at line " + annotationIndex + ", found blank line");
103 }
104 return index;
105 }
106 }
107 throw new RuntimeException("Unable to locate field for property at line " + annotationIndex);
108 }
109
110 private String parseGetStyle(List<String> content) {
111 String line = content.get(annotationIndex).trim();
112 Matcher matcher = GETTER_PATTERN.matcher(line);
113 if (matcher.matches()) {
114 return matcher.group(1);
115 }
116 return "smart";
117 }
118
119 private String parseSetStyle(List<String> content) {
120 String line = content.get(annotationIndex).trim();
121 Matcher matcher = SETTER_PATTERN.matcher(line);
122 if (matcher.matches()) {
123 return matcher.group(1);
124 }
125 return "smart";
126 }
127
128 private String parseValidation(List<String> content) {
129 String line = content.get(annotationIndex).trim();
130 Matcher matcher = VALIDATION_PATTERN.matcher(line);
131 if (matcher.matches()) {
132 return matcher.group(1);
133 }
134 return "";
135 }
136
137 private boolean parseDeprecated(List<String> content) {
138 for (int index = annotationIndex + 1; index < fieldIndex; index++) {
139 String line = content.get(index).trim();
140 if (line.equals("@Deprecated") || line.startsWith("@Deprecated ")) {
141 return true;
142 }
143 }
144 return false;
145 }
146
147
148 private String parseFieldName(List<String> content) {
149 String line = parseFieldDefinition(content);
150 String[] parts = line.split(" ");
151 String last = parts[parts.length - 1];
152 if (last.endsWith(";") && last.length() > 1) {
153 return last.substring(0, last.length() - 1);
154 }
155 throw new RuntimeException("Unable to locate field name at line " + annotationIndex);
156 }
157
158 private boolean parseFinal(List<String> content) {
159 String line = parseFieldDefinition(content);
160 String[] parts = line.split(" ");
161 if (parts.length < 2) {
162 throw new RuntimeException("Unable to locate field type at line " + annotationIndex);
163 }
164 String first = parts[0];
165 String second = parts[1];
166 if (first.equals("final") || second.equals("final")) {
167 return true;
168 }
169 return false;
170 }
171
172 private String parseFieldType(List<String> content) {
173 String line = parseFieldDefinition(content);
174 String[] parts = line.split(" ");
175 if (parts.length < 2) {
176 throw new RuntimeException("Unable to locate field type at line " + annotationIndex);
177 }
178 int partsPos = parts.length - 2;
179 String type = parts[partsPos];
180 while (true) {
181 int open = 0, openPos = 0, close = 0, closePos = 0;
182 while ((openPos = type.indexOf('<', openPos)) >= 0) {
183 open++;
184 openPos++;
185 }
186 while ((closePos = type.indexOf('>', closePos)) >= 0) {
187 close++;
188 closePos++;
189 }
190 if (open == close) {
191 break;
192 }
193 if (partsPos == 0) {
194 throw new RuntimeException("Unable to locate field type at line " + annotationIndex + ", mismatched generics");
195 }
196 partsPos--;
197 type = parts[partsPos] + " " + type;
198 }
199 return type;
200 }
201
202 private String parseFieldDefinition(List<String> content) {
203 String line = content.get(fieldIndex).trim();
204 if (line.contains(" = ")) {
205 line = line.substring(0, line.indexOf(" = ")).trim() + ";";
206 }
207 return line;
208 }
209
210
211 private String parseMethodNameAsPropertyName(List<String> content) {
212 String[] parts = parseMethodDefinition(content);
213 if (parts[1].length() == 0 || Character.isUpperCase(parts[1].charAt(0)) == false) {
214 throw new RuntimeException("@DerivedProperty method name invalid'");
215 }
216 return Character.toLowerCase(parts[1].charAt(0)) + parts[1].substring(1);
217 }
218
219 private String parseMethodType(List<String> content) {
220 String[] parts = parseMethodDefinition(content);
221 return parts[0];
222 }
223
224 private String[] parseMethodDefinition(List<String> content) {
225 String line = content.get(fieldIndex).trim();
226 if (line.startsWith("public ")) {
227 line = line.substring(7).trim();
228 } else if (line.startsWith("private ")) {
229 line = line.substring(8).trim();
230 } else if (line.startsWith("protected ")) {
231 line = line.substring(10).trim();
232 }
233 String lineEnd = "() {";
234 if (line.startsWith("abstract ")) {
235 line = line.substring(9).trim();
236 lineEnd = "();";
237 } else if (line.startsWith("final ")) {
238 line = line.substring(6).trim();
239 } else if (line.startsWith("static ")) {
240 throw new RuntimeException("@DerivedProperty method cannot be static");
241 }
242 int getIndex = line.indexOf(" get");
243 if (getIndex < 0) {
244 throw new RuntimeException("@DerivedProperty method must start with 'get'");
245 }
246 if (line.endsWith(lineEnd) == false) {
247 throw new RuntimeException("@DerivedProperty method must end with '" + lineEnd + "'");
248 }
249 line = line.substring(0, line.length() - lineEnd.length());
250 String[] split = new String[2];
251 split[0] = line.substring(0, getIndex).trim();
252 split[1] = line.substring(getIndex + 4).trim();
253 return split;
254 }
255
256
257 private List<String> parseComment(List<String> content, String propertyName) {
258 List<String> comments = new ArrayList<String>();
259 String commentEnd = content.get(annotationIndex - 1).trim();
260 if (commentEnd.equals("*/")) {
261 int startCommentIndex = -1;
262 for (int index = annotationIndex - 1; index >= 0; index--) {
263 String line = content.get(index).trim();
264 if (line.equals("/**")) {
265 startCommentIndex = index + 1;
266 break;
267 }
268 }
269 if (startCommentIndex == -1) {
270 throw new RuntimeException("Unable to locate comment start at line " + annotationIndex);
271 }
272 if (startCommentIndex < annotationIndex - 1) {
273 for (int i = startCommentIndex; i < annotationIndex - 1; i++) {
274 String commentLine = content.get(i).trim();
275 if (commentLine.startsWith("*")) {
276 commentLine = commentLine.substring(1).trim();
277 }
278 if (commentLine.startsWith("@return") == false && commentLine.startsWith("@param") == false &&
279 commentLine.startsWith("@throws") == false && commentLine.startsWith("@exception") == false) {
280 comments.add(commentLine);
281 }
282 }
283 String firstLine = comments.get(0);
284 if (firstLine.length() > 0) {
285 comments.set(0, firstLine.substring(0, 1).toLowerCase() + firstLine.substring(1));
286 } else {
287 comments.remove(0);
288 }
289 }
290 } else if (commentEnd.startsWith("/**") && commentEnd.endsWith("*/")) {
291 int startPos = commentEnd.indexOf("/**") + 3;
292 int endPos = commentEnd.lastIndexOf("*/");
293 String comment = commentEnd.substring(startPos, endPos).trim();
294 if (comment.length() > 0) {
295 comments.add(comment.substring(0, 1).toLowerCase() + comment.substring(1));
296 }
297 }
298 if (comments.size() == 0) {
299 comments.add("the " + propertyName + ".");
300 }
301 return comments;
302 }
303
304
305 List<String> generateMetaPropertyConstant() {
306 data.getBean().ensureImport(MetaProperty.class);
307 data.getBean().ensureImport(DirectMetaProperty.class);
308 List<String> list = new ArrayList<String>();
309 list.add("\t\t/**");
310 list.add("\t\t * The meta-property for the {@code " + data.getPropertyName() + "} property.");
311 list.add("\t\t */");
312 if (data.isBeanGenericType()) {
313 list.add("\t\t@SuppressWarnings({\"unchecked\", \"rawtypes\" })");
314 list.add("\t\tprivate final MetaProperty<" + data.getBean().getTypeGenericName(false) + "> " + metaFieldName() +
315 " = (DirectMetaProperty) DirectMetaProperty.of" + readWrite() + "(");
316 list.add("\t\t\t\tthis, \"" + data.getPropertyName() + "\", " +
317 data.getBean().getTypeRaw() + ".class, " + actualType() + ");");
318 } else {
319 String propertyType = propertyType();
320 if (propertyType.length() == 1) {
321 propertyType = "Object";
322 }
323 if (data.isGenericParamType()) {
324 list.add("\t\t@SuppressWarnings({\"unchecked\", \"rawtypes\" })");
325 }
326 list.add("\t\tprivate final MetaProperty<" + propertyType + "> " + metaFieldName() +
327 " = DirectMetaProperty.of" + readWrite() + "(");
328 list.add("\t\t\t\tthis, \"" + data.getPropertyName() + "\", " +
329 data.getBean().getTypeRaw() + ".class, " + actualType() + ");");
330 }
331 return list;
332 }
333
334 List<String> generateMetaPropertyGetCase() {
335 List<String> list = new ArrayList<String>();
336 list.add("\t\t\t\tcase " + data.getPropertyName().hashCode() + ": // " + data.getPropertyName());
337 list.add("\t\t\t\t\treturn " + metaFieldName() + ";");
338 return list;
339 }
340
341 List<String> generateGetter() {
342 return GetterGen.of(data).generateGetter(data);
343 }
344
345 List<String> generateSetter() {
346 return SetterGen.of(data).generateSetter(data);
347 }
348
349 List<String> generateProperty() {
350 data.getBean().ensureImport(Property.class);
351 List<String> list = new ArrayList<String>();
352 list.add("\t/**");
353 list.add("\t * Gets the the {@code " + data.getPropertyName() + "} property.");
354 for (String comment : data.getComments()) {
355 list.add("\t * " + comment);
356 }
357 list.add("\t * @return the property, not null");
358 list.add("\t */");
359 if (data.isDeprecated()) {
360 list.add("\t@Deprecated");
361 }
362 list.add("\tpublic final Property<" + propertyType() + "> " + data.getPropertyName() + "() {");
363 list.add("\t\treturn metaBean()." + data.getPropertyName() + "().createProperty(this);");
364 list.add("\t}");
365 list.add("");
366 return list;
367 }
368
369 List<String> generateMetaProperty() {
370 List<String> list = new ArrayList<String>();
371 String propertyType = propertyType();
372 if (propertyType.length() == 1) {
373 propertyType = "Object";
374 }
375 if (data.isBeanGenericType()) {
376 propertyType = data.getBean().getTypeGenericName(false);
377 }
378 list.add("\t\t/**");
379 list.add("\t\t * The meta-property for the {@code " + data.getPropertyName() + "} property.");
380 list.add("\t\t * @return the meta-property, not null");
381 list.add("\t\t */");
382 if (data.isDeprecated()) {
383 list.add("\t\t@Deprecated");
384 }
385 list.add("\t\tpublic final MetaProperty<" + propertyType + "> " + data.getPropertyName() + "() {");
386 list.add("\t\t\treturn " + metaFieldName() + ";");
387 list.add("\t\t}");
388 list.add("");
389 return list;
390 }
391
392 List<String> generatePropertyGetCase() {
393 List<String> list = new ArrayList<String>();
394 list.add("\t\t\tcase " + data.getPropertyName().hashCode() + ": // " + data.getPropertyName());
395 if (data.getReadWrite().isReadable()) {
396 list.add("\t\t\t\treturn " + GetterGen.of(data).generateGetInvoke(data) + ";");
397 } else {
398 list.add("\t\t\t\tif (quiet) {");
399 list.add("\t\t\t\t\treturn null;");
400 list.add("\t\t\t\t}");
401 list.add("\t\t\t\tthrow new UnsupportedOperationException(\"Property cannot be read: " + data.getPropertyName() + "\");");
402 }
403 return list;
404 }
405
406 List<String> generatePropertySetCase() {
407 List<String> list = new ArrayList<String>();
408 list.add("\t\t\tcase " + data.getPropertyName().hashCode() + ": // " + data.getPropertyName());
409 String setter = SetterGen.of(data).generateSetInvoke(data);
410 if (data.getReadWrite().isWritable() && setter != null) {
411 list.add("\t\t\t\t" + setter + "(" + castObject() + "newValue);");
412 list.add("\t\t\t\treturn;");
413 } else {
414 list.add("\t\t\t\tif (quiet) {");
415 list.add("\t\t\t\t\treturn;");
416 list.add("\t\t\t\t}");
417 list.add("\t\t\t\tthrow new UnsupportedOperationException(\"Property cannot be written: " + data.getPropertyName() + "\");");
418 }
419 return list;
420 }
421
422
423 private String readWrite() {
424 switch (data.getReadWrite()) {
425 case READ_WRITE:
426 return "ReadWrite";
427 case READ_ONLY:
428 return "ReadOnly";
429 case WRITE_ONLY:
430 return "WriteOnly";
431 default:
432 break;
433 }
434 throw new RuntimeException("Invalid read-write type");
435 }
436
437 private String actualType() {
438 String pt = propertyType();
439 if (pt.equals(data.getType())) {
440 int genericStart = pt.indexOf('<');
441 if (genericStart >= 0) {
442 return "(Class) " + pt.substring(0, genericStart) + ".class";
443 }
444 if (data.getType().length() == 1) {
445 return "Object.class";
446 }
447 return pt + ".class";
448 }
449 return pt + ".TYPE";
450 }
451
452 private String castObject() {
453 String pt = propertyType();
454 if (pt.equals(data.getType())) {
455 return "(" + pt + ") ";
456 }
457 return "(" + pt + ") ";
458
459 }
460
461 private String propertyType() {
462 if (data.getType().equals("boolean")) {
463 return "Boolean";
464 }
465 if (data.getType().equals("byte")) {
466 return "Byte";
467 }
468 if (data.getType().equals("short")) {
469 return "Short";
470 }
471 if (data.getType().equals("char")) {
472 return "Character";
473 }
474 if (data.getType().equals("int")) {
475 return "Integer";
476 }
477 if (data.getType().equals("long")) {
478 return "Long";
479 }
480 if (data.getType().equals("float")) {
481 return "Float";
482 }
483 if (data.getType().equals("double")) {
484 return "Double";
485 }
486 return data.getType();
487 }
488
489 private String metaFieldName() {
490 return bean.getFieldPrefix() + data.getPropertyName();
491 }
492
493 GeneratableProperty getData() {
494 return data;
495 }
496
497 }