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}