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}