    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    }