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 gololang;
012
013import org.eclipse.golo.runtime.TypeMatching;
014import org.eclipse.golo.runtime.adapters.AdapterDefinition;
015import org.eclipse.golo.runtime.adapters.JavaBytecodeAdapterGenerator;
016
017import java.lang.reflect.Constructor;
018import java.util.Arrays;
019import java.util.Map;
020import java.util.concurrent.atomic.AtomicLong;
021
022/**
023 * An adapter fabric can provide instance makers of adapter objects defined at runtime.
024 * <p>
025 * An adapter object inherits from a parent class or {@code java.lang.Object}, implements a set of specified
026 * interfaces, and provides method implementation / overrides defined by Golo closures.
027 * <p>
028 * Adapter instance makers are created based on a configuration that is defined by a simple collections-based
029 * representation where the root is a {@code java.util.Map} instance with keys:
030 * <ul>
031 * <li>{@code extends}: a string for the parent class, {@code java.lang.Object} if not specified,</li>
032 * <li>{@code interfaces}: a {@code java.lang.Iterable} of strings specifying which interfaces to implement,</li>
033 * <li>{@code implements}: points to a map where keys are strings of method names to implement, and values are the
034 * implementation closures, </li>
035 * <li>{@code overrides}: same as a {@code implements} to provides overrides.</li>
036 * </ul>
037 * <p>
038 * The signature for closures is as follows:
039 * <ul>
040 * <li>implementations: {@code |receiver, argument1, argument2|} (and so on),</li>
041 * <li>overrides: {@code |handle_in_superclass, receiver, argument1, argument2|} (and so on).</li>
042 * </ul>
043 * <p>Star implementations or overrides can also be provided, in which case any unimplemented or not overridden method
044 * is dispatched to the provided closure. Note that both a star implementation and a star override cannot be defined.
045 * The signatures are as follows.
046 * <ul>
047 * <li>*-implementation: {@code |method_name, arguments_array|},</li>
048 * <li>*-overrides: {@code |handle_in_superclass, method_name, arguments_array|}.</li>
049 * </ul>
050 * <p>
051 * It is important to note that adapters are useful for interoperability with 3rd-party Java code, as that allow
052 * passing adequate objects from Golo to such libraries. Their usage for pure Golo code is discouraged.
053 */
054public final class AdapterFabric {
055
056  /**
057   * An adapter maker can produce instances of Golo adapter objects.
058   */
059  public static final class Maker {
060
061    private final AdapterDefinition adapterDefinition;
062    private final Class<?> adapterClass;
063
064    private Maker(AdapterDefinition adapterDefinition, Class<?> adapterClass) {
065      this.adapterDefinition = adapterDefinition;
066      this.adapterClass = adapterClass;
067    }
068
069    /**
070     * Creates a new instance, calling the right constructor based on the adapter super class.
071     *
072     * @param args the constructor arguments.
073     * @return an adapter instance.
074     * @throws ReflectiveOperationException thrown when no constructor can be found based on the argument types.
075     */
076    public Object newInstance(Object... args) throws ReflectiveOperationException {
077      Object[] cargs = new Object[args.length + 1];
078      cargs[0] = adapterDefinition;
079      System.arraycopy(args, 0, cargs, 1, args.length);
080      for (Constructor<?> constructor : adapterClass.getConstructors()) {
081        Class<?>[] parameterTypes = constructor.getParameterTypes();
082        if ((cargs.length == parameterTypes.length) || (constructor.isVarArgs() && (cargs.length >= parameterTypes.length))) {
083          if (TypeMatching.canAssign(parameterTypes, cargs, constructor.isVarArgs())) {
084            return constructor.newInstance(cargs);
085          }
086        }
087      }
088      throw new IllegalArgumentException("Could not create an instance for arguments " + Arrays.toString(cargs));
089    }
090  }
091
092  private final ClassLoader classLoader;
093  private final AtomicLong nextId = new AtomicLong();
094  private final JavaBytecodeAdapterGenerator adapterGenerator = new JavaBytecodeAdapterGenerator();
095
096  /**
097   * Makes an adapter fabric using a classloader.
098   *
099   * @param classLoader the classloader to use.
100   */
101  public AdapterFabric(ClassLoader classLoader) {
102    this.classLoader = classLoader;
103  }
104
105  /**
106   * Makes an adapter fabric whose parent is the current thread context classloader.
107   */
108  public AdapterFabric() {
109    this(new ClassLoader(Thread.currentThread().getContextClassLoader()) {
110    });
111  }
112
113  /**
114   * Makes an adapter fabric whose parent classloader is provided.
115   *
116   * @param parentClassLoader the parent classloader.
117   * @return an adapter fabric.
118   */
119  public static AdapterFabric withParentClassLoader(ClassLoader parentClassLoader) {
120    return new AdapterFabric(new ClassLoader(parentClassLoader) {
121    });
122  }
123
124  /**
125   * Provides an instance maker based on an adapter definition.
126   *
127   * @param configuration the adapter configuration.
128   * @return an adapter maker for that configuration.
129   */
130  public Maker maker(Map<String, Object> configuration) {
131    String parent = "java.lang.Object";
132    if (configuration.containsKey("extends")) {
133      parent = (String) configuration.get("extends");
134    }
135    String name = "$Golo$Adapter$" + nextId.getAndIncrement();
136    AdapterDefinition definition = new AdapterDefinition(classLoader, name, parent);
137
138    if (configuration.containsKey("interfaces")) {
139      @SuppressWarnings("unchecked")
140      Iterable<String> interfaces = (Iterable<String>) configuration.get("interfaces");
141      for (String iface : interfaces) {
142        definition.implementsInterface(iface);
143      }
144    }
145    if (configuration.containsKey("implements")) {
146      @SuppressWarnings("unchecked")
147      Map<String, FunctionReference> implementations = (Map<String, FunctionReference>) configuration.get("implements");
148      for (Map.Entry<String, FunctionReference> implementation : implementations.entrySet()) {
149        definition.implementsMethod(implementation.getKey(), implementation.getValue());
150      }
151    }
152    if (configuration.containsKey("overrides")) {
153      @SuppressWarnings("unchecked")
154      Map<String, FunctionReference> overrides = (Map<String, FunctionReference>) configuration.get("overrides");
155      for (Map.Entry<String, FunctionReference> override : overrides.entrySet()) {
156        definition.overridesMethod(override.getKey(), override.getValue());
157      }
158    }
159    definition.validate();
160    Class<?> adapterClass = adapterGenerator.generateIntoDefinitionClassloader(definition);
161    return new Maker(definition, adapterClass);
162  }
163}