1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package com.sun.tools.jextract; 24 25 import java.foreign.layout.Layout; 26 import java.lang.annotation.ElementType; 27 import java.lang.annotation.RetentionPolicy; 28 import java.io.File; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.LinkedHashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 42 import com.sun.tools.jextract.parser.MacroParser; 43 import jdk.internal.clang.SourceLocation; 44 import jdk.internal.clang.Type; 45 import com.sun.tools.jextract.tree.EnumTree; 46 import com.sun.tools.jextract.tree.FieldTree; 47 import com.sun.tools.jextract.tree.FunctionTree; 48 import com.sun.tools.jextract.tree.MacroTree; 49 import com.sun.tools.jextract.tree.SimpleTreeVisitor; 50 import com.sun.tools.jextract.tree.StructTree; 51 import com.sun.tools.jextract.tree.Tree; 52 import com.sun.tools.jextract.tree.TypedefTree; 53 import com.sun.tools.jextract.tree.VarTree; 54 55 /* 56 * Scan a header file and generate Java source items for entities defined in that header 57 * file. Tree visitor visit methods return true/false depending on whether a 58 * particular Tree is processed or skipped. 59 */ 60 class JavaSourceFactory extends SimpleTreeVisitor<Boolean, JType> { 61 // simple names of java.foreign annotations 62 private static final String NATIVE_CALLBACK = "NativeCallback"; 63 private static final String NATIVE_HEADER = "NativeHeader"; 64 private static final String NATIVE_LOCATION = "NativeLocation"; 65 private static final String NATIVE_STRUCT = "NativeStruct"; 66 private static final String NATIVE_FUNCTION = "NativeFunction"; 67 private static final String NATIVE_GETTER = "NativeGetter"; 68 private static final String NATIVE_SETTER = "NativeSetter"; 69 private static final String NATIVE_ADDRESSOF = "NativeAddressof"; 70 private static final String NATIVE_NUM_CONST = "NativeNumericConstant"; 71 private static final String NATIVE_STR_CONST = "NativeStringConstant"; 72 73 protected final String headerClassName; 74 protected final HeaderFile headerFile; 75 private final Map<String, JavaSourceBuilder> types; 76 private final List<String> libraryNames; 77 private final List<String> libraryPaths; 78 private final boolean noNativeLocations; 79 private final JavaSourceBuilder global_jsb; 80 protected final Path srcDir; 81 protected final Log log; 82 83 JavaSourceFactory(Context ctx, HeaderFile header) { 84 this.log = ctx.log; 85 log.print(Level.INFO, () -> "Instantiate JavaSourceFactory for " + header.path); 86 this.headerFile = header; 87 this.headerClassName = headerFile.pkgName + "." + headerFile.headerClsName; 88 this.types = new HashMap<>(); 89 this.libraryNames = ctx.options.libraryNames; 90 this.libraryPaths = ctx.options.recordLibraryPath? ctx.options.libraryPaths : null; 91 this.noNativeLocations = ctx.options.noNativeLocations; 92 this.global_jsb = new JavaSourceBuilder(); 93 this.srcDir = Paths.get(ctx.options.srcDumpDir) 94 .resolve(headerFile.pkgName.replace('.', File.separatorChar)); 95 } 96 97 // main entry point that generates & saves .java files for the header file 98 public void generate(List<Tree> decls) { 99 global_jsb.addPackagePrefix(headerFile.pkgName); 100 101 Map<String, Object> header = new HashMap<>(); 102 header.put("path", headerFile.path.toAbsolutePath().toString().replace("\\", "\\\\")); 103 if (!libraryNames.isEmpty()) { 104 header.put("libraries", libraryNames.toArray(new String[0])); 105 if (libraryPaths != null && !libraryPaths.isEmpty()) { 106 header.put("libraryPaths", libraryPaths.toArray(new String[0])); 107 } 108 } 109 110 JType.ClassType[] classes = headerFile.dictionary().resolutionRoots() 111 .toArray(JType.ClassType[]::new); 112 if (classes.length != 0) { 113 header.put("resolutionContext", classes); 114 } 115 116 Set<Layout> global_layouts = new LinkedHashSet<>(); 117 for (Tree tr : decls) { 118 if (tr instanceof VarTree) { 119 VarTree varTree = (VarTree)tr; 120 global_layouts.add(varTree.layout().withAnnotation(Layout.NAME, varTree.name())); 121 } 122 } 123 if (!global_layouts.isEmpty()) { 124 String[] globals = global_layouts.stream().map(Object::toString).toArray(String[]::new); 125 header.put("globals", globals); 126 } 127 128 global_jsb.addAnnotation(false, NATIVE_HEADER, header); 129 String clsName = headerFile.headerClsName; 130 global_jsb.interfaceBegin(clsName, false); 131 132 //generate all decls 133 decls.forEach(this::generateDecl); 134 //generate functional interfaces 135 headerFile.dictionary().functionalInterfaces() 136 .forEach(fi -> createFunctionalInterface((JType.FunctionalInterfaceType)fi)); 137 138 for (JavaSourceBuilder jsb : types.values()) { 139 global_jsb.addNestedType(jsb); 140 } 141 142 global_jsb.interfaceEnd(); 143 String src = global_jsb.build(); 144 try { 145 Files.createDirectories(srcDir); 146 Path srcPath = srcDir.resolve(clsName + ".java"); 147 Files.write(srcPath, List.of(src)); 148 } catch (Exception ex) { 149 handleException(ex); 150 } 151 } 152 153 protected void handleException(Exception ex) { 154 log.printError("cannot.write.class.file", headerFile.pkgName + "." + headerFile.headerClsName, ex); 155 log.printStackTrace(ex); 156 } 157 158 private void addNativeLocation(JavaSourceBuilder jsb, Tree tree) { 159 addNativeLocation(jsb, tree.location()); 160 } 161 162 private void addNativeLocation(JavaSourceBuilder jsb, SourceLocation src) { 163 addNativeLocation(true, jsb, src); 164 } 165 166 private void addNativeLocation(boolean align, JavaSourceBuilder jsb, SourceLocation src) { 167 if (! noNativeLocations) { 168 SourceLocation.Location loc = src.getFileLocation(); 169 Path p = loc.path(); 170 Map<String, Object> fields = new HashMap<>(); 171 fields.put("file", p == null ? "builtin" : p.toAbsolutePath().toString().replace("\\", "\\\\")); 172 fields.put("line", loc.line()); 173 fields.put("column", loc.column()); 174 jsb.addAnnotation(align, NATIVE_LOCATION, fields); 175 } 176 } 177 178 private void addClassIfNeeded(String clsName, JavaSourceBuilder jsb) { 179 if (null != types.put(clsName, jsb)) { 180 log.printWarning("warn.class.overwritten", clsName); 181 } 182 } 183 184 private static boolean isBitField(Tree tree) { 185 return tree instanceof FieldTree && ((FieldTree)tree).isBitField(); 186 } 187 188 /** 189 * 190 * @param jsb JavaSourceBuilder for the struct 191 * @param tree The Tree 192 * @param parentType The struct type 193 */ 194 private boolean addField(JavaSourceBuilder jsb, Tree tree, Type parentType) { 195 String fieldName = tree.name(); 196 assert !fieldName.isEmpty(); 197 Type type = tree.type(); 198 JType jt = headerFile.dictionary().lookup(type); 199 assert (jt != null); 200 201 addNativeLocation(jsb, tree); 202 jsb.addAnnotation(NATIVE_GETTER, Map.of("value", fieldName)); 203 jsb.addGetter(fieldName + "$get", jt); 204 205 jsb.addAnnotation(NATIVE_SETTER, Map.of("value", fieldName)); 206 jsb.addSetter(fieldName + "$set", jt); 207 208 if (tree instanceof VarTree || !isBitField(tree)) { 209 JType ptrType = JType.GenericType.ofPointer(jt); 210 jsb.addAnnotation(NATIVE_ADDRESSOF, Map.of("value", fieldName)); 211 jsb.addGetter(fieldName + "$ptr", ptrType); 212 } 213 214 return true; 215 } 216 217 @Override 218 public Boolean visitVar(VarTree varTree, JType jt) { 219 return addField(global_jsb, varTree, null); 220 } 221 222 private void addConstant(JavaSourceBuilder jsb, SourceLocation src, String name, JType type, Object value) { 223 addNativeLocation(jsb, src); 224 225 if (value instanceof String) { 226 jsb.addAnnotation(NATIVE_STR_CONST, Map.of("value", value)); 227 } else { 228 //numeric (int, long or double) 229 final long longValue; 230 if (value instanceof Integer) { 231 longValue = (Integer)value; 232 } else if (value instanceof Long) { 233 longValue = (Long)value; 234 } else if (value instanceof Double) { 235 longValue = Double.doubleToRawLongBits((Double)value); 236 } else { 237 throw new IllegalStateException("Unexpected constant: " + value); 238 } 239 jsb.addAnnotation(NATIVE_NUM_CONST, Map.of("value", longValue)); 240 } 241 242 jsb.addGetter(name, type); 243 } 244 245 @Override 246 public Boolean visitStruct(StructTree structTree, JType jt) { 247 //generate nested structs recursively 248 structTree.nestedTypes().forEach(this::generateDecl); 249 250 if (structTree.isAnonymous()) { 251 //skip anonymous 252 return false; 253 } 254 String nativeName = structTree.name(); 255 Type type = structTree.type(); 256 log.print(Level.FINE, () -> "Create struct: " + nativeName); 257 258 String intf = Utils.toClassName(nativeName); 259 String name = headerClassName + "." + intf; 260 261 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName); 262 263 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 264 addNativeLocation(false, jsb, structTree.location()); 265 jsb.addAnnotation(false, NATIVE_STRUCT, Map.of("value", structTree.layout().toString())); 266 jsb.interfaceBegin(intf, true, "Struct<" + intf + ">"); 267 // fields 268 structTree.fields().forEach(fieldTree -> addField(jsb, fieldTree, type)); 269 jsb.interfaceEnd(); 270 addClassIfNeeded(headerClassName + "." + intf, jsb); 271 272 return true; 273 } 274 275 @Override 276 public Boolean visitEnum(EnumTree enumTree, JType jt) { 277 // define enum constants in global_cw 278 enumTree.constants().forEach(constant -> addConstant(global_jsb, 279 constant.location(), 280 constant.name(), 281 headerFile.dictionary().lookup(constant.type()), 282 constant.enumConstant().get())); 283 284 if (enumTree.name().isEmpty()) { 285 // We are done with anonymous enum 286 return true; 287 } 288 289 // generate annotation class for named enum 290 createAnnotationCls(enumTree); 291 return true; 292 } 293 294 private void createAnnotationCls(Tree tree) { 295 String nativeName = tree.name(); 296 log.print(Level.FINE, () -> "Create annotation for: " + nativeName); 297 298 String intf = Utils.toClassName(nativeName); 299 String name = headerClassName + "." + intf; 300 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 301 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName); 302 303 addNativeLocation(false, jsb, tree.location()); 304 jsb.addAnnotation(false, "Target", Map.of("value", ElementType.TYPE_USE)); 305 jsb.addAnnotation(false, "Retention", Map.of("value", RetentionPolicy.RUNTIME)); 306 jsb.interfaceBegin(intf, true, true); 307 jsb.interfaceEnd(); 308 309 addClassIfNeeded(headerClassName + "." + intf, jsb); 310 } 311 312 private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) { 313 JType.Function fn = fnif.getFunction(); 314 String intf; 315 String nativeName; 316 String nDesc = fnif.getFunction().getNativeDescriptor(); 317 intf = fnif.getSimpleName(); 318 nativeName = "anonymous function"; 319 log.print(Level.FINE, () -> "Create FunctionalInterface " + intf); 320 321 final String name = headerClassName + "." + intf; 322 323 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 324 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName + nDesc); 325 326 jsb.addAnnotation(false, "FunctionalInterface", Map.of()); 327 jsb.addAnnotation(false, NATIVE_CALLBACK, Map.of("value", nDesc)); 328 jsb.interfaceBegin(intf, true); 329 jsb.addMethod("fn", fn); 330 jsb.interfaceEnd(); 331 332 // add the method 333 addClassIfNeeded(headerClassName + "." + intf, jsb); 334 } 335 336 @Override 337 public Boolean visitTypedef(TypedefTree typedefTree, JType jt) { 338 createAnnotationCls(typedefTree); 339 return true; 340 } 341 342 @Override 343 public Boolean visitTree(Tree tree, JType jt) { 344 log.print(Level.WARNING, () -> "Unsupported declaration tree:"); 345 log.print(Level.WARNING, () -> tree.toString()); 346 return true; 347 } 348 349 @Override 350 public Boolean visitFunction(FunctionTree funcTree, JType jt) { 351 assert (jt instanceof JType.Function); 352 JType.Function fn = (JType.Function)jt; 353 log.print(Level.FINE, () -> "Add method: " + fn.getSignature(false)); 354 355 addNativeLocation(global_jsb, funcTree); 356 Type type = funcTree.type(); 357 final String descStr = Utils.getFunction(type).toString(); 358 global_jsb.addAnnotation(NATIVE_FUNCTION, Map.of("value", descStr)); 359 global_jsb.addMethod(funcTree, fn); 360 361 return true; 362 } 363 364 private void generateDecl(Tree tree) { 365 try { 366 log.print(Level.FINE, () -> "Process tree " + tree.name()); 367 tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type())); 368 } catch (Exception ex) { 369 handleException(ex); 370 log.print(Level.WARNING, () -> "Tree causing above exception is: " + tree.name()); 371 log.print(Level.WARNING, () -> tree.toString()); 372 } 373 } 374 375 @Override 376 public Boolean visitMacro(MacroTree macroTree, JType jt) { 377 if (!macroTree.isConstant()) { 378 log.print(Level.FINE, () -> "Skipping unrecognized object-like macro " + macroTree.name()); 379 return false; 380 } 381 String name = macroTree.name(); 382 MacroParser.Macro macro = macroTree.macro().get(); 383 log.print(Level.FINE, () -> "Adding macro " + name); 384 385 addConstant(global_jsb, macroTree.location(), name, macro.type(), macro.value()); 386 387 return true; 388 } 389 }