From 44a7d625fdc1a298f32168304b259b8674b5b06c Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Mon, 23 Oct 2017 21:16:18 +0200
Subject: [PATCH 1/4] feature(test): SpoonMetaModel
---
.../java/spoon/metamodel/MMContainerType.java | 60 ++
src/test/java/spoon/metamodel/MMField.java | 519 ++++++++++++++++++
.../java/spoon/metamodel/MMMethodKind.java | 115 ++++
src/test/java/spoon/metamodel/MMType.java | 144 +++++
src/test/java/spoon/metamodel/MMTypeKind.java | 33 ++
src/test/java/spoon/metamodel/MMethod.java | 110 ++++
.../java/spoon/metamodel/SpoonMetaModel.java | 506 +++++++++++++++++
7 files changed, 1487 insertions(+)
create mode 100644 src/test/java/spoon/metamodel/MMContainerType.java
create mode 100644 src/test/java/spoon/metamodel/MMField.java
create mode 100644 src/test/java/spoon/metamodel/MMMethodKind.java
create mode 100644 src/test/java/spoon/metamodel/MMType.java
create mode 100644 src/test/java/spoon/metamodel/MMTypeKind.java
create mode 100644 src/test/java/spoon/metamodel/MMethod.java
create mode 100644 src/test/java/spoon/metamodel/SpoonMetaModel.java
diff --git a/src/test/java/spoon/metamodel/MMContainerType.java b/src/test/java/spoon/metamodel/MMContainerType.java
new file mode 100644
index 00000000000..d77415b9fab
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMContainerType.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents type of container used for field value.
+ */
+public enum MMContainerType {
+ /**
+ * it is a single value field
+ * Example: CtClassImpl.simpleName
+ */
+ SINGLE,
+ /**
+ * It is a list of values
+ * Example: CtClassImpl.typeMembers
+ */
+ LIST,
+ /**
+ * It is a set of values
+ * Example: CtPackageImpl.types
+ */
+ SET,
+ /**
+ * It is a map<String, T> of values
+ * Example: CtAnnotationImpl.elementValues
+ */
+ MAP;
+
+ public static MMContainerType valueOf(Class> valueClass) {
+ if (List.class.isAssignableFrom(valueClass)) {
+ return LIST;
+ }
+ if (Map.class.isAssignableFrom(valueClass)) {
+ return MAP;
+ }
+ if (Set.class.isAssignableFrom(valueClass)) {
+ return SET;
+ }
+ return SINGLE;
+ }
+}
diff --git a/src/test/java/spoon/metamodel/MMField.java b/src/test/java/spoon/metamodel/MMField.java
new file mode 100644
index 00000000000..e2eedfa3f7a
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMField.java
@@ -0,0 +1,519 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import spoon.SpoonException;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.factory.Factory;
+import spoon.reflect.path.CtRole;
+import spoon.reflect.reference.CtTypeParameterReference;
+import spoon.reflect.reference.CtTypeReference;
+import spoon.support.DerivedProperty;
+
+import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
+import static spoon.metamodel.SpoonMetaModel.getOrCreate;
+
+/**
+ * Represents a field of Spoon model type.
+ * Each MMField belongs to one MMType
+ */
+public class MMField {
+ /**
+ * Name of the field
+ */
+ private final String name;
+ /**
+ * {@link CtRole} of the field
+ */
+ private final CtRole role;
+ /**
+ * The list of {@link MMType}s which contains this field
+ */
+ private final MMType ownerType;
+ /**
+ * Type of value container [single, list, set, map]
+ */
+ private MMContainerType valueContainerType;
+ /**
+ * The type of value of this field - can be Set, List, Map or any non collection type
+ */
+ private CtTypeReference> valueType;
+ /**
+ * The item type of value of this field - can be non collection type
+ */
+ private CtTypeReference> itemValueType;
+
+ private Boolean derived;
+
+ private Map> methodsByKind;
+
+ /**
+ * methods of this field defined directly on ownerType.
+ * There is PropertyGetter or PropertySetter annotation with `role` of this {@link MMField}
+ */
+ private final List roleMethods = new ArrayList<>();
+ /**
+ * methods of this field grouped by signature defined directly on ownerType.
+ * There is PropertyGetter or PropertySetter annotation with `role` of this {@link MMField}
+ * note: There can be up to 2 methods in this list. 1) declaration from interface, 2) implementation from class
+ */
+ private final Map roleMethodsBySignature = new HashMap<>();
+ /**
+ * List of fields with same `role`, from super type of `ownerType` {@link MMType}
+ */
+ private final List superFields = new ArrayList<>();
+
+ private Set unhandledSignatures;
+ private List ambiguousMethodKinds = new ArrayList<>();
+
+
+ MMField(String name, CtRole role, MMType ownerType) {
+ super();
+ this.name = name;
+ this.role = role;
+ this.ownerType = ownerType;
+ }
+
+ void addMethod(CtMethod> method) {
+ MMethod mmMethod = getOwnMethod(method, true);
+ mmMethod.ownMethods.add(method);
+ }
+
+ MMethod getOwnMethod(CtMethod> method, boolean createIfNotExist) {
+ for (MMethod mmMethod : roleMethods) {
+ if (mmMethod.overrides(method)) {
+ return mmMethod;
+ }
+ }
+ if (createIfNotExist) {
+ MMethod mmMethod = new MMethod(this, method);
+ roleMethods.add(mmMethod);
+ MMethod conflict = roleMethodsBySignature.put(mmMethod.getSignature(), mmMethod);
+ if (conflict != null) {
+ throw new SpoonException("Conflict on " + getOwnerType().getName() + "." + name + " method signature: " + mmMethod.getSignature());
+ }
+ return mmMethod;
+ }
+ return null;
+ }
+
+ void addSuperField(MMField superMMField) {
+ if (addUniqueObject(superFields, superMMField)) {
+ for (MMethod superMethod : superMMField.getRoleMethods()) {
+ getOwnMethod(superMethod.getMethodOf(getOwnerType()), true).addSuperMethod(superMethod);
+ }
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public CtRole getRole() {
+ return role;
+ }
+
+ public MMType getOwnerType() {
+ return ownerType;
+ }
+
+ public MMContainerType getValueContainerType() {
+ return valueContainerType;
+ }
+
+ public CtTypeReference> getValueType() {
+ if (valueType == null) {
+ throw new SpoonException("Model is not initialized yet");
+ }
+ return valueType;
+ }
+
+ CtTypeReference> detectValueType() {
+ MMethod mmGetMethod = getMethod(MMMethodKind.GET);
+ if (mmGetMethod == null) {
+ throw new SpoonException("No getter exists for " + getOwnerType().getName() + "." + getName());
+ }
+ MMethod mmSetMethod = getMethod(MMMethodKind.SET);
+ if (mmSetMethod == null) {
+ return mmGetMethod.getType();
+ }
+ CtTypeReference> getterValueType = mmGetMethod.getType();
+ CtTypeReference> setterValueType = mmSetMethod.getValueType();
+ if (getterValueType.equals(setterValueType)) {
+ return mmGetMethod.getType();
+ }
+ if (MMContainerType.valueOf(getterValueType.getActualClass()) != MMContainerType.SINGLE) {
+ getterValueType = getItemValueType(getterValueType);
+ setterValueType = getItemValueType(setterValueType);
+ }
+ if (getterValueType.equals(setterValueType)) {
+ return mmGetMethod.getType();
+ }
+ if (getterValueType.isSubtypeOf(setterValueType)) {
+ /*
+ * Getter and setter have different type
+ * For example:
+ * CtBlock CtCatch#getBody
+ * and
+ * CtCatch#setBody(CtStatement)
+ * In current metamodel we take type of setter to keep it simple
+ */
+ return mmSetMethod.getValueType();
+ }
+ throw new SpoonException("Incompatible getter and setter for " + getOwnerType().getName() + "." + getName());
+ }
+
+ void setValueType(CtTypeReference> valueType) {
+ Factory f = valueType.getFactory();
+ if (valueType instanceof CtTypeParameterReference) {
+ valueType = ((CtTypeParameterReference) valueType).getBoundingType();
+ if (valueType == null) {
+ valueType = f.Type().OBJECT;
+ }
+ }
+
+ this.valueType = valueType;
+ this.valueContainerType = MMContainerType.valueOf(valueType.getActualClass());
+ if (valueContainerType != MMContainerType.SINGLE) {
+ itemValueType = getItemValueType(valueType);
+ } else {
+ itemValueType = valueType;
+ }
+ }
+
+ public CtTypeReference> getItemValueType() {
+ if (itemValueType == null) {
+ getValueType();
+ }
+ return itemValueType;
+ }
+ public void setItemValueType(CtTypeReference> itemValueType) {
+ this.itemValueType = itemValueType;
+ }
+
+ public MMethod getMethod(MMMethodKind kind) {
+ List ms = getMethods(kind);
+ return ms.size() > 0 ? ms.get(0) : null;
+ }
+
+ public List getMethods(MMMethodKind kind) {
+ if (methodsByKind == null) {
+ throw new SpoonException("Metamodel is not initialized yet");
+ }
+ List ms = methodsByKind.get(kind);
+ return ms == null ? Collections.emptyList() : Collections.unmodifiableList(ms);
+ }
+
+ public Set getUnhandledSignatures() {
+ if (unhandledSignatures == null) {
+ throw new SpoonException("Model is not initialized yet");
+ }
+ return unhandledSignatures;
+ }
+
+ void groupMethodsByKind() {
+ methodsByKind = new HashMap<>();
+ unhandledSignatures = new HashSet<>();
+
+ for (MMethod mmMethod : roleMethods) {
+ MMMethodKind k = MMMethodKind.valueOf(mmMethod.getMethod());
+ if (k == MMMethodKind.OTHER) {
+ unhandledSignatures.add(mmMethod.getSignature());
+ continue;
+ }
+ getOrCreate(methodsByKind, k, () -> new ArrayList<>()).add(mmMethod);
+ }
+ }
+
+ void sortByBestMatch() {
+ //resolve conflicts using value type. Move the most matching method to 0 index
+ //in order GET, SET and others
+ for (MMMethodKind mk : MMMethodKind.values()) {
+ sortByBestMatch(mk);
+ }
+ }
+
+ void sortByBestMatch(MMMethodKind key) {
+ List methods = methodsByKind.get(key);
+ if (methods != null && methods.size() > 1) {
+ int idx = getIdxOfBestMatch(methods, key);
+ if (idx >= 0) {
+ if (idx > 0) {
+ //move the matching to the beginning
+ methods.add(0, methods.remove(idx));
+ }
+ //add next items as unhandled
+ methods.subList(1, methods.size()).forEach(mmMethod -> unhandledSignatures.add(mmMethod.getSignature()));
+ } else {
+ //add all methods as ambiguous
+ ambiguousMethodKinds.add(key);
+ }
+ }
+ }
+
+ /**
+ * @param methods
+ * @param key
+ * @return index of the method which best matches the `key` accessor of this field
+ * -1 if it cannot be resolved
+ */
+ private int getIdxOfBestMatch(List methods, MMMethodKind key) {
+ MMethod mmMethod = methods.get(0);
+ if (mmMethod.getMethod().getParameters().size() == 0) {
+ return getIdxOfBestMatchByReturnType(methods, key);
+ } else {
+ MMethod mmGetMethod = getMethod(MMMethodKind.GET);
+ if (mmGetMethod == null) {
+ //we have no getter so we do not know the expected value type. Setters are ambiguous
+ return -1;
+ }
+ return getIdxOfBestMatchByInputParameter(methods, key, mmGetMethod.getType());
+ }
+ }
+
+ private int getIdxOfBestMatchByReturnType(List methods, MMMethodKind key) {
+ if (methods.size() > 2) {
+ throw new SpoonException("Resolving of more then 2 conflicting getters is not supported. There are: " + methods.toString());
+ }
+ // There is no input parameter. We are resolving getter field.
+ // choose the getter whose return value is a collection
+ // of second one
+ CtTypeReference> returnType1 = methods.get(0).getMethod().getType();
+ CtTypeReference> returnType2 = methods.get(1).getMethod().getType();
+ Factory f = returnType1.getFactory();
+ boolean is1Iterable = returnType1.isSubtypeOf(f.Type().ITERABLE);
+ boolean is2Iterable = returnType2.isSubtypeOf(f.Type().ITERABLE);
+ if (is1Iterable != is2Iterable) {
+ // they are not some. Only one of them is iterable
+ if (is1Iterable) {
+ if (isIterableOf(returnType1, returnType2)) {
+ // use 1st method, which is multivalue
+ // representation of 2nd method
+ return 0;
+ }
+ } else {
+ if (isIterableOf(returnType2, returnType1)) {
+ // use 2nd method, which is multivalue
+ // representation of 1st method
+ return 1;
+ }
+ }
+ }
+ // else report ambiguity
+ return -1;
+ }
+
+ /**
+ * @return true if item type of `iterableType` is super type of `itemType`
+ */
+ private boolean isIterableOf(CtTypeReference> iterableType, CtTypeReference> itemType) {
+ CtTypeReference> iterableItemType = getItemValueType(iterableType);
+ if (iterableItemType != null) {
+ return itemType.isSubtypeOf(iterableItemType);
+ }
+ return false;
+ }
+
+ private int getIdxOfBestMatchByInputParameter(List methods, MMMethodKind key, CtTypeReference> expectedValueType) {
+ int idx = -1;
+ MatchLevel maxMatchLevel = null;
+ CtTypeReference> newValueType = null;
+ if (key.isMulti()) {
+ expectedValueType = getItemValueType(expectedValueType);
+ }
+
+ for (int i = 0; i < methods.size(); i++) {
+ MMethod mMethod = methods.get(i);
+ MatchLevel matchLevel = getMatchLevel(expectedValueType, mMethod.getValueType());
+ if (matchLevel != null) {
+ //it is matching
+ if (idx == -1) {
+ idx = i;
+ maxMatchLevel = matchLevel;
+ newValueType = mMethod.getValueType();
+ } else {
+ //both methods have matching value type. Use the better match
+ if (maxMatchLevel.ordinal() < matchLevel.ordinal()) {
+ idx = i;
+ maxMatchLevel = matchLevel;
+ newValueType = mMethod.getValueType();
+ } else if (maxMatchLevel == matchLevel) {
+ //there is conflict
+ return -1;
+ } //else OK, we already have better match
+ }
+ }
+ }
+ return idx;
+ }
+
+ private static CtTypeReference> getItemValueType(CtTypeReference> valueType) {
+ MMContainerType valueContainerType = MMContainerType.valueOf(valueType.getActualClass());
+ if (valueContainerType == MMContainerType.SINGLE) {
+ return null;
+ }
+ CtTypeReference> itemValueType;
+ if (valueContainerType == MMContainerType.MAP) {
+ if (String.class.getName().equals(valueType.getActualTypeArguments().get(0).getQualifiedName()) == false) {
+ throw new SpoonException("Unexpected container of type: " + valueType.toString());
+ }
+ itemValueType = valueType.getActualTypeArguments().get(1);
+ } else {
+ //List or Set
+ itemValueType = valueType.getActualTypeArguments().get(0);
+ }
+ if (itemValueType instanceof CtTypeParameterReference) {
+ itemValueType = ((CtTypeParameterReference) itemValueType).getBoundingType();
+ if (itemValueType == null) {
+ itemValueType = valueType.getFactory().Type().OBJECT;
+ }
+ }
+ return itemValueType;
+ }
+
+ private enum MatchLevel {
+ SUBTYPE,
+ ERASED_EQUALS,
+ EQUALS
+ }
+
+ /**
+ * Checks whether expectedType and realType are matching.
+ *
+ * @param expectedType
+ * @param realType
+ * @return new expectedType or null if it is not matching
+ */
+ private MatchLevel getMatchLevel(CtTypeReference> expectedType, CtTypeReference> realType) {
+ if (expectedType.equals(realType)) {
+ return MatchLevel.EQUALS;
+ }
+ if (expectedType.getTypeErasure().equals(realType.getTypeErasure())) {
+ return MatchLevel.ERASED_EQUALS;
+ }
+ if (expectedType.isSubtypeOf(realType)) {
+ /*
+ * CtFieldReference CtFieldAccess#getVariable() CtFieldAccess
+ * inherits from CtVariableAccess which has
+ * #setVariable(CtVariableReference) it is OK to use expected
+ * type CtFieldReference, when setter has CtVariableReference
+ */
+ return MatchLevel.SUBTYPE;
+ }
+ return null;
+ }
+
+ /**
+ * @param valueType whose Map value type is needed
+ * @return Map value type If valueType is an Map. null if it is not
+ */
+ private CtTypeReference> getMapValueType(CtTypeReference> valueType) {
+ if (valueType != null) {
+ Factory f = valueType.getFactory();
+ if (valueType.isSubtypeOf(f.Type().MAP) && valueType.getActualTypeArguments().size() == 2) {
+ return valueType.getActualTypeArguments().get(1);
+ }
+ }
+ return null;
+ }
+
+ public boolean isDerived() {
+ if (derived == null) {
+ //if DerivedProperty is found on any getter of this type, then this field is derived
+ MMethod getter = getMethod(MMMethodKind.GET);
+ if (getter == null) {
+ throw new SpoonException("No getter defined for " + this);
+ }
+ CtTypeReference derivedProperty = getter.getMethod().getFactory().createCtTypeReference(DerivedProperty.class);
+
+ boolean isConreteMethod = false;
+ for (CtMethod> ctMethod : getter.getOwnMethods()) {
+ if (ctMethod.getAnnotation(derivedProperty) != null) {
+ derived = Boolean.TRUE;
+ return true;
+ }
+ isConreteMethod = isConreteMethod || ctMethod.getBody() != null;
+ }
+ if (isConreteMethod) {
+ //there exists a implementation of getter for this field in this type and there is no DerivedProperty here, so it is not derived!
+ derived = Boolean.FALSE;
+ return false;
+ }
+ //inherit derived property from super type
+ //if DerivedProperty annotation is not found on any get method, then it is not derived
+ derived = Boolean.FALSE;
+ //check all super fields. If any of them is derived then this field is derived too
+ for (MMField superField : superFields) {
+ if (superField.isDerived()) {
+ derived = Boolean.TRUE;
+ break;
+ }
+ }
+ }
+ return derived;
+ }
+
+ public List getRoleMethods() {
+ return Collections.unmodifiableList(roleMethods);
+ }
+
+ public Map getRoleMethodsBySignature() {
+ return Collections.unmodifiableMap(roleMethodsBySignature);
+ }
+
+ public List getSuperFields() {
+ return Collections.unmodifiableList(superFields);
+ }
+
+ @Override
+ public String toString() {
+ return ownerType.getName() + "#" + getName() + "<" + valueType + ">";
+ }
+
+ /**
+ * @return the super MMField which has same valueType and which is in root of the most implementations
+ */
+ public MMField getRootSuperField() {
+ List potentialRootSuperFields = new ArrayList<>();
+ if (roleMethods.size() > 0) {
+ potentialRootSuperFields.add(this);
+ }
+ superFields.forEach(superField -> {
+ addUniqueObject(potentialRootSuperFields, superField.getRootSuperField());
+ });
+ int idx = 0;
+ if (potentialRootSuperFields.size() > 1) {
+ CtTypeReference> expectedValueType = this.getValueType().getTypeErasure();
+ for (int i = 1; i < potentialRootSuperFields.size(); i++) {
+ MMField superField = potentialRootSuperFields.get(i);
+ if (superField.getValueType().getTypeErasure().equals(expectedValueType) == false) {
+ break;
+ }
+ idx = i;
+ }
+ }
+ return potentialRootSuperFields.get(idx);
+ }
+}
diff --git a/src/test/java/spoon/metamodel/MMMethodKind.java b/src/test/java/spoon/metamodel/MMMethodKind.java
new file mode 100644
index 00000000000..7bc90a7bb5f
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMMethodKind.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.function.Predicate;
+
+import spoon.SpoonException;
+import spoon.reflect.declaration.CtMethod;
+
+/**
+ * Represents type of value accessor
+ */
+public enum MMMethodKind {
+ /**
+ * Getter.
+ * T get()
+ */
+ GET(false, 1, m -> m.getParameters().size() == 0 && (m.getSimpleName().startsWith("get") || m.getSimpleName().startsWith("is"))),
+ /**
+ * Setter
+ * void set(T)
+ */
+ SET(false, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("set")),
+ /**
+ * void addFirst(T)
+ */
+ ADD_FIRST(true, 10, m -> {
+ if (m.getParameters().size() == 1) {
+ if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
+ if (m.getSimpleName().endsWith("AtTop") || m.getSimpleName().endsWith("Begin")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }),
+ /**
+ * void add(T)
+ */
+ ADD_LAST(true, 1, m -> {
+ if (m.getParameters().size() == 1) {
+ if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
+ return true;
+ }
+ }
+ return false;
+ }),
+ /**
+ * void addOn(int, T)
+ */
+ ADD_ON(true, 1, m -> {
+ if (m.getParameters().size() == 2 && m.getParameters().get(0).getType().getSimpleName().equals("int")) {
+ if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
+ return true;
+ }
+ }
+ return false;
+ }),
+ /**
+ * void remove(T)
+ */
+ REMOVE(true, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("remove")),
+
+ OTHER(false, 0, m -> true);
+
+ private final Predicate> detector;
+ private final int level;
+ private final boolean multi;
+
+ MMMethodKind(boolean multi, int level, Predicate> detector) {
+ this.multi = multi;
+ this.level = level;
+ this.detector = detector;
+ }
+
+ /**
+ * @return true if this accessor provides access to elements of an collection.
+ * false if it accessed full value of attribute
+ */
+ public boolean isMulti() {
+ return multi;
+ }
+
+ /**
+ * Detect kind of method
+ * @param method to be check method
+ * @return detected {@link MMMethodKind}, which fits to the `method`
+ */
+ public static MMMethodKind valueOf(CtMethod> method) {
+ MMMethodKind result = OTHER;
+ for (MMMethodKind k : values()) {
+ if (k.detector.test(method) && result.level < k.level) {
+ if (result.level == k.level) {
+ throw new SpoonException("Ambiguous method kinds " + result.name() + " X " + k.name() + " for method " + method.getSignature());
+ }
+ result = k;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/spoon/metamodel/MMType.java b/src/test/java/spoon/metamodel/MMType.java
new file mode 100644
index 00000000000..8c173d2f8b9
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMType.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import spoon.SpoonException;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtInterface;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.declaration.ModifierKind;
+import spoon.reflect.path.CtRole;
+import spoon.support.visitor.ClassTypingContext;
+
+import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
+
+/**
+ * Represents a type of Spoon model class
+ */
+public class MMType {
+ /**
+ * Kind of this type
+ */
+ MMTypeKind kind;
+ /**
+ * Name of the type
+ */
+ String name;
+ /**
+ * List of fields ordered same like CtScanner scans them
+ */
+ final Map role2field = new LinkedHashMap<>();
+
+ /**
+ * List of super types of this type
+ */
+ private final List superTypes = new ArrayList<>();
+ private final List subTypes = new ArrayList<>();
+
+ /**
+ * The {@link CtClass} linked to this {@link MMType}. Is null in case of class without interface
+ */
+ private CtClass> modelClass;
+ /**
+ * The {@link CtInterface} linked to this {@link MMType}. Is null in case of interface without class
+ */
+ private CtInterface> modelInteface;
+
+ private ClassTypingContext typeContext;
+
+ /**
+ * own methods of MMType, which does not belong to any role
+ */
+ final List> otherMethods = new ArrayList<>();
+
+ MMType() {
+ super();
+ }
+
+ MMField getOrCreateMMField(CtRole role) {
+ return SpoonMetaModel.getOrCreate(role2field, role, () -> new MMField(role.getCamelCaseName(), role, this));
+ }
+
+ public MMTypeKind getKind() {
+ if (kind == null) {
+ if (modelClass != null && modelClass.hasModifier(ModifierKind.ABSTRACT) == false) {
+ kind = MMTypeKind.LEAF;
+ } else {
+ kind = MMTypeKind.ABSTRACT;
+ }
+ }
+ return kind;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map getRole2field() {
+ return role2field;
+ }
+
+ public List getSuperTypes() {
+ return superTypes;
+ }
+
+ void addSuperType(MMType superType) {
+ if (superType == this) {
+ throw new SpoonException("Cannot add supertype to itself");
+ }
+ if (addUniqueObject(superTypes, superType)) {
+ superType.subTypes.add(this);
+ superType.role2field.forEach((role, superMMField) -> {
+ MMField mmField = getOrCreateMMField(role);
+ mmField.addSuperField(superMMField);
+ });
+ }
+ }
+
+ public CtClass> getModelClass() {
+ return modelClass;
+ }
+
+ void setModelClass(CtClass> modelClass) {
+ this.modelClass = modelClass;
+ }
+
+ public CtInterface> getModelInteface() {
+ return modelInteface;
+ }
+
+ void setModelInteface(CtInterface> modelInteface) {
+ this.modelInteface = modelInteface;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ public ClassTypingContext getTypeContext() {
+ if (typeContext == null) {
+ typeContext = new ClassTypingContext(modelClass != null ? modelClass : modelInteface);
+ }
+ return typeContext;
+ }
+}
diff --git a/src/test/java/spoon/metamodel/MMTypeKind.java b/src/test/java/spoon/metamodel/MMTypeKind.java
new file mode 100644
index 00000000000..aab982941a0
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMTypeKind.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+/**
+ * Represents type of Spoon model class
+ */
+public enum MMTypeKind {
+ /**
+ * Kind of type which represents leaf of Spoon model.
+ * Examples: CtClass, CtField, CtThrow
+ */
+ LEAF,
+ /**
+ * Kind of type which represents some abstract concept of Spoon model
+ * Examples: CtExecutable, CtReference, CtBodyHolder, ...
+ */
+ ABSTRACT;
+}
diff --git a/src/test/java/spoon/metamodel/MMethod.java b/src/test/java/spoon/metamodel/MMethod.java
new file mode 100644
index 00000000000..cb2ae3cc21b
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMethod.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.reference.CtTypeReference;
+import spoon.support.visitor.MethodTypingContext;
+
+import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
+
+/**
+ * Represents a method of a {@link MMField} of a {@link MMType}.
+ * Each MMMethod belongs to one MMField
+ */
+public class MMethod {
+ final MMField field;
+ final CtMethod> method;
+ final List> ownMethods = new ArrayList<>();
+ final List superMethods = new ArrayList<>();
+ final String signature;
+
+ MMethod(MMField field, CtMethod> method) {
+ this.field = field;
+ //adapt method to scope of field.ownType
+ MethodTypingContext mtc = new MethodTypingContext().setClassTypingContext(field.getOwnerType().getTypeContext()).setMethod(method);
+ this.method = (CtMethod>) mtc.getAdaptationScope();
+ signature = this.method.getSignature();
+ }
+
+ public CtMethod> getMethod() {
+ return method;
+ }
+
+ public CtMethod> getMethodOf(MMType targetType) {
+ for (CtMethod> ctMethod : ownMethods) {
+ if (targetType.getTypeContext().isSubtypeOf(ctMethod.getDeclaringType().getReference())) {
+ return ctMethod;
+ }
+ }
+ for (MMethod mmMethod : superMethods) {
+ CtMethod> m = mmMethod.getMethodOf(targetType);
+ if (m != null) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ boolean overrides(CtMethod> method) {
+ return field.getOwnerType().getTypeContext().isOverriding(this.method, method);
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public void addSuperMethod(MMethod superMethod) {
+ addUniqueObject(superMethods, superMethod);
+ }
+
+ public MMField getField() {
+ return field;
+ }
+
+ public List> getOwnMethods() {
+ return Collections.unmodifiableList(ownMethods);
+ }
+
+ public List getSuperMethods() {
+ return Collections.unmodifiableList(superMethods);
+ }
+
+ public String getSimpleName() {
+ return method.getSimpleName();
+ }
+
+ public CtTypeReference> getType() {
+ return method.getType();
+ }
+
+ @Override
+ public String toString() {
+ return getField().getOwnerType().getName() + "#" + getSignature();
+ }
+
+ public CtTypeReference> getValueType() {
+ if (method.getParameters().isEmpty()) {
+ return method.getType();
+ }
+ return method.getParameters().get(method.getParameters().size() - 1).getType();
+ }
+}
diff --git a/src/test/java/spoon/metamodel/SpoonMetaModel.java b/src/test/java/spoon/metamodel/SpoonMetaModel.java
new file mode 100644
index 00000000000..a5bf3b8d67f
--- /dev/null
+++ b/src/test/java/spoon/metamodel/SpoonMetaModel.java
@@ -0,0 +1,506 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import spoon.Launcher;
+import spoon.SpoonException;
+import spoon.reflect.annotations.PropertyGetter;
+import spoon.reflect.annotations.PropertySetter;
+import spoon.reflect.code.CtStatement;
+import spoon.reflect.declaration.CtAnnotation;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtInterface;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.declaration.CtType;
+import spoon.reflect.factory.Factory;
+import spoon.reflect.path.CtRole;
+import spoon.reflect.reference.CtTypeReference;
+import spoon.reflect.visitor.PrinterHelper;
+import spoon.reflect.visitor.filter.AllTypeMembersFunction;
+import spoon.reflect.visitor.filter.TypeFilter;
+import spoon.support.compiler.FileSystemFolder;
+import spoon.support.visitor.ClassTypingContext;
+
+/**
+ * Represents a Spoon meta model
+ */
+public class SpoonMetaModel {
+ public static final String CLASS_SUFFIX = "Impl";
+ /**
+ * qualified names of packages which contains interfaces of spoon model
+ */
+ public static final Set MODEL_IFACE_PACKAGES = new HashSet<>(Arrays.asList(
+ "spoon.reflect.code",
+ "spoon.reflect.declaration",
+ "spoon.reflect.reference"));
+
+ private PrinterHelper problems;
+
+ /**
+ * {@link MMType}s by name
+ */
+ private final Map name2mmType = new HashMap<>();
+
+ private final CtTypeReference> statementRef;
+ private final CtTypeReference> iterableRef;
+ private final CtTypeReference> mapRef;
+
+ /**
+ * Parses spoon sources and creates factory with spoon model.
+ *
+ * @param spoonJavaSourcesDirectory the root directory of java sources of spoon model.
+ * The directory must contain "spoon" directory.
+ */
+ public SpoonMetaModel(File spoonJavaSourcesDirectory) {
+ this(createFactory(spoonJavaSourcesDirectory));
+ }
+
+ /**
+ * @param factory already loaded factory with all Spoon model types
+ */
+ public SpoonMetaModel(Factory factory) {
+ statementRef = factory.createCtTypeReference(CtStatement.class);
+ iterableRef = factory.createCtTypeReference(Iterable.class);
+ mapRef = factory.createCtTypeReference(Map.class);
+ problems = new PrinterHelper(factory.getEnvironment());
+
+ factory.getModel().getRootPackage().filterChildren(new TypeFilter<>(CtInterface.class))
+ .forEach((CtInterface> iface) -> {
+ if (MODEL_IFACE_PACKAGES.contains(iface.getPackage().getQualifiedName())) {
+ getOrCreateMMType(iface);
+ }
+ });
+ }
+
+ private static Factory createFactory(File spoonJavaSourcesDirectory) {
+ final Launcher launcher = new Launcher();
+ launcher.getEnvironment().setNoClasspath(true);
+ launcher.getEnvironment().setCommentEnabled(true);
+// // Spoon model interfaces
+ Arrays.asList("spoon/reflect/code",
+ "spoon/reflect/declaration",
+ "spoon/reflect/reference",
+ "spoon/support/reflect/declaration",
+ "spoon/support/reflect/code",
+ "spoon/support/reflect/reference").forEach(path -> {
+ launcher.addInputResource(new FileSystemFolder(new File(spoonJavaSourcesDirectory, path)));
+ });
+ launcher.buildModel();
+ return launcher.getFactory();
+ }
+
+ private MMType getOrCreateMMType(CtType> type) {
+ String mmTypeName = getMMTypeIntefaceName(type);
+ MMType mmType = getOrCreate(name2mmType, mmTypeName, () -> new MMType());
+ if (mmType.name == null) {
+ //it is not initialized yet. Do it now
+ mmType.name = mmTypeName;
+ if (type instanceof CtInterface>) {
+ CtInterface> iface = (CtInterface>) type;
+ mmType.setModelClass(getImplementationOfInterface(iface));
+ mmType.setModelInteface(iface);
+ } else if (type instanceof CtClass>) {
+ CtClass> clazz = (CtClass>) type;
+ mmType.setModelClass(clazz);
+ mmType.setModelInteface(getInterfaceOfImplementation(clazz));
+ } else {
+ throw new SpoonException("Unexpected spoon model type: " + type.getQualifiedName());
+ }
+
+ if (mmType.getModelClass() != null) {
+ addFieldsOfType(mmType, mmType.getModelClass());
+ }
+ if (mmType.getModelInteface() != null) {
+ //add fields of interface too. They are not added by above call of addFieldsOfType, because the MMType already exists in name2mmType
+ addFieldsOfType(mmType, mmType.getModelInteface());
+ }
+// detectMethodKinds(mmType);
+ mmType.getRole2field().forEach((role, mmField) -> {
+ mmField.groupMethodsByKind();
+ mmField.sortByBestMatch();
+ mmField.setValueType(mmField.detectValueType());
+ });
+
+
+ }
+ return mmType;
+ }
+/*
+ private void detectMethodKinds(MMType mmType) {
+ // collect getters, setters, ...
+ for (MMField mmField : mmType.role2field.values()) {
+
+ Set unhandledSignatures = new HashSet<>(mmField.getAllRoleMethodsBySignature().keySet());
+ // look for getter
+ forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), new String[] { "get", "is" }, (k, v) -> {
+ if (v.get(0).getParameters().isEmpty() == false) {
+ // ignore getters with some parameters
+ return;
+ }
+ createFieldHandler(unhandledSignatures, mmField, null, -1, () -> mmField.get, (m) -> mmField.get = m, "get()")
+ .accept(k, v);
+ });
+
+ if (mmField.get != null) {
+ mmField.setValueType(mmField.get.getType());
+ }
+ // look for setter
+ forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), "set", (k, v) -> {
+ if (v.get(0).getParameters().size() == 1) {
+ // setters with 1 parameter equal to getter return value
+ createFieldHandler(unhandledSignatures, mmField, mmField.getValueType(), 0, () -> mmField.set,
+ (m) -> mmField.set = m, "set(value)").accept(k, v);
+ return;
+ }
+ });
+
+ if (mmField.getValueContainerType() != MMContainerType.SINGLE) {
+ forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), new String[] { "add", "insert" },
+ (k, v) -> {
+ if (v.get(0).getParameters().size() == 1) {
+ if (v.get(0).getSimpleName().endsWith("AtTop")
+ || v.get(0).getSimpleName().endsWith("Begin")) {
+ // adders with 1 parameter and fitting
+ // value type
+ createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
+ () -> mmField.addFirst, (m) -> mmField.addFirst = m, "addFirst(value)")
+ .accept(k, v);
+ return;
+ } else if (v.get(0).getSimpleName().endsWith("AtBottom")
+ || v.get(0).getSimpleName().endsWith("End")) {
+ // adders with 1 parameter and fitting
+ // value type
+ createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
+ () -> mmField.addLast, (m) -> mmField.addLast = m, "addLast(value)").accept(k,
+ v);
+ return;
+ } else {
+ // adders with 1 parameter and fitting
+ // value type
+ createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
+ () -> mmField.add, (m) -> mmField.add = m, "add(value)").accept(k, v);
+ return;
+ }
+ }
+ if (v.get(0).getParameters().size() == 2) {
+ // adders with 2 parameters. First int
+ if (v.get(0).getParameters().get(0).getType().getSimpleName().equals("int")) {
+ createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 1,
+ () -> mmField.addOn, (m) -> mmField.addOn = m, "addOn(int, value)").accept(k,
+ v);
+ }
+ return;
+ }
+ });
+ forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), "remove", (k, v) -> {
+ if (v.get(0).getParameters().size() == 1) {
+ createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0, () -> mmField.remove,
+ (m) -> mmField.remove = m, "remove(value)").accept(k, v);
+ }
+ });
+ }
+ unhandledSignatures.forEach(key -> {
+ CtMethod> representant = mmField.getAllRoleMethodsBySignature(key).get(0);
+ getOrCreate(allUnhandledMethodSignatures, getDeclaringTypeSignature(representant),
+ () -> representant);
+ });
+ }
+ }*/
+
+ /**
+ * @return all the problems found in spoon meta model
+ */
+ public String getProblems() {
+ return problems.toString();
+ }
+
+ private void forEachValueWithKeyPrefix(Map map, String prefix, BiConsumer consumer) {
+ forEachValueWithKeyPrefix(map, new String[] { prefix }, consumer);
+ }
+
+ private void forEachValueWithKeyPrefix(Map map, String[] prefixes, BiConsumer consumer) {
+ for (Map.Entry e : map.entrySet()) {
+ for (String prefix : prefixes) {
+ if (e.getKey().startsWith(prefix)) {
+ consumer.accept(e.getKey(), e.getValue());
+ }
+ }
+ }
+ }
+//
+// private BiConsumer>> createFieldHandler(Set unhandledSignatures,
+// MMField mmField, CtTypeReference valueType, int valueParamIdx, Supplier> fieldGetter,
+// Consumer> fieldSetter, String fieldName) {
+// return (k, v) -> {
+// //Look for first method which has a body
+// CtMethod> method = getConcreteMethod(v);
+// // use getTypeErasure() comparison, because some getters and setters
+// // does not fit exactly
+// List> params = method.getParameters();
+// boolean checkParameterType = params.size() > 0 || valueType != null;
+// CtTypeReference> newValueType = typeMatches(valueType,
+// checkParameterType ? params.get(valueParamIdx).getType() : null);
+// if (newValueType != null || checkParameterType == false) {
+// // the method parameter matches with expected valueType
+// if (newValueType != valueType) {
+// // there is new value type, we have to update it in mmField
+// if (mmField.getValueType() == valueType) {
+// mmField.setValueType(newValueType);
+// } else if (mmField.getItemValueType() == valueType) {
+// mmField.setItemValueType(newValueType);
+// } else {
+// throw new SpoonException("Cannot update valueType");
+// }
+// }
+// unhandledSignatures.remove(k);
+// if (fieldGetter.get() == null) {
+// // we do not have method yet for this field
+// fieldSetter.accept(method);
+// } else {
+// // there is already a method for this field
+// if (valueType != null) {
+// // 1) choose the method with more fitting valueType
+// boolean oldMatches = valueType
+// .equals(fieldGetter.get().getParameters().get(valueParamIdx).getType());
+// boolean newMatches = valueType.equals(method.getParameters().get(valueParamIdx).getType());
+// if (oldMatches != newMatches) {
+// if (oldMatches) {
+// // old is matching
+// // add new as unhandled signature
+// unhandledSignatures.add(k);
+// } else {
+// // new is matching
+// // add old as unhandled signature
+// unhandledSignatures.add(fieldGetter.get().getSignature());
+// // and set new is current field value
+// fieldSetter.accept(method);
+// }
+// // do report problem. The conflict was resolved
+// return;
+// }
+// } else if (params.isEmpty()) {
+// // the value type is not known yet and there is no input
+// // parameter. We are resolving getter field.
+// // choose the getter whose return value is a collection
+// // of second one
+// CtTypeReference> oldReturnType = fieldGetter.get().getType();
+// CtTypeReference> newReturnType = method.getType();
+// boolean isOldIterable = oldReturnType.isSubtypeOf(iterableRef);
+// boolean isNewIterable = newReturnType.isSubtypeOf(iterableRef);
+// if (isOldIterable != isNewIterable) {
+// // they are not some. Only one of them is iterable
+// if (isOldIterable) {
+// if (isIterableOf(oldReturnType, newReturnType)) {
+// // use old method, which is multivalue
+// // represantation of new method
+// // OK - no conflict. Finish
+// return;
+// }
+// } else {
+// if (isIterableOf(newReturnType, oldReturnType)) {
+// // use new method, which is multivalue
+// // represantation of old method
+// fieldSetter.accept(method);
+// // OK - no conflict. Finish
+// return;
+// }
+// }
+// // else report ambiguity
+// }
+// }
+//
+// problems.write(mmField.getOwnerType().getName() + " has ambiguous " + fieldName + " :").writeln().incTab()
+// .write(fieldGetter.get().getType() + "#" + getDeclaringTypeSignature(fieldGetter.get()))
+// .writeln().write(method.getType() + "#" + getDeclaringTypeSignature(method)).writeln()
+// .decTab();
+// }
+// }
+// };
+// }
+
+
+
+ private Set propertyGetterAndSetter = new HashSet<>(
+ Arrays.asList(PropertyGetter.class.getName(), PropertySetter.class.getName()));
+
+ private void addFieldsOfType(MMType mmType, CtType> ctType) {
+ ctType.getTypeMembers().forEach(typeMember -> {
+ if (typeMember instanceof CtMethod>) {
+ CtMethod> method = (CtMethod>) typeMember;
+ CtRole role = getRoleOfMethod(method);
+ if (role != null) {
+ MMField field = mmType.getOrCreateMMField(role);
+ field.addMethod(method);
+ } else {
+ mmType.otherMethods.add(method);
+ }
+ }
+ });
+ addFieldsOfSuperType(mmType, ctType.getSuperclass());
+ ctType.getSuperInterfaces().forEach(superIfaceRef -> addFieldsOfSuperType(mmType, superIfaceRef));
+ }
+
+ private static Set EXPECTED_TYPES_NOT_IN_CLASSPATH = new HashSet<>(Arrays.asList(
+ "java.lang.Cloneable",
+ "spoon.processing.FactoryAccessor",
+ "spoon.reflect.visitor.CtVisitable",
+ "spoon.reflect.visitor.chain.CtQueryable",
+ "spoon.template.TemplateParameter",
+ "java.lang.Iterable"));
+
+
+ /**
+ * add all fields of `superTypeRef` into `mmType`
+ * @param mmType sub type
+ * @param superTypeRef super type
+ */
+ private void addFieldsOfSuperType(MMType mmType, CtTypeReference> superTypeRef) {
+ if (superTypeRef == null) {
+ return;
+ }
+ CtType> superType = superTypeRef.getDeclaration();
+ if (superType == null) {
+ if (EXPECTED_TYPES_NOT_IN_CLASSPATH.contains(superTypeRef.getQualifiedName()) == false) {
+ problems.write("Type not in model: " + superTypeRef.getQualifiedName()).writeln();
+ }
+ return;
+ }
+ MMType superMMType = getOrCreateMMType(superType);
+ if (superMMType != mmType) {
+ mmType.addSuperType(superMMType);
+ }
+ }
+
+ /**
+ * @param type
+ * @return name of {@link MMType}, which represents Spoon model {@link CtType}
+ */
+ public static String getMMTypeIntefaceName(CtType> type) {
+ String name = type.getSimpleName();
+ if (name.endsWith(CLASS_SUFFIX)) {
+ name = name.substring(0, name.length() - CLASS_SUFFIX.length());
+ }
+ return name;
+ }
+
+ static V getOrCreate(Map map, K key, Supplier valueCreator) {
+ V value = map.get(key);
+ if (value == null) {
+ value = valueCreator.get();
+ map.put(key, value);
+ }
+ return value;
+ }
+ static boolean addUniqueObject(Collection col, T o) {
+ if (containsObject(col, o)) {
+ return false;
+ }
+ col.add(o);
+ return true;
+ }
+ static boolean containsObject(Iterable extends Object> iter, Object o) {
+ for (Object object : iter) {
+ if (object == o) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param iface the interface of spoon model element
+ * @return {@link CtClass} of Spoon model which implements the spoon model interface. null if there is no implementation.
+ */
+ public static CtClass> getImplementationOfInterface(CtInterface> iface) {
+ String impl = iface.getQualifiedName().replace("spoon.reflect", "spoon.support.reflect") + CLASS_SUFFIX;
+ return (CtClass>) iface.getFactory().Type().get(impl);
+ }
+
+ /**
+ * @param impl the implementation of spoon model element
+ * @return {@link CtInterface} of Spoon model which represents API of the spoon model class. null if there is no implementation.
+ */
+ public static CtInterface> getInterfaceOfImplementation(CtClass> impl) {
+ String iface = impl.getQualifiedName();
+ if (iface.endsWith(CLASS_SUFFIX) == false || iface.startsWith("spoon.support.reflect.") == false) {
+ throw new SpoonException("Unexpected spoon model implementation class: " + impl.getQualifiedName());
+ }
+ iface = iface.substring(0, iface.length() - CLASS_SUFFIX.length());
+ iface = iface.replace("spoon.support.reflect", "spoon.reflect");
+ return (CtInterface>) impl.getFactory().Type().get(iface);
+ }
+
+ /**
+ * @param method to be checked method
+ * @return {@link CtRole} of spoon model method. Looking into all super class/interface implementations of this method
+ */
+ public static CtRole getRoleOfMethod(CtMethod> method) {
+ Factory f = method.getFactory();
+ CtAnnotation getter = getInheritedAnnotation(method, f.createCtTypeReference(PropertyGetter.class));
+ if (getter != null) {
+ return getter.getActualAnnotation().role();
+ }
+ CtAnnotation setter = getInheritedAnnotation(method, f.createCtTypeReference(PropertySetter.class));
+ if (setter != null) {
+ return setter.getActualAnnotation().role();
+ }
+ return null;
+ }
+
+ /**
+ * Looks for method in superClass and superInterface hierarchy for the method with required annotationType
+ * @param method
+ * @param annotationType
+ * @return
+ */
+ private static CtAnnotation getInheritedAnnotation(CtMethod> method, CtTypeReference annotationType) {
+ CtAnnotation annotation = method.getAnnotation(annotationType);
+ if (annotation == null) {
+ CtType> declType = method.getDeclaringType();
+ final ClassTypingContext ctc = new ClassTypingContext(declType);
+ annotation = declType.map(new AllTypeMembersFunction(CtMethod.class)).map((CtMethod> currentMethod) -> {
+ if (method == currentMethod) {
+ return null;
+ }
+ if (ctc.isSameSignature(method, currentMethod)) {
+ CtAnnotation annotation2 = currentMethod.getAnnotation(annotationType);
+ if (annotation2 != null) {
+ return annotation2;
+ }
+ }
+ return null;
+ }).first();
+ }
+ return annotation;
+ }
+
+ public Collection getMMTypes() {
+ return Collections.unmodifiableCollection(name2mmType.values());
+ }
+}
From c160bc935c6cb8cd127809c6388ef5049dbc754d Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Sat, 28 Oct 2017 17:39:45 +0200
Subject: [PATCH 2/4] improve Spoon meta model documentation + some fixes
---
src/test/java/spoon/metamodel/MMField.java | 109 +++--
src/test/java/spoon/metamodel/MMMethod.java | 174 ++++++++
.../java/spoon/metamodel/MMMethodKind.java | 38 +-
src/test/java/spoon/metamodel/MMType.java | 52 ++-
src/test/java/spoon/metamodel/MMethod.java | 110 ------
.../java/spoon/metamodel/SpoonMetaModel.java | 373 +++++-------------
6 files changed, 395 insertions(+), 461 deletions(-)
create mode 100644 src/test/java/spoon/metamodel/MMMethod.java
delete mode 100644 src/test/java/spoon/metamodel/MMethod.java
diff --git a/src/test/java/spoon/metamodel/MMField.java b/src/test/java/spoon/metamodel/MMField.java
index e2eedfa3f7a..daafbefa6a1 100644
--- a/src/test/java/spoon/metamodel/MMField.java
+++ b/src/test/java/spoon/metamodel/MMField.java
@@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
import spoon.SpoonException;
import spoon.reflect.declaration.CtMethod;
@@ -67,25 +68,24 @@ public class MMField {
private Boolean derived;
- private Map> methodsByKind;
+ private Map> methodsByKind = new HashMap<>();
/**
* methods of this field defined directly on ownerType.
* There is PropertyGetter or PropertySetter annotation with `role` of this {@link MMField}
*/
- private final List roleMethods = new ArrayList<>();
+ private final List roleMethods = new ArrayList<>();
/**
* methods of this field grouped by signature defined directly on ownerType.
* There is PropertyGetter or PropertySetter annotation with `role` of this {@link MMField}
* note: There can be up to 2 methods in this list. 1) declaration from interface, 2) implementation from class
*/
- private final Map roleMethodsBySignature = new HashMap<>();
+ private final Map roleMethodsBySignature = new HashMap<>();
/**
* List of fields with same `role`, from super type of `ownerType` {@link MMType}
*/
private final List superFields = new ArrayList<>();
- private Set unhandledSignatures;
private List ambiguousMethodKinds = new ArrayList<>();
@@ -97,20 +97,26 @@ public class MMField {
}
void addMethod(CtMethod> method) {
- MMethod mmMethod = getOwnMethod(method, true);
- mmMethod.ownMethods.add(method);
+ MMMethod mmMethod = getOwnMethod(method, true);
+ mmMethod.addOwnMethod(method);
}
- MMethod getOwnMethod(CtMethod> method, boolean createIfNotExist) {
- for (MMethod mmMethod : roleMethods) {
+ /**
+ * @param method
+ * @param createIfNotExist
+ * @return existing {@link MMMethod}, which overrides `method` or creates and registers new one if `createIfNotExist`==true
+ */
+ MMMethod getOwnMethod(CtMethod> method, boolean createIfNotExist) {
+ for (MMMethod mmMethod : roleMethods) {
if (mmMethod.overrides(method)) {
return mmMethod;
}
}
if (createIfNotExist) {
- MMethod mmMethod = new MMethod(this, method);
+ MMMethod mmMethod = new MMMethod(this, method);
roleMethods.add(mmMethod);
- MMethod conflict = roleMethodsBySignature.put(mmMethod.getSignature(), mmMethod);
+ getOrCreate(methodsByKind, mmMethod.getMethodKind(), () -> new ArrayList<>()).add(mmMethod);
+ MMMethod conflict = roleMethodsBySignature.put(mmMethod.getSignature(), mmMethod);
if (conflict != null) {
throw new SpoonException("Conflict on " + getOwnerType().getName() + "." + name + " method signature: " + mmMethod.getSignature());
}
@@ -121,8 +127,8 @@ MMethod getOwnMethod(CtMethod> method, boolean createIfNotExist) {
void addSuperField(MMField superMMField) {
if (addUniqueObject(superFields, superMMField)) {
- for (MMethod superMethod : superMMField.getRoleMethods()) {
- getOwnMethod(superMethod.getMethodOf(getOwnerType()), true).addSuperMethod(superMethod);
+ for (MMMethod superMethod : superMMField.getRoleMethods()) {
+ getOwnMethod(superMethod.getFirstOwnMethod(getOwnerType()), true).addSuperMethod(superMethod);
}
}
}
@@ -151,25 +157,25 @@ public CtTypeReference> getValueType() {
}
CtTypeReference> detectValueType() {
- MMethod mmGetMethod = getMethod(MMMethodKind.GET);
+ MMMethod mmGetMethod = getMethod(MMMethodKind.GET);
if (mmGetMethod == null) {
throw new SpoonException("No getter exists for " + getOwnerType().getName() + "." + getName());
}
- MMethod mmSetMethod = getMethod(MMMethodKind.SET);
+ MMMethod mmSetMethod = getMethod(MMMethodKind.SET);
if (mmSetMethod == null) {
- return mmGetMethod.getType();
+ return mmGetMethod.getReturnType();
}
- CtTypeReference> getterValueType = mmGetMethod.getType();
+ CtTypeReference> getterValueType = mmGetMethod.getReturnType();
CtTypeReference> setterValueType = mmSetMethod.getValueType();
if (getterValueType.equals(setterValueType)) {
- return mmGetMethod.getType();
+ return mmGetMethod.getReturnType();
}
if (MMContainerType.valueOf(getterValueType.getActualClass()) != MMContainerType.SINGLE) {
getterValueType = getItemValueType(getterValueType);
setterValueType = getItemValueType(setterValueType);
}
if (getterValueType.equals(setterValueType)) {
- return mmGetMethod.getType();
+ return mmGetMethod.getReturnType();
}
if (getterValueType.isSubtypeOf(setterValueType)) {
/*
@@ -213,38 +219,29 @@ public void setItemValueType(CtTypeReference> itemValueType) {
this.itemValueType = itemValueType;
}
- public MMethod getMethod(MMMethodKind kind) {
- List ms = getMethods(kind);
+ public MMMethod getMethod(MMMethodKind kind) {
+ List ms = getMethods(kind);
return ms.size() > 0 ? ms.get(0) : null;
}
- public List getMethods(MMMethodKind kind) {
- if (methodsByKind == null) {
- throw new SpoonException("Metamodel is not initialized yet");
- }
- List ms = methodsByKind.get(kind);
+ public List getMethods(MMMethodKind kind) {
+ List ms = methodsByKind.get(kind);
return ms == null ? Collections.emptyList() : Collections.unmodifiableList(ms);
}
-
- public Set getUnhandledSignatures() {
- if (unhandledSignatures == null) {
- throw new SpoonException("Model is not initialized yet");
- }
- return unhandledSignatures;
- }
-
- void groupMethodsByKind() {
- methodsByKind = new HashMap<>();
- unhandledSignatures = new HashSet<>();
-
- for (MMethod mmMethod : roleMethods) {
- MMMethodKind k = MMMethodKind.valueOf(mmMethod.getMethod());
- if (k == MMMethodKind.OTHER) {
- unhandledSignatures.add(mmMethod.getSignature());
- continue;
+
+ /**
+ * @param consumer is called for each CtMethod of this field which is not covered by this meta model
+ */
+ public void forEachUnhandledMethod(Consumer> consumer) {
+ methodsByKind.forEach((kind, mmMethods) -> {
+ if(kind == MMMethodKind.OTHER) {
+ mmMethods.forEach(mmMethod -> mmMethod.getOwnMethods().forEach(consumer));
+ } else {
+ if (mmMethods.size() > 1) {
+ mmMethods.subList(1, mmMethods.size()).forEach(mmMethod -> mmMethod.getOwnMethods().forEach(consumer));
+ }
}
- getOrCreate(methodsByKind, k, () -> new ArrayList<>()).add(mmMethod);
- }
+ });
}
void sortByBestMatch() {
@@ -256,7 +253,7 @@ void sortByBestMatch() {
}
void sortByBestMatch(MMMethodKind key) {
- List methods = methodsByKind.get(key);
+ List methods = methodsByKind.get(key);
if (methods != null && methods.size() > 1) {
int idx = getIdxOfBestMatch(methods, key);
if (idx >= 0) {
@@ -264,8 +261,6 @@ void sortByBestMatch(MMMethodKind key) {
//move the matching to the beginning
methods.add(0, methods.remove(idx));
}
- //add next items as unhandled
- methods.subList(1, methods.size()).forEach(mmMethod -> unhandledSignatures.add(mmMethod.getSignature()));
} else {
//add all methods as ambiguous
ambiguousMethodKinds.add(key);
@@ -279,21 +274,21 @@ void sortByBestMatch(MMMethodKind key) {
* @return index of the method which best matches the `key` accessor of this field
* -1 if it cannot be resolved
*/
- private int getIdxOfBestMatch(List methods, MMMethodKind key) {
- MMethod mmMethod = methods.get(0);
+ private int getIdxOfBestMatch(List methods, MMMethodKind key) {
+ MMMethod mmMethod = methods.get(0);
if (mmMethod.getMethod().getParameters().size() == 0) {
return getIdxOfBestMatchByReturnType(methods, key);
} else {
- MMethod mmGetMethod = getMethod(MMMethodKind.GET);
+ MMMethod mmGetMethod = getMethod(MMMethodKind.GET);
if (mmGetMethod == null) {
//we have no getter so we do not know the expected value type. Setters are ambiguous
return -1;
}
- return getIdxOfBestMatchByInputParameter(methods, key, mmGetMethod.getType());
+ return getIdxOfBestMatchByInputParameter(methods, key, mmGetMethod.getReturnType());
}
}
- private int getIdxOfBestMatchByReturnType(List methods, MMMethodKind key) {
+ private int getIdxOfBestMatchByReturnType(List methods, MMMethodKind key) {
if (methods.size() > 2) {
throw new SpoonException("Resolving of more then 2 conflicting getters is not supported. There are: " + methods.toString());
}
@@ -336,7 +331,7 @@ private boolean isIterableOf(CtTypeReference> iterableType, CtTypeReference>
return false;
}
- private int getIdxOfBestMatchByInputParameter(List methods, MMMethodKind key, CtTypeReference> expectedValueType) {
+ private int getIdxOfBestMatchByInputParameter(List methods, MMMethodKind key, CtTypeReference> expectedValueType) {
int idx = -1;
MatchLevel maxMatchLevel = null;
CtTypeReference> newValueType = null;
@@ -345,7 +340,7 @@ private int getIdxOfBestMatchByInputParameter(List methods, MMMethodKin
}
for (int i = 0; i < methods.size(); i++) {
- MMethod mMethod = methods.get(i);
+ MMMethod mMethod = methods.get(i);
MatchLevel matchLevel = getMatchLevel(expectedValueType, mMethod.getValueType());
if (matchLevel != null) {
//it is matching
@@ -442,7 +437,7 @@ private CtTypeReference> getMapValueType(CtTypeReference> valueType) {
public boolean isDerived() {
if (derived == null) {
//if DerivedProperty is found on any getter of this type, then this field is derived
- MMethod getter = getMethod(MMMethodKind.GET);
+ MMMethod getter = getMethod(MMMethodKind.GET);
if (getter == null) {
throw new SpoonException("No getter defined for " + this);
}
@@ -475,11 +470,11 @@ public boolean isDerived() {
return derived;
}
- public List getRoleMethods() {
+ public List getRoleMethods() {
return Collections.unmodifiableList(roleMethods);
}
- public Map getRoleMethodsBySignature() {
+ public Map getRoleMethodsBySignature() {
return Collections.unmodifiableMap(roleMethodsBySignature);
}
diff --git a/src/test/java/spoon/metamodel/MMMethod.java b/src/test/java/spoon/metamodel/MMMethod.java
new file mode 100644
index 00000000000..2c16edb9ff1
--- /dev/null
+++ b/src/test/java/spoon/metamodel/MMMethod.java
@@ -0,0 +1,174 @@
+/**
+ * Copyright (C) 2006-2017 INRIA and contributors
+ * Spoon - http://spoon.gforge.inria.fr/
+ *
+ * This software is governed by the CeCILL-C License under French law and
+ * abiding by the rules of distribution of free software. You can use, modify
+ * and/or redistribute the software under the terms of the CeCILL-C license as
+ * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+package spoon.metamodel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import spoon.SpoonException;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.reference.CtTypeReference;
+import spoon.support.visitor.MethodTypingContext;
+
+import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
+
+/**
+ * Represents a method of a {@link MMField} of a {@link MMType}.
+ * Each MMMethod belongs to one MMField
+ */
+public class MMMethod {
+ private final MMField ownerField;
+ private final CtMethod> method;
+ private final List> ownMethods = new ArrayList<>();
+ private final List superMethods = new ArrayList<>();
+ private final String signature;
+ private final MMMethodKind methodKind;
+
+ /**
+ * Creates a {@link MMMethod} of a {@link MMField}
+ * @param field a owner field
+ * @param method a method from ownerType or nearest super type
+ */
+ MMMethod(MMField field, CtMethod> method) {
+ this.ownerField = field;
+ //adapt method to scope of field.ownType
+ MethodTypingContext mtc = new MethodTypingContext().setClassTypingContext(field.getOwnerType().getTypeContext()).setMethod(method);
+ this.method = (CtMethod>) mtc.getAdaptationScope();
+ signature = this.method.getSignature();
+ methodKind = MMMethodKind.valueOf(this.method);
+ }
+
+ /**
+ * @return a {@link CtMethod}, which represents this {@link MMMethod}
+ */
+ public CtMethod> getMethod() {
+ return method;
+ }
+
+ /**
+ * @return name of this {@link MMMethod}. It is equal to simple name of related {@link CtMethod}
+ */
+ public String getName() {
+ return method.getSimpleName();
+ }
+
+ /**
+ * @return signature of this method, without the declaring type
+ */
+ public String getSignature() {
+ return signature;
+ }
+
+ /**
+ * @return kind of this method. Getter, setter, ...
+ */
+ public MMMethodKind getMethodKind() {
+ return methodKind;
+ }
+
+ /**
+ * @return first own method in super type hierarchy of `targetType`
+ */
+ public CtMethod> getFirstOwnMethod(MMType targetType) {
+ for (CtMethod> ctMethod : ownMethods) {
+ if (targetType.getTypeContext().isSubtypeOf(ctMethod.getDeclaringType().getReference())) {
+ return ctMethod;
+ }
+ }
+ for (MMMethod mmMethod : superMethods) {
+ CtMethod> m = mmMethod.getFirstOwnMethod(targetType);
+ if (m != null) {
+ return m;
+ }
+ }
+ throw new SpoonException("No own method exists in type " + ownerField);
+ }
+
+ /**
+ * @param method
+ * @return true of this {@link MMMethod} overrides `method`. In different words, if it represents the same method
+ */
+ public boolean overrides(CtMethod> method) {
+ return ownerField.getOwnerType().getTypeContext().isOverriding(this.method, method);
+ }
+
+ /**
+ * Adds a `mmMethod` as super method of this {@link MMMethod}
+ * @param mmMethod
+ */
+ void addSuperMethod(MMMethod mmMethod) {
+ addUniqueObject(superMethods, mmMethod);
+ }
+
+ /**
+ * @return {@link MMField} which owns this {@link MMMethod}
+ */
+ public MMField getOwnerField() {
+ return ownerField;
+ }
+
+ /**
+ * @return {@link MMType} where this {@link MMMethod} belongs to
+ */
+ public MMType getOwnerType() {
+ return getOwnerField().getOwnerType();
+ }
+
+ /**
+ * @return {@link CtMethod}s, which are declared directly in the getOwnType().
+ * It does not return methods which are inherited from super types.
+ * It returns empty list, if there is no own implementation and method is inherited only
+ */
+ public List> getOwnMethods() {
+ return Collections.unmodifiableList(ownMethods);
+ }
+
+ void addOwnMethod(CtMethod> method) {
+ ownMethods.add(method);
+ }
+
+ /**
+ * @return List of {@link MMMethod}s, which comes from super types of type getOwnType()
+ */
+ public List getSuperMethods() {
+ return Collections.unmodifiableList(superMethods);
+ }
+
+ /**
+ * @return a type returned by this method
+ */
+ public CtTypeReference> getReturnType() {
+ return method.getType();
+ }
+
+ /**
+ * @return a value type of this method
+ */
+ public CtTypeReference> getValueType() {
+ if (method.getParameters().isEmpty()) {
+ return method.getType();
+ }
+ return method.getParameters().get(method.getParameters().size() - 1).getType();
+ }
+
+ @Override
+ public String toString() {
+ return getOwnerType().getName() + "#" + getSignature();
+ }
+
+}
diff --git a/src/test/java/spoon/metamodel/MMMethodKind.java b/src/test/java/spoon/metamodel/MMMethodKind.java
index 7bc90a7bb5f..5e71f2816d9 100644
--- a/src/test/java/spoon/metamodel/MMMethodKind.java
+++ b/src/test/java/spoon/metamodel/MMMethodKind.java
@@ -29,16 +29,16 @@ public enum MMMethodKind {
* Getter.
* T get()
*/
- GET(false, 1, m -> m.getParameters().size() == 0 && (m.getSimpleName().startsWith("get") || m.getSimpleName().startsWith("is"))),
+ GET(-1, false, 1, m -> m.getParameters().size() == 0 && (m.getSimpleName().startsWith("get") || m.getSimpleName().startsWith("is"))),
/**
* Setter
* void set(T)
*/
- SET(false, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("set")),
+ SET(0, false, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("set")),
/**
* void addFirst(T)
*/
- ADD_FIRST(true, 10, m -> {
+ ADD_FIRST(0, true, 10, m -> {
if (m.getParameters().size() == 1) {
if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
if (m.getSimpleName().endsWith("AtTop") || m.getSimpleName().endsWith("Begin")) {
@@ -51,7 +51,7 @@ public enum MMMethodKind {
/**
* void add(T)
*/
- ADD_LAST(true, 1, m -> {
+ ADD_LAST(0, true, 1, m -> {
if (m.getParameters().size() == 1) {
if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
return true;
@@ -62,7 +62,7 @@ public enum MMMethodKind {
/**
* void addOn(int, T)
*/
- ADD_ON(true, 1, m -> {
+ ADD_ON(1, true, 1, m -> {
if (m.getParameters().size() == 2 && m.getParameters().get(0).getType().getSimpleName().equals("int")) {
if (m.getSimpleName().startsWith("add") || m.getSimpleName().startsWith("insert")) {
return true;
@@ -73,18 +73,30 @@ public enum MMMethodKind {
/**
* void remove(T)
*/
- REMOVE(true, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("remove")),
+ REMOVE(0, true, 1, m -> m.getParameters().size() == 1 && m.getSimpleName().startsWith("remove")),
- OTHER(false, 0, m -> true);
+ /**
+ * Return element by its name
+ * T get(String)
+ */
+ GET_BY(-1, true, 1, m -> m.getSimpleName().startsWith("get")
+ && m.getParameters().size() == 1 && m.getParameters().get(0).getType().getQualifiedName().equals(String.class.getName())),
+
+ /**
+ * The not matching method
+ */
+ OTHER(-2, false, 0, m -> true);
private final Predicate> detector;
private final int level;
private final boolean multi;
+ private final int valueParameterIndex;
- MMMethodKind(boolean multi, int level, Predicate> detector) {
+ MMMethodKind(int valueParameterIndex, boolean multi, int level, Predicate> detector) {
this.multi = multi;
this.level = level;
this.detector = detector;
+ this.valueParameterIndex = valueParameterIndex;
}
/**
@@ -95,6 +107,16 @@ public boolean isMulti() {
return multi;
}
+ /**
+ * @return index of parameter, which contains the field value.
+ * idx >= 0 - for method parameters
+ * idx = -1 - for return value of the method
+ * idx = -2 - unknown
+ */
+ public int getValueParameterIndex() {
+ return valueParameterIndex;
+ }
+
/**
* Detect kind of method
* @param method to be check method
diff --git a/src/test/java/spoon/metamodel/MMType.java b/src/test/java/spoon/metamodel/MMType.java
index 8c173d2f8b9..d957119c054 100644
--- a/src/test/java/spoon/metamodel/MMType.java
+++ b/src/test/java/spoon/metamodel/MMType.java
@@ -17,6 +17,7 @@
package spoon.metamodel;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -32,7 +33,7 @@
import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
/**
- * Represents a type of Spoon model class
+ * Represents a type of Spoon model AST node
*/
public class MMType {
/**
@@ -52,6 +53,9 @@ public class MMType {
* List of super types of this type
*/
private final List superTypes = new ArrayList<>();
+ /**
+ * List of sub types of this type
+ */
private final List subTypes = new ArrayList<>();
/**
@@ -63,6 +67,9 @@ public class MMType {
*/
private CtInterface> modelInteface;
+ /**
+ * {@link ClassTypingContext} of this type used to adapt methods from super type implementations to this {@link MMType}
+ */
private ClassTypingContext typeContext;
/**
@@ -74,10 +81,22 @@ public class MMType {
super();
}
+ /**
+ * @return interface name of {@link MMType}. For example CtClass, CtForEach, ...
+ * It is never followed by xxxImpl
+ */
+ public String getName() {
+ return name;
+ }
+
+
MMField getOrCreateMMField(CtRole role) {
return SpoonMetaModel.getOrCreate(role2field, role, () -> new MMField(role.getCamelCaseName(), role, this));
}
+ /**
+ * @return kind of this {@link MMType}. Is it helper type or type of real AST node?
+ */
public MMTypeKind getKind() {
if (kind == null) {
if (modelClass != null && modelClass.hasModifier(ModifierKind.ABSTRACT) == false) {
@@ -89,14 +108,16 @@ public MMTypeKind getKind() {
return kind;
}
- public String getName() {
- return name;
- }
-
+ /**
+ * @return map of {@link MMField}s by their {@link CtRole}
+ */
public Map getRole2field() {
- return role2field;
+ return Collections.unmodifiableMap(role2field);
}
+ /**
+ * @return super types
+ */
public List getSuperTypes() {
return superTypes;
}
@@ -114,6 +135,9 @@ void addSuperType(MMType superType) {
}
}
+ /**
+ * @return {@link CtClass} which represents this {@link MMType}
+ */
public CtClass> getModelClass() {
return modelClass;
}
@@ -122,6 +146,9 @@ void setModelClass(CtClass> modelClass) {
this.modelClass = modelClass;
}
+ /**
+ * @return {@link CtInterface} which represents this {@link MMType}
+ */
public CtInterface> getModelInteface() {
return modelInteface;
}
@@ -130,15 +157,18 @@ void setModelInteface(CtInterface> modelInteface) {
this.modelInteface = modelInteface;
}
- @Override
- public String toString() {
- return getName();
- }
-
+ /**
+ * @return {@link ClassTypingContext}, which can be used to adapt super type methods to this {@link MMType}
+ */
public ClassTypingContext getTypeContext() {
if (typeContext == null) {
typeContext = new ClassTypingContext(modelClass != null ? modelClass : modelInteface);
}
return typeContext;
}
+
+ @Override
+ public String toString() {
+ return getName();
+ }
}
diff --git a/src/test/java/spoon/metamodel/MMethod.java b/src/test/java/spoon/metamodel/MMethod.java
deleted file mode 100644
index cb2ae3cc21b..00000000000
--- a/src/test/java/spoon/metamodel/MMethod.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * Copyright (C) 2006-2017 INRIA and contributors
- * Spoon - http://spoon.gforge.inria.fr/
- *
- * This software is governed by the CeCILL-C License under French law and
- * abiding by the rules of distribution of free software. You can use, modify
- * and/or redistribute the software under the terms of the CeCILL-C license as
- * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
- *
- * The fact that you are presently reading this means that you have had
- * knowledge of the CeCILL-C license and that you accept its terms.
- */
-package spoon.metamodel;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import spoon.reflect.declaration.CtMethod;
-import spoon.reflect.reference.CtTypeReference;
-import spoon.support.visitor.MethodTypingContext;
-
-import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
-
-/**
- * Represents a method of a {@link MMField} of a {@link MMType}.
- * Each MMMethod belongs to one MMField
- */
-public class MMethod {
- final MMField field;
- final CtMethod> method;
- final List> ownMethods = new ArrayList<>();
- final List superMethods = new ArrayList<>();
- final String signature;
-
- MMethod(MMField field, CtMethod> method) {
- this.field = field;
- //adapt method to scope of field.ownType
- MethodTypingContext mtc = new MethodTypingContext().setClassTypingContext(field.getOwnerType().getTypeContext()).setMethod(method);
- this.method = (CtMethod>) mtc.getAdaptationScope();
- signature = this.method.getSignature();
- }
-
- public CtMethod> getMethod() {
- return method;
- }
-
- public CtMethod> getMethodOf(MMType targetType) {
- for (CtMethod> ctMethod : ownMethods) {
- if (targetType.getTypeContext().isSubtypeOf(ctMethod.getDeclaringType().getReference())) {
- return ctMethod;
- }
- }
- for (MMethod mmMethod : superMethods) {
- CtMethod> m = mmMethod.getMethodOf(targetType);
- if (m != null) {
- return m;
- }
- }
- return null;
- }
-
- boolean overrides(CtMethod> method) {
- return field.getOwnerType().getTypeContext().isOverriding(this.method, method);
- }
-
- public String getSignature() {
- return signature;
- }
-
- public void addSuperMethod(MMethod superMethod) {
- addUniqueObject(superMethods, superMethod);
- }
-
- public MMField getField() {
- return field;
- }
-
- public List> getOwnMethods() {
- return Collections.unmodifiableList(ownMethods);
- }
-
- public List getSuperMethods() {
- return Collections.unmodifiableList(superMethods);
- }
-
- public String getSimpleName() {
- return method.getSimpleName();
- }
-
- public CtTypeReference> getType() {
- return method.getType();
- }
-
- @Override
- public String toString() {
- return getField().getOwnerType().getName() + "#" + getSignature();
- }
-
- public CtTypeReference> getValueType() {
- if (method.getParameters().isEmpty()) {
- return method.getType();
- }
- return method.getParameters().get(method.getParameters().size() - 1).getType();
- }
-}
diff --git a/src/test/java/spoon/metamodel/SpoonMetaModel.java b/src/test/java/spoon/metamodel/SpoonMetaModel.java
index a5bf3b8d67f..617365ffea2 100644
--- a/src/test/java/spoon/metamodel/SpoonMetaModel.java
+++ b/src/test/java/spoon/metamodel/SpoonMetaModel.java
@@ -48,7 +48,7 @@
import spoon.support.visitor.ClassTypingContext;
/**
- * Represents a Spoon meta model
+ * Represents a Spoon meta model of the AST nodes.
*/
public class SpoonMetaModel {
public static final String CLASS_SUFFIX = "Impl";
@@ -60,17 +60,13 @@ public class SpoonMetaModel {
"spoon.reflect.declaration",
"spoon.reflect.reference"));
- private PrinterHelper problems;
-
+ private final Factory factory;
+
/**
* {@link MMType}s by name
*/
private final Map name2mmType = new HashMap<>();
- private final CtTypeReference> statementRef;
- private final CtTypeReference> iterableRef;
- private final CtTypeReference> mapRef;
-
/**
* Parses spoon sources and creates factory with spoon model.
*
@@ -85,11 +81,8 @@ public SpoonMetaModel(File spoonJavaSourcesDirectory) {
* @param factory already loaded factory with all Spoon model types
*/
public SpoonMetaModel(Factory factory) {
- statementRef = factory.createCtTypeReference(CtStatement.class);
- iterableRef = factory.createCtTypeReference(Iterable.class);
- mapRef = factory.createCtTypeReference(Map.class);
- problems = new PrinterHelper(factory.getEnvironment());
-
+ this.factory = factory;
+ //search for all interfaces of spoon model and create MMTypes for them
factory.getModel().getRootPackage().filterChildren(new TypeFilter<>(CtInterface.class))
.forEach((CtInterface> iface) -> {
if (MODEL_IFACE_PACKAGES.contains(iface.getPackage().getQualifiedName())) {
@@ -98,6 +91,48 @@ public SpoonMetaModel(Factory factory) {
});
}
+ /**
+ * @return all {@link MMType}s of spoon meta model
+ */
+ public Collection getMMTypes() {
+ return Collections.unmodifiableCollection(name2mmType.values());
+ }
+
+ /**
+ * @param type a spoon model type
+ * @return name of {@link MMType}, which represents Spoon model {@link CtType}
+ */
+ public static String getMMTypeIntefaceName(CtType> type) {
+ String name = type.getSimpleName();
+ if (name.endsWith(CLASS_SUFFIX)) {
+ name = name.substring(0, name.length() - CLASS_SUFFIX.length());
+ }
+ return name;
+ }
+
+ /**
+ * @param iface the interface of spoon model element
+ * @return {@link CtClass} of Spoon model which implements the spoon model interface. null if there is no implementation.
+ */
+ public static CtClass> getImplementationOfInterface(CtInterface> iface) {
+ String impl = iface.getQualifiedName().replace("spoon.reflect", "spoon.support.reflect") + CLASS_SUFFIX;
+ return (CtClass>) iface.getFactory().Type().get(impl);
+ }
+
+ /**
+ * @param impl the implementation of spoon model element
+ * @return {@link CtInterface} of Spoon model which represents API of the spoon model class. null if there is no implementation.
+ */
+ public static CtInterface> getInterfaceOfImplementation(CtClass> impl) {
+ String iface = impl.getQualifiedName();
+ if (iface.endsWith(CLASS_SUFFIX) == false || iface.startsWith("spoon.support.reflect.") == false) {
+ throw new SpoonException("Unexpected spoon model implementation class: " + impl.getQualifiedName());
+ }
+ iface = iface.substring(0, iface.length() - CLASS_SUFFIX.length());
+ iface = iface.replace("spoon.support.reflect", "spoon.reflect");
+ return (CtInterface>) impl.getFactory().Type().get(iface);
+ }
+
private static Factory createFactory(File spoonJavaSourcesDirectory) {
final Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
@@ -115,240 +150,62 @@ private static Factory createFactory(File spoonJavaSourcesDirectory) {
return launcher.getFactory();
}
+ /**
+ * @param type can be class or interface of Spoon model element
+ * @return existing or creates and initializes new {@link MMType} which represents `type`, which
+ */
private MMType getOrCreateMMType(CtType> type) {
String mmTypeName = getMMTypeIntefaceName(type);
MMType mmType = getOrCreate(name2mmType, mmTypeName, () -> new MMType());
if (mmType.name == null) {
- //it is not initialized yet. Do it now
mmType.name = mmTypeName;
- if (type instanceof CtInterface>) {
- CtInterface> iface = (CtInterface>) type;
- mmType.setModelClass(getImplementationOfInterface(iface));
- mmType.setModelInteface(iface);
- } else if (type instanceof CtClass>) {
- CtClass> clazz = (CtClass>) type;
- mmType.setModelClass(clazz);
- mmType.setModelInteface(getInterfaceOfImplementation(clazz));
- } else {
- throw new SpoonException("Unexpected spoon model type: " + type.getQualifiedName());
- }
-
- if (mmType.getModelClass() != null) {
- addFieldsOfType(mmType, mmType.getModelClass());
- }
- if (mmType.getModelInteface() != null) {
- //add fields of interface too. They are not added by above call of addFieldsOfType, because the MMType already exists in name2mmType
- addFieldsOfType(mmType, mmType.getModelInteface());
- }
-// detectMethodKinds(mmType);
- mmType.getRole2field().forEach((role, mmField) -> {
- mmField.groupMethodsByKind();
- mmField.sortByBestMatch();
- mmField.setValueType(mmField.detectValueType());
- });
-
-
+ initializeMMType(type, mmType);
}
return mmType;
}
-/*
- private void detectMethodKinds(MMType mmType) {
- // collect getters, setters, ...
- for (MMField mmField : mmType.role2field.values()) {
-
- Set unhandledSignatures = new HashSet<>(mmField.getAllRoleMethodsBySignature().keySet());
- // look for getter
- forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), new String[] { "get", "is" }, (k, v) -> {
- if (v.get(0).getParameters().isEmpty() == false) {
- // ignore getters with some parameters
- return;
- }
- createFieldHandler(unhandledSignatures, mmField, null, -1, () -> mmField.get, (m) -> mmField.get = m, "get()")
- .accept(k, v);
- });
-
- if (mmField.get != null) {
- mmField.setValueType(mmField.get.getType());
- }
- // look for setter
- forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), "set", (k, v) -> {
- if (v.get(0).getParameters().size() == 1) {
- // setters with 1 parameter equal to getter return value
- createFieldHandler(unhandledSignatures, mmField, mmField.getValueType(), 0, () -> mmField.set,
- (m) -> mmField.set = m, "set(value)").accept(k, v);
- return;
- }
- });
-
- if (mmField.getValueContainerType() != MMContainerType.SINGLE) {
- forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), new String[] { "add", "insert" },
- (k, v) -> {
- if (v.get(0).getParameters().size() == 1) {
- if (v.get(0).getSimpleName().endsWith("AtTop")
- || v.get(0).getSimpleName().endsWith("Begin")) {
- // adders with 1 parameter and fitting
- // value type
- createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
- () -> mmField.addFirst, (m) -> mmField.addFirst = m, "addFirst(value)")
- .accept(k, v);
- return;
- } else if (v.get(0).getSimpleName().endsWith("AtBottom")
- || v.get(0).getSimpleName().endsWith("End")) {
- // adders with 1 parameter and fitting
- // value type
- createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
- () -> mmField.addLast, (m) -> mmField.addLast = m, "addLast(value)").accept(k,
- v);
- return;
- } else {
- // adders with 1 parameter and fitting
- // value type
- createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0,
- () -> mmField.add, (m) -> mmField.add = m, "add(value)").accept(k, v);
- return;
- }
- }
- if (v.get(0).getParameters().size() == 2) {
- // adders with 2 parameters. First int
- if (v.get(0).getParameters().get(0).getType().getSimpleName().equals("int")) {
- createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 1,
- () -> mmField.addOn, (m) -> mmField.addOn = m, "addOn(int, value)").accept(k,
- v);
- }
- return;
- }
- });
- forEachValueWithKeyPrefix(mmField.getAllRoleMethodsBySignature(), "remove", (k, v) -> {
- if (v.get(0).getParameters().size() == 1) {
- createFieldHandler(unhandledSignatures, mmField, mmField.getItemValueType(), 0, () -> mmField.remove,
- (m) -> mmField.remove = m, "remove(value)").accept(k, v);
- }
- });
- }
- unhandledSignatures.forEach(key -> {
- CtMethod> representant = mmField.getAllRoleMethodsBySignature(key).get(0);
- getOrCreate(allUnhandledMethodSignatures, getDeclaringTypeSignature(representant),
- () -> representant);
- });
- }
- }*/
-
+
/**
- * @return all the problems found in spoon meta model
+ * is called once for each MMType, to initialize it.
+ * @param type a class or inteface of the spoon model element
+ * @param mmType to be initialize MMType
*/
- public String getProblems() {
- return problems.toString();
- }
-
- private void forEachValueWithKeyPrefix(Map map, String prefix, BiConsumer consumer) {
- forEachValueWithKeyPrefix(map, new String[] { prefix }, consumer);
- }
+ private void initializeMMType(CtType> type, MMType mmType) {
+ //it is not initialized yet. Do it now
+ if (type instanceof CtInterface>) {
+ CtInterface> iface = (CtInterface>) type;
+ mmType.setModelClass(getImplementationOfInterface(iface));
+ mmType.setModelInteface(iface);
+ } else if (type instanceof CtClass>) {
+ CtClass> clazz = (CtClass>) type;
+ mmType.setModelClass(clazz);
+ mmType.setModelInteface(getInterfaceOfImplementation(clazz));
+ } else {
+ throw new SpoonException("Unexpected spoon model type: " + type.getQualifiedName());
+ }
- private void forEachValueWithKeyPrefix(Map map, String[] prefixes, BiConsumer consumer) {
- for (Map.Entry e : map.entrySet()) {
- for (String prefix : prefixes) {
- if (e.getKey().startsWith(prefix)) {
- consumer.accept(e.getKey(), e.getValue());
- }
- }
+ //add fields of class
+ if (mmType.getModelClass() != null) {
+ addFieldsOfType(mmType, mmType.getModelClass());
+ }
+ //add fields of interface
+ if (mmType.getModelInteface() != null) {
+ //add fields of interface too. They are not added by above call of addFieldsOfType, because the MMType already exists in name2mmType
+ addFieldsOfType(mmType, mmType.getModelInteface());
}
+ //initialize all fields
+ mmType.getRole2field().forEach((role, mmField) -> {
+ //if there are more methods for the same field then choose the one which best matches the field type
+ mmField.sortByBestMatch();
+ //finally initialize value type of this field
+ mmField.setValueType(mmField.detectValueType());
+ });
}
-//
-// private BiConsumer>> createFieldHandler(Set unhandledSignatures,
-// MMField mmField, CtTypeReference valueType, int valueParamIdx, Supplier> fieldGetter,
-// Consumer> fieldSetter, String fieldName) {
-// return (k, v) -> {
-// //Look for first method which has a body
-// CtMethod> method = getConcreteMethod(v);
-// // use getTypeErasure() comparison, because some getters and setters
-// // does not fit exactly
-// List> params = method.getParameters();
-// boolean checkParameterType = params.size() > 0 || valueType != null;
-// CtTypeReference> newValueType = typeMatches(valueType,
-// checkParameterType ? params.get(valueParamIdx).getType() : null);
-// if (newValueType != null || checkParameterType == false) {
-// // the method parameter matches with expected valueType
-// if (newValueType != valueType) {
-// // there is new value type, we have to update it in mmField
-// if (mmField.getValueType() == valueType) {
-// mmField.setValueType(newValueType);
-// } else if (mmField.getItemValueType() == valueType) {
-// mmField.setItemValueType(newValueType);
-// } else {
-// throw new SpoonException("Cannot update valueType");
-// }
-// }
-// unhandledSignatures.remove(k);
-// if (fieldGetter.get() == null) {
-// // we do not have method yet for this field
-// fieldSetter.accept(method);
-// } else {
-// // there is already a method for this field
-// if (valueType != null) {
-// // 1) choose the method with more fitting valueType
-// boolean oldMatches = valueType
-// .equals(fieldGetter.get().getParameters().get(valueParamIdx).getType());
-// boolean newMatches = valueType.equals(method.getParameters().get(valueParamIdx).getType());
-// if (oldMatches != newMatches) {
-// if (oldMatches) {
-// // old is matching
-// // add new as unhandled signature
-// unhandledSignatures.add(k);
-// } else {
-// // new is matching
-// // add old as unhandled signature
-// unhandledSignatures.add(fieldGetter.get().getSignature());
-// // and set new is current field value
-// fieldSetter.accept(method);
-// }
-// // do report problem. The conflict was resolved
-// return;
-// }
-// } else if (params.isEmpty()) {
-// // the value type is not known yet and there is no input
-// // parameter. We are resolving getter field.
-// // choose the getter whose return value is a collection
-// // of second one
-// CtTypeReference> oldReturnType = fieldGetter.get().getType();
-// CtTypeReference> newReturnType = method.getType();
-// boolean isOldIterable = oldReturnType.isSubtypeOf(iterableRef);
-// boolean isNewIterable = newReturnType.isSubtypeOf(iterableRef);
-// if (isOldIterable != isNewIterable) {
-// // they are not some. Only one of them is iterable
-// if (isOldIterable) {
-// if (isIterableOf(oldReturnType, newReturnType)) {
-// // use old method, which is multivalue
-// // represantation of new method
-// // OK - no conflict. Finish
-// return;
-// }
-// } else {
-// if (isIterableOf(newReturnType, oldReturnType)) {
-// // use new method, which is multivalue
-// // represantation of old method
-// fieldSetter.accept(method);
-// // OK - no conflict. Finish
-// return;
-// }
-// }
-// // else report ambiguity
-// }
-// }
-//
-// problems.write(mmField.getOwnerType().getName() + " has ambiguous " + fieldName + " :").writeln().incTab()
-// .write(fieldGetter.get().getType() + "#" + getDeclaringTypeSignature(fieldGetter.get()))
-// .writeln().write(method.getType() + "#" + getDeclaringTypeSignature(method)).writeln()
-// .decTab();
-// }
-// }
-// };
-// }
-
-
-
- private Set propertyGetterAndSetter = new HashSet<>(
- Arrays.asList(PropertyGetter.class.getName(), PropertySetter.class.getName()));
+ /**
+ * adds all {@link MMField}s of `ctType`
+ * @param mmType the owner of to be created fields
+ * @param ctType to be scanned {@link CtType}
+ */
private void addFieldsOfType(MMType mmType, CtType> ctType) {
ctType.getTypeMembers().forEach(typeMember -> {
if (typeMember instanceof CtMethod>) {
@@ -372,7 +229,8 @@ private void addFieldsOfType(MMType mmType, CtType> ctType) {
"spoon.reflect.visitor.CtVisitable",
"spoon.reflect.visitor.chain.CtQueryable",
"spoon.template.TemplateParameter",
- "java.lang.Iterable"));
+ "java.lang.Iterable",
+ "java.io.Serializable"));
/**
@@ -387,28 +245,17 @@ private void addFieldsOfSuperType(MMType mmType, CtTypeReference> superTypeRef
CtType> superType = superTypeRef.getDeclaration();
if (superType == null) {
if (EXPECTED_TYPES_NOT_IN_CLASSPATH.contains(superTypeRef.getQualifiedName()) == false) {
- problems.write("Type not in model: " + superTypeRef.getQualifiedName()).writeln();
+ throw new SpoonException("Cannot create spoon meta model. The class " + superTypeRef.getQualifiedName() + " is missing class path");
}
return;
}
+ //call getOrCreateMMType recursively for super types
MMType superMMType = getOrCreateMMType(superType);
if (superMMType != mmType) {
mmType.addSuperType(superMMType);
}
}
- /**
- * @param type
- * @return name of {@link MMType}, which represents Spoon model {@link CtType}
- */
- public static String getMMTypeIntefaceName(CtType> type) {
- String name = type.getSimpleName();
- if (name.endsWith(CLASS_SUFFIX)) {
- name = name.substring(0, name.length() - CLASS_SUFFIX.length());
- }
- return name;
- }
-
static V getOrCreate(Map map, K key, Supplier valueCreator) {
V value = map.get(key);
if (value == null) {
@@ -433,29 +280,6 @@ static boolean containsObject(Iterable extends Object> iter, Object o) {
return false;
}
- /**
- * @param iface the interface of spoon model element
- * @return {@link CtClass} of Spoon model which implements the spoon model interface. null if there is no implementation.
- */
- public static CtClass> getImplementationOfInterface(CtInterface> iface) {
- String impl = iface.getQualifiedName().replace("spoon.reflect", "spoon.support.reflect") + CLASS_SUFFIX;
- return (CtClass>) iface.getFactory().Type().get(impl);
- }
-
- /**
- * @param impl the implementation of spoon model element
- * @return {@link CtInterface} of Spoon model which represents API of the spoon model class. null if there is no implementation.
- */
- public static CtInterface> getInterfaceOfImplementation(CtClass> impl) {
- String iface = impl.getQualifiedName();
- if (iface.endsWith(CLASS_SUFFIX) == false || iface.startsWith("spoon.support.reflect.") == false) {
- throw new SpoonException("Unexpected spoon model implementation class: " + impl.getQualifiedName());
- }
- iface = iface.substring(0, iface.length() - CLASS_SUFFIX.length());
- iface = iface.replace("spoon.support.reflect", "spoon.reflect");
- return (CtInterface>) impl.getFactory().Type().get(iface);
- }
-
/**
* @param method to be checked method
* @return {@link CtRole} of spoon model method. Looking into all super class/interface implementations of this method
@@ -474,10 +298,9 @@ public static CtRole getRoleOfMethod(CtMethod> method) {
}
/**
- * Looks for method in superClass and superInterface hierarchy for the method with required annotationType
- * @param method
- * @param annotationType
- * @return
+ * @param method a start method
+ * @param annotationType a searched annotation type
+ * @return annotation from the first method in superClass and superInterface hierarchy for the method with required annotationType
*/
private static CtAnnotation getInheritedAnnotation(CtMethod> method, CtTypeReference annotationType) {
CtAnnotation annotation = method.getAnnotation(annotationType);
@@ -500,7 +323,7 @@ private static CtAnnotation getInheritedAnnotation(CtM
return annotation;
}
- public Collection getMMTypes() {
- return Collections.unmodifiableCollection(name2mmType.values());
+ public Factory getFactory() {
+ return factory;
}
}
From 745f25ad92ab72bbeba5ba33575f59697f83a1d1 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Sat, 28 Oct 2017 13:45:38 +0200
Subject: [PATCH 3/4] fix(test): Meta model doesn't have to follow naming of
test classes
---
src/test/java/spoon/test/main/MainTest.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java
index 426acf6927e..47c0d6f5eeb 100644
--- a/src/test/java/spoon/test/main/MainTest.java
+++ b/src/test/java/spoon/test/main/MainTest.java
@@ -410,6 +410,11 @@ public void testTest() throws Exception {
// if one analyzes src/main/java and src/test/java at the same time
// this helps a lot to easily automatically differentiate app classes and test classes
for (CtType t : launcher.getFactory().getModel().getAllTypes()) {
+ if (t.getPackage().getQualifiedName().equals("spoon.metamodel")
+ || t.getPackage().getQualifiedName().startsWith("spoon.generating")) {
+ //Meta model classes doesn't have to follow test class naming conventions
+ continue;
+ }
assertTrue(t.getQualifiedName() + " is not clearly a test class, it should contain 'test' either in its package name or class name", t.getQualifiedName().matches("(?i:.*test.*)"));
}
}
From c7ddd92860e42362a7f6651a3567ffa90b97eec1 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Sun, 29 Oct 2017 08:18:59 +0100
Subject: [PATCH 4/4] move SpoonMetaModel to spoon.test.metamodel
---
.../java/spoon/{ => test}/metamodel/MMContainerType.java | 2 +-
src/test/java/spoon/{ => test}/metamodel/MMField.java | 8 ++++----
src/test/java/spoon/{ => test}/metamodel/MMMethod.java | 6 +++---
.../java/spoon/{ => test}/metamodel/MMMethodKind.java | 2 +-
src/test/java/spoon/{ => test}/metamodel/MMType.java | 6 +++---
src/test/java/spoon/{ => test}/metamodel/MMTypeKind.java | 2 +-
.../java/spoon/{ => test}/metamodel/SpoonMetaModel.java | 2 +-
7 files changed, 14 insertions(+), 14 deletions(-)
rename src/test/java/spoon/{ => test}/metamodel/MMContainerType.java (98%)
rename src/test/java/spoon/{ => test}/metamodel/MMField.java (99%)
rename src/test/java/spoon/{ => test}/metamodel/MMMethod.java (98%)
rename src/test/java/spoon/{ => test}/metamodel/MMMethodKind.java (99%)
rename src/test/java/spoon/{ => test}/metamodel/MMType.java (97%)
rename src/test/java/spoon/{ => test}/metamodel/MMTypeKind.java (97%)
rename src/test/java/spoon/{ => test}/metamodel/SpoonMetaModel.java (99%)
diff --git a/src/test/java/spoon/metamodel/MMContainerType.java b/src/test/java/spoon/test/metamodel/MMContainerType.java
similarity index 98%
rename from src/test/java/spoon/metamodel/MMContainerType.java
rename to src/test/java/spoon/test/metamodel/MMContainerType.java
index d77415b9fab..f56c1afe621 100644
--- a/src/test/java/spoon/metamodel/MMContainerType.java
+++ b/src/test/java/spoon/test/metamodel/MMContainerType.java
@@ -14,7 +14,7 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
import java.util.List;
import java.util.Map;
diff --git a/src/test/java/spoon/metamodel/MMField.java b/src/test/java/spoon/test/metamodel/MMField.java
similarity index 99%
rename from src/test/java/spoon/metamodel/MMField.java
rename to src/test/java/spoon/test/metamodel/MMField.java
index daafbefa6a1..7c32668106f 100644
--- a/src/test/java/spoon/metamodel/MMField.java
+++ b/src/test/java/spoon/test/metamodel/MMField.java
@@ -14,7 +14,10 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
+
+import static spoon.test.metamodel.SpoonMetaModel.addUniqueObject;
+import static spoon.test.metamodel.SpoonMetaModel.getOrCreate;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,9 +36,6 @@
import spoon.reflect.reference.CtTypeReference;
import spoon.support.DerivedProperty;
-import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
-import static spoon.metamodel.SpoonMetaModel.getOrCreate;
-
/**
* Represents a field of Spoon model type.
* Each MMField belongs to one MMType
diff --git a/src/test/java/spoon/metamodel/MMMethod.java b/src/test/java/spoon/test/metamodel/MMMethod.java
similarity index 98%
rename from src/test/java/spoon/metamodel/MMMethod.java
rename to src/test/java/spoon/test/metamodel/MMMethod.java
index 2c16edb9ff1..fbde851e353 100644
--- a/src/test/java/spoon/metamodel/MMMethod.java
+++ b/src/test/java/spoon/test/metamodel/MMMethod.java
@@ -14,7 +14,9 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
+
+import static spoon.test.metamodel.SpoonMetaModel.addUniqueObject;
import java.util.ArrayList;
import java.util.Collections;
@@ -25,8 +27,6 @@
import spoon.reflect.reference.CtTypeReference;
import spoon.support.visitor.MethodTypingContext;
-import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
-
/**
* Represents a method of a {@link MMField} of a {@link MMType}.
* Each MMMethod belongs to one MMField
diff --git a/src/test/java/spoon/metamodel/MMMethodKind.java b/src/test/java/spoon/test/metamodel/MMMethodKind.java
similarity index 99%
rename from src/test/java/spoon/metamodel/MMMethodKind.java
rename to src/test/java/spoon/test/metamodel/MMMethodKind.java
index 5e71f2816d9..7d1693da928 100644
--- a/src/test/java/spoon/metamodel/MMMethodKind.java
+++ b/src/test/java/spoon/test/metamodel/MMMethodKind.java
@@ -14,7 +14,7 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
import java.util.function.Predicate;
diff --git a/src/test/java/spoon/metamodel/MMType.java b/src/test/java/spoon/test/metamodel/MMType.java
similarity index 97%
rename from src/test/java/spoon/metamodel/MMType.java
rename to src/test/java/spoon/test/metamodel/MMType.java
index d957119c054..4fd261e0c7e 100644
--- a/src/test/java/spoon/metamodel/MMType.java
+++ b/src/test/java/spoon/test/metamodel/MMType.java
@@ -14,7 +14,9 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
+
+import static spoon.test.metamodel.SpoonMetaModel.addUniqueObject;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,8 +32,6 @@
import spoon.reflect.path.CtRole;
import spoon.support.visitor.ClassTypingContext;
-import static spoon.metamodel.SpoonMetaModel.addUniqueObject;
-
/**
* Represents a type of Spoon model AST node
*/
diff --git a/src/test/java/spoon/metamodel/MMTypeKind.java b/src/test/java/spoon/test/metamodel/MMTypeKind.java
similarity index 97%
rename from src/test/java/spoon/metamodel/MMTypeKind.java
rename to src/test/java/spoon/test/metamodel/MMTypeKind.java
index aab982941a0..617964eeab9 100644
--- a/src/test/java/spoon/metamodel/MMTypeKind.java
+++ b/src/test/java/spoon/test/metamodel/MMTypeKind.java
@@ -14,7 +14,7 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
/**
* Represents type of Spoon model class
diff --git a/src/test/java/spoon/metamodel/SpoonMetaModel.java b/src/test/java/spoon/test/metamodel/SpoonMetaModel.java
similarity index 99%
rename from src/test/java/spoon/metamodel/SpoonMetaModel.java
rename to src/test/java/spoon/test/metamodel/SpoonMetaModel.java
index 617365ffea2..cbce83b966f 100644
--- a/src/test/java/spoon/metamodel/SpoonMetaModel.java
+++ b/src/test/java/spoon/test/metamodel/SpoonMetaModel.java
@@ -14,7 +14,7 @@
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
-package spoon.metamodel;
+package spoon.test.metamodel;
import java.io.File;
import java.lang.annotation.Annotation;