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