001package ball.tools.javadoc; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: JavadocHTMLTemplates.java 6035 2020-05-25 03:01:46Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/tools/javadoc/JavadocHTMLTemplates.java $ 007 * %% 008 * Copyright (C) 2008 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import ball.xml.FluentNode; 024import ball.xml.HTMLTemplates; 025import com.sun.javadoc.ProgramElementDoc; 026import com.sun.javadoc.Tag; 027import com.sun.tools.doclets.Taglet; 028import java.lang.annotation.Annotation; 029/* import java.lang.reflect.AnnotatedElement; */ 030import java.lang.reflect.Constructor; 031import java.lang.reflect.Field; 032import java.lang.reflect.Member; 033import java.lang.reflect.Method; 034import java.lang.reflect.Modifier; 035import java.lang.reflect.Parameter; 036import java.lang.reflect.ParameterizedType; 037import java.lang.reflect.Type; 038import java.util.Arrays; 039import java.util.Collection; 040import java.util.List; 041import java.util.stream.Collectors; 042import java.util.stream.IntStream; 043import java.util.stream.Stream; 044import javax.swing.table.TableModel; 045import org.apache.commons.lang3.ArrayUtils; 046import org.apache.commons.lang3.exception.ExceptionUtils; 047import org.w3c.dom.Node; 048 049import static org.apache.commons.lang3.StringUtils.SPACE; 050import static org.apache.commons.lang3.StringUtils.isAllBlank; 051import static org.apache.commons.lang3.StringUtils.isNotEmpty; 052 053/** 054 * Javadoc {@link HTMLTemplates}. 055 * 056 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 057 * @version $Revision: 6035 $ 058 */ 059public interface JavadocHTMLTemplates extends HTMLTemplates { 060 FluentNode a(Tag tag, ProgramElementDoc target, Node node); 061 FluentNode a(Tag tag, Class<?> type, Node node); 062 FluentNode a(Tag tag, Member member, Node node); 063 FluentNode a(Tag tag, String name, Node node); 064 065 /** 066 * {@code <}{@code p><b><u>}{@link Tag tag}{@code </u></b></p}{@code >} 067 * {@code <!}{@code -- }{@link Throwable stack trace}{@code --}{@code >} 068 * 069 * @param tag The offending {@link Tag}. 070 * @param throwable The {@link Throwable}. 071 * 072 * @return {@link org.w3c.dom.DocumentFragment} 073 */ 074 default FluentNode warning(Tag tag, Throwable throwable) { 075 System.err.println(tag.position() + ": " + throwable); 076 077 String string = "@" + ((Taglet) this).getName(); 078 079 if (isNotEmpty(tag.text())) { 080 string += SPACE + tag.text(); 081 } 082 083 if (((Taglet) this).isInlineTag()) { 084 string = "{" + string + "}"; 085 } 086 087 return fragment(p(b(u(string))), 088 comment(ExceptionUtils.getStackTrace(throwable))); 089 } 090 091 /** 092 * {@code <a href="}{@link com.sun.javadoc.ClassDoc type}{@code ">}{@link com.sun.javadoc.ClassDoc#name() ClassDoc.name()}{@code </a>} 093 * 094 * @param tag The {@link Tag}. 095 * @param type The target {@link Class}. 096 * 097 * @return {@link org.w3c.dom.Element} 098 */ 099 default FluentNode a(Tag tag, Class<?> type) { 100 return a(tag, type, (String) null); 101 } 102 103 /** 104 * {@code <a href="}{@link com.sun.javadoc.ClassDoc type}{@code ">}{@link #code(String) code(name)}{@code </a>} 105 * 106 * @param tag The {@link Tag}. 107 * @param type The target {@link Class}. 108 * @param name The link name. 109 * 110 * @return {@link org.w3c.dom.Element} 111 */ 112 default FluentNode a(Tag tag, Class<?> type, String name) { 113 return a(tag, type, (name != null) ? code(name) : null); 114 } 115 116 /** 117 * {@code <a href="}{@link com.sun.javadoc.MemberDoc member}{@code ">}{@link com.sun.javadoc.MemberDoc#name() MemberDoc.name()}{@code </a>} 118 * 119 * @param tag The {@link Tag}. 120 * @param member The target {@link Member}. 121 * 122 * @return {@link org.w3c.dom.Element} 123 */ 124 default FluentNode a(Tag tag, Member member) { 125 return a(tag, member, (String) null); 126 } 127 128 /** 129 * {@code <a href="}{@link com.sun.javadoc.MemberDoc member}{@code ">}{@link #code(String) code(name)}{@code </a>} 130 * 131 * @param tag The {@link Tag}. 132 * @param member The target {@link Member}. 133 * @param name The link name. 134 * 135 * @return {@link org.w3c.dom.Element} 136 */ 137 default FluentNode a(Tag tag, Member member, String name) { 138 return a(tag, member, (name != null) ? code(name) : null); 139 } 140 141 /** 142 * {@code <a href="}{@link com.sun.javadoc.ClassDoc constant}{@code ">}{@link Enum#name() constant.name()}{@code </a>} 143 * 144 * @param tag The {@link Tag}. 145 * @param constant The target {@link Enum}. 146 * 147 * @return {@link org.w3c.dom.Element} 148 */ 149 default FluentNode a(Tag tag, Enum<?> constant) { 150 return a(tag, constant.getDeclaringClass(), constant.name()); 151 } 152 153 /** 154 * Dispatches call to {@link #declaration(Tag,Field)} or 155 * {@link #declaration(Tag,Method)} as appropriate. 156 * 157 * @param tag The {@link Tag}. 158 * @param member The target {@link Member}. 159 * 160 * @return {@link org.w3c.dom.DocumentFragment} 161 */ 162 default FluentNode declaration(Tag tag, Member member) { 163 FluentNode node = null; 164 165 if (member instanceof Field) { 166 node = declaration(tag, (Field) member); 167 } else if (member instanceof Method) { 168 node = declaration(tag, (Method) member); 169 } else { 170 throw new IllegalArgumentException(String.valueOf(member)); 171 } 172 173 return node; 174 } 175 176 /** 177 * Method to generate a {@link Field} declaration with javadoc 178 * hyperlinks. 179 * 180 * @param tag The {@link Tag}. 181 * @param field The target {@link Field}. 182 * 183 * @return {@link org.w3c.dom.DocumentFragment} 184 */ 185 default FluentNode declaration(Tag tag, Field field) { 186 return fragment(modifiers(field.getModifiers()), 187 type(tag, field.getGenericType()), 188 code(SPACE), 189 a(tag, field, (String) null)); 190 } 191 192 /** 193 * Method to generate a {@link Method} declaration with javadoc 194 * hyperlinks. 195 * 196 * @param tag The {@link Tag}. 197 * @param method The target {@link Method}. 198 * 199 * @return {@link org.w3c.dom.DocumentFragment} 200 */ 201 default FluentNode declaration(Tag tag, Method method) { 202 FluentNode node = 203 fragment(modifiers(method.getModifiers()), 204 type(tag, method.getGenericReturnType()), 205 code(SPACE), 206 a(tag, method, (String) null)); 207 208 Parameter[] parameters = method.getParameters(); 209 210 node.add(code("(")); 211 212 for (int i = 0; i < parameters.length; i += 1) { 213 if (i > 0) { 214 node.add(code(", ")); 215 } 216 217 node.add(declaration(tag, parameters[i])); 218 } 219 220 node.add(code(")")); 221 222 return node; 223 } 224 225 /** 226 * Method to generate a {@link Parameter} declaration with javadoc 227 * hyperlinks. 228 * 229 * @param tag The {@link Tag}. 230 * @param parameter The target {@link Parameter}. 231 * 232 * @return {@link org.w3c.dom.DocumentFragment} 233 */ 234 default FluentNode declaration(Tag tag, Parameter parameter) { 235 return fragment(modifiers(parameter.getModifiers()), 236 type(tag, parameter.getParameterizedType()), 237 code(SPACE), 238 code(parameter.getName())); 239 } 240 /* 241 * default FluentNode annotations(Tag tag, AnnotatedElement element) { 242 * return annotations(tag, element.getDeclaredAnnotations()); 243 * } 244 * 245 * default FluentNode annotations(Tag tag, Annotation... annotations) { 246 * return fragment().add(Stream.of(annotations) 247 * .map(t -> annotation(tag, t))); 248 * } 249 */ 250 /** 251 * {@code <a href="}{@link com.sun.javadoc.ClassDoc annotation}{@code ">}{@link #code(String) code(String.valueOf(annotation))}{@code </a>} 252 * 253 * @param tag The {@link Tag}. 254 * @param annotation The target {@link Annotation}. 255 * 256 * @return {@link org.w3c.dom.Element} 257 */ 258 default FluentNode annotation(Tag tag, Annotation annotation) { 259 Class<?> type = annotation.annotationType(); 260 String string = 261 String.valueOf(annotation) 262 .replace(type.getCanonicalName(), type.getSimpleName()); 263 264 return fragment().add(a(tag, type, code(string))); 265 } 266 267 /** 268 * Method to generate modifiers for {@code declaration()} methods. 269 * 270 * @param modifiers See {@link Modifier}. 271 * 272 * @return {@link org.w3c.dom.DocumentFragment} 273 */ 274 default FluentNode modifiers(int modifiers) { 275 FluentNode node = fragment(); 276 String string = Modifier.toString(modifiers); 277 278 if (isNotEmpty(string)) { 279 node.add(code(string + SPACE)); 280 } 281 282 return node; 283 } 284 285 /** 286 * Method to generate types for {@code declaration()} methods. 287 * 288 * @param tag The {@link Tag}. 289 * @param type The target {@link Type}. 290 * 291 * @return {@link org.w3c.dom.DocumentFragment} 292 */ 293 default FluentNode type(Tag tag, Type type) { 294 FluentNode node = null; 295 296 if (type instanceof ParameterizedType) { 297 node = 298 fragment(type(tag, ((ParameterizedType) type).getRawType())); 299 300 Type[] types = ((ParameterizedType) type).getActualTypeArguments(); 301 302 node = node.add(code("<")); 303 304 for (int i = 0; i < types.length; i += 1) { 305 if (i > 0) { 306 node.add(code(",")); 307 } 308 309 node.add(type(tag, types[i])); 310 } 311 312 node.add(code(">")); 313 } else if (type instanceof Class<?>) { 314 node = a(tag, (Class<?>) type); 315 } else { 316 node = code(type.getTypeName()); 317 } 318 319 return node; 320 } 321 322 /** 323 * {@code <table>}{@link TableModel model}{@code </table>} 324 * 325 * @param tag The {@link Tag}. 326 * @param model The {@link TableModel} to use to create the 327 * new table {@link org.w3c.dom.Element}. 328 * @param stream The {@link Stream} of {@link Node}s to 329 * append to the newly created 330 * {@link org.w3c.dom.Element}. 331 * 332 * @return {@link org.w3c.dom.Element} 333 */ 334 default FluentNode table(Tag tag, TableModel model, Stream<Node> stream) { 335 return table(tag, model, stream.toArray(Node[]::new)); 336 } 337 338 /** 339 * {@code <table>}{@link TableModel model}{@code </table>} 340 * 341 * @param tag The {@link Tag}. 342 * @param model The {@link TableModel} to use to create the 343 * new table {@link org.w3c.dom.Element}. 344 * @param nodes The {@link Node}s to append to the newly 345 * created 346 * {@link org.w3c.dom.Element}. 347 * 348 * @return {@link org.w3c.dom.Element} 349 */ 350 default FluentNode table(Tag tag, TableModel model, Node... nodes) { 351 FluentNode table = table(); 352 String[] names = 353 IntStream.range(0, model.getColumnCount()) 354 .boxed() 355 .map(model::getColumnName) 356 .toArray(String[]::new); 357 358 if (! isAllBlank(names)) { 359 table.add(thead(tr(Stream.of(names).map(this::th)))); 360 } 361 362 table 363 .add(tbody(IntStream.range(0, model.getRowCount()) 364 .boxed() 365 .map(y -> tr(IntStream.range(0, names.length) 366 .boxed() 367 .map(x -> td(toHTML(tag, 368 model.getValueAt(y, x)))))))); 369 370 return table.add(nodes); 371 } 372 373 /** 374 * Method to get a Javadoc HTML representation of an {@link Object}. 375 * 376 * @param tag The {@link Tag}. 377 * @param object The target {@link Object}. 378 * 379 * @return {@link org.w3c.dom.Node} 380 */ 381 default FluentNode toHTML(Tag tag, Object object) { 382 FluentNode node = null; 383 384 if (object instanceof byte[]) { 385 node = 386 text(Stream.of(ArrayUtils.toObject((byte[]) object)) 387 .map (t -> String.format("0x%02X", t)) 388 .collect(Collectors.joining(", ", "[", "]"))); 389 } else if (object instanceof boolean[]) { 390 node = text(Arrays.toString((boolean[]) object)); 391 } else if (object instanceof double[]) { 392 node = text(Arrays.toString((double[]) object)); 393 } else if (object instanceof float[]) { 394 node = text(Arrays.toString((float[]) object)); 395 } else if (object instanceof int[]) { 396 node = text(Arrays.toString((int[]) object)); 397 } else if (object instanceof long[]) { 398 node = text(Arrays.toString((long[]) object)); 399 } else if (object instanceof Object[]) { 400 node = toHTML(tag, Arrays.asList((Object[]) object)); 401 } else if (object instanceof Type) { 402 node = type(tag, (Type) object); 403 } else if (object instanceof Enum<?>) { 404 node = a(tag, (Enum<?>) object); 405 } else if (object instanceof Field) { 406 node = a(tag, (Field) object); 407 } else if (object instanceof Constructor) { 408 node = a(tag, (Constructor) object); 409 } else if (object instanceof Method) { 410 node = a(tag, (Method) object); 411 } else if (object instanceof Collection<?>) { 412 List<Node> nodes = 413 ((Collection<?>) object) 414 .stream() 415 .map(t -> toHTML(tag, t)) 416 .collect(Collectors.toList()); 417 418 for (int i = nodes.size() - 1; i > 0; i -= 1) { 419 nodes.add(i, text(", ")); 420 } 421 422 node = 423 fragment() 424 .add(text("[")) 425 .add(nodes.stream()) 426 .add(text("]")); 427 } else { 428 node = text(String.valueOf(object)); 429 } 430 431 return node; 432 } 433}