NativeJavaPackage.java revision 1428:c209abbe9b24
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
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
29import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
30
31import java.lang.invoke.MethodHandle;
32import java.lang.invoke.MethodHandles;
33import java.lang.invoke.MethodType;
34import jdk.internal.dynalink.CallSiteDescriptor;
35import jdk.internal.dynalink.beans.BeansLinker;
36import jdk.internal.dynalink.beans.StaticClass;
37import jdk.internal.dynalink.linker.GuardedInvocation;
38import jdk.internal.dynalink.linker.LinkRequest;
39import jdk.internal.dynalink.support.Guards;
40import jdk.nashorn.internal.lookup.MethodHandleFactory;
41import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
42import jdk.nashorn.internal.objects.annotations.Attribute;
43import 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 */
76public 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 boolean isScope, 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}
277