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