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