001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: ManifestProcessor.java 7486 2021-02-15 00:02:29Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/annotation/processing/ManifestProcessor.java $
007 * %%
008 * Copyright (C) 2008 - 2021 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 java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027import java.lang.annotation.Annotation;
028import java.lang.reflect.Method;
029import java.net.URI;
030import java.util.Set;
031import java.util.jar.Attributes;
032import java.util.jar.Manifest;
033import javax.annotation.processing.Processor;
034import javax.annotation.processing.RoundEnvironment;
035import javax.lang.model.element.Element;
036import javax.lang.model.element.ExecutableElement;
037import javax.lang.model.element.TypeElement;
038import javax.tools.FileObject;
039import javax.tools.JavaFileManager;
040import lombok.NoArgsConstructor;
041import lombok.ToString;
042
043import static ball.annotation.Manifest.Attribute;
044import static ball.annotation.Manifest.DependsOn;
045import static ball.annotation.Manifest.DesignTimeOnly;
046import static ball.annotation.Manifest.JavaBean;
047import static ball.annotation.Manifest.MainClass;
048import static ball.annotation.Manifest.Section;
049import static javax.tools.Diagnostic.Kind.ERROR;
050import static javax.tools.JavaFileObject.Kind.CLASS;
051import static javax.tools.StandardLocation.CLASS_OUTPUT;
052import static javax.tools.StandardLocation.CLASS_PATH;
053import static org.apache.commons.lang3.StringUtils.EMPTY;
054
055/**
056 * {@link Processor} implementation to scan {@link ball.annotation.Manifest}
057 * {@link java.lang.annotation.Annotation}s and generate a
058 * {@link java.util.jar.Manifest META-INF/MANIFEST.MF}.
059 *
060 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
061 * @version $Revision: 7486 $
062 */
063@ServiceProviderFor({ Processor.class })
064@For({
065        Attribute.class, MainClass.class, Section.class,
066            JavaBean.class, DependsOn.class, DesignTimeOnly.class
067     })
068@NoArgsConstructor @ToString
069public class ManifestProcessor extends AnnotatedProcessor
070                               implements ClassFileProcessor {
071    private static final String PATH = "META-INF/MANIFEST.MF";
072
073    private static abstract class PROTOTYPE {
074        public static void main(String[] argv) { }
075    }
076
077    private static final Method PROTOTYPE =
078        PROTOTYPE.class.getDeclaredMethods()[0];
079
080    static { PROTOTYPE.setAccessible(true); }
081
082    @Override
083    protected void process(RoundEnvironment roundEnv,
084                           TypeElement annotation, Element element) {
085        super.process(roundEnv, annotation, element);
086
087        Attribute attribute = element.getAnnotation(Attribute.class);
088        MainClass main = element.getAnnotation(MainClass.class);
089
090        if (main != null) {
091            switch (element.getKind()) {
092            case CLASS:
093            case INTERFACE:
094                TypeElement type = (TypeElement) element;
095                ExecutableElement method = getMethod(type, PROTOTYPE);
096
097                if (method != null
098                    && (method.getModifiers()
099                        .containsAll(getModifiers(PROTOTYPE)))) {
100                } else {
101                    print(ERROR, element,
102                          "@%s: %s does not implement '%s'",
103                          main.annotationType().getSimpleName(),
104                          element.getKind(), declaration(PROTOTYPE));
105                }
106                break;
107
108            default:
109                break;
110            }
111        }
112
113        Section section = element.getAnnotation(Section.class);
114        JavaBean bean = element.getAnnotation(JavaBean.class);
115        DependsOn depends = element.getAnnotation(DependsOn.class);
116        DesignTimeOnly design =
117            element.getAnnotation(DesignTimeOnly.class);
118    }
119
120    @Override
121    public void process(Set<Class<?>> set, JavaFileManager fm) throws Exception {
122        ManifestImpl manifest = new ManifestImpl();
123        FileObject file = fm.getFileForInput(CLASS_PATH, EMPTY, PATH);
124
125        if (file != null) {
126            try (InputStream in = file.openInputStream()) {
127                manifest.read(in);
128            } catch (IOException exception) {
129            }
130        } else {
131            manifest.init();
132        }
133
134        file = fm.getFileForOutput(CLASS_OUTPUT, EMPTY, PATH, null);
135
136        URI root = file.toUri().resolve("..").normalize();
137
138        for (Class<?> type : set) {
139            URI uri =
140                fm.getJavaFileForInput(CLASS_OUTPUT, type.getName(), CLASS)
141                .toUri();
142
143            uri = root.relativize(uri);
144
145            Attribute attribute = type.getAnnotation(Attribute.class);
146            MainClass main = type.getAnnotation(MainClass.class);
147
148            if (main != null) {
149                manifest.put(main, type.getName());
150            }
151
152            Section section = type.getAnnotation(Section.class);
153
154            if (section != null) {
155                manifest.put(uri.resolve("").toString(), section);
156            }
157
158            JavaBean bean = type.getAnnotation(JavaBean.class);
159            DependsOn depends = type.getAnnotation(DependsOn.class);
160            DesignTimeOnly design = type.getAnnotation(DesignTimeOnly.class);
161
162            if (bean != null || depends != null || design != null) {
163                manifest.put(uri.toString(),
164                             bean, depends, design);
165            }
166        }
167
168        try (OutputStream out = file.openOutputStream()) {
169            manifest.write(out);
170         }
171    }
172
173    @NoArgsConstructor @ToString
174    private class ManifestImpl extends Manifest {
175        private static final String MANIFEST_VERSION = "Manifest-Version";
176
177        protected void init() {
178            if (getMainAttributes().getValue(MANIFEST_VERSION) == null) {
179                getMainAttributes().putValue(MANIFEST_VERSION, "1.0");
180            }
181        }
182
183        public Attributes putAttributes(String name, Attributes attributes) {
184            return getEntries().put(name, attributes);
185        }
186
187        public String put(MainClass main, String name) {
188            return (getMainAttributes()
189                    .putValue(getAttributeName(main.getClass()), name));
190        }
191
192        public void put(String path, Section section) {
193            Attributes attributes = getEntries().get(path);
194
195            if (attributes == null) {
196                attributes = new Attributes();
197                getEntries().put(path, attributes);
198            }
199
200            attributes.putValue("Sealed", String.valueOf(section.sealed()));
201        }
202
203        public void put(String path,
204                        JavaBean bean,
205                        DependsOn depends, DesignTimeOnly design) {
206            Attributes attributes = getEntries().get(path);
207
208            if (attributes == null) {
209                attributes = new Attributes();
210                getEntries().put(path, attributes);
211            }
212
213            attributes.putValue(getAttributeName(JavaBean.class),
214                                String.valueOf(true));
215
216            if (depends != null) {
217                attributes.putValue(getAttributeName(depends.getClass()),
218                                    String.join(" ", depends.value()));
219            }
220
221            if (design != null) {
222                attributes.putValue(getAttributeName(design.getClass()),
223                                    String.join(" ", design.value()));
224            }
225        }
226
227        private String getAttributeName(Class<? extends Annotation> type) {
228            return type.getAnnotation(Attribute.class).value();
229        }
230    }
231}