001package ball.tools.javadoc;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: AntTaskTaglet.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/AntTaskTaglet.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.annotation.ServiceProviderFor;
024import ball.util.ant.taskdefs.AntTask;
025import ball.xml.FluentNode;
026import com.sun.javadoc.ClassDoc;
027import com.sun.javadoc.Tag;
028import com.sun.tools.doclets.Taglet;
029import java.net.URL;
030import java.util.AbstractMap.SimpleEntry;
031import java.util.HashSet;
032import java.util.Map;
033import java.util.Set;
034import java.util.regex.Pattern;
035import lombok.NoArgsConstructor;
036import lombok.ToString;
037import org.apache.tools.ant.ComponentHelper;
038import org.apache.tools.ant.IntrospectionHelper;
039import org.apache.tools.ant.Project;
040import org.apache.tools.ant.Task;
041import org.apache.tools.ant.taskdefs.Antlib;
042import org.w3c.dom.Node;
043
044import static org.apache.commons.lang3.StringUtils.isNotEmpty;
045import static org.apache.commons.lang3.StringUtils.repeat;
046import static org.apache.tools.ant.MagicNames.ANTLIB_PREFIX;
047
048/**
049 * Inline {@link Taglet} to document {@link.uri http://ant.apache.org/ Ant}
050 * {@link Task}s.
051 *
052 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
053 * @version $Revision: 5431 $
054 */
055@ServiceProviderFor({ Taglet.class })
056@TagletName("ant.task")
057@NoArgsConstructor @ToString
058public class AntTaskTaglet extends AbstractInlineTaglet
059                           implements SunToolsInternalToolkitTaglet {
060    private static final AntTaskTaglet INSTANCE = new AntTaskTaglet();
061
062    public static void register(Map<Object,Object> map) {
063        register(map, INSTANCE);
064    }
065
066    private static final String NO = "no";
067    private static final String YES = "yes";
068
069    private static final String INDENTATION = "  ";
070
071    private static final String DOCUMENTED = "DOCUMENTED";
072
073    @Override
074    public String toString(Tag tag) throws IllegalStateException {
075        String string = null;
076
077        try {
078            String template =
079                render(toNode(tag), INDENTATION.length())
080                .replaceAll(Pattern.quote(DOCUMENTED + "=\"\""), "...");
081
082            string =
083                render(div(attr("class", "block"),
084                           pre("xml", template)));
085        } catch (IllegalStateException exception) {
086            throw exception;
087        } catch (Throwable throwable) {
088            string = render(warning(tag, throwable));
089        }
090
091        return string;
092    }
093
094    @Override
095    public FluentNode toNode(Tag tag) throws Throwable {
096        ClassDoc doc = null;
097        String name = tag.text().trim();
098
099        if (isNotEmpty(name)) {
100            doc = getClassDocFor(tag, name);
101        } else {
102            doc = getContainingClassDocFor(tag);
103        }
104
105        Class<?> type = getClassFor(doc);
106
107        if (! Task.class.isAssignableFrom(type)) {
108            throw new IllegalArgumentException(type.getCanonicalName()
109                                               + " is not a subclass of "
110                                               + Task.class.getCanonicalName());
111        }
112
113        return template(tag, type);
114    }
115
116    private FluentNode template(Tag tag, Class<?> type) {
117        String name = null;
118
119        if (name == null) {
120            Project project = new Project();
121            String pkg = type.getPackage().getName();
122
123            while (pkg != null) {
124                URL url =
125                    type.getResource("/"
126                                     + String.join("/", pkg.split(Pattern.quote(".")))
127                                     + "/antlib.xml");
128
129                if (url != null) {
130                    try {
131                        Antlib.createAntlib(project, url, ANTLIB_PREFIX + pkg)
132                            .execute();
133                        break;
134                    } catch (Exception exception) {
135                    }
136                }
137
138                int index = pkg.lastIndexOf(".");
139
140                if (! (index < 0)) {
141                    pkg = pkg.substring(0, index);
142                } else {
143                    pkg = null;
144                }
145            }
146
147            ComponentHelper helper =
148                ComponentHelper.getComponentHelper(project);
149
150            name =
151                helper.getTaskDefinitions().entrySet()
152                .stream()
153                .filter(t -> t.getValue().equals(type))
154                .map(t -> t.getKey())
155                .findFirst().orElse(null);
156        }
157
158        if (name == null) {
159            AntTask annotation = type.getAnnotation(AntTask.class);
160
161            name = (annotation != null) ? annotation.value() : null;
162        }
163
164        if (name == null) {
165            name = type.getSimpleName();
166        }
167
168        return type(0, new HashSet<>(), tag, new SimpleEntry<>(name, type));
169    }
170
171    private FluentNode type(int depth, Set<Map.Entry<?,?>> set,
172                            Tag tag, Map.Entry<String,Class<?>> entry) {
173        IntrospectionHelper helper =
174            IntrospectionHelper.getHelper(entry.getValue());
175        FluentNode node = element(entry.getKey());
176
177        if (set.add(entry)
178            && (! entry.getValue().getName()
179                  .startsWith(Task.class.getPackage().getName()))) {
180            node
181                .add(attributes(tag, helper))
182                .add(content(depth + 1, set, tag, helper));
183
184            if (helper.supportsCharacters()) {
185                String content = "... text ...";
186
187                if (node.hasChildNodes()) {
188                    content =
189                        "\n" + repeat(INDENTATION, depth + 1) + content + "\n";
190                }
191
192                node.add(text(content));
193            }
194        } else {
195            node.add(attr(DOCUMENTED));
196        }
197
198        return node;
199    }
200
201    private Node[] attributes(Tag tag, IntrospectionHelper helper) {
202        Node[] array =
203            helper.getAttributeMap().entrySet()
204            .stream()
205            .map(t -> attr(t.getKey(), t.getValue().getSimpleName()))
206            .toArray(Node[]::new);
207
208        return array;
209    }
210
211    private FluentNode content(int depth, Set<Map.Entry<?,?>> set,
212                               Tag tag, IntrospectionHelper helper) {
213        return fragment(helper.getNestedElementMap().entrySet()
214                        .stream()
215                        .map(t -> type(depth, set, tag, t)));
216    }
217}