001package ball.tools.javadoc;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: InjectedFieldsTaglet.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/InjectedFieldsTaglet.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.annotation.ServiceProviderFor;
024import ball.xml.FluentNode;
025import com.sun.javadoc.ClassDoc;
026import com.sun.javadoc.Tag;
027import com.sun.tools.doclets.Taglet;
028import java.lang.annotation.Annotation;
029import java.lang.annotation.Retention;
030import java.lang.reflect.Field;
031import java.util.HashSet;
032import java.util.Map;
033import java.util.Set;
034import java.util.stream.Stream;
035import lombok.NoArgsConstructor;
036import lombok.ToString;
037
038import static org.apache.commons.lang3.StringUtils.isNotEmpty;
039
040/**
041 * Inline {@link Taglet} to provide a report of members whose values are
042 * injected.
043 *
044 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
045 * @version $Revision: 6035 $
046 */
047@ServiceProviderFor({ Taglet.class })
048@TagletName("injected.fields")
049@NoArgsConstructor @ToString
050public class InjectedFieldsTaglet extends AbstractInlineTaglet
051                                  implements SunToolsInternalToolkitTaglet {
052    private static final InjectedFieldsTaglet INSTANCE =
053        new InjectedFieldsTaglet();
054
055    public static void register(Map<Object,Object> map) {
056        register(map, INSTANCE);
057    }
058
059    private static final String[] NAMES = new String[] {
060        javax.annotation.Resource.class.getName(),
061        javax.annotation.Resources.class.getName(),
062        "javax.inject.Inject",
063        "javax.inject.Named",
064        "org.springframework.beans.factory.annotation.Autowired",
065        "org.springframework.beans.factory.annotation.Value"
066    };
067
068    @Override
069    public FluentNode toNode(Tag tag) throws Throwable {
070        ClassDoc doc = null;
071        String[] argv = tag.text().trim().split("[\\p{Space}]+", 2);
072
073        if (isNotEmpty(argv[0])) {
074            doc = getClassDocFor(tag, argv[0]);
075        } else {
076            doc = containingClass(tag);
077        }
078
079        Set<Class<? extends Annotation>> set = new HashSet<>();
080
081        for (String name : NAMES) {
082            Class<?> type = null;
083
084            try {
085                type = Class.forName(name);
086            } catch (Exception exception) {
087            }
088
089            if (type != null) {
090                Class<? extends Annotation> annotation =
091                    type.asSubclass(Annotation.class);
092                Retention retention =
093                    annotation.getAnnotation(Retention.class);
094
095                if (retention == null) {
096                    throw new IllegalStateException(annotation.getCanonicalName()
097                                                    + " does not specify a retention policy");
098                }
099
100                switch (retention.value()) {
101                case RUNTIME:
102                    break;
103
104                case CLASS:
105                case SOURCE:
106                default:
107                    throw new IllegalStateException(annotation.getCanonicalName()
108                                                    + " specifies "
109                                                    + retention.value()
110                                                    + " retention policy");
111                    /* break; */
112                }
113
114                set.add(annotation);
115            }
116        }
117
118        if (set.isEmpty()) {
119            throw new IllegalStateException("No annotations to map");
120        }
121
122        return div(attr("class", "summary"),
123                   h3("Injected Field Summary"),
124                   table(tag, getClassFor(doc), set));
125    }
126
127    private FluentNode table(Tag tag, Class<?> type,
128                             Set<Class<? extends Annotation>> set) {
129        return table(thead(tr(th("Annotation(s)"), th("Field"))),
130                     tbody(Stream.of(type.getDeclaredFields())
131                           .filter(t -> (Stream.of(t.getAnnotations())
132                                         .filter(a -> set.contains(a.annotationType()))
133                                         .findFirst().isPresent()))
134                           .map(t -> tr(tag, t, set))));
135    }
136
137    private FluentNode tr(Tag tag, Field field,
138                          Set<Class<? extends Annotation>> set) {
139        return tr(td(fragment(Stream.of(field.getAnnotations())
140                              .filter(t -> set.contains(t.annotationType()))
141                              .map(t -> annotation(tag, t)))),
142                  td(declaration(tag, field)));
143    }
144}