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 }