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}