blob: 5153a271a856f20d4c3e4dffa31fd5d67309c128 [file] [log] [blame]
/*
* 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;
}
}