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 jdk.internal.clang.Cursor;
  27 import jdk.internal.clang.Type;
  28 import jdk.internal.clang.TypeKind;
  29 
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.math.BigDecimal;
  34 import java.math.BigInteger;
  35 import java.nicl.metadata.NativeType;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.util.HashMap;
  39 import java.util.Map;
  40 import java.util.concurrent.atomic.AtomicInteger;
  41 import java.util.function.Function;
  42 import java.util.jar.JarEntry;
  43 import java.util.jar.JarInputStream;
  44 import java.util.logging.Logger;
  45 import java.util.stream.Stream;
  46 
  47 import static java.nio.file.StandardOpenOption.READ;
  48 
  49 /**
  50  * A dictionary that find Java type for a given native type.
  51  * Each instance of TypeDictionary presents types for a given java package.
  52  */
  53 public final class TypeDictionary {
  54     // package name to TypeDictionary
  55     final static Map<String, TypeDictionary> tdMap;
  56     final Logger logger = Logger.getLogger(getClass().getPackage().getName());
  57 
  58     final String pkgName;
  59     // clang.Type.spelling() to java type
  60     final Map<String, JType> typeMap;
  61 
  62     static {
  63         tdMap = new HashMap<>();
  64     }
  65 
  66     final AtomicInteger serialNo;
  67 
  68     private int serialNo() {
  69         return serialNo.incrementAndGet();
  70     }
  71 
  72     private TypeDictionary(String pkg) {
  73         pkgName = pkg;
  74         typeMap = new HashMap<>();
  75         serialNo = new AtomicInteger();
  76     }
  77 
  78     public static TypeDictionary of(String pkg) {
  79         return tdMap.computeIfAbsent(pkg, TypeDictionary::new);
  80     }
  81 /*
  82     public static boolean addType(Type t, Class<?> cls) {
  83         if (cls.isAnnotation() || cls.isArray()) {
  84             throw new IllegalArgumentException();
  85         }
  86 
  87         String pkg = cls.getPackage().getName();
  88         TypeDictionary dict = TypeDictionary.of(pkg);
  89 
  90         String type = t.spelling();
  91         return dict.addWithClass(type, cls);
  92     }
  93 
  94     boolean addType(String type, Class<?> cls) {
  95         String pkg = cls.getPackage().getName();
  96         // Class must be in this or sub packages
  97         if (! pkg.startsWith(pkgName)) {
  98             return false;
  99         }
 100         return addWithClass(type, cls);
 101     }
 102 
 103     private boolean addWithClass(String type, Class<?> cls) {
 104         assert(! cls.isArray());
 105         assert(! cls.isAnnotation());
 106 
 107         return (null == typeMap.putIfAbsent(type, JType.of(cls)));
 108     }
 109 */
 110 
 111     static class JarClassStreamer extends ClassLoader {
 112         private final HashMap<String, byte[]> bytecodes = new HashMap<>();
 113 
 114         JarClassStreamer(Path jar) {
 115             try (JarInputStream jis = new JarInputStream(Files.newInputStream(jar, READ))) {
 116                 // List all classes in the jar
 117                 for (JarEntry e = jis.getNextJarEntry(); e != null; e = jis.getNextJarEntry()) {
 118                     if (e.isDirectory()) {
 119                         jis.closeEntry();
 120                         continue;
 121                     }
 122                     String name = e.getName();
 123                     if (! name.endsWith(".class")) {
 124                         // Should not have file not class files
 125                         Context.getInstance().err.println("Warning: unexpected file " + name);
 126                     }
 127                     name = name.substring(0, name.length() - 6);
 128                     byte[] buf = new byte[4096];
 129                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
 130                     int n;
 131                     while ((n = jis.read(buf)) > -1) {
 132                         bos.write(buf, 0, n);
 133                     }
 134                     bytecodes.put(name.replace(File.separatorChar, '.'), bos.toByteArray());
 135                 }
 136             } catch (IOException ioe) {
 137                 Context.getInstance().err.println("Failed to load types from jar file: " + jar.toString());
 138             }
 139         }
 140 
 141         public Stream<Class<?>> stream() {
 142             return bytecodes.entrySet().stream()
 143                     .map(e -> {
 144                         byte[] bytecode = e.getValue();
 145                         return defineClass(e.getKey(), bytecode, 0, bytecode.length);
 146                     });
 147         }
 148     }
 149 
 150     public static void loadJar(Path jar) {
 151         JarClassStreamer cl = new JarClassStreamer(jar);
 152         cl.stream().forEach(cls -> {
 153             Package pkg = cls.getPackage();
 154             TypeDictionary dict = TypeDictionary.of(pkg.getName());
 155             NativeType nt = cls.getAnnotation(NativeType.class);
 156             dict.typeMap.putIfAbsent(nt.ctype(), JType.of(cls));
 157         });
 158     }
 159 
 160     private static JType checkPrimitive(Type t) {
 161         switch (t.kind()) {
 162             case Void:
 163                 return JType.Void;
 164             // signed integer
 165             case Char_S:
 166             case SChar:
 167             case Short:
 168             case Int:
 169             case Long:
 170             case LongLong:
 171             case Int128:
 172             // unsigned integer, size-compatible at groveller level
 173             // accomodate value-range needed at civilizer level
 174             case UShort:
 175             case UInt:
 176             case UInt128:
 177             case ULong:
 178             case ULongLong:
 179             case Char_U:
 180             case UChar:
 181                 long size = t.size();
 182                 if (size <= 1) {
 183                     return JType.Byte;
 184                 } else if (size <= 2) {
 185                     return JType.Short;
 186                 } else if (size <= 4) {
 187                     return JType.Int;
 188                 } else if (size <= 8) {
 189                     return JType.Long;
 190                 } else {
 191                     return JType.of(BigInteger.class);
 192                 }
 193             case WChar:
 194             case Char16:
 195                 return JType.Char;
 196             case Bool:
 197                 return JType.Bool;
 198             case Double:
 199                 return JType.Double;
 200             case Float:
 201                 return JType.Float;
 202             case LongDouble:
 203                 return JType.of(BigDecimal.class);
 204         }
 205         return null;
 206     }
 207 
 208     private JType exist(Type t) {
 209         JType jt = checkPrimitive(t);
 210         if (jt == null) {
 211             jt = typeMap.get(t.spelling());
 212         }
 213         return jt;
 214     }
 215 
 216     /**
 217      * @param t
 218      * @return
 219      */
 220     public final JType get(Type t) {
 221         JType jt;
 222 
 223         switch(t.kind()) {
 224             case Unexposed:
 225                 // Always look at canonical type
 226                 jt = get(t.canonicalType());
 227                 break;
 228             case ConstantArray:
 229                 // Array of element type
 230                 jt = get(t.getElementType());
 231                 if (jt != null) {
 232                     jt = new JType.Array(jt);
 233                 }
 234                 break;
 235             case IncompleteArray:
 236                 jt = get(t.getElementType());
 237                 if (jt != null) {
 238                     jt = new PointerType(jt);
 239                 }
 240                 break;
 241             case Pointer:
 242                 // Pointer type to non-primitive type need to be added to pointee dictionary explicitly
 243                 // Pointee type can be from other dictionary
 244                 jt = exist(t);
 245                 if (jt == null) {
 246                     Type pointee = t.getPointeeType();
 247                     jt = checkPrimitive(pointee);
 248                     if (jt != null) {
 249                         jt = new PointerType(jt);
 250                     }
 251                     // leave out non-primitive type for caller to define
 252                 }
 253                 break;
 254             case Vector:
 255                 switch ((int) t.size()) {
 256                     case 8:
 257                         jt = JType.Long;
 258                         break;
 259                     case 16:
 260                     case 32:
 261                     case 64:
 262                     default:
 263                         throw new UnsupportedOperationException("Support for vector size: " + t.size());
 264                 }
 265                 break;
 266             default:
 267                 jt = exist(t);
 268         }
 269 
 270         if (null == jt) {
 271             logger.fine(() -> "Cannot find type for " + t.spelling());
 272         } else {
 273             final JType finalJt = jt;
 274             logger.fine(() -> "Found type " + finalJt.getDescriptor() + " for " + t.spelling());
 275             jt = (jt instanceof JType2) ? jt : JType2.bind(jt, t, t.getDeclarationCursor());
 276         }
 277         return jt;
 278     }
 279 
 280     public final JType computeIfAbsent(Type t, Function<Type, JType> fn) {
 281         JType jt = get(t);
 282         if (jt != null) {
 283             return jt;
 284         }
 285 
 286         // avoid nested call of computeAbsent as fn is likely to define a type
 287         jt = fn.apply(t);
 288         JType rv = typeMap.putIfAbsent(t.spelling(), jt);
 289         // should we be alert in this situation?
 290         return (rv == null) ? jt : rv;
 291     }
 292 
 293     /**
 294      * Look up a type in this instance first, if cannot find it, try to
 295      * look into the origin(declaring) TypeDictionary.
 296      * @param t
 297      * @return
 298      * @throws com.sun.tools.jextract.TypeDictionary.NotDeclaredException
 299      */
 300     public final JType lookup(Type t) throws NotDeclaredException {
 301         JType jt = get(t);
 302         if (jt == null && t.kind() != TypeKind.Pointer) {
 303             // Pointer type need to check with pointee type, as the declaration
 304             // might still be in same TypeDictionary
 305             Cursor c = t.getDeclarationCursor();
 306             if (c.isInvalid()) {
 307                 logger.info(() -> "Type " + t.spelling() + " has invalid declaration cursor.");
 308                 logger.fine(() -> Printer.Stringifier(p -> p.dumpType(t)));
 309                 throw new NotDeclaredException(t);
 310             }
 311             jt = Context.getInstance().getJType(t.getDeclarationCursor());
 312         }
 313         return jt;
 314     }
 315 
 316     static class NotDeclaredException extends RuntimeException {
 317         private static final long serialVersionUID = -1L;
 318 
 319         private final Type type;
 320 
 321         public NotDeclaredException(Type t) {
 322             type = t;
 323         }
 324 
 325         public final Type getType() { return type; }
 326     }
 327 }