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