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 = ctx.options.srcDumpDir != null? 94 Paths.get(ctx.options.srcDumpDir).resolve(headerFile.pkgName.replace('.', File.separatorChar)) : 95 null; 96 } 97 98 // main entry point that generates & saves .java files for the header file 99 public Map<String, String> generate(List<Tree> decls) { 100 global_jsb.addPackagePrefix(headerFile.pkgName); 101 102 Map<String, Object> header = new HashMap<>(); 103 header.put("path", headerFile.path.toAbsolutePath().toString()); 104 if (!libraryNames.isEmpty()) { 105 header.put("libraries", libraryNames.toArray(new String[0])); 106 if (libraryPaths != null && !libraryPaths.isEmpty()) { 107 header.put("libraryPaths", libraryPaths.toArray(new String[0])); 108 } 109 } 110 111 JType.ClassType[] classes = headerFile.dictionary().resolutionRoots() 112 .toArray(JType.ClassType[]::new); 113 if (classes.length != 0) { 114 header.put("resolutionContext", classes); 115 } 116 117 Set<Layout> global_layouts = new LinkedHashSet<>(); 118 for (Tree tr : decls) { 119 if (tr instanceof VarTree) { 120 VarTree varTree = (VarTree)tr; 121 global_layouts.add(varTree.layout().withAnnotation(Layout.NAME, varTree.name())); 122 } 123 } 124 if (!global_layouts.isEmpty()) { 125 String[] globals = global_layouts.stream().map(Object::toString).toArray(String[]::new); 126 header.put("globals", globals); 127 } 128 129 global_jsb.addAnnotation(false, NATIVE_HEADER, header); 130 String clsName = headerFile.headerClsName; 131 global_jsb.interfaceBegin(clsName, false); 132 133 //generate all decls 134 decls.forEach(this::generateDecl); 135 //generate functional interfaces 136 headerFile.dictionary().functionalInterfaces() 137 .forEach(fi -> createFunctionalInterface((JType.FunctionalInterfaceType)fi)); 138 139 for (JavaSourceBuilder jsb : types.values()) { 140 global_jsb.addNestedType(jsb); 141 } 142 143 global_jsb.interfaceEnd(); 144 String src = global_jsb.build(); 145 if (srcDir != null) { 146 try { 147 Files.createDirectories(srcDir); 148 Path srcPath = srcDir.resolve(clsName + ".java"); 149 Files.write(srcPath, List.of(src)); 150 } catch (Exception ex) { 151 handleException(ex); 152 } 153 } 154 155 Map<String, String> srcMap = new HashMap<>(); 156 srcMap.put(headerClassName, src); 157 return srcMap; 158 } 159 160 protected void handleException(Exception ex) { 161 log.printError("cannot.write.class.file", headerFile.pkgName + "." + headerFile.headerClsName, ex); 162 log.printStackTrace(ex); 163 } 164 165 private void addNativeLocation(JavaSourceBuilder jsb, Tree tree) { 166 addNativeLocation(jsb, tree.location()); 167 } 168 169 private void addNativeLocation(JavaSourceBuilder jsb, SourceLocation src) { 170 addNativeLocation(true, jsb, src); 171 } 172 173 private void addNativeLocation(boolean align, JavaSourceBuilder jsb, SourceLocation src) { 174 if (! noNativeLocations) { 175 SourceLocation.Location loc = src.getFileLocation(); 176 Path p = loc.path(); 177 Map<String, Object> fields = new HashMap<>(); 178 fields.put("file", p == null ? "<builtin>" : p.toAbsolutePath().toString()); 179 fields.put("line", loc.line()); 180 fields.put("column", loc.column()); 181 jsb.addAnnotation(align, NATIVE_LOCATION, fields); 182 } 183 } 184 185 private void addClassIfNeeded(String clsName, JavaSourceBuilder jsb) { 186 if (null != types.put(clsName, jsb)) { 187 log.printWarning("warn.class.overwritten", clsName); 188 } 189 } 190 191 private static boolean isBitField(Tree tree) { 192 return tree instanceof FieldTree && ((FieldTree)tree).isBitField(); 193 } 194 195 /** 196 * 197 * @param jsb JavaSourceBuilder for the struct 198 * @param tree The Tree 199 * @param parentType The struct type 200 */ 201 private boolean addField(JavaSourceBuilder jsb, Tree tree, Type parentType) { 202 String fieldName = tree.name(); 203 assert !fieldName.isEmpty(); 204 Type type = tree.type(); 205 JType jt = headerFile.dictionary().lookup(type); 206 assert (jt != null); 207 208 addNativeLocation(jsb, tree); 209 jsb.addAnnotation(NATIVE_GETTER, Map.of("value", fieldName)); 210 jsb.addGetter(fieldName + "$get", jt); 211 212 jsb.addAnnotation(NATIVE_SETTER, Map.of("value", fieldName)); 213 jsb.addSetter(fieldName + "$set", jt); 214 215 if (tree instanceof VarTree || !isBitField(tree)) { 216 JType ptrType = JType.GenericType.ofPointer(jt); 217 jsb.addAnnotation(NATIVE_ADDRESSOF, Map.of("value", fieldName)); 218 jsb.addGetter(fieldName + "$ptr", ptrType); 219 } 220 221 return true; 222 } 223 224 @Override 225 public Boolean visitVar(VarTree varTree, JType jt) { 226 return addField(global_jsb, varTree, null); 227 } 228 229 private void addConstant(JavaSourceBuilder jsb, SourceLocation src, String name, JType type, Object value) { 230 addNativeLocation(jsb, src); 231 232 if (value instanceof String) { 233 jsb.addAnnotation(NATIVE_STR_CONST, Map.of("value", value)); 234 } else { 235 //numeric (int, long or double) 236 final long longValue; 237 if (value instanceof Integer) { 238 longValue = (Integer)value; 239 } else if (value instanceof Long) { 240 longValue = (Long)value; 241 } else if (value instanceof Double) { 242 longValue = Double.doubleToRawLongBits((Double)value); 243 } else { 244 throw new IllegalStateException("Unexpected constant: " + value); 245 } 246 jsb.addAnnotation(NATIVE_NUM_CONST, Map.of("value", longValue)); 247 } 248 249 jsb.addGetter(name, type); 250 } 251 252 @Override 253 public Boolean visitStruct(StructTree structTree, JType jt) { 254 //generate nested structs recursively 255 structTree.nestedTypes().forEach(this::generateDecl); 256 257 if (structTree.isAnonymous()) { 258 //skip anonymous 259 return false; 260 } 261 String nativeName = structTree.name(); 262 Type type = structTree.type(); 263 log.print(Level.FINE, () -> "Create struct: " + nativeName); 264 265 String intf = Utils.toClassName(nativeName); 266 String name = headerClassName + "." + intf; 267 268 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName); 269 270 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 271 addNativeLocation(false, jsb, structTree.location()); 272 jsb.addAnnotation(false, NATIVE_STRUCT, Map.of("value", structTree.layout().toString())); 273 jsb.interfaceBegin(intf, true, "Struct<" + intf + ">"); 274 // fields 275 structTree.fields().forEach(fieldTree -> addField(jsb, fieldTree, type)); 276 jsb.interfaceEnd(); 277 addClassIfNeeded(headerClassName + "." + intf, jsb); 278 279 return true; 280 } 281 282 @Override 283 public Boolean visitEnum(EnumTree enumTree, JType jt) { 284 // define enum constants in global_cw 285 enumTree.constants().forEach(constant -> addConstant(global_jsb, 286 constant.location(), 287 constant.name(), 288 headerFile.dictionary().lookup(constant.type()), 289 constant.enumConstant().get())); 290 291 if (enumTree.name().isEmpty()) { 292 // We are done with anonymous enum 293 return true; 294 } 295 296 // generate annotation class for named enum 297 createAnnotationCls(enumTree); 298 return true; 299 } 300 301 private void createAnnotationCls(Tree tree) { 302 String nativeName = tree.name(); 303 log.print(Level.FINE, () -> "Create annotation for: " + nativeName); 304 305 String intf = Utils.toClassName(nativeName); 306 String name = headerClassName + "." + intf; 307 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 308 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName); 309 310 addNativeLocation(false, jsb, tree.location()); 311 jsb.addAnnotation(false, "Target", Map.of("value", ElementType.TYPE_USE)); 312 jsb.addAnnotation(false, "Retention", Map.of("value", RetentionPolicy.RUNTIME)); 313 jsb.interfaceBegin(intf, true, true); 314 jsb.interfaceEnd(); 315 316 addClassIfNeeded(headerClassName + "." + intf, jsb); 317 } 318 319 private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) { 320 JType.Function fn = fnif.getFunction(); 321 String intf; 322 String nativeName; 323 String nDesc = fnif.getFunction().getNativeDescriptor(); 324 intf = fnif.getSimpleName(); 325 nativeName = "anonymous function"; 326 log.print(Level.FINE, () -> "Create FunctionalInterface " + intf); 327 328 final String name = headerClassName + "." + intf; 329 330 JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1); 331 log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName + nDesc); 332 333 jsb.addAnnotation(false, "FunctionalInterface", Map.of()); 334 jsb.addAnnotation(false, NATIVE_CALLBACK, Map.of("value", nDesc)); 335 jsb.interfaceBegin(intf, true); 336 jsb.addMethod("fn", fn); 337 jsb.interfaceEnd(); 338 339 // add the method 340 addClassIfNeeded(headerClassName + "." + intf, jsb); 341 } 342 343 @Override 344 public Boolean visitTypedef(TypedefTree typedefTree, JType jt) { 345 createAnnotationCls(typedefTree); 346 return true; 347 } 348 349 @Override 350 public Boolean visitTree(Tree tree, JType jt) { 351 log.print(Level.WARNING, () -> "Unsupported declaration tree:"); 352 log.print(Level.WARNING, () -> tree.toString()); 353 return true; 354 } 355 356 @Override 357 public Boolean visitFunction(FunctionTree funcTree, JType jt) { 358 assert (jt instanceof JType.Function); 359 JType.Function fn = (JType.Function)jt; 360 log.print(Level.FINE, () -> "Add method: " + fn.getSourceSignature(false)); 361 362 addNativeLocation(global_jsb, funcTree); 363 Type type = funcTree.type(); 364 final String descStr = Utils.getFunction(type).toString(); 365 global_jsb.addAnnotation(NATIVE_FUNCTION, Map.of("value", descStr)); 366 global_jsb.addMethod(funcTree, fn); 367 368 return true; 369 } 370 371 private void generateDecl(Tree tree) { 372 try { 373 log.print(Level.FINE, () -> "Process tree " + tree.name()); 374 tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type())); 375 } catch (Exception ex) { 376 handleException(ex); 377 log.print(Level.WARNING, () -> "Tree causing above exception is: " + tree.name()); 378 log.print(Level.WARNING, () -> tree.toString()); 379 } 380 } 381 382 @Override 383 public Boolean visitMacro(MacroTree macroTree, JType jt) { 384 if (!macroTree.isConstant()) { 385 log.print(Level.FINE, () -> "Skipping unrecognized object-like macro " + macroTree.name()); 386 return false; 387 } 388 String name = macroTree.name(); 389 MacroParser.Macro macro = macroTree.macro().get(); 390 log.print(Level.FINE, () -> "Adding macro " + name); 391 392 addConstant(global_jsb, macroTree.location(), Utils.toMacroName(name), macro.type(), macro.value()); 393 394 return true; 395 } 396 }