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}