1 /*
   2  * Copyright (c) 2010, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  29 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
  30 
  31 import java.lang.invoke.MethodHandle;
  32 import java.lang.invoke.MethodHandles;
  33 import java.lang.invoke.MethodType;
  34 import jdk.dynalink.CallSiteDescriptor;
  35 import jdk.dynalink.beans.BeansLinker;
  36 import jdk.dynalink.beans.StaticClass;
  37 import jdk.dynalink.linker.GuardedInvocation;
  38 import jdk.dynalink.linker.LinkRequest;
  39 import jdk.dynalink.linker.support.Guards;
  40 import jdk.nashorn.internal.lookup.MethodHandleFactory;
  41 import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
  42 import jdk.nashorn.internal.objects.annotations.Attribute;
  43 import jdk.nashorn.internal.objects.annotations.Function;
  44 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  45 
  46 /**
  47  * An object that exposes Java packages and classes as its properties. Packages are exposed as objects that have further
  48  * sub-packages and classes as their properties. Normally, three instances of this class are exposed as built-in objects
  49  * in Nashorn: {@code "Packages"}, {@code "java"}, and {@code "javax"}. Typical usages are:
  50  * <pre>
  51  * var list = new java.util.ArrayList()
  52  * var sprocket = new Packages.com.acme.Sprocket()
  53  * </pre>
  54  * or you can store the type objects in a variable for later reuse:
  55  * <pre>
  56  * var ArrayList = java.util.ArrayList
  57  * var list = new ArrayList
  58  * </pre>
  59  * You can also use {@link jdk.nashorn.internal.objects.NativeJava#type(Object, Object)} to access Java classes. These two statements are mostly
  60  * equivalent:
  61  * <pre>
  62  * var listType1 = java.util.ArrayList
  63  * var listType2 = Java.type("java.util.ArrayList")
  64  * </pre>
  65  * The difference is that {@code Java.type()} will throw an error if the class does not exist, while the first
  66  * expression will return an empty object, as it must treat all non-existent classes as potentially being further
  67  * subpackages. As such, {@code Java.type()} has the potential to catch typos earlier. A further difference is that
  68  * {@code Java.type()} doesn't recognize {@code .} (dot) as the separator between outer class name and inner class name,
  69  * it only recognizes the dollar sign. These are equivalent:
  70  * <pre>
  71  * var ftype1 = java.awt.geom.Arc2D$Float
  72  * var ftype2 = java.awt.geom.Arc2D.Float
  73  * var ftype3 = Java.asType("java.awt.geom.Arc2D$Float")
  74  * var ftype4 = Java.asType("java.awt.geom.Arc2D").Float
  75  * </pre>
  76  */
  77 public final class NativeJavaPackage extends ScriptObject {
  78     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
  79     private static final MethodHandle CLASS_NOT_FOUND = findOwnMH("classNotFound", Void.TYPE, NativeJavaPackage.class);
  80     private static final MethodHandle TYPE_GUARD = Guards.getClassGuard(NativeJavaPackage.class);
  81 
  82     /** Full name of package (includes path.) */
  83     private final String name;
  84 
  85     /**
  86      * Public constructor to be accessible from {@link jdk.nashorn.internal.objects.Global}
  87      * @param name  package name
  88      * @param proto proto
  89      */
  90     public NativeJavaPackage(final String name, final ScriptObject proto) {
  91         super(proto, null);
  92         // defense-in-path, check here for sensitive packages
  93         Context.checkPackageAccess(name);
  94         this.name = name;
  95     }
  96 
  97     @Override
  98     public String getClassName() {
  99         return "JavaPackage";
 100     }
 101 
 102     @Override
 103     public boolean equals(final Object other) {
 104         if (other instanceof NativeJavaPackage) {
 105             return name.equals(((NativeJavaPackage)other).name);
 106         }
 107         return false;
 108     }
 109 
 110     @Override
 111     public int hashCode() {
 112         return name == null ? 0 : name.hashCode();
 113     }
 114 
 115     /**
 116      * Get the full name of the package
 117      * @return the name
 118      */
 119     public String getName() {
 120         return name;
 121     }
 122 
 123     @Override
 124     public String safeToString() {
 125         return toString();
 126     }
 127 
 128     @Override
 129     public String toString() {
 130         return "[JavaPackage " + name + "]";
 131     }
 132 
 133     @Override
 134     public Object getDefaultValue(final Class<?> hint) {
 135         if (hint == String.class) {
 136             return toString();
 137         }
 138 
 139         return super.getDefaultValue(hint);
 140     }
 141 
 142     @Override
 143     protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) {
 144         return createClassNotFoundInvocation(desc);
 145     }
 146 
 147     @Override
 148     protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) {
 149         return createClassNotFoundInvocation(desc);
 150     }
 151 
 152     private static GuardedInvocation createClassNotFoundInvocation(final CallSiteDescriptor desc) {
 153         // If NativeJavaPackage is invoked either as a constructor or as a function, throw a ClassNotFoundException as
 154         // we can assume the user attempted to instantiate a non-existent class.
 155         final MethodType type = desc.getMethodType();
 156         return new GuardedInvocation(
 157                 MH.dropArguments(CLASS_NOT_FOUND, 1, type.parameterList().subList(1, type.parameterCount())),
 158                 type.parameterType(0) == NativeJavaPackage.class ? null : TYPE_GUARD);
 159     }
 160 
 161     @SuppressWarnings("unused")
 162     private static void classNotFound(final NativeJavaPackage pkg) throws ClassNotFoundException {
 163         throw new ClassNotFoundException(pkg.name);
 164     }
 165 
 166     /**
 167      * "No such property" call placeholder.
 168      *
 169      * This can never be called as we override {@link ScriptObject#noSuchProperty}. We do declare it here as it's a signal
 170      * to {@link WithObject} that it's worth trying doing a {@code noSuchProperty} on this object.
 171      *
 172      * @param self self reference
 173      * @param name property name
 174      * @return never returns
 175      */
 176     @Function(attributes = Attribute.NOT_ENUMERABLE)
 177     public static Object __noSuchProperty__(final Object self, final Object name) {
 178         throw new AssertionError("__noSuchProperty__ placeholder called");
 179     }
 180 
 181     /**
 182      * "No such method call" placeholder
 183      *
 184      * This can never be called as we override {@link ScriptObject#noSuchMethod}. We do declare it here as it's a signal
 185      * to {@link WithObject} that it's worth trying doing a noSuchProperty on this object.
 186      *
 187      * @param self self reference
 188      * @param args arguments to method
 189      * @return never returns
 190      */
 191     @Function(attributes = Attribute.NOT_ENUMERABLE)
 192     public static Object __noSuchMethod__(final Object self, final Object... args) {
 193         throw new AssertionError("__noSuchMethod__ placeholder called");
 194     }
 195 
 196     /**
 197      * Handle creation of new attribute.
 198      * @param desc the call site descriptor
 199      * @param request the link request
 200      * @return Link to be invoked at call site.
 201      */
 202     @Override
 203     public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc, final LinkRequest request) {
 204         final String propertyName = NashornCallSiteDescriptor.getOperand(desc);
 205         createProperty(propertyName);
 206         return super.lookup(desc, request);
 207     }
 208 
 209     @Override
 210     protected Object invokeNoSuchProperty(final Object key, final boolean isScope, final int programPoint) {
 211         if (!(key instanceof String)) {
 212             return super.invokeNoSuchProperty(key, isScope, programPoint);
 213         }
 214         final Object retval = createProperty((String) key);
 215         if (isValid(programPoint)) {
 216             throw new UnwarrantedOptimismException(retval, programPoint);
 217         }
 218         return retval;
 219     }
 220 
 221     @Override
 222     public GuardedInvocation noSuchMethod(final CallSiteDescriptor desc, final LinkRequest request) {
 223         return noSuchProperty(desc, request);
 224     }
 225 
 226     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 227         return MH.findStatic(MethodHandles.lookup(), NativeJavaPackage.class, name, MH.type(rtype, types));
 228     }
 229 
 230     private Object createProperty(final String propertyName) {
 231         final String fullName     = name.isEmpty() ? propertyName : name + "." + propertyName;
 232         final Context context = Context.getContextTrusted();
 233 
 234         Class<?> javaClass = null;
 235         try {
 236             javaClass = context.findClass(fullName);
 237         } catch (final NoClassDefFoundError | ClassNotFoundException e) {
 238             //ignored
 239         }
 240 
 241         // Check for explicit constructor signature use
 242         // Example: new (java.awt["Color(int, int,int)"])(2, 3, 4);
 243         final int openBrace = propertyName.indexOf('(');
 244         final int closeBrace = propertyName.lastIndexOf(')');
 245         if (openBrace != -1 || closeBrace != -1) {
 246             final int lastChar = propertyName.length() - 1;
 247             if (openBrace == -1 || closeBrace != lastChar) {
 248                 throw typeError("improper.constructor.signature", propertyName);
 249             }
 250 
 251             // get the class name and try to load it
 252             final String className = name + "." + propertyName.substring(0, openBrace);
 253             try {
 254                 javaClass = context.findClass(className);
 255             } catch (final NoClassDefFoundError | ClassNotFoundException e) {
 256                 throw typeError(e, "no.such.java.class", className);
 257             }
 258 
 259             // try to find a matching constructor
 260             final Object constructor = BeansLinker.getConstructorMethod(
 261                     javaClass, propertyName.substring(openBrace + 1, lastChar));
 262             if (constructor != null) {
 263                 set(propertyName, constructor, 0);
 264                 return constructor;
 265             }
 266             // we didn't find a matching constructor!
 267             throw typeError("no.such.java.constructor", propertyName);
 268         }
 269 
 270         final Object propertyValue;
 271         if (javaClass == null) {
 272             propertyValue = new NativeJavaPackage(fullName, getProto());
 273         } else {
 274             propertyValue = StaticClass.forClass(javaClass);
 275         }
 276 
 277         set(propertyName, propertyValue, 0);
 278         return propertyValue;
 279     }
 280 }