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}