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         jsb.addAnnotation(false, NATIVE_STRUCT, Map.of("value", structTree.layout().toString()));
 265         jsb.interfaceBegin(intf, true, "Struct<" + intf + ">");
 266         // fields
 267         structTree.fields().forEach(fieldTree -> addField(jsb, fieldTree, type));
 268         jsb.interfaceEnd();
 269         addClassIfNeeded(headerClassName + "." + intf, jsb);
 270 
 271         return true;
 272     }
 273 
 274     @Override
 275     public Boolean visitEnum(EnumTree enumTree, JType jt) {
 276         // define enum constants in global_cw
 277         enumTree.constants().forEach(constant -> addConstant(global_jsb,
 278                 constant.location(),
 279                 constant.name(),
 280                 headerFile.dictionary().lookup(constant.type()),
 281                 constant.enumConstant().get()));
 282 
 283         if (enumTree.name().isEmpty()) {
 284             // We are done with anonymous enum
 285             return true;
 286         }
 287 
 288         // generate annotation class for named enum
 289         createAnnotationCls(enumTree);
 290         return true;
 291     }
 292 
 293     private void createAnnotationCls(Tree tree) {
 294         String nativeName = tree.name();
 295         log.print(Level.FINE, () -> "Create annotation for: " + nativeName);
 296 
 297         String intf = Utils.toClassName(nativeName);
 298         String name = headerClassName + "." + intf;
 299         JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1);
 300         log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName);
 301 
 302         addNativeLocation(false, jsb, tree.location());
 303         jsb.addAnnotation(false, "Target", Map.of("value", ElementType.TYPE_USE));
 304         jsb.addAnnotation(false, "Retention", Map.of("value", RetentionPolicy.RUNTIME));
 305         jsb.interfaceBegin(intf, true, true);
 306         jsb.interfaceEnd();
 307 
 308         addClassIfNeeded(headerClassName + "." + intf, jsb);
 309     }
 310 
 311     private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) {
 312         JType.Function fn = fnif.getFunction();
 313         String intf;
 314         String nativeName;
 315         String nDesc = fnif.getFunction().getNativeDescriptor();
 316         intf = fnif.getSimpleName();
 317         nativeName = "anonymous function";
 318         log.print(Level.FINE, () -> "Create FunctionalInterface " + intf);
 319 
 320         final String name = headerClassName + "." + intf;
 321 
 322         JavaSourceBuilder jsb = new JavaSourceBuilder(global_jsb.align() + 1);
 323         log.print(Level.FINE, () -> "Define class " + name + " for native type " + nativeName + nDesc);
 324 
 325         jsb.addAnnotation(false, "FunctionalInterface", Map.of());
 326         jsb.addAnnotation(false, NATIVE_CALLBACK, Map.of("value", nDesc));
 327         jsb.interfaceBegin(intf, true);
 328         jsb.addMethod("fn", fn);
 329         jsb.interfaceEnd();
 330 
 331         // add the method
 332         addClassIfNeeded(headerClassName + "." + intf, jsb);
 333     }
 334 
 335     @Override
 336     public Boolean visitTypedef(TypedefTree typedefTree, JType jt) {
 337         createAnnotationCls(typedefTree);
 338         return true;
 339     }
 340 
 341     @Override
 342     public Boolean visitTree(Tree tree, JType jt) {
 343         log.print(Level.WARNING, () -> "Unsupported declaration tree:");
 344         log.print(Level.WARNING, () -> tree.toString());
 345         return true;
 346     }
 347 
 348     @Override
 349     public Boolean visitFunction(FunctionTree funcTree, JType jt) {
 350         assert (jt instanceof JType.Function);
 351         JType.Function fn = (JType.Function)jt;
 352         log.print(Level.FINE, () -> "Add method: " + fn.getSignature(false));
 353 
 354         addNativeLocation(global_jsb, funcTree);
 355         Type type = funcTree.type();
 356         final String descStr = Utils.getFunction(type).toString();
 357         global_jsb.addAnnotation(NATIVE_FUNCTION, Map.of("value", descStr));
 358         global_jsb.addMethod(funcTree, fn);
 359 
 360         return true;
 361     }
 362 
 363     private void generateDecl(Tree tree) {
 364         try {
 365             log.print(Level.FINE, () -> "Process tree " + tree.name());
 366             tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type()));
 367         } catch (Exception ex) {
 368             handleException(ex);
 369             log.print(Level.WARNING, () -> "Tree causing above exception is: " + tree.name());
 370             log.print(Level.WARNING, () -> tree.toString());
 371         }
 372     }
 373 
 374     @Override
 375     public Boolean visitMacro(MacroTree macroTree, JType jt) {
 376         if (!macroTree.isConstant()) {
 377             log.print(Level.FINE, () -> "Skipping unrecognized object-like macro " + macroTree.name());
 378             return false;
 379         }
 380         String name = macroTree.name();
 381         MacroParser.Macro macro = macroTree.macro().get();
 382         log.print(Level.FINE, () -> "Adding macro " + name);
 383 
 384         addConstant(global_jsb, macroTree.location(), name, macro.type(), macro.value());
 385 
 386         return true;
 387     }
 388 }