1 /*
   2  *  Copyright (c) 2018, 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 
  24 package com.sun.tools.jextract;
  25 
  26 import java.nio.file.Files;
  27 import java.nio.file.Path;
  28 import java.util.ArrayList;
  29 import java.util.LinkedHashMap;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.logging.Level;
  33 
  34 public class HeaderResolver {
  35 
  36     // The folder path mapping to package name
  37     private final Map<Path, String> pkgMap = new LinkedHashMap<>();
  38     // The header file parsed
  39     private final Map<Path, HeaderFile> headerMap = new LinkedHashMap<>();
  40     private final Log log;
  41     private final Path builtinHeader;
  42 
  43     public HeaderResolver(Context ctx) {
  44         this.log = ctx.log;
  45         usePackageForFolder(Context.getBuiltinHeadersDir(), "clang_support");
  46         this.builtinHeader = Context.getBuiltinHeaderFile();
  47         ctx.sources.stream()
  48                 .map(Path::getParent)
  49                 .forEach(p -> usePackageForFolder(p, ctx.options.targetPackage));
  50         ctx.options.pkgMappings.forEach(this::usePackageForFolder);
  51     }
  52 
  53     private static String safeFileName(String filename) {
  54         int ext = filename.lastIndexOf('.');
  55         String name = ext != -1 ? filename.substring(0, ext) : filename;
  56         return Utils.toClassName(name);
  57     }
  58 
  59     public static String headerInterfaceName(String filename) {
  60         return safeFileName(filename) + "_h";
  61     }
  62 
  63     public static String staticForwarderName(String filename) {
  64         return safeFileName(filename) + "_lib";
  65     }
  66 
  67     private void usePackageForFolder(Path folder, String pkg) {
  68         folder = folder.normalize().toAbsolutePath();
  69         String existing = pkgMap.putIfAbsent(folder, pkg);
  70         final String finalFolder = (null == folder) ? "all folders not configured" : folder.toString();
  71         if (existing == null) {
  72             log.print(Level.CONFIG, () -> "Package " + pkg + " is selected for " + finalFolder);
  73         } else {
  74             String pkgName = pkg.isEmpty() ? "<default-package>" : pkg;
  75             log.print(Level.WARNING, () -> "Package " + existing + " had been selected for " + finalFolder + ", request to use " + pkgName + " is ignored.");
  76         }
  77     }
  78 
  79     // start of header file resolution logic
  80 
  81     static class HeaderPath {
  82         final String pkg;
  83         final String headerCls;
  84         final String forwarderCls;
  85 
  86         HeaderPath(String pkg, String headerCls, String forwarderCls) {
  87             this.pkg = pkg;
  88             this.headerCls = headerCls;
  89             this.forwarderCls = forwarderCls;
  90         }
  91     }
  92 
  93     /**
  94      * Determine package and interface name given a path. If the path is
  95      * a folder, then only package name is determined. The package name is
  96      * determined with the longest path matching the setup. If the path is not
  97      * setup for any package, the default package name is returned.
  98      *
  99      * @param origin The source path
 100      * @return The HeaderPath
 101      * @see Context::usePackageForFolder(Path, String)
 102      */
 103     private HeaderPath resolveHeaderPath(Path origin) {
 104         // normalize to absolute path
 105         origin = origin.normalize().toAbsolutePath();
 106         if (Files.isDirectory(origin)) {
 107             throw new IllegalStateException("Not an header file: " + origin);
 108         }
 109         String filename = origin.getFileName().toString();
 110         origin = origin.getParent();
 111         Path path = origin;
 112 
 113         // search the map for a hit with longest path
 114         while (path != null && !pkgMap.containsKey(path)) {
 115             path = path.getParent();
 116         }
 117 
 118         String pkg;
 119         if (path != null) {
 120             pkg = pkgMap.get(path);
 121             if (path.getNameCount() != origin.getNameCount()) {
 122                 String sep = pkg.isEmpty() ? "" : ".";
 123                 for (int i = path.getNameCount() ; i < origin.getNameCount() ; i++) {
 124                     pkg += sep + Utils.toJavaIdentifier(origin.getName(i).toString());
 125                     sep = ".";
 126                 }
 127                 usePackageForFolder(origin, pkg);
 128             }
 129         } else {
 130             //infer a package name from path
 131             List<String> parts = new ArrayList<>();
 132             for (Path p : origin) {
 133                 parts.add(Utils.toJavaIdentifier(p.toString()));
 134             }
 135             pkg = String.join(".", parts);
 136             usePackageForFolder(origin, pkg);
 137         }
 138 
 139         return new HeaderPath(pkg, headerInterfaceName(filename), staticForwarderName(filename));
 140     }
 141 
 142     private HeaderFile getHeaderFile(Path header) {
 143         if (!Files.isRegularFile(header)) {
 144             log.print(Level.WARNING, () -> "Not a regular file: " + header.toString());
 145             throw new IllegalArgumentException(header.toString());
 146         }
 147 
 148         final HeaderPath hp = resolveHeaderPath(header);
 149         return new HeaderFile(this, header, hp.pkg, hp.headerCls, hp.forwarderCls);
 150     }
 151 
 152     public HeaderFile headerFor(Path path) {
 153         if (path == null) {
 154             path = builtinHeader;
 155         }
 156 
 157         return headerMap.computeIfAbsent(path.normalize().toAbsolutePath(), this::getHeaderFile);
 158     }
 159 }