001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: ManifestProcessor.java 6110 2020-06-04 01:42:33Z 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 - 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 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: 6110 $
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    @Override
081    protected void process(RoundEnvironment roundEnv,
082                           TypeElement annotation, Element element) {
083        super.process(roundEnv, annotation, element);
084
085        Attribute attribute = element.getAnnotation(Attribute.class);
086        MainClass main = element.getAnnotation(MainClass.class);
087
088        if (main != null) {
089            switch (element.getKind()) {
090            case CLASS:
091            case INTERFACE:
092                TypeElement type = (TypeElement) element;
093                ExecutableElement method = getMethod(type, PROTOTYPE);
094
095                if (method != null
096                    && (method.getModifiers()
097                        .containsAll(getModifiers(PROTOTYPE)))) {
098                } else {
099                    print(ERROR, element,
100                          "@%s: %s does not implement '%s'",
101                          main.annotationType().getSimpleName(),
102                          element.getKind(), declaration(PROTOTYPE));
103                }
104                break;
105
106            default:
107                break;
108            }
109        }
110
111        Section section = element.getAnnotation(Section.class);
112        JavaBean bean = element.getAnnotation(JavaBean.class);
113        DependsOn depends = element.getAnnotation(DependsOn.class);
114        DesignTimeOnly design =
115            element.getAnnotation(DesignTimeOnly.class);
116    }
117
118    @Override
119    public void process(Set<Class<?>> set,
120                        JavaFileManager fm) throws Throwable {
121        ManifestImpl manifest = new ManifestImpl();
122        FileObject file = fm.getFileForInput(CLASS_PATH, EMPTY, PATH);
123
124        if (file != null) {
125            try (InputStream in = file.openInputStream()) {
126                manifest.read(in);
127            } catch (IOException exception) {
128            }
129        } else {
130            manifest.init();
131        }
132
133        file = fm.getFileForOutput(CLASS_OUTPUT, EMPTY, PATH, null);
134
135        URI root = file.toUri().resolve("..").normalize();
136
137        for (Class<?> type : set) {
138            URI uri =
139                fm.getJavaFileForInput(CLASS_OUTPUT, type.getName(), CLASS)
140                .toUri();
141
142            uri = root.relativize(uri);
143
144            Attribute attribute = type.getAnnotation(Attribute.class);
145            MainClass main = type.getAnnotation(MainClass.class);
146
147            if (main != null) {
148                manifest.put(main, type.getName());
149            }
150
151            Section section = type.getAnnotation(Section.class);
152
153            if (section != null) {
154                manifest.put(uri.resolve("").toString(), section);
155            }
156
157            JavaBean bean = type.getAnnotation(JavaBean.class);
158            DependsOn depends = type.getAnnotation(DependsOn.class);
159            DesignTimeOnly design = type.getAnnotation(DesignTimeOnly.class);
160
161            if (bean != null || depends != null || design != null) {
162                manifest.put(uri.toString(),
163                             bean, depends, design);
164            }
165        }
166
167        try (OutputStream out = file.openOutputStream()) {
168            manifest.write(out);
169         }
170    }
171
172    @NoArgsConstructor @ToString
173    private class ManifestImpl extends Manifest {
174        private static final String MANIFEST_VERSION = "Manifest-Version";
175
176        protected void init() {
177            if (getMainAttributes().getValue(MANIFEST_VERSION) == null) {
178                getMainAttributes().putValue(MANIFEST_VERSION, "1.0");
179            }
180        }
181
182        public Attributes putAttributes(String name, Attributes attributes) {
183            return getEntries().put(name, attributes);
184        }
185
186        public String put(MainClass main, String name) {
187            return (getMainAttributes()
188                    .putValue(getAttributeName(main.getClass()), name));
189        }
190
191        public void put(String path, Section section) {
192            Attributes attributes = getEntries().get(path);
193
194            if (attributes == null) {
195                attributes = new Attributes();
196                getEntries().put(path, attributes);
197            }
198
199            attributes.putValue("Sealed", String.valueOf(section.sealed()));
200        }
201
202        public void put(String path,
203                        JavaBean bean,
204                        DependsOn depends, DesignTimeOnly design) {
205            Attributes attributes = getEntries().get(path);
206
207            if (attributes == null) {
208                attributes = new Attributes();
209                getEntries().put(path, attributes);
210            }
211
212            attributes.putValue(getAttributeName(JavaBean.class),
213                                String.valueOf(true));
214
215            if (depends != null) {
216                attributes.putValue(getAttributeName(depends.getClass()),
217                                    String.join(" ", depends.value()));
218            }
219
220            if (design != null) {
221                attributes.putValue(getAttributeName(design.getClass()),
222                                    String.join(" ", design.value()));
223            }
224        }
225
226        private String getAttributeName(Class<? extends Annotation> type) {
227            return type.getAnnotation(Attribute.class).value();
228        }
229    }
230}