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