NativeJavaPackage.java revision 1033:c1f651636d9c
150472Speter/*
27130Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
378822Snik * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
450203Srgrimes *
57130Srgrimes * This code is free software; you can redistribute it and/or modify it
639161Sobrien * under the terms of the GNU General Public License version 2 only, as
78571Srgrimes * published by the Free Software Foundation.  Oracle designates this
88571Srgrimes * particular file as subject to the "Classpath" exception as provided
97130Srgrimes * by Oracle in the LICENSE file that accompanied this code.
10122402Sharti *
11122402Sharti * This code is distributed in the hope that it will be useful, but WITHOUT
12123051Sru * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13123051Sru * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14123051Sru * version 2 for more details (a copy is included in the LICENSE file that
15123051Sru * accompanied this code).
16123051Sru *
17123051Sru * You should have received a copy of the GNU General Public License version
18123051Sru * 2 along with this work; if not, write to the Free Software Foundation,
19123051Sru * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20123051Sru *
21123051Sru * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2239250Sgibbs * or visit www.oracle.com if you need additional information or have any
2339250Sgibbs * questions.
2439250Sgibbs */
2539250Sgibbs
26104489Ssampackage jdk.nashorn.internal.runtime;
27104489Ssam
2856583Sn_hibmaimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
2988748Sambriskoimport static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
3088748Sambrisko
31123051Sruimport java.lang.invoke.MethodHandle;
32123051Sruimport java.lang.invoke.MethodHandles;
3377756Sruimport java.lang.invoke.MethodType;
3477756Sruimport jdk.internal.dynalink.CallSiteDescriptor;
35103627Struckmanimport jdk.internal.dynalink.beans.BeansLinker;
36103627Struckmanimport jdk.internal.dynalink.beans.StaticClass;
37105400Stmmimport jdk.internal.dynalink.linker.GuardedInvocation;
38105400Stmmimport jdk.internal.dynalink.linker.LinkRequest;
3960724Speterimport jdk.internal.dynalink.support.Guards;
4060724Speterimport jdk.nashorn.internal.lookup.MethodHandleFactory;
41103627Struckmanimport jdk.nashorn.internal.lookup.MethodHandleFunctionality;
42103627Struckmanimport jdk.nashorn.internal.objects.annotations.Attribute;
4356583Sn_hibmaimport jdk.nashorn.internal.objects.annotations.Function;
4456583Sn_hibma
45116258Sharti/**
46116258Sharti * An object that exposes Java packages and classes as its properties. Packages are exposed as objects that have further
4770811Speter * sub-packages and classes as their properties. Normally, three instances of this class are exposed as built-in objects
4870811Speter * in Nashorn: {@code "Packages"}, {@code "java"}, and {@code "javax"}. Typical usages are:
4956583Sn_hibma * <pre>
5075415Sbp * var list = new java.util.ArrayList()
5188050Sgreen * var sprocket = new Packages.com.acme.Sprocket()
5288050Sgreen * </pre>
5377031Sru * or you can store the type objects in a variable for later reuse:
5477031Sru * <pre>
5577031Sru * var ArrayList = java.util.ArrayList
5677031Sru * var list = new ArrayList
5777162Sru * </pre>
5877162Sru * You can also use {@link jdk.nashorn.internal.objects.NativeJava#type(Object, Object)} to access Java classes. These two statements are mostly
5977223Sru * equivalent:
6077223Sru * <pre>
6177031Sru * var listType1 = java.util.ArrayList
6277031Sru * var listType2 = Java.type("java.util.ArrayList")
6377223Sru * </pre>
6477223Sru * The difference is that {@code Java.type()} will throw an error if the class does not exist, while the first
6577031Sru * expression will return an empty object, as it must treat all non-existent classes as potentially being further
6677031Sru * subpackages. As such, {@code Java.type()} has the potential to catch typos earlier. A further difference is that
6777031Sru * {@code Java.type()} doesn't recognize {@code .} (dot) as the separator between outer class name and inner class name,
6877031Sru * it only recognizes the dollar sign. These are equivalent:
6975461Sru * <pre>
7075461Sru * var ftype1 = java.awt.geom.Arc2D$Float
71123051Sru * var ftype2 = java.awt.geom.Arc2D.Float
72123051Sru * var ftype3 = Java.asType("java.awt.geom.Arc2D$Float")
7377031Sru * var ftype4 = Java.asType("java.awt.geom.Arc2D").Float
7477031Sru * </pre>
7577031Sru */
7677031Srupublic final class NativeJavaPackage extends ScriptObject {
7775415Sbp    private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
78110542Sphk    private static final MethodHandle CLASS_NOT_FOUND = findOwnMH("classNotFound", Void.TYPE, NativeJavaPackage.class);
79110542Sphk    private static final MethodHandle TYPE_GUARD = Guards.getClassGuard(NativeJavaPackage.class);
8067523Sarchie
8167523Sarchie    /** Full name of package (includes path.) */
8226453Sache    private final String name;
8326453Sache
8426453Sache    /**
8526453Sache     * Public constructor to be accessible from {@link jdk.nashorn.internal.objects.Global}
86120950Snectar     * @param name  package name
87120950Snectar     * @param proto proto
8890804Sgshapiro     */
8990804Sgshapiro    public NativeJavaPackage(final String name, final ScriptObject proto) {
9026453Sache        super(proto, null);
9193229Sru        // defense-in-path, check here for sensitive packages
9293229Sru        Context.checkPackageAccess(name);
9326453Sache        this.name = name;
9426453Sache    }
9526453Sache
96116818Ssam    @Override
97116818Ssam    public String getClassName() {
9826453Sache        return "JavaPackage";
9926453Sache    }
10039271Sphk
10193229Sru    @Override
10293229Sru    public boolean equals(final Object other) {
10393229Sru        if (other instanceof NativeJavaPackage) {
10493229Sru            return name.equals(((NativeJavaPackage)other).name);
10593229Sru        }
10693229Sru        return false;
10793229Sru    }
10893229Sru
10939271Sphk    @Override
11052419Sjulian    public int hashCode() {
111116811Sharti        return name == null ? 0 : name.hashCode();
112116811Sharti    }
113107123Sjulian
114107123Sjulian    /**
115107123Sjulian     * Get the full name of the package
116107123Sjulian     * @return the name
11752419Sjulian     */
11826453Sache    public String getName() {
11926453Sache        return name;
12052905Sjlemon    }
12152905Sjlemon
122105376Ssam    @Override
123105376Ssam    public String safeToString() {
12426453Sache        return toString();
12526453Sache    }
12626453Sache
12726453Sache    @Override
12867128Sbrian    public String toString() {
129121949Sharti        return "[JavaPackage " + name + "]";
130121949Sharti    }
131121337Sharti
132121337Sharti    @Override
133122211Sharti    public Object getDefaultValue(final Class<?> hint) {
134122211Sharti        if (hint == String.class) {
13567128Sbrian            return toString();
13652228Sbp        }
13752228Sbp
13875375Sbp        return super.getDefaultValue(hint);
13975375Sbp    }
14026453Sache
14126453Sache    @Override
14283653Speter    protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) {
14383653Speter        return createClassNotFoundInvocation(desc);
14483653Speter    }
14583653Speter
1468571Srgrimes    @Override
1477130Srgrimes    protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) {
14855097Skris        return createClassNotFoundInvocation(desc);
14955097Skris    }
15026453Sache
15126453Sache    private static GuardedInvocation createClassNotFoundInvocation(final CallSiteDescriptor desc) {
15234030Sdufault        // If NativeJavaPackage is invoked either as a constructor or as a function, throw a ClassNotFoundException as
15334030Sdufault        // we can assume the user attempted to instantiate a non-existent class.
1548571Srgrimes        final MethodType type = desc.getMethodType();
1557130Srgrimes        return new GuardedInvocation(
1568571Srgrimes                MH.dropArguments(CLASS_NOT_FOUND, 1, type.parameterList().subList(1, type.parameterCount())),
1577130Srgrimes                type.parameterType(0) == NativeJavaPackage.class ? null : TYPE_GUARD);
1588571Srgrimes    }
1597130Srgrimes
1608571Srgrimes    @SuppressWarnings("unused")
1617130Srgrimes    private static void classNotFound(final NativeJavaPackage pkg) throws ClassNotFoundException {
16241230Sjdp        throw new ClassNotFoundException(pkg.name);
163101192Srwatson    }
164101192Srwatson
165101192Srwatson    /**
166101192Srwatson     * "No such property" call placeholder.
167107547Srwatson     *
168107547Srwatson     * This can never be called as we override {@link ScriptObject#noSuchProperty}. We do declare it here as it's a signal
169101192Srwatson     * to {@link WithObject} that it's worth trying doing a {@code noSuchProperty} on this object.
170101192Srwatson     *
171105875Srwatson     * @param self self reference
172105875Srwatson     * @param name property name
17341230Sjdp     * @return never returns
17426453Sache     */
17526453Sache    @Function(attributes = Attribute.NOT_ENUMERABLE)
17626453Sache    public static Object __noSuchProperty__(final Object self, final Object name) {
17726453Sache        throw new AssertionError("__noSuchProperty__ placeholder called");
17826453Sache    }
17926453Sache
18026453Sache    /**
18126453Sache     * "No such method call" placeholder
18226453Sache     *
18326453Sache     * This can never be called as we override {@link ScriptObject#noSuchMethod}. We do declare it here as it's a signal
1847130Srgrimes     * 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}
277