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.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   * A property parsed from the source file.
29   * 
30   * @author Stephen Colebourne
31   */
32  class PropertyGen {
33  
34      /** The getter pattern. */
35      private static final Pattern GETTER_PATTERN = Pattern.compile(".*[ ,(]get[ ]*[=][ ]*[\"]([a-zA-Z-]*)[\"].*");
36      /** The setter pattern. */
37      private static final Pattern SETTER_PATTERN = Pattern.compile(".*[ ,(]set[ ]*[=][ ]*[\"]([a-zA-Z-]*)[\"].*");
38      /** The validation pattern. */
39      private static final Pattern VALIDATION_PATTERN = Pattern.compile(".*[ ,(]validate[ ]*[=][ ]*[\"]([a-zA-Z_.]*)[\"].*");
40  
41      /** Annotation line index in input file. */
42      private final int annotationIndex;
43      /** Field line index in input file. */
44      private final int fieldIndex;
45      /** The bean generator. */
46      private final BeanGen bean;
47      /** The data model of the property. */
48      private final GeneratableProperty data;
49  
50      /**
51       * Constructor.
52       * @param bean  the bean generator
53       * @param content  the lines, not null
54       * @param lineIndex  the index of a PropertyDefinition
55       * @param derived  true if derived
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 //        return "(" + data.getType() + ") (" + pt + ") ";
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 }