1 /*
   2  * Copyright (c) 2014, 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.foreign.layout.Function;
  27 import java.foreign.layout.Layout;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.util.ArrayList;
  31 import java.util.Arrays;
  32 import java.util.List;
  33 import java.util.Optional;
  34 import java.util.stream.Collectors;
  35 import java.util.stream.Stream;
  36 import javax.lang.model.SourceVersion;
  37 
  38 import jdk.internal.clang.Cursor;
  39 import jdk.internal.clang.CursorKind;
  40 import jdk.internal.clang.SourceLocation;
  41 import jdk.internal.clang.Type;
  42 import com.sun.tools.jextract.tree.LayoutUtils;
  43 import jdk.internal.clang.TypeKind;
  44 
  45 /**
  46  * General utility functions
  47  */
  48 public class Utils {
  49     public static void validSimpleIdentifier(String name) {
  50         int length = name.length();
  51         if (length == 0) {
  52             throw new IllegalArgumentException();
  53         }
  54 
  55         int ch = name.codePointAt(0);
  56         if (length == 1 && ch == '_') {
  57             throw new IllegalArgumentException("'_' is no longer valid identifier.");
  58         }
  59 
  60         if (!Character.isJavaIdentifierStart(ch)) {
  61             throw new IllegalArgumentException("Invalid start character for an identifier: " + ch);
  62         }
  63 
  64         for (int i = 1; i < length; i++) {
  65             ch = name.codePointAt(i);
  66             if (!Character.isJavaIdentifierPart(ch)) {
  67                 throw new IllegalArgumentException("Invalid character for an identifier: " + ch);
  68             }
  69         }
  70     }
  71 
  72     public static void validPackageName(String name) {
  73         if (name.isEmpty()) {
  74             throw new IllegalArgumentException();
  75         }
  76         int idx = name.lastIndexOf('.');
  77         if (idx == -1) {
  78            validSimpleIdentifier(name);
  79         } else {
  80             validSimpleIdentifier(name.substring(idx + 1));
  81             validPackageName(name.substring(0, idx));
  82         }
  83     }
  84 
  85     public static String toJavaIdentifier(String str) {
  86         final int size = str.length();
  87         StringBuilder sb = new StringBuilder(size);
  88         if (! Character.isJavaIdentifierStart(str.charAt(0))) {
  89             sb.append('_');
  90         }
  91         for (int i = 0; i < size; i++) {
  92             char ch = str.charAt(i);
  93             if (Character.isJavaIdentifierPart(ch)) {
  94                 sb.append(ch);
  95             } else {
  96                 sb.append('_');
  97             }
  98         }
  99         return sb.toString();
 100     }
 101 
 102     private static String toSafeName(String name) {
 103         StringBuilder sb = new StringBuilder(name.length());
 104         name = toJavaIdentifier(name);
 105         sb.append(name);
 106         if (SourceVersion.isKeyword(name)) {
 107             sb.append("$");
 108         }
 109         return sb.toString();
 110     }
 111 
 112     public static String toClassName(String cname) {
 113         return toSafeName(cname);
 114     }
 115 
 116     public static String toMacroName(String mname) {
 117         return toSafeName(mname);
 118     }
 119 
 120     public static String toInternalName(String pkg, String name, String... nested) {
 121         if ((pkg == null || pkg.isEmpty()) && nested == null) {
 122             return name;
 123         }
 124 
 125         StringBuilder sb = new StringBuilder();
 126         if (pkg != null && ! pkg.isEmpty()) {
 127             sb.append(pkg.replace('.', '/'));
 128             if (sb.charAt(sb.length() - 1) != '/') {
 129                 sb.append('/');
 130             }
 131         }
 132         sb.append(name);
 133         for (String n: nested) {
 134             sb.append('$');
 135             sb.append(n);
 136         }
 137         return sb.toString();
 138     }
 139 
 140     public static String getName(Type type) {
 141         return LayoutUtils.getName(type);
 142     }
 143 
 144     public static Function getFunction(Type type) {
 145         return LayoutUtils.getFunction(type);
 146     }
 147 
 148     public static Class<?> unboxIfNeeded(Class<?> clazz) {
 149         if (clazz == Boolean.class) {
 150             return boolean.class;
 151         } else if (clazz == Void.class) {
 152             return void.class;
 153         } else if (clazz == Byte.class) {
 154             return byte.class;
 155         } else if (clazz == Character.class) {
 156             return char.class;
 157         } else if (clazz == Short.class) {
 158             return short.class;
 159         } else if (clazz == Integer.class) {
 160             return int.class;
 161         } else if (clazz == Long.class) {
 162             return long.class;
 163         } else if (clazz == Float.class) {
 164             return float.class;
 165         } else if (clazz == Double.class) {
 166             return double.class;
 167         } else {
 168             return clazz;
 169         }
 170     }
 171 
 172     public static Stream<Cursor> flattenableChildren(Cursor c) {
 173         return c.children()
 174                 .filter(cx -> cx.isAnonymousStruct() || cx.kind() == CursorKind.FieldDecl);
 175     }
 176 
 177     public static Optional<Cursor> lastChild(Cursor c) {
 178         List<Cursor> children = flattenableChildren(c)
 179                 .collect(Collectors.toList());
 180         return children.isEmpty() ? Optional.empty() : Optional.of(children.get(children.size() - 1));
 181     }
 182 
 183     public static boolean hasIncompleteArray(Cursor c) {
 184         switch (c.kind()) {
 185             case FieldDecl:
 186                 return c.type().kind() == TypeKind.IncompleteArray;
 187             case UnionDecl:
 188                 return flattenableChildren(c)
 189                         .anyMatch(Utils::hasIncompleteArray);
 190             case StructDecl:
 191                 return lastChild(c).map(Utils::hasIncompleteArray).orElse(false);
 192             default:
 193                 throw new IllegalStateException("Unhandled cursor kind: " + c.kind());
 194         }
 195     }
 196 
 197     // return builtin Record types accessible from the given Type
 198     public static Stream<Cursor> getBuiltinRecordTypes(Type type) {
 199         List<Cursor> recordTypes = new ArrayList<>();
 200         fillBuiltinRecordTypes(type, recordTypes);
 201         return recordTypes.stream().distinct();
 202     }
 203 
 204     private static void fillBuiltinRecordTypes(Type type, List<Cursor> recordTypes) {
 205         type = type.canonicalType();
 206         switch (type.kind()) {
 207             case ConstantArray:
 208             case IncompleteArray:
 209                 fillBuiltinRecordTypes(type.getElementType(), recordTypes);
 210                 break;
 211 
 212             case FunctionProto:
 213             case FunctionNoProto: {
 214                 final int numArgs = type.numberOfArgs();
 215                 for (int i = 0; i < numArgs; i++) {
 216                     fillBuiltinRecordTypes(type.argType(i), recordTypes);
 217                 }
 218                 fillBuiltinRecordTypes(type.resultType(), recordTypes);
 219             }
 220             break;
 221 
 222             case Record: {
 223                 Cursor c = type.getDeclarationCursor();
 224                 if (c.isDefinition()) {
 225                     SourceLocation sloc = c.getSourceLocation();
 226                     if (sloc != null && sloc.getFileLocation().path() == null) {
 227                         recordTypes.add(c);
 228                     }
 229                 }
 230             }
 231             break;
 232 
 233             case BlockPointer:
 234             case Pointer:
 235                 fillBuiltinRecordTypes(type.getPointeeType(), recordTypes);
 236                 break;
 237 
 238             case Unexposed:
 239             case Elaborated:
 240             case Typedef:
 241                 fillBuiltinRecordTypes(type, recordTypes);
 242                 break;
 243 
 244             default: // nothing to do
 245         }
 246     }
 247 
 248     // return the absolute path of the library of given name by searching
 249     // in the given array of paths.
 250     public static Optional<Path> findLibraryPath(Path[] paths, String libName) {
 251         return Arrays.stream(paths).
 252                 map(p -> p.resolve(System.mapLibraryName(libName))).
 253                 filter(Files::isRegularFile).map(Path::toAbsolutePath).findFirst();
 254     }
 255 
 256     /*
 257      * FIXME: when we add jdk.compiler dependency from jdk.jextract module, revisit
 258      * the following. The following methods 'quote', 'quote' and 'isPrintableAscii'
 259      * are from javac source. See also com.sun.tools.javac.util.Convert.java.
 260      */
 261 
 262     /**
 263      * Escapes each character in a string that has an escape sequence or
 264      * is non-printable ASCII.  Leaves non-ASCII characters alone.
 265      */
 266     public static String quote(String s) {
 267         StringBuilder buf = new StringBuilder();
 268         for (int i = 0; i < s.length(); i++) {
 269             buf.append(quote(s.charAt(i)));
 270         }
 271         return buf.toString();
 272     }
 273 
 274     /**
 275      * Escapes a character if it has an escape sequence or is
 276      * non-printable ASCII.  Leaves non-ASCII characters alone.
 277      */
 278     public static String quote(char ch) {
 279         switch (ch) {
 280         case '\b':  return "\\b";
 281         case '\f':  return "\\f";
 282         case '\n':  return "\\n";
 283         case '\r':  return "\\r";
 284         case '\t':  return "\\t";
 285         case '\'':  return "\\'";
 286         case '\"':  return "\\\"";
 287         case '\\':  return "\\\\";
 288         default:
 289             return (isPrintableAscii(ch))
 290                 ? String.valueOf(ch)
 291                 : String.format("\\u%04x", (int) ch);
 292         }
 293     }
 294 
 295     /**
 296      * Is a character printable ASCII?
 297      */
 298     private static boolean isPrintableAscii(char ch) {
 299         return ch >= ' ' && ch <= '~';
 300     }
 301 }