    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
    015package org.apache.tapestry5.javadoc;
    016
    017import java.util.Map;
    018
    019import org.apache.tapestry5.BindingConstants;
    020import org.apache.tapestry5.annotations.Component;
    021import org.apache.tapestry5.annotations.Events;
    022import org.apache.tapestry5.annotations.Parameter;
    023import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    024import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    025
    026import com.sun.javadoc.AnnotationDesc;
    027import com.sun.javadoc.AnnotationDesc.ElementValuePair;
    028import com.sun.javadoc.AnnotationValue;
    029import com.sun.javadoc.ClassDoc;
    030import com.sun.javadoc.Doc;
    031import com.sun.javadoc.FieldDoc;
    032import com.sun.javadoc.ProgramElementDoc;
    033import com.sun.javadoc.Tag;
    034
    035public 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}