| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime; |
| |
| import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import jdk.internal.dynalink.CallSiteDescriptor; |
| import jdk.internal.dynalink.beans.BeansLinker; |
| import jdk.internal.dynalink.beans.StaticClass; |
| import jdk.internal.dynalink.linker.GuardedInvocation; |
| import jdk.internal.dynalink.linker.LinkRequest; |
| import jdk.internal.dynalink.support.Guards; |
| import jdk.nashorn.internal.lookup.MethodHandleFactory; |
| import jdk.nashorn.internal.lookup.MethodHandleFunctionality; |
| import jdk.nashorn.internal.objects.annotations.Attribute; |
| import jdk.nashorn.internal.objects.annotations.Function; |
| |
| /** |
| * An object that exposes Java packages and classes as its properties. Packages are exposed as objects that have further |
| * sub-packages and classes as their properties. Normally, three instances of this class are exposed as built-in objects |
| * in Nashorn: {@code "Packages"}, {@code "java"}, and {@code "javax"}. Typical usages are: |
| * <pre> |
| * var list = new java.util.ArrayList() |
| * var sprocket = new Packages.com.acme.Sprocket() |
| * </pre> |
| * or you can store the type objects in a variable for later reuse: |
| * <pre> |
| * var ArrayList = java.util.ArrayList |
| * var list = new ArrayList |
| * </pre> |
| * You can also use {@link jdk.nashorn.internal.objects.NativeJava#type(Object, Object)} to access Java classes. These two statements are mostly |
| * equivalent: |
| * <pre> |
| * var listType1 = java.util.ArrayList |
| * var listType2 = Java.type("java.util.ArrayList") |
| * </pre> |
| * The difference is that {@code Java.type()} will throw an error if the class does not exist, while the first |
| * expression will return an empty object, as it must treat all non-existent classes as potentially being further |
| * subpackages. As such, {@code Java.type()} has the potential to catch typos earlier. A further difference is that |
| * {@code Java.type()} doesn't recognize {@code .} (dot) as the separator between outer class name and inner class name, |
| * it only recognizes the dollar sign. These are equivalent: |
| * <pre> |
| * var ftype1 = java.awt.geom.Arc2D$Float |
| * var ftype2 = java.awt.geom.Arc2D.Float |
| * var ftype3 = Java.asType("java.awt.geom.Arc2D$Float") |
| * var ftype4 = Java.asType("java.awt.geom.Arc2D").Float |
| * </pre> |
| */ |
| public final class NativeJavaPackage extends ScriptObject { |
| private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality(); |
| private static final MethodHandle CLASS_NOT_FOUND = findOwnMH("classNotFound", Void.TYPE, NativeJavaPackage.class); |
| private static final MethodHandle TYPE_GUARD = Guards.getClassGuard(NativeJavaPackage.class); |
| |
| /** Full name of package (includes path.) */ |
| private final String name; |
| |
| /** |
| * Public constructor to be accessible from {@link jdk.nashorn.internal.objects.Global} |
| * @param name package name |
| * @param proto proto |
| */ |
| public NativeJavaPackage(final String name, final ScriptObject proto) { |
| super(proto, null); |
| // defense-in-path, check here for sensitive packages |
| Context.checkPackageAccess(name); |
| this.name = name; |
| } |
| |
| @Override |
| public String getClassName() { |
| return "JavaPackage"; |
| } |
| |
| @Override |
| public boolean equals(final Object other) { |
| if (other instanceof NativeJavaPackage) { |
| return name.equals(((NativeJavaPackage)other).name); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return name == null ? 0 : name.hashCode(); |
| } |
| |
| /** |
| * Get the full name of the package |
| * @return the name |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public String safeToString() { |
| return toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return "[JavaPackage " + name + "]"; |
| } |
| |
| @Override |
| public Object getDefaultValue(final Class<?> hint) { |
| if (hint == String.class) { |
| return toString(); |
| } |
| |
| return super.getDefaultValue(hint); |
| } |
| |
| @Override |
| protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return createClassNotFoundInvocation(desc); |
| } |
| |
| @Override |
| protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return createClassNotFoundInvocation(desc); |
| } |
| |
| private static GuardedInvocation createClassNotFoundInvocation(final CallSiteDescriptor desc) { |
| // If NativeJavaPackage is invoked either as a constructor or as a function, throw a ClassNotFoundException as |
| // we can assume the user attempted to instantiate a non-existent class. |
| final MethodType type = desc.getMethodType(); |
| return new GuardedInvocation( |
| MH.dropArguments(CLASS_NOT_FOUND, 1, type.parameterList().subList(1, type.parameterCount())), |
| type.parameterType(0) == NativeJavaPackage.class ? null : TYPE_GUARD); |
| } |
| |
| @SuppressWarnings("unused") |
| private static void classNotFound(final NativeJavaPackage pkg) throws ClassNotFoundException { |
| throw new ClassNotFoundException(pkg.name); |
| } |
| |
| /** |
| * "No such property" call placeholder. |
| * |
| * This can never be called as we override {@link ScriptObject#noSuchProperty}. We do declare it here as it's a signal |
| * to {@link WithObject} that it's worth trying doing a {@code noSuchProperty} on this object. |
| * |
| * @param self self reference |
| * @param name property name |
| * @return never returns |
| */ |
| @Function(attributes = Attribute.NOT_ENUMERABLE) |
| public static Object __noSuchProperty__(final Object self, final Object name) { |
| throw new AssertionError("__noSuchProperty__ placeholder called"); |
| } |
| |
| /** |
| * "No such method call" placeholder |
| * |
| * This can never be called as we override {@link ScriptObject#noSuchMethod}. We do declare it here as it's a signal |
| * to {@link WithObject} that it's worth trying doing a noSuchProperty on this object. |
| * |
| * @param self self reference |
| * @param args arguments to method |
| * @return never returns |
| */ |
| @Function(attributes = Attribute.NOT_ENUMERABLE) |
| public static Object __noSuchMethod__(final Object self, final Object... args) { |
| throw new AssertionError("__noSuchMethod__ placeholder called"); |
| } |
| |
| /** |
| * Handle creation of new attribute. |
| * @param desc the call site descriptor |
| * @param request the link request |
| * @return Link to be invoked at call site. |
| */ |
| @Override |
| public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc, final LinkRequest request) { |
| final String propertyName = desc.getNameToken(2); |
| createProperty(propertyName); |
| return super.lookup(desc, request); |
| } |
| |
| @Override |
| protected Object invokeNoSuchProperty(final String key, final boolean isScope, final int programPoint) { |
| final Object retval = createProperty(key); |
| if (isValid(programPoint)) { |
| throw new UnwarrantedOptimismException(retval, programPoint); |
| } |
| return retval; |
| } |
| |
| @Override |
| public GuardedInvocation noSuchMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return noSuchProperty(desc, request); |
| } |
| |
| private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(MethodHandles.lookup(), NativeJavaPackage.class, name, MH.type(rtype, types)); |
| } |
| |
| private Object createProperty(final String propertyName) { |
| final String fullName = name.isEmpty() ? propertyName : name + "." + propertyName; |
| final Context context = Context.getContextTrusted(); |
| |
| Class<?> javaClass = null; |
| try { |
| javaClass = context.findClass(fullName); |
| } catch (final NoClassDefFoundError | ClassNotFoundException e) { |
| //ignored |
| } |
| |
| // Check for explicit constructor signature use |
| // Example: new (java.awt["Color(int, int,int)"])(2, 3, 4); |
| final int openBrace = propertyName.indexOf('('); |
| final int closeBrace = propertyName.lastIndexOf(')'); |
| if (openBrace != -1 || closeBrace != -1) { |
| final int lastChar = propertyName.length() - 1; |
| if (openBrace == -1 || closeBrace != lastChar) { |
| throw typeError("improper.constructor.signature", propertyName); |
| } |
| |
| // get the class name and try to load it |
| final String className = name + "." + propertyName.substring(0, openBrace); |
| try { |
| javaClass = context.findClass(className); |
| } catch (final NoClassDefFoundError | ClassNotFoundException e) { |
| throw typeError(e, "no.such.java.class", className); |
| } |
| |
| // try to find a matching constructor |
| final Object constructor = BeansLinker.getConstructorMethod( |
| javaClass, propertyName.substring(openBrace + 1, lastChar)); |
| if (constructor != null) { |
| set(propertyName, constructor, 0); |
| return constructor; |
| } |
| // we didn't find a matching constructor! |
| throw typeError("no.such.java.constructor", propertyName); |
| } |
| |
| final Object propertyValue; |
| if (javaClass == null) { |
| propertyValue = new NativeJavaPackage(fullName, getProto()); |
| } else { |
| propertyValue = StaticClass.forClass(javaClass); |
| } |
| |
| set(propertyName, propertyValue, 0); |
| return propertyValue; |
| } |
| } |