NashornBeansLinker.java revision 1477:dd36e980905b
1109998Smarkm/*
2194206Ssimon * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3109998Smarkm * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4109998Smarkm *
5109998Smarkm * This code is free software; you can redistribute it and/or modify it
6238405Sjkim * under the terms of the GNU General Public License version 2 only, as
7109998Smarkm * published by the Free Software Foundation.  Oracle designates this
8109998Smarkm * particular file as subject to the "Classpath" exception as provided
9109998Smarkm * by Oracle in the LICENSE file that accompanied this code.
10109998Smarkm *
11109998Smarkm * This code is distributed in the hope that it will be useful, but WITHOUT
12109998Smarkm * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13109998Smarkm * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14109998Smarkm * version 2 for more details (a copy is included in the LICENSE file that
15109998Smarkm * accompanied this code).
16109998Smarkm *
17109998Smarkm * You should have received a copy of the GNU General Public License version
18109998Smarkm * 2 along with this work; if not, write to the Free Software Foundation,
19109998Smarkm * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20109998Smarkm *
21109998Smarkm * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22109998Smarkm * or visit www.oracle.com if you need additional information or have any
23109998Smarkm * questions.
24109998Smarkm */
25109998Smarkm
26109998Smarkmpackage jdk.nashorn.internal.runtime.linker;
27109998Smarkm
28109998Smarkmimport static jdk.nashorn.internal.lookup.Lookup.MH;
29109998Smarkm
30109998Smarkmimport java.lang.invoke.MethodHandle;
31109998Smarkmimport java.lang.invoke.MethodHandles;
32109998Smarkmimport java.lang.invoke.MethodType;
33109998Smarkmimport java.lang.reflect.Method;
34109998Smarkmimport java.lang.reflect.Modifier;
35109998Smarkmimport jdk.internal.dynalink.CallSiteDescriptor;
36109998Smarkmimport jdk.internal.dynalink.beans.BeansLinker;
37109998Smarkmimport jdk.internal.dynalink.linker.ConversionComparator.Comparison;
38109998Smarkmimport jdk.internal.dynalink.linker.GuardedInvocation;
39109998Smarkmimport jdk.internal.dynalink.linker.GuardingDynamicLinker;
40109998Smarkmimport jdk.internal.dynalink.linker.LinkRequest;
41109998Smarkmimport jdk.internal.dynalink.linker.LinkerServices;
42109998Smarkmimport jdk.internal.dynalink.linker.MethodHandleTransformer;
43109998Smarkmimport jdk.internal.dynalink.linker.support.DefaultInternalObjectFilter;
44109998Smarkmimport jdk.internal.dynalink.linker.support.Lookup;
45109998Smarkmimport jdk.nashorn.api.scripting.ScriptUtils;
46109998Smarkmimport jdk.nashorn.internal.runtime.ConsString;
47109998Smarkmimport jdk.nashorn.internal.runtime.Context;
48109998Smarkmimport jdk.nashorn.internal.runtime.ScriptObject;
49109998Smarkmimport jdk.nashorn.internal.runtime.options.Options;
50109998Smarkm
51109998Smarkm/**
52109998Smarkm * This linker delegates to a {@code BeansLinker} but passes it a special linker services object that has a modified
53109998Smarkm * {@code compareConversion} method that favors conversion of {@link ConsString} to either {@link String} or
54109998Smarkm * {@link CharSequence}. It also provides a {@link #createHiddenObjectFilter()} method for use with bootstrap that will
55109998Smarkm * ensure that we never pass internal engine objects that should not be externally observable (currently ConsString and
56109998Smarkm * ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add this functionality as
57109998Smarkm * custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
5855714Skris * the target method handle parameter signature is {@code Object}. This linker also makes sure that primitive
5955714Skris * {@link String} operations can be invoked on a {@link ConsString}, and allows invocation of objects implementing
6055714Skris * the {@link FunctionalInterface} attribute.
6155714Skris */
6255714Skrispublic class NashornBeansLinker implements GuardingDynamicLinker {
63109998Smarkm    // System property to control whether to wrap ScriptObject->ScriptObjectMirror for
64205128Ssimon    // Object type arguments of Java method calls, field set and array set.
6555714Skris    private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true);
66109998Smarkm
67238405Sjkim    private static final MethodHandle EXPORT_ARGUMENT;
68238405Sjkim    private static final MethodHandle IMPORT_RESULT;
6955714Skris    private static final MethodHandle FILTER_CONSSTRING;
70109998Smarkm
71109998Smarkm    static {
72109998Smarkm        final Lookup lookup  = new Lookup(MethodHandles.lookup());
73238405Sjkim        EXPORT_ARGUMENT      = lookup.findOwnStatic("exportArgument", Object.class, Object.class);
74238405Sjkim        IMPORT_RESULT        = lookup.findOwnStatic("importResult", Object.class, Object.class);
75238405Sjkim        FILTER_CONSSTRING    = lookup.findOwnStatic("consStringFilter", Object.class, Object.class);
76238405Sjkim    }
77238405Sjkim
78109998Smarkm    // cache of @FunctionalInterface method of implementor classes
79109998Smarkm    private static final ClassValue<String> FUNCTIONAL_IFACE_METHOD_NAME = new ClassValue<String>() {
80109998Smarkm        @Override
81238405Sjkim        protected String computeValue(final Class<?> type) {
82109998Smarkm            return findFunctionalInterfaceMethodName(type);
83109998Smarkm        }
8455714Skris    };
8555714Skris
86109998Smarkm    private final BeansLinker beansLinker = new BeansLinker();
87109998Smarkm
88109998Smarkm    @Override
89109998Smarkm    public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
90109998Smarkm        final Object self = linkRequest.getReceiver();
91238405Sjkim        final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
92109998Smarkm        if (self instanceof ConsString) {
93109998Smarkm            // In order to treat ConsString like a java.lang.String we need a link request with a string receiver.
94238405Sjkim            final Object[] arguments = linkRequest.getArguments();
95238405Sjkim            arguments[0] = "";
9655714Skris            final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments);
97109998Smarkm            final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices);
98109998Smarkm            // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings.
99109998Smarkm            return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING);
100109998Smarkm        }
101109998Smarkm
102109998Smarkm        if (self != null && "call".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) {
103109998Smarkm            // Support dyn:call on any object that supports some @FunctionalInterface
104109998Smarkm            // annotated interface. This way Java method, constructor references or
105109998Smarkm            // implementations of java.util.function.* interfaces can be called as though
106109998Smarkm            // those are script functions.
10755714Skris            final String name = getFunctionalInterfaceMethodName(self.getClass());
10855714Skris            if (name != null) {
109109998Smarkm                final MethodType callType = desc.getMethodType();
110109998Smarkm                // drop callee (Undefined ScriptFunction) and change the request to be dyn:callMethod:<name>
111109998Smarkm                final NashornCallSiteDescriptor newDesc = NashornCallSiteDescriptor.get(
112109998Smarkm                        NashornCallSiteDescriptor.getLookupPrivileged(desc), "dyn:callMethod:" + name,
113109998Smarkm                        desc.getMethodType().dropParameterTypes(1, 2),
114109998Smarkm                        NashornCallSiteDescriptor.getFlags(desc));
115109998Smarkm                final GuardedInvocation gi = getGuardedInvocation(beansLinker,
116109998Smarkm                        linkRequest.replaceArguments(newDesc, linkRequest.getArguments()),
11755714Skris                        new NashornBeansLinkerServices(linkerServices));
118109998Smarkm
11955714Skris                // drop 'thiz' passed from the script.
120109998Smarkm                return gi.replaceMethods(
121109998Smarkm                    MH.dropArguments(linkerServices.filterInternalObjects(gi.getInvocation()), 1, callType.parameterType(1)),
122109998Smarkm                    gi.getGuard());
123109998Smarkm            }
124109998Smarkm        }
12555714Skris        return getGuardedInvocation(beansLinker, linkRequest, linkerServices);
126109998Smarkm    }
12755714Skris
128109998Smarkm    /**
129109998Smarkm     * Delegates to the specified linker but injects its linker services wrapper so that it will apply all special
130109998Smarkm     * conversions that this class does.
131109998Smarkm     * @param delegateLinker the linker to which the actual work is delegated to.
132109998Smarkm     * @param linkRequest the delegated link request
13355714Skris     * @param linkerServices the original link services that will be augmented with special conversions
134109998Smarkm     * @return the guarded invocation from the delegate, possibly augmented with special conversions
135109998Smarkm     * @throws Exception if the delegate throws an exception
136109998Smarkm     */
137109998Smarkm    public static GuardedInvocation getGuardedInvocation(final GuardingDynamicLinker delegateLinker, final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
138109998Smarkm        return delegateLinker.getGuardedInvocation(linkRequest, new NashornBeansLinkerServices(linkerServices));
139109998Smarkm    }
14055714Skris
141109998Smarkm    @SuppressWarnings("unused")
142109998Smarkm    private static Object exportArgument(final Object arg) {
143109998Smarkm        return exportArgument(arg, MIRROR_ALWAYS);
144109998Smarkm    }
145109998Smarkm
146109998Smarkm    static Object exportArgument(final Object arg, final boolean mirrorAlways) {
147194206Ssimon        if (arg instanceof ConsString) {
148238405Sjkim            return arg.toString();
149238405Sjkim        } else if (mirrorAlways && arg instanceof ScriptObject) {
150238405Sjkim            return ScriptUtils.wrap((ScriptObject)arg);
151238405Sjkim        } else {
152238405Sjkim            return arg;
153194206Ssimon        }
154194206Ssimon    }
155194206Ssimon
156194206Ssimon    @SuppressWarnings("unused")
157205128Ssimon    private static Object importResult(final Object arg) {
158194206Ssimon        return ScriptUtils.unwrap(arg);
159194206Ssimon    }
160194206Ssimon
161194206Ssimon    @SuppressWarnings("unused")
162194206Ssimon    private static Object consStringFilter(final Object arg) {
163194206Ssimon        return arg instanceof ConsString ? arg.toString() : arg;
164194206Ssimon    }
165194206Ssimon
166194206Ssimon    private static String findFunctionalInterfaceMethodName(final Class<?> clazz) {
167194206Ssimon        if (clazz == null) {
168194206Ssimon            return null;
169194206Ssimon        }
170194206Ssimon
171194206Ssimon        for (final Class<?> iface : clazz.getInterfaces()) {
172194206Ssimon            // check accessibility up-front
173194206Ssimon            if (! Context.isAccessibleClass(iface)) {
174194206Ssimon                continue;
175194206Ssimon            }
176194206Ssimon
177194206Ssimon            // check for @FunctionalInterface
178194206Ssimon            if (iface.isAnnotationPresent(FunctionalInterface.class)) {
179194206Ssimon                // return the first abstract method
180194206Ssimon                for (final Method m : iface.getMethods()) {
181194206Ssimon                    if (Modifier.isAbstract(m.getModifiers())) {
182194206Ssimon                        return m.getName();
183194206Ssimon                    }
184194206Ssimon                }
185194206Ssimon            }
186194206Ssimon        }
187194206Ssimon
188194206Ssimon        // did not find here, try super class
189        return findFunctionalInterfaceMethodName(clazz.getSuperclass());
190    }
191
192    // Returns @FunctionalInterface annotated interface's single abstract
193    // method name. If not found, returns null.
194    static String getFunctionalInterfaceMethodName(final Class<?> clazz) {
195        return FUNCTIONAL_IFACE_METHOD_NAME.get(clazz);
196    }
197
198    static MethodHandleTransformer createHiddenObjectFilter() {
199        return new DefaultInternalObjectFilter(EXPORT_ARGUMENT, MIRROR_ALWAYS ? IMPORT_RESULT : null);
200    }
201
202    private static class NashornBeansLinkerServices implements LinkerServices {
203        private final LinkerServices linkerServices;
204
205        NashornBeansLinkerServices(final LinkerServices linkerServices) {
206            this.linkerServices = linkerServices;
207        }
208
209        @Override
210        public MethodHandle asType(final MethodHandle handle, final MethodType fromType) {
211            return linkerServices.asType(handle, fromType);
212        }
213
214        @Override
215        public MethodHandle getTypeConverter(final Class<?> sourceType, final Class<?> targetType) {
216            return linkerServices.getTypeConverter(sourceType, targetType);
217        }
218
219        @Override
220        public boolean canConvert(final Class<?> from, final Class<?> to) {
221            return linkerServices.canConvert(from, to);
222        }
223
224        @Override
225        public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest) throws Exception {
226            return linkerServices.getGuardedInvocation(linkRequest);
227        }
228
229        @Override
230        public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) {
231            if (sourceType == ConsString.class) {
232                if (String.class == targetType1 || CharSequence.class == targetType1) {
233                    return Comparison.TYPE_1_BETTER;
234                }
235
236                if (String.class == targetType2 || CharSequence.class == targetType2) {
237                    return Comparison.TYPE_2_BETTER;
238                }
239            }
240            return linkerServices.compareConversion(sourceType, targetType1, targetType2);
241        }
242
243        @Override
244        public MethodHandle filterInternalObjects(final MethodHandle target) {
245            return linkerServices.filterInternalObjects(target);
246        }
247    }
248}
249