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    }