001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.runtime.adapters;
012
013import gololang.FunctionReference;
014
015import java.lang.invoke.*;
016import java.lang.invoke.MethodHandles.Lookup;
017import java.util.Map;
018
019import static java.lang.invoke.MethodType.genericMethodType;
020
021public final class AdapterSupport {
022
023  private AdapterSupport() {
024    throw new UnsupportedOperationException("Don't instantiate invokedynamic bootstrap class");
025  }
026
027  public static final String DEFINITION_FIELD = "_$_$adapter_$definition";
028
029  private static final MethodHandle FALLBACK;
030
031  static {
032    Lookup lookup = MethodHandles.lookup();
033    try {
034      FALLBACK = lookup.findStatic(AdapterSupport.class, "fallback",
035          MethodType.methodType(Object.class, AdapterCallSite.class, Object[].class));
036    } catch (NoSuchMethodException | IllegalAccessException e) {
037      throw new Error("Could not bootstrap the required method handles", e);
038    }
039  }
040
041  static final class AdapterCallSite extends MutableCallSite {
042
043    final Lookup callerLookup;
044    final String name;
045
046    AdapterCallSite(MethodType type, Lookup callerLookup, String name) {
047      super(type);
048      this.callerLookup = callerLookup;
049      this.name = name;
050    }
051  }
052
053  public static CallSite bootstrap(Lookup caller, String name, MethodType type) {
054    AdapterCallSite callSite = new AdapterCallSite(type, caller, name);
055    MethodHandle fallbackHandle = FALLBACK
056        .bindTo(callSite)
057        .asCollector(Object[].class, type.parameterCount())
058        .asType(type);
059    callSite.setTarget(fallbackHandle);
060    return callSite;
061  }
062
063  public static Object fallback(AdapterCallSite callSite, Object[] args) throws Throwable {
064    Class<?> receiverClass = args[0].getClass();
065    Class<?> receiverParentClass = receiverClass.getSuperclass();
066    AdapterDefinition definition = (AdapterDefinition) receiverClass.getField(DEFINITION_FIELD).get(args[0]);
067    Map<String, FunctionReference> implementations = definition.getImplementations();
068    MethodHandle target = null;
069    if (implementations.containsKey(callSite.name)) {
070      target = implementations.get(callSite.name).handle();
071    }
072    if (target == null) {
073      if (implementations.containsKey("*")) {
074        target = implementations.get("*").handle();
075        target = target.bindTo(callSite.name).asCollector(Object[].class, args.length);
076      }
077    }
078    if (target == null) {
079      Map<String, FunctionReference> overrides = definition.getOverrides();
080      MethodHandle superTarget = callSite.callerLookup.findSpecial(receiverParentClass,
081          callSite.name, callSite.type().dropParameterTypes(0, 1), receiverClass);
082      if (superTarget.isVarargsCollector()) {
083        superTarget = superTarget.asType(genericMethodType(superTarget.type().parameterCount() - 1, true))
084          .asVarargsCollector(Object[].class);
085      } else {
086        superTarget = superTarget.asType(genericMethodType(superTarget.type().parameterCount()));
087      }
088      if (overrides.containsKey(callSite.name)) {
089        target = overrides.get(callSite.name).handle();
090      }
091      boolean star = false;
092      if (target == null) {
093        if (overrides.containsKey("*")) {
094          target = overrides.get("*").handle();
095        }
096        star = true;
097      }
098      if (target == null) {
099        target = superTarget;
100      } else {
101        target = target.bindTo(new FunctionReference(superTarget));
102        if (star) {
103          target = target.bindTo(callSite.name);
104          target = target.asCollector(Object[].class, args.length);
105        }
106      }
107    }
108    callSite.setTarget(target.asType(callSite.type()));
109    return target.invokeWithArguments(args);
110  }
111}