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