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 }