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}