001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.annotation.ServiceProviderFor;
022import ball.util.ant.taskdefs.AntLib;
023import ball.util.ant.taskdefs.AntTask;
024import ball.xml.FluentDocument;
025import ball.xml.FluentDocumentBuilderFactory;
026import ball.xml.XalanConstants;
027import java.io.OutputStream;
028import java.util.Set;
029import java.util.TreeMap;
030import java.util.TreeSet;
031import javax.annotation.processing.Processor;
032import javax.tools.FileObject;
033import javax.tools.JavaFileManager;
034import javax.xml.transform.Transformer;
035import javax.xml.transform.TransformerFactory;
036import javax.xml.transform.dom.DOMSource;
037import javax.xml.transform.stream.StreamResult;
038import lombok.NoArgsConstructor;
039import lombok.ToString;
040import org.apache.tools.ant.Task;
041
042import static java.lang.reflect.Modifier.isAbstract;
043import static javax.tools.StandardLocation.CLASS_OUTPUT;
044import static javax.xml.transform.OutputKeys.INDENT;
045import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;
046
047/**
048 * Generates {@code antlib.xml} (at location(s) specified by {@link AntLib})
049 * at the end of annotation processing.
050 *
051 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
052 */
053@ServiceProviderFor({ Processor.class })
054@For({ AntLib.class, AntTask.class })
055@NoArgsConstructor @ToString
056public class AntLibProcessor extends AnnotatedProcessor implements ClassFileProcessor, XalanConstants {
057    private static final String ANTLIB_XML = "antlib.xml";
058
059    private static final Transformer TRANSFORMER;
060
061    static {
062        try {
063            TRANSFORMER = TransformerFactory.newInstance().newTransformer();
064            TRANSFORMER.setOutputProperty(OMIT_XML_DECLARATION, NO);
065            TRANSFORMER.setOutputProperty(INDENT, YES);
066            TRANSFORMER.setOutputProperty(XALAN_INDENT_AMOUNT.toString(), String.valueOf(2));
067        } catch (Exception exception) {
068            throw new ExceptionInInitializerError(exception);
069        }
070    }
071
072    @Override
073    public void process(Set<Class<?>> set, JavaFileManager fm) throws Exception {
074        TreeSet<String> paths = new TreeSet<>();
075        AntLibXML antlib = new AntLibXML();
076
077        for (Class<?> type : set) {
078            AntTask annotation = type.getAnnotation(AntTask.class);
079
080            if (annotation != null) {
081                if (Task.class.isAssignableFrom(type)) {
082                    if (! isAbstract(type.getModifiers())) {
083                        antlib.put(annotation.value(), type);
084                    }
085                }
086            }
087
088            AntLib lib = type.getAnnotation(AntLib.class);
089
090            if (lib != null) {
091                paths.add(type.getPackage().getName());
092            }
093        }
094
095        for (String path : paths) {
096            FileObject file = fm.getFileForOutput(CLASS_OUTPUT, path, ANTLIB_XML, null);
097
098            try (OutputStream out = file.openOutputStream()) {
099                antlib.writeTo(out);
100            }
101        }
102    }
103
104    @NoArgsConstructor
105    private static class AntLibXML extends TreeMap<String,Class<?>> {
106        private static final long serialVersionUID = -8903476717502118017L;
107
108        public FluentDocument asDocument() throws Exception {
109            FluentDocument d =
110                FluentDocumentBuilderFactory.newInstance()
111                .newDocumentBuilder()
112                .newDocument();
113
114            d.add(d.element("antlib",
115                            entrySet().stream()
116                            .map(t -> d.element("taskdef",
117                                                d.attr("name", t.getKey()),
118                                                d.attr("classname", t.getValue().getName())))));
119
120            return d;
121        }
122
123        public void writeTo(OutputStream out) throws Exception {
124            TRANSFORMER.transform(new DOMSource(asDocument()), new StreamResult(out));
125        }
126    }
127}