NashornBeansLinker.java revision 1483:7cb19fa78763
11541Srgrimes/*
21541Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
31541Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41541Srgrimes *
51541Srgrimes * This code is free software; you can redistribute it and/or modify it
61541Srgrimes * under the terms of the GNU General Public License version 2 only, as
71541Srgrimes * published by the Free Software Foundation.  Oracle designates this
81541Srgrimes * particular file as subject to the "Classpath" exception as provided
91541Srgrimes * by Oracle in the LICENSE file that accompanied this code.
101541Srgrimes *
111541Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121541Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131541Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141541Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151541Srgrimes * accompanied this code).
161541Srgrimes *
171541Srgrimes * You should have received a copy of the GNU General Public License version
181541Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191541Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201541Srgrimes *
211541Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221541Srgrimes * or visit www.oracle.com if you need additional information or have any
231541Srgrimes * questions.
241541Srgrimes */
251541Srgrimes
261541Srgrimespackage jdk.nashorn.internal.runtime.linker;
271541Srgrimes
281541Srgrimesimport static jdk.nashorn.internal.lookup.Lookup.MH;
2985052Sru
3050477Speterimport java.lang.invoke.MethodHandle;
311541Srgrimesimport java.lang.invoke.MethodHandles;
321541Srgrimesimport java.lang.invoke.MethodType;
332168Spaulimport java.lang.reflect.Method;
342168Spaulimport java.lang.reflect.Modifier;
352168Spaulimport jdk.internal.dynalink.CallSiteDescriptor;
361541Srgrimesimport jdk.internal.dynalink.NamedOperation;
371541Srgrimesimport jdk.internal.dynalink.StandardOperation;
388876Srgrimesimport jdk.internal.dynalink.beans.BeansLinker;
391541Srgrimesimport jdk.internal.dynalink.linker.ConversionComparator.Comparison;
401541Srgrimesimport jdk.internal.dynalink.linker.GuardedInvocation;
411541Srgrimesimport jdk.internal.dynalink.linker.GuardingDynamicLinker;
421541Srgrimesimport jdk.internal.dynalink.linker.LinkRequest;
431541Srgrimesimport jdk.internal.dynalink.linker.LinkerServices;
441541Srgrimesimport jdk.internal.dynalink.linker.MethodHandleTransformer;
451541Srgrimesimport jdk.internal.dynalink.linker.support.DefaultInternalObjectFilter;
461541Srgrimesimport jdk.internal.dynalink.linker.support.Lookup;
471541Srgrimesimport jdk.nashorn.api.scripting.ScriptUtils;
481541Srgrimesimport jdk.nashorn.internal.runtime.ConsString;
491541Srgrimesimport jdk.nashorn.internal.runtime.Context;
501541Srgrimesimport jdk.nashorn.internal.runtime.ScriptObject;
511541Srgrimesimport jdk.nashorn.internal.runtime.options.Options;
521541Srgrimes
531541Srgrimes/**
541541Srgrimes * This linker delegates to a {@code BeansLinker} but passes it a special linker services object that has a modified
551541Srgrimes * {@code compareConversion} method that favors conversion of {@link ConsString} to either {@link String} or
561541Srgrimes * {@link CharSequence}. It also provides a {@link #createHiddenObjectFilter()} method for use with bootstrap that will
57122922Sandre * ensure that we never pass internal engine objects that should not be externally observable (currently ConsString and
58122922Sandre * ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add this functionality as
59122922Sandre * custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
60122922Sandre * the target method handle parameter signature is {@code Object}. This linker also makes sure that primitive
61122922Sandre * {@link String} operations can be invoked on a {@link ConsString}, and allows invocation of objects implementing
62122922Sandre * the {@link FunctionalInterface} attribute.
631541Srgrimes */
641541Srgrimespublic class NashornBeansLinker implements GuardingDynamicLinker {
651541Srgrimes    // System property to control whether to wrap ScriptObject->ScriptObjectMirror for
661541Srgrimes    // Object type arguments of Java method calls, field set and array set.
671541Srgrimes    private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true);
6813765Smpp
6913765Smpp    private static final MethodHandle EXPORT_ARGUMENT;
701541Srgrimes    private static final MethodHandle IMPORT_RESULT;
711541Srgrimes    private static final MethodHandle FILTER_CONSSTRING;
721541Srgrimes
731541Srgrimes    static {
745791Swollman        final Lookup lookup  = new Lookup(MethodHandles.lookup());
751541Srgrimes        EXPORT_ARGUMENT      = lookup.findOwnStatic("exportArgument", Object.class, Object.class);
761541Srgrimes        IMPORT_RESULT        = lookup.findOwnStatic("importResult", Object.class, Object.class);
771541Srgrimes        FILTER_CONSSTRING    = lookup.findOwnStatic("consStringFilter", Object.class, Object.class);
781541Srgrimes    }
791541Srgrimes
801541Srgrimes    // cache of @FunctionalInterface method of implementor classes
811541Srgrimes    private static final ClassValue<String> FUNCTIONAL_IFACE_METHOD_NAME = new ClassValue<String>() {
821541Srgrimes        @Override
831541Srgrimes        protected String computeValue(final Class<?> type) {
841541Srgrimes            return findFunctionalInterfaceMethodName(type);
851541Srgrimes        }
865833Sbde    };
875833Sbde
885833Sbde    private final BeansLinker beansLinker = new BeansLinker();
895833Sbde
905833Sbde    @Override
911541Srgrimes    public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
921541Srgrimes        final Object self = linkRequest.getReceiver();
931541Srgrimes        final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
941541Srgrimes        if (self instanceof ConsString) {
951541Srgrimes            // In order to treat ConsString like a java.lang.String we need a link request with a string receiver.
961541Srgrimes            final Object[] arguments = linkRequest.getArguments();
971541Srgrimes            arguments[0] = "";
981541Srgrimes            final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments);
991541Srgrimes            final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices);
1001541Srgrimes            // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings.
1011541Srgrimes            return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING);
1021541Srgrimes        }
103128454Sluigi
104128454Sluigi        if (self != null && NamedOperation.getBaseOperation(desc.getOperation()) == StandardOperation.CALL) {
105128454Sluigi            // Support CALL on any object that supports some @FunctionalInterface
106128454Sluigi            // annotated interface. This way Java method, constructor references or
107128454Sluigi            // implementations of java.util.function.* interfaces can be called as though
1081541Srgrimes            // those are script functions.
1091541Srgrimes            final String name = getFunctionalInterfaceMethodName(self.getClass());
1101541Srgrimes            if (name != null) {
1117197Swollman                final MethodType callType = desc.getMethodType();
1121541Srgrimes                // drop callee (Undefined ScriptFunction) and change the request to be CALL_METHOD:<name>
113122922Sandre                final CallSiteDescriptor newDesc = new CallSiteDescriptor(
114127828Sluigi                        NashornCallSiteDescriptor.getLookupInternal(desc),
115127828Sluigi                        new NamedOperation(StandardOperation.CALL_METHOD, name),
1161541Srgrimes                        desc.getMethodType().dropParameterTypes(1, 2));
1171541Srgrimes                final GuardedInvocation gi = getGuardedInvocation(beansLinker,
1181541Srgrimes                        linkRequest.replaceArguments(newDesc, linkRequest.getArguments()),
1195791Swollman                        new NashornBeansLinkerServices(linkerServices));
120120727Ssam
121120727Ssam                // drop 'thiz' passed from the script.
122120727Ssam                return gi.replaceMethods(
123120727Ssam                    MH.dropArguments(linkerServices.filterInternalObjects(gi.getInvocation()), 1, callType.parameterType(1)),
1241541Srgrimes                    gi.getGuard());
1251541Srgrimes            }
1261541Srgrimes        }
1271541Srgrimes        return getGuardedInvocation(beansLinker, linkRequest, linkerServices);
1281541Srgrimes    }
1291541Srgrimes
1301541Srgrimes    /**
1311541Srgrimes     * Delegates to the specified linker but injects its linker services wrapper so that it will apply all special
1321541Srgrimes     * conversions that this class does.
1331541Srgrimes     * @param delegateLinker the linker to which the actual work is delegated to.
1341541Srgrimes     * @param linkRequest the delegated link request
1351541Srgrimes     * @param linkerServices the original link services that will be augmented with special conversions
1361541Srgrimes     * @return the guarded invocation from the delegate, possibly augmented with special conversions
1371541Srgrimes     * @throws Exception if the delegate throws an exception
1381541Srgrimes     */
1391541Srgrimes    public static GuardedInvocation getGuardedInvocation(final GuardingDynamicLinker delegateLinker, final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
1404104Swollman        return delegateLinker.getGuardedInvocation(linkRequest, new NashornBeansLinkerServices(linkerServices));
1414104Swollman    }
1421541Srgrimes
1431541Srgrimes    @SuppressWarnings("unused")
1441541Srgrimes    private static Object exportArgument(final Object arg) {
1451541Srgrimes        return exportArgument(arg, MIRROR_ALWAYS);
1461541Srgrimes    }
1471541Srgrimes
1481541Srgrimes    static Object exportArgument(final Object arg, final boolean mirrorAlways) {
14986764Sjlemon        if (arg instanceof ConsString) {
1501541Srgrimes            return arg.toString();
1511541Srgrimes        } else if (mirrorAlways && arg instanceof ScriptObject) {
15217835Sjulian            return ScriptUtils.wrap((ScriptObject)arg);
1531541Srgrimes        } else {
1541541Srgrimes            return arg;
1551541Srgrimes        }
1561541Srgrimes    }
1571541Srgrimes
158122921Sandre    @SuppressWarnings("unused")
159122921Sandre    private static Object importResult(final Object arg) {
160122921Sandre        return ScriptUtils.unwrap(arg);
161122921Sandre    }
162122921Sandre
1635099Swollman    @SuppressWarnings("unused")
1645099Swollman    private static Object consStringFilter(final Object arg) {
16518839Swollman        return arg instanceof ConsString ? arg.toString() : arg;
1666245Swollman    }
16715652Swollman
16815652Swollman    private static String findFunctionalInterfaceMethodName(final Class<?> clazz) {
16915652Swollman        if (clazz == null) {
17015652Swollman            return null;
1711541Srgrimes        }
1721541Srgrimes
1731541Srgrimes        for (final Class<?> iface : clazz.getInterfaces()) {
1741541Srgrimes            // check accessibility up-front
1751541Srgrimes            if (! Context.isAccessibleClass(iface)) {
1761541Srgrimes                continue;
1771541Srgrimes            }
1781541Srgrimes
1791541Srgrimes            // check for @FunctionalInterface
1801541Srgrimes            if (iface.isAnnotationPresent(FunctionalInterface.class)) {
1811541Srgrimes                // return the first abstract method
1821541Srgrimes                for (final Method m : iface.getMethods()) {
1831541Srgrimes                    if (Modifier.isAbstract(m.getModifiers())) {
1841541Srgrimes                        return m.getName();
1851541Srgrimes                    }
1861541Srgrimes                }
1871541Srgrimes            }
1881541Srgrimes        }
1891541Srgrimes
1901541Srgrimes        // did not find here, try super class
1911541Srgrimes        return findFunctionalInterfaceMethodName(clazz.getSuperclass());
1921541Srgrimes    }
1931541Srgrimes
1941541Srgrimes    // Returns @FunctionalInterface annotated interface's single abstract
1951541Srgrimes    // method name. If not found, returns null.
1961541Srgrimes    static String getFunctionalInterfaceMethodName(final Class<?> clazz) {
1971541Srgrimes        return FUNCTIONAL_IFACE_METHOD_NAME.get(clazz);
1981541Srgrimes    }
1991541Srgrimes
2005791Swollman    static MethodHandleTransformer createHiddenObjectFilter() {
2011541Srgrimes        return new DefaultInternalObjectFilter(EXPORT_ARGUMENT, MIRROR_ALWAYS ? IMPORT_RESULT : null);
20251252Sru    }
20351252Sru
20451252Sru    private static class NashornBeansLinkerServices implements LinkerServices {
2051541Srgrimes        private final LinkerServices linkerServices;
2061541Srgrimes
2071541Srgrimes        NashornBeansLinkerServices(final LinkerServices linkerServices) {
2081541Srgrimes            this.linkerServices = linkerServices;
2091541Srgrimes        }
2101541Srgrimes
2111541Srgrimes        @Override
2121541Srgrimes        public MethodHandle asType(final MethodHandle handle, final MethodType fromType) {
2131541Srgrimes            return linkerServices.asType(handle, fromType);
2141541Srgrimes        }
2151541Srgrimes
2161541Srgrimes        @Override
2171541Srgrimes        public MethodHandle getTypeConverter(final Class<?> sourceType, final Class<?> targetType) {
2181541Srgrimes            return linkerServices.getTypeConverter(sourceType, targetType);
21921666Swollman        }
22021666Swollman
22189498Sru        @Override
2221541Srgrimes        public boolean canConvert(final Class<?> from, final Class<?> to) {
22351252Sru            return linkerServices.canConvert(from, to);
22451252Sru        }
22551252Sru
2261541Srgrimes        @Override
2271541Srgrimes        public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest) throws Exception {
22851252Sru            return linkerServices.getGuardedInvocation(linkRequest);
2291541Srgrimes        }
2301541Srgrimes
2311541Srgrimes        @Override
2321541Srgrimes        public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) {
2331541Srgrimes            if (sourceType == ConsString.class) {
2341541Srgrimes                if (String.class == targetType1 || CharSequence.class == targetType1) {
2351541Srgrimes                    return Comparison.TYPE_1_BETTER;
23651252Sru                }
2371541Srgrimes
2381541Srgrimes                if (String.class == targetType2 || CharSequence.class == targetType2) {
2391541Srgrimes                    return Comparison.TYPE_2_BETTER;
2401541Srgrimes                }
2411541Srgrimes            }
2421541Srgrimes            return linkerServices.compareConversion(sourceType, targetType1, targetType2);
2431541Srgrimes        }
2441541Srgrimes
2451541Srgrimes        @Override
2461541Srgrimes        public MethodHandle filterInternalObjects(final MethodHandle target) {
2471541Srgrimes            return linkerServices.filterInternalObjects(target);
2481541Srgrimes        }
2491541Srgrimes    }
2501541Srgrimes}
2511541Srgrimes