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