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