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.reflect.Method;
016import java.util.*;
017
018import static java.lang.invoke.MethodType.genericMethodType;
019import static java.lang.reflect.Modifier.*;
020import static java.util.Collections.unmodifiableMap;
021import static java.util.Collections.unmodifiableSet;
022
023public final class AdapterDefinition {
024
025  private final ClassLoader classLoader;
026  private final String name;
027  private final String parent;
028  private final TreeSet<String> interfaces = new TreeSet<>();
029  private final LinkedHashMap<String, FunctionReference> implementations = new LinkedHashMap<>();
030  private final LinkedHashMap<String, FunctionReference> overrides = new LinkedHashMap<>();
031
032  public AdapterDefinition(ClassLoader classLoader, String name, String parent) {
033    this.classLoader = classLoader;
034    this.name = name;
035    this.parent = parent;
036  }
037
038  public ClassLoader getClassLoader() {
039    return classLoader;
040  }
041
042  public String getName() {
043    return name;
044  }
045
046  public String getParent() {
047    return parent;
048  }
049
050  public Set<String> getInterfaces() {
051    return unmodifiableSet(interfaces);
052  }
053
054  public Map<String, FunctionReference> getImplementations() {
055    return unmodifiableMap(implementations);
056  }
057
058  public Map<String, FunctionReference> getOverrides() {
059    return unmodifiableMap(overrides);
060  }
061
062  public AdapterDefinition implementsInterface(String iface) {
063    interfaces.add(iface);
064    return this;
065  }
066
067  public AdapterDefinition implementsMethod(String name, FunctionReference target) throws AdapterDefinitionProblem {
068    checkForImplementation(target);
069    checkStarImplementationType(name, target);
070    implementations.put(name, target);
071    return this;
072  }
073
074  public AdapterDefinition overridesMethod(String name, FunctionReference target) throws AdapterDefinitionProblem {
075    checkForOverriding(target);
076    checkStarOverrideType(name, target);
077    overrides.put(name, target);
078    return this;
079  }
080
081  public boolean hasStarImplementation() {
082    return implementations.containsKey("*");
083  }
084
085  public boolean hasStarOverride() {
086    return overrides.containsKey("*");
087  }
088
089  public AdapterDefinition validate() throws AdapterDefinitionProblem {
090    checkSuperTypesExistence();
091    checkStarConflict();
092    checkMethodsToBeImplemented();
093    checkOverridesImplementationsConflict();
094    checkAllOverridesAndImplementationsExist();
095    return this;
096  }
097
098  private void checkOverridesImplementationsConflict() {
099    for (String key : implementations.keySet()) {
100      if (!"*".equals(key) && overrides.containsKey(key)) {
101        throw new AdapterDefinitionProblem("Conflict: there is both an implementation and an override for method " + key);
102      }
103    }
104  }
105
106  private void checkStarImplementationType(String name, FunctionReference target) {
107    if ("*".equals(name) && !target.type().equals(genericMethodType(2))) {
108      throw new AdapterDefinitionProblem("A * implementation must be of type (Object methodName, Object args)Object: " + target);
109    }
110  }
111
112  private void checkStarOverrideType(String name, FunctionReference target) {
113    if ("*".equals(name) && !target.type().equals(genericMethodType(3))) {
114      throw new AdapterDefinitionProblem("A * override must be of type (Object superHandle, Object methodName, Object args)Object: " + target);
115    }
116  }
117
118  private void checkAllOverridesAndImplementationsExist() {
119    try {
120      Class<?> parentClass = Class.forName(parent, true, classLoader);
121      HashSet<String> canBeOverridden = new HashSet<>();
122      for (Method method : parentClass.getMethods()) {
123        if (!isStatic(method.getModifiers())) {
124          canBeOverridden.add(method.getName());
125        }
126      }
127      for (Method method : parentClass.getDeclaredMethods()) {
128        if (!isStatic(method.getModifiers())) {
129          canBeOverridden.add(method.getName());
130        }
131      }
132      for (Method method : parentClass.getMethods()) {
133        if (!isStatic(method.getModifiers())) {
134          canBeOverridden.add(method.getName());
135        }
136      }
137      for (String key : overrides.keySet()) {
138        if (!"*".equals(key) && !canBeOverridden.contains(key)) {
139          throw new AdapterDefinitionProblem("There is no method named " + key + " to be overridden in " + parentClass);
140        }
141      }
142      for (String iface : interfaces) {
143        for (Method method : Class.forName(iface, true, classLoader).getMethods()) {
144          canBeOverridden.add(method.getName());
145        }
146      }
147      for (String key : implementations.keySet()) {
148        if (!"*".equals(key) && !canBeOverridden.contains(key)) {
149          throw new AdapterDefinitionProblem("There is no method named " + key + " to be implemented in " + parentClass + " or interfaces " + interfaces);
150        }
151      }
152    } catch (ClassNotFoundException e) {
153      throw new AdapterDefinitionProblem(e);
154    }
155  }
156
157  private Set<Method> abstractMethodsIn(Class<?> klass) {
158    LinkedHashSet<Method> abstractMethods = new LinkedHashSet<>();
159    for (Method method : klass.getMethods()) {
160      if (isAbstract(method.getModifiers())) {
161        abstractMethods.add(method);
162      }
163    }
164    for (Method method : klass.getDeclaredMethods()) {
165      if (isAbstract(method.getModifiers())) {
166        abstractMethods.add(method);
167      }
168    }
169    return abstractMethods;
170  }
171
172  private void checkMethodsToBeImplemented() {
173    try {
174      LinkedHashSet<Method> abstractMethods = new LinkedHashSet<>();
175      abstractMethods.addAll(abstractMethodsIn(Class.forName(parent, true, classLoader)));
176      for (String iface : interfaces) {
177        abstractMethods.addAll(abstractMethodsIn(Class.forName(iface, true, classLoader)));
178      }
179      for (Method abstractMethod : abstractMethods) {
180        String name = abstractMethod.getName();
181        if (!implementations.containsKey(name) && !hasStarImplementation()) {
182          throw new AdapterDefinitionProblem("There is no implementation or override for: " + abstractMethod);
183        }
184        if (implementations.containsKey(name)) {
185          FunctionReference target = implementations.get(name);
186          if (argsDifferForImplementation(abstractMethod, target) || varargsMismatch(abstractMethod, target)) {
187            throw new AdapterDefinitionProblem("Types do not match to implement " + abstractMethod + " with " + target);
188          }
189        }
190        if (overrides.containsKey(name)) {
191          FunctionReference target = overrides.get(name);
192          if (argsDifferForOverride(abstractMethod, target) || varargsMismatch(abstractMethod, target)) {
193            throw new AdapterDefinitionProblem("Types do not match to implement " + abstractMethod + " with " + target);
194          }
195        }
196      }
197    } catch (ClassNotFoundException e) {
198      throw new AdapterDefinitionProblem(e);
199    }
200  }
201
202  private boolean varargsMismatch(Method abstractMethod, FunctionReference target) {
203    return abstractMethod.isVarArgs() != target.isVarargsCollector();
204  }
205
206  private boolean argsDifferForImplementation(Method abstractMethod, FunctionReference target) {
207    return (target.type().parameterCount() - 1 != abstractMethod.getParameterTypes().length);
208  }
209
210  private boolean argsDifferForOverride(Method abstractMethod, FunctionReference target) {
211    return (target.type().parameterCount() - 2 != abstractMethod.getParameterTypes().length);
212  }
213
214  private void checkStarConflict() {
215    if (hasStarImplementation() && hasStarOverride()) {
216      throw new AdapterDefinitionProblem("Having both a star implementation and a star override is forbidden.");
217    }
218  }
219
220  private void checkSuperTypesExistence() {
221    try {
222      Class<?> parentClass = Class.forName(parent, true, classLoader);
223      if (parentClass.isInterface()) {
224        throw new AdapterDefinitionProblem("The parent class cannot be an interface: " + parentClass.getName());
225      }
226      if (isFinal(parentClass.getModifiers())) {
227        throw new AdapterDefinitionProblem("The parent class is final: " + parentClass.getName());
228      }
229      for (String iface : interfaces) {
230        Class.forName(iface, true, classLoader);
231      }
232    } catch (ClassNotFoundException e) {
233      throw new AdapterDefinitionProblem(e);
234    }
235  }
236
237  private void checkForImplementation(FunctionReference target) throws AdapterDefinitionProblem {
238    if (target.type().parameterCount() < 1) {
239      throw new AdapterDefinitionProblem("An implemented method target must take at least 1 argument (the receiver): " + target);
240    }
241  }
242
243  private void checkForOverriding(FunctionReference target) throws AdapterDefinitionProblem {
244    if (target.type().parameterCount() < 2) {
245      throw new AdapterDefinitionProblem("An overriden method target must take at least 2 arguments (the 'super' function reference followed by the receiver): " + target);
246    }
247  }
248}