001 // Copyright 2007, 2009, 2011 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry5.javadoc;
016
017 import java.util.Map;
018
019 import org.apache.tapestry5.BindingConstants;
020 import org.apache.tapestry5.annotations.Component;
021 import org.apache.tapestry5.annotations.Events;
022 import org.apache.tapestry5.annotations.Parameter;
023 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
024 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025
026 import com.sun.javadoc.AnnotationDesc;
027 import com.sun.javadoc.AnnotationDesc.ElementValuePair;
028 import com.sun.javadoc.AnnotationValue;
029 import com.sun.javadoc.ClassDoc;
030 import com.sun.javadoc.Doc;
031 import com.sun.javadoc.FieldDoc;
032 import com.sun.javadoc.ProgramElementDoc;
033 import com.sun.javadoc.Tag;
034
035 public class ClassDescription
036 {
037 public final ClassDoc classDoc;
038
039 public final Map<String, ParameterDescription> parameters = CollectionFactory.newCaseInsensitiveMap();
040
041 /**
042 * Case insensitive map, keyed on event name, value is optional description (often blank).
043 */
044 public final Map<String, String> events = CollectionFactory.newCaseInsensitiveMap();
045
046 public ClassDescription()
047 {
048 this.classDoc = null;
049 }
050
051 public ClassDescription(ClassDoc classDoc, ClassDescriptionSource source)
052 {
053 this.classDoc = classDoc;
054
055 loadEvents();
056 loadParameters(source);
057
058 ClassDoc parentDoc = classDoc.superclass();
059
060 if (parentDoc != null)
061 {
062 ClassDescription parentDescription = source.getDescription(classDoc.superclass().qualifiedName());
063
064 mergeInto(events, parentDescription.events);
065 mergeInto(parameters, parentDescription.parameters);
066 }
067 }
068
069 private void loadEvents()
070 {
071 AnnotationDesc eventsAnnotation = getAnnotation(classDoc, Events.class);
072
073 if (eventsAnnotation == null)
074 return;
075
076 // Events has only a single attribute: value(), so we know its the first element
077 // in the array.
078
079 ElementValuePair pair = eventsAnnotation.elementValues()[0];
080
081 AnnotationValue annotationValue = pair.value();
082 AnnotationValue[] values = (AnnotationValue[]) annotationValue.value();
083
084 for (AnnotationValue eventValue : values)
085 {
086 String event = (String) eventValue.value();
087 int ws = event.indexOf(' ');
088
089 String name = ws < 0 ? event : event.substring(0, ws);
090 String description = ws < 0 ? "" : event.substring(ws + 1).trim();
091
092 events.put(name, description);
093 }
094 }
095
096 private static <K, V> void mergeInto(Map<K, V> target, Map<K, V> source)
097 {
098 for (K key : source.keySet())
099 {
100 if (!target.containsKey(key))
101 {
102 V value = source.get(key);
103 target.put(key, value);
104 }
105 }
106 }
107
108 private void loadParameters(ClassDescriptionSource source)
109 {
110 for (FieldDoc fd : classDoc.fields(false))
111 {
112 if (fd.isStatic())
113 continue;
114
115 if (!fd.isPrivate())
116 continue;
117
118 Map<String, String> values = getAnnotationValues(fd, Parameter.class);
119
120 if (values != null)
121 {
122 String name = values.get("name");
123
124 if (name == null)
125 name = fd.name().replaceAll("^[$_]*", "");
126
127 ParameterDescription pd = new ParameterDescription(fd, name, fd.type().qualifiedTypeName(), get(values,
128 "value", ""), get(values, "defaultPrefix", BindingConstants.PROP), getBoolean(values,
129 "required", false), getBoolean(values, "allowNull", true), getBoolean(values, "cache", true),
130 getSinceTagValue(fd), isDeprecated(fd));
131
132 parameters.put(name, pd);
133
134 continue;
135 }
136
137 values = getAnnotationValues(fd, Component.class);
138
139 if (values != null)
140 {
141 String names = get(values, "publishParameters", "");
142
143 if (InternalUtils.isBlank(names))
144 continue;
145
146 for (String name : names.split("\\s*,\\s*"))
147 {
148 ParameterDescription pd = getPublishedParameterDescription(source, fd, name);
149 parameters.put(name, pd);
150 }
151
152 }
153
154 }
155 }
156
157 private ParameterDescription getPublishedParameterDescription(ClassDescriptionSource source, FieldDoc fd,
158 String name)
159 {
160 String currentClassName = fd.type().qualifiedTypeName();
161
162 while (true)
163 {
164 ClassDescription componentCD = source.getDescription(currentClassName);
165
166 if (componentCD.classDoc == null)
167 throw new IllegalArgumentException(String.format("Published parameter '%s' from %s not found.", name,
168 fd.qualifiedName()));
169
170 if (componentCD.parameters.containsKey(name)) { return componentCD.parameters.get(name); }
171
172 currentClassName = componentCD.classDoc.superclass().typeName();
173 }
174 }
175
176 private static boolean isDeprecated(ProgramElementDoc doc)
177 {
178 return (getAnnotation(doc, Deprecated.class) != null) || (doc.tags("deprecated").length != 0);
179 }
180
181 private static String getSinceTagValue(Doc doc)
182 {
183 return getTagValue(doc, "since");
184 }
185
186 private static String getTagValue(Doc doc, String tagName)
187 {
188 Tag[] tags = doc.tags(tagName);
189
190 return 0 < tags.length ? tags[0].text() : "";
191 }
192
193 private static boolean getBoolean(Map<String, String> map, String key, boolean defaultValue)
194 {
195 if (map.containsKey(key))
196 return Boolean.parseBoolean(map.get(key));
197
198 return defaultValue;
199 }
200
201 private static String get(Map<String, String> map, String key, String defaultValue)
202 {
203 if (map.containsKey(key))
204 return map.get(key);
205
206 return defaultValue;
207 }
208
209 private static AnnotationDesc getAnnotation(ProgramElementDoc source, Class annotationType)
210 {
211 String name = annotationType.getName();
212
213 for (AnnotationDesc ad : source.annotations())
214 {
215 if (ad.annotationType().qualifiedTypeName().equals(name)) { return ad; }
216 }
217
218 return null;
219 }
220
221 private static Map<String, String> getAnnotationValues(ProgramElementDoc source, Class annotationType)
222 {
223 AnnotationDesc annotation = getAnnotation(source, annotationType);
224
225 if (annotation == null)
226 return null;
227
228 Map<String, String> result = CollectionFactory.newMap();
229
230 for (ElementValuePair pair : annotation.elementValues())
231 {
232 result.put(pair.element().name(), pair.value().value().toString());
233 }
234
235 return result;
236 }
237 }