1 /* 2 * Copyright (c) 2010, Michael Bien. All rights reserved. 3 * Copyright (c) 2013 JogAmp Community. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * * Neither the name of Michael Bien nor the 13 * names of its contributors may be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 * DISCLAIMED. IN NO EVENT SHALL Michael Bien BE LIABLE FOR ANY 20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package com.jogamp.gluegen.structgen; 29 30 import com.jogamp.common.util.PropertyAccess; 31 import com.jogamp.gluegen.GlueGen; 32 import com.jogamp.gluegen.JavaEmitter; 33 34 import java.io.BufferedReader; 35 import java.io.File; 36 import java.io.FileNotFoundException; 37 import java.io.FileReader; 38 import java.io.FileWriter; 39 import java.io.IOException; 40 import java.io.PrintWriter; 41 import java.io.Reader; 42 import java.util.ArrayList; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Set; 46 47 import javax.annotation.processing.AbstractProcessor; 48 import javax.annotation.processing.Filer; 49 import javax.annotation.processing.Messager; 50 import javax.annotation.processing.ProcessingEnvironment; 51 import javax.annotation.processing.RoundEnvironment; 52 import javax.annotation.processing.SupportedAnnotationTypes; 53 import javax.annotation.processing.SupportedSourceVersion; 54 import javax.lang.model.SourceVersion; 55 import javax.lang.model.element.Element; 56 import javax.lang.model.element.TypeElement; 57 import javax.lang.model.util.Elements; 58 import javax.tools.Diagnostic.Kind; 59 import javax.tools.FileObject; 60 import javax.tools.StandardLocation; 61 62 import jogamp.common.Debug; 63 64 /** 65 * <p> 66 * If the <i>header file</i> is absolute, the <i>root path</i> is the parent folder of the folder containing the package source, i.e.: 67 * <pre> 68 * Header: /gluegen/src/junit/com/jogamp/test/structgen/TestStruct01.h 69 * Root: /gluegen/src/junit/.. 70 * Root: /gluegen/src 71 * </pre> 72 * Otherwise the <i>user.dir</i> is being used as the <i>root path</i> 73 * and the relative <i>header file</i> is appended to it. 74 * </p> 75 * The property <code>jogamp.gluegen.structgen.output</code> allows setting a default <i>outputPath</i> 76 * for the generated sources, if the {@link ProcessingEnvironment}'s <code>structgen.output</code> option is not set. 77 * <p> 78 * If the <i>outputPath</i> is relative, it is appended to the <i>root path</i>, 79 * otherwise it is taken as-is. 80 * </p> 81 * <p> 82 * User can enable DEBUG while defining property <code>jogamp.gluegen.structgen.debug</code>. 83 * </p> 84 * 85 * @author Michael Bien, et al. 86 */ 87 @SupportedAnnotationTypes(value = {"com.jogamp.gluegen.structgen.CStruct", "com.jogamp.gluegen.structgen.CStructs"}) 88 @SupportedSourceVersion(SourceVersion.RELEASE_6) 89 public class CStructAnnotationProcessor extends AbstractProcessor { 90 private static final String DEFAULT = "_default_"; 91 static final boolean DEBUG; 92 93 static { Debug.initSingleton()94 Debug.initSingleton(); 95 DEBUG = PropertyAccess.isPropertyDefined("jogamp.gluegen.structgen.debug", true); 96 } 97 98 private static final String STRUCTGENOUTPUT_OPTION = "structgen.output"; 99 private static final String STRUCTGENOUTPUT = PropertyAccess.getProperty("jogamp.gluegen."+STRUCTGENOUTPUT_OPTION, true, "gensrc"); 100 101 private Filer filer; 102 private Messager messager; 103 private Elements eltUtils; 104 private String outputPath; 105 106 private final static Set<String> generatedStructs = new HashSet<String>(); 107 108 109 @Override init(final ProcessingEnvironment processingEnv)110 public void init(final ProcessingEnvironment processingEnv) { 111 super.init(processingEnv); 112 113 filer = processingEnv.getFiler(); 114 messager = processingEnv.getMessager(); 115 eltUtils = processingEnv.getElementUtils(); 116 117 outputPath = processingEnv.getOptions().get(STRUCTGENOUTPUT_OPTION); 118 outputPath = outputPath == null ? STRUCTGENOUTPUT : outputPath; 119 } 120 locateSource(final String packageName, final String relativeName)121 private File locateSource(final String packageName, final String relativeName) { 122 try { 123 if( DEBUG ) { 124 System.err.println("CStruct.locateSource.0: p "+packageName+", r "+relativeName); 125 } 126 final FileObject h = filer.getResource(StandardLocation.SOURCE_PATH, packageName, relativeName); 127 if( DEBUG ) { 128 System.err.println("CStruct.locateSource.1: h "+h.toUri()); 129 } 130 final File f = new File( h.toUri().getPath() ); // URI is incomplete (no scheme), hence use path only! 131 if( f.exists() ) { 132 return f; 133 } 134 } catch (final IOException e) { 135 if(DEBUG) { 136 System.err.println("Caught "+e.getClass().getSimpleName()+": "+e.getMessage()); /* e.printStackTrace(); */ 137 } 138 } 139 return null; 140 } 141 142 @Override process(final Set<? extends TypeElement> annotations, final RoundEnvironment env)143 public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment env) { 144 final String user_dir = System.getProperty("user.dir"); 145 146 final Set<? extends Element> cStructsElements = env.getElementsAnnotatedWith(CStructs.class); 147 for (final Element structsElement : cStructsElements) { 148 final String packageName = eltUtils.getPackageOf(structsElement).toString(); 149 final CStructs cstructs = structsElement.getAnnotation(CStructs.class); 150 if( null != cstructs ) { 151 final CStruct[] cstructArray = cstructs.value(); 152 for(final CStruct cstruct : cstructArray) { 153 processCStruct(cstruct, structsElement, packageName, user_dir); 154 } 155 } 156 } 157 158 final Set<? extends Element> cStructElements = env.getElementsAnnotatedWith(CStruct.class); 159 for (final Element structElement : cStructElements) { 160 final String packageName = eltUtils.getPackageOf(structElement).toString(); 161 final CStruct cstruct = structElement.getAnnotation(CStruct.class); 162 if( null != cstruct ) { 163 processCStruct(cstruct, structElement, packageName, user_dir); 164 } 165 } 166 return true; 167 } 168 processCStruct(final CStruct struct, final Element element, final String packageName, final String user_dir)169 private void processCStruct(final CStruct struct, final Element element, final String packageName, final String user_dir) { 170 try { 171 final String headerRelPath = struct.header(); 172 final Element enclElement = element.getEnclosingElement(); 173 final boolean isPackageOrType = null == enclElement; 174 175 System.err.println("CStruct: "+struct+", package "+packageName+", header "+headerRelPath); 176 if(DEBUG) { 177 System.err.println("CStruct.0: user.dir: "+user_dir); 178 System.err.println("CStruct.0: element: "+element+", .simpleName "+element.getSimpleName()); 179 System.err.print("CStruct.0: isPackageOrType "+isPackageOrType+", enclElement: "+enclElement); 180 if( !isPackageOrType ) { 181 System.err.println(", .simpleName "+enclElement.getSimpleName()+", .package "+eltUtils.getPackageOf(enclElement).toString()); 182 } else { 183 System.err.println(""); 184 } 185 } 186 if( isPackageOrType && struct.name().equals(DEFAULT) ) { 187 throw new IllegalArgumentException("CStruct annotation on package or type must have name specified: "+struct+" @ "+element); 188 } 189 190 final File headerFile; 191 { 192 File f = locateSource(packageName, headerRelPath); 193 if( null == f ) { 194 f = locateSource("", headerRelPath); 195 if( null == f ) { 196 // bail out 197 throw new RuntimeException("Could not locate header "+headerRelPath+", package "+packageName); 198 } 199 } 200 headerFile = f; 201 } 202 203 final String rootOut, headerParent; 204 { 205 final String root0 = headerFile.getAbsolutePath(); 206 headerParent = root0.substring(0, root0.length()-headerFile.getName().length()-1); 207 rootOut = headerParent.substring(0, headerParent.length()-packageName.length()) + ".."; 208 } 209 System.err.println("CStruct: "+headerFile+", abs: "+headerFile.isAbsolute()+", headerParent "+headerParent+", rootOut "+rootOut); 210 211 generateStructBinding(element, struct, isPackageOrType, rootOut, packageName, headerFile, headerParent); 212 } catch (final IOException ex) { 213 throw new RuntimeException("IOException while processing!", ex); 214 } 215 } 216 generateStructBinding(final Element element, final CStruct struct, final boolean isPackageOrType, final String rootOut, final String pakage, final File header, final String headerParent)217 private void generateStructBinding(final Element element, final CStruct struct, final boolean isPackageOrType, final String rootOut, final String pakage, final File header, final String headerParent) throws IOException { 218 final String declaredType = element.asType().toString(); 219 final boolean useStructName = !struct.name().equals(DEFAULT); 220 final String structName = useStructName ? struct.name() : declaredType; 221 final boolean useJavaName = !struct.jname().equals(DEFAULT); 222 223 final String finalType = useJavaName ? struct.jname() : ( !isPackageOrType ? declaredType : structName ); 224 System.err.println("CStruct: Generating struct accessor for struct: "+structName+" -> "+finalType+" [struct.name "+struct.name()+", struct.jname "+struct.jname()+", declaredType "+declaredType+"]"); 225 if( generatedStructs.contains(finalType) ) { 226 messager.printMessage(Kind.NOTE, "struct "+structName+" already defined elsewhere, skipping.", element); 227 return; 228 } 229 230 final boolean outputDirAbs; 231 { 232 final File outputDirFile = new File(outputPath); 233 outputDirAbs = outputDirFile.isAbsolute(); 234 } 235 final String outputPath1 = outputDirAbs ? outputPath : rootOut + File.separator + outputPath; 236 final String config = outputPath1 + File.separator + header.getName() + ".cfg"; 237 final File configFile = new File(config); 238 if(DEBUG) { 239 System.err.println("CStruct: OutputDir: "+outputPath+", is-abs "+outputDirAbs); 240 System.err.println("CStruct: OutputPath: "+outputPath1); 241 System.err.println("CStruct: ConfigFile: "+configFile); 242 } 243 244 FileWriter writer = null; 245 try{ 246 writer = new FileWriter(configFile); 247 writer.write("Package "+pakage+"\n"); 248 writer.write("EmitStruct "+structName+"\n"); 249 if( finalType != structName ) { 250 // We allow renaming the structType to the element's declaredType (FIELD annotation only) 251 writer.write("RenameJavaType " + struct.name()+" " + declaredType +"\n"); 252 } 253 } finally { 254 if( null != writer ) { 255 writer.close(); 256 } 257 } 258 final List<String> cfgFiles = new ArrayList<String>(); 259 cfgFiles.add(config); 260 final List<String> includePaths = new ArrayList<String>(); 261 includePaths.add(headerParent); 262 includePaths.add(outputPath1); 263 final Reader reader; 264 final String filename = header.getPath(); 265 try { 266 reader = new BufferedReader(new FileReader(filename)); 267 } catch (final FileNotFoundException ex) { 268 throw new RuntimeException("input file not found", ex); 269 } 270 if( DEBUG ) { 271 GlueGen.setDebug(true); 272 } 273 new GlueGen().run(reader, filename, AnnotationProcessorJavaStructEmitter.class, 274 includePaths, cfgFiles, outputPath1, false /* copyCPPOutput2Stderr */); 275 276 configFile.delete(); 277 generatedStructs.add(finalType); 278 } 279 280 public static class AnnotationProcessorJavaStructEmitter extends JavaEmitter { 281 282 @Override openFile(final String filename, final String simpleClassName)283 protected PrintWriter openFile(final String filename, final String simpleClassName) throws IOException { 284 285 if( generatedStructs.contains(simpleClassName) ) { 286 System.err.println("skipping -> " + simpleClassName); 287 return null; 288 } 289 290 // look for recursive generated structs... keep it DRY 291 if( !simpleClassName.endsWith("32") && 292 !simpleClassName.endsWith("64") ) { 293 System.err.println("generating -> " + simpleClassName); 294 generatedStructs.add(simpleClassName); 295 } 296 return super.openFile(filename, simpleClassName); 297 } 298 299 } 300 301 } 302