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 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 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;