1 /*
   2  * Copyright (c) 2020, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.incubator.jextract.tool;
  26 
  27 import jdk.incubator.jextract.Declaration;
  28 import jdk.incubator.jextract.Type;
  29 import jdk.incubator.foreign.FunctionDescriptor;
  30 import jdk.incubator.foreign.MemoryAddress;
  31 import jdk.incubator.foreign.MemoryLayout;
  32 import jdk.incubator.foreign.MemorySegment;
  33 
  34 import javax.tools.JavaFileObject;
  35 import javax.tools.SimpleJavaFileObject;
  36 import java.io.File;
  37 import java.io.IOException;
  38 import java.io.UncheckedIOException;
  39 import java.lang.invoke.MethodType;
  40 import java.net.URI;
  41 import java.net.URL;
  42 import java.net.URISyntaxException;
  43 import java.nio.file.Files;
  44 import java.nio.file.Paths;
  45 import java.util.HashSet;
  46 import java.util.List;
  47 import java.util.Optional;
  48 import java.util.Set;
  49 import java.util.stream.Collectors;
  50 
  51 /*
  52  * Scan a header file and generate Java source items for entities defined in that header
  53  * file. Tree visitor visit methods return true/false depending on whether a
  54  * particular Tree is processed or skipped.
  55  */
  56 public class HandleSourceFactory implements Declaration.Visitor<Void, Declaration> {
  57 
  58     private final Set<String> constants = new HashSet<>();
  59     protected final JavaSourceBuilder builder = new JavaSourceBuilder();
  60     protected final TypeTranslator typeTranslator = new TypeTranslator();
  61     private final List<String> libraryNames;
  62     private final List<String> libraryPaths;
  63     private final String clsName;
  64     private final String pkgName;
  65 
  66     static JavaFileObject[] generateRaw(Declaration.Scoped decl, String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) {
  67         return new HandleSourceFactory(clsName, pkgName, libraryNames, libraryPaths).generate(decl);
  68     }
  69 
  70     static JavaFileObject[] generateWrapped(Declaration.Scoped decl, String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) {
  71         return new StaticWrapperSourceFactory(clsName, pkgName, libraryNames, libraryPaths).generate(decl);
  72     }
  73 
  74     public HandleSourceFactory(String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) {
  75         this.libraryNames = libraryNames;
  76         this.libraryPaths = libraryPaths;
  77         this.clsName = clsName;
  78         this.pkgName = pkgName;
  79     }
  80 
  81     public JavaFileObject[] generate(Declaration.Scoped decl) {
  82         builder.addPackagePrefix(pkgName);
  83         builder.classBegin(clsName);
  84         builder.addLibraries(libraryNames.toArray(new String[0]),
  85                 libraryPaths != null ? libraryPaths.toArray(new String[0]) : null);
  86         //generate all decls
  87         decl.members().forEach(this::generateDecl);
  88 
  89         //generate functional interfaces
  90         generateFunctionalInterfaces(decl);
  91 
  92         builder.classEnd();
  93         String src = builder.build();
  94 
  95         URL runtimeHelper = HandleSourceFactory.class.getResource("resources/RuntimeHelper.template");
  96 
  97         try {
  98             return new JavaFileObject[] {
  99                     fileFromString(pkgName, clsName, src),
 100                     fileFromString(pkgName,"RuntimeHelper", (pkgName.isEmpty()? "" : "package " + pkgName + ";\n") +
 101                             Files.readAllLines(Paths.get(runtimeHelper.toURI()))
 102                             .stream().collect(Collectors.joining("\n")))
 103             };
 104         } catch (IOException ex) {
 105             throw new UncheckedIOException(ex);
 106         } catch (URISyntaxException ex2) {
 107             throw new RuntimeException(ex2);
 108         }
 109     }
 110 
 111     protected void generateFunctionalInterfaces(Declaration.Scoped decl) {
 112         //generate functional interfaces
 113         Set<FunctionDescriptor> functionalInterfaces = new HashSet<>();
 114         new FunctionalInterfaceScanner(functionalInterfaces).scan(decl);
 115         functionalInterfaces.forEach(builder::addUpcallFactory);
 116     }
 117 
 118     private void generateDecl(Declaration tree) {
 119         try {
 120             tree.accept(this, null);
 121         } catch (Exception ex) {
 122             ex.printStackTrace();
 123         }
 124     }
 125 
 126     private JavaFileObject fileFromString(String pkgName, String clsName, String contents) {
 127         String pkgPrefix = pkgName.isEmpty() ? "" : pkgName.replaceAll("\\.", "/") + "/";
 128         return new SimpleJavaFileObject(URI.create(pkgPrefix + clsName + ".java"), JavaFileObject.Kind.SOURCE) {
 129             @Override
 130             public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
 131                 return contents;
 132             }
 133         };
 134     }
 135 
 136     @Override
 137     public Void visitVariable(Declaration.Variable tree, Declaration parent) {
 138         String fieldName = tree.name();
 139         String symbol = tree.name();
 140         assert !symbol.isEmpty();
 141         assert !fieldName.isEmpty();
 142         Type type = tree.type();
 143         MemoryLayout layout = tree.layout().orElse(Type.layoutFor(type).orElse(null));
 144         if (layout == null) {
 145             //no layout - abort
 146             return null;
 147         }
 148         Class<?> clazz = typeTranslator.getJavaType(type);
 149         if (clazz == MemoryAddress.class || clazz == MemorySegment.class || layout.byteSize() > 8) {
 150             //skip
 151             return null;
 152         }
 153 
 154         if (parent != null) {
 155             //struct field
 156             builder.addVarHandle(fieldName, clazz, parent.name());
 157         } else {
 158             builder.addLayout(fieldName, layout);
 159             builder.addVarHandle(fieldName, clazz, null);
 160             builder.addAddress(fieldName);
 161         }
 162 
 163         return null;
 164     }
 165 
 166     @Override
 167     public Void visitFunction(Declaration.Function funcTree, Declaration parent) {
 168         FunctionDescriptor descriptor = Type.descriptorFor(funcTree.type()).orElse(null);
 169         if (descriptor == null) {
 170             //abort
 171         }
 172         MethodType mtype = typeTranslator.getMethodType(funcTree.type());
 173         builder.addMethodHandle(funcTree, mtype, descriptor);
 174         return null;
 175     }
 176 
 177     @Override
 178     public Void visitConstant(Declaration.Constant constant, Declaration parent) {
 179         if (!constants.add(constant.name())) {
 180             //skip
 181             return null;
 182         }
 183 
 184         builder.addConstant(constant.name(), typeTranslator.getJavaType(constant.type()), constant.value());
 185         return null;
 186     }
 187 
 188     @Override
 189     public Void visitScoped(Declaration.Scoped d, Declaration parent) {
 190         if (d.kind() == Declaration.Scoped.Kind.TYPEDEF) {
 191             return d.members().get(0).accept(this, d);
 192         }
 193         if (d.layout().isEmpty()) {
 194             //skip decl-only
 195             return null;
 196         }
 197         String name = d.name();
 198         if (d.name().isEmpty() && parent != null) {
 199             name = parent.name();
 200         }
 201 
 202         if (!d.name().isEmpty() || !isRecord(parent)) {
 203             //only add explicit struct layout if the struct is not to be flattened inside another struct
 204             switch (d.kind()) {
 205                 case STRUCT:
 206                 case UNION:
 207                     builder.addLayout(name, d.layout().get());
 208                     break;
 209             }
 210         }
 211         d.members().forEach(fieldTree -> fieldTree.accept(this, d.name().isEmpty() ? parent : d));
 212         return null;
 213     }
 214 
 215     private boolean isRecord(Declaration declaration) {
 216         if (declaration == null) {
 217             return false;
 218         } else if (!(declaration instanceof Declaration.Scoped)) {
 219             return false;
 220         } else {
 221             Declaration.Scoped scope = (Declaration.Scoped)declaration;
 222             return scope.kind() == Declaration.Scoped.Kind.CLASS ||
 223                     scope.kind() == Declaration.Scoped.Kind.STRUCT ||
 224                     scope.kind() == Declaration.Scoped.Kind.UNION;
 225         }
 226     }
 227 }