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