diff --git a/src/main/java/spoon/reflect/meta/impl/RoleHandlerHelper.java b/src/main/java/spoon/reflect/meta/impl/RoleHandlerHelper.java index d34e164b317..9915ea866bc 100644 --- a/src/main/java/spoon/reflect/meta/impl/RoleHandlerHelper.java +++ b/src/main/java/spoon/reflect/meta/impl/RoleHandlerHelper.java @@ -17,7 +17,11 @@ package spoon.reflect.meta.impl; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import spoon.SpoonException; import spoon.reflect.declaration.CtElement; @@ -32,6 +36,8 @@ public class RoleHandlerHelper { private RoleHandlerHelper() { } + private static Map, List> roleHandlersByClass = new HashMap<>(); + @SuppressWarnings("unchecked") private static final List[] roleHandlers = new List[CtRole.values().length]; static { @@ -47,7 +53,7 @@ private RoleHandlerHelper() { * @param targetClass the class of the to be manipulated node * @param role defines the to be manipulated attribute * @return {@link RoleHandler} implementation which knows how to manipulate the attribute of {@link CtRole} on `targetClass` - * or throws exception if such role does not exists on the `targetClass` + * or throws exception if such role doesn't exist on the `targetClass` */ public static RoleHandler getRoleHandler(Class targetClass, CtRole role) { RoleHandler rh = getOptionalRoleHandler(targetClass, role); @@ -56,11 +62,12 @@ public static RoleHandler getRoleHandler(Class targetClass, } return rh; } + /** - * @param targetClass the class of the to be manipulated node + * @param targetClass the Class of the to be manipulated node * @param role defines the to be manipulated attribute - * @return {@link RoleHandler} implementation which knows how to manipulate the attribute of {@link CtRole} on `targetClass` - * or returns null if such role does not exists on the `targetClass` + * @return {@link RoleHandler} implementation, which knows how to manipulate the attribute of {@link CtRole} on `targetClass` + * or returns null if such role doesn't exist on the `targetClass` */ public static RoleHandler getOptionalRoleHandler(Class targetClass, CtRole role) { List handlers = roleHandlers[role.ordinal()]; @@ -71,4 +78,51 @@ public static RoleHandler getOptionalRoleHandler(Class targ } return null; } + + /** + * @param targetClass a Class whose handlers we are looking for + * @return all {@link RoleHandler}s available for the `targetClass` + */ + public static List getRoleHandlers(Class targetClass) { + List handlers = roleHandlersByClass.get(targetClass); + if (handlers == null) { + List modifiableHandlers = new ArrayList<>(); + for (CtRole role : CtRole.values()) { + RoleHandler roleHandler = getOptionalRoleHandler(targetClass, role); + if (roleHandler != null) { + modifiableHandlers.add(roleHandler); + } + } + handlers = Collections.unmodifiableList(modifiableHandlers); + roleHandlersByClass.put(targetClass, handlers); + } + return handlers; + } + + /** + * @param consumer is called for each {@link RoleHandler} of SpoonModel + */ + public static void forEachRoleHandler(Consumer consumer) { + for (List list : roleHandlers) { + for (RoleHandler roleHandler : list) { + consumer.accept(roleHandler); + } + } + } + + /** + * @param element the {@link CtElement} whose relation from `element.getParent()` to `element` is needed. + * @return {@link RoleHandler} handling relation from `element.getParent()` to `element` + */ + public static RoleHandler getRoleHandlerWrtParent(CtElement element) { + if (element.isParentInitialized() == false) { + return null; + } + CtElement parent = element.getParent(); + CtRole roleInParent = element.getRoleInParent(); + if (roleInParent == null) { + return null; + } + return RoleHandlerHelper.getRoleHandler(parent.getClass(), roleInParent); + } } diff --git a/src/test/java/spoon/test/reflect/meta/MetaModelTest.java b/src/test/java/spoon/test/reflect/meta/MetaModelTest.java index d864e8503ec..d316dc750b6 100644 --- a/src/test/java/spoon/test/reflect/meta/MetaModelTest.java +++ b/src/test/java/spoon/test/reflect/meta/MetaModelTest.java @@ -3,9 +3,12 @@ import org.junit.Test; import spoon.Launcher; +import spoon.Metamodel; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; @@ -69,6 +72,49 @@ public void spoonMetaModelTest() { // assertTrue(String.join("\n", problems), problems.isEmpty()); } @Test + public void testGetRoleHandlersOfClass() { + int countOfIfaces = 0; + for (CtType spoonIface : Metamodel.getAllMetamodelInterfaces()) { + countOfIfaces++; + checkRoleHandlersOfType(spoonIface); + } + assertTrue(countOfIfaces > 10); + } + + private void checkRoleHandlersOfType(CtType iface) { + Class ifaceClass = iface.getActualClass(); + //contract: check that for each Spoon model interface we have correct list of Role handlers + List roleHandlersOfIFace = new ArrayList<>(RoleHandlerHelper.getRoleHandlers(ifaceClass)); + Set allRoleHandlers = new HashSet<>(); + RoleHandlerHelper.forEachRoleHandler(rh -> allRoleHandlers.add(rh)); + for (CtRole role : CtRole.values()) { + RoleHandler rh = RoleHandlerHelper.getOptionalRoleHandler(ifaceClass, role); + if (rh != null) { + assertTrue("RoleHandler for role " + role + " is missing for " + ifaceClass, roleHandlersOfIFace.remove(rh)); + assertTrue("RoleHandler " + rh + " is not accessible by RoleHandlerHelper#forEachRoleHandler()", allRoleHandlers.contains(rh)); + } + } + assertTrue("There are unexpected RoleHandlers " + roleHandlersOfIFace + " for " + ifaceClass, roleHandlersOfIFace.isEmpty()); + } + + @Test + public void testGetParentRoleHandler() { + Launcher launcher = new Launcher(); + Factory factory = launcher.getFactory(); + CtClass type = (CtClass) factory.Core().create(CtClass.class); + CtField field = factory.Field().create(type, Collections.emptySet(), factory.Type().booleanPrimitiveType(), "someField"); + assertSame(type, field.getDeclaringType()); + //contract: RoleHandlerHelper#getParentRoleHandler returns role handler which handles it's relationship to parent + assertSame(CtRole.TYPE_MEMBER, RoleHandlerHelper.getRoleHandlerWrtParent(field).getRole()); + assertSame(CtRole.TYPE_MEMBER, field.getRoleInParent()); + //contract: RoleHandlerHelper#getParentRoleHandler returns null if there is no parent + field.setParent(null); + assertNull(RoleHandlerHelper.getRoleHandlerWrtParent(field)); + //contract: RoleHandlerHelper#getParentRoleHandler returns null if parent relation cannot be handled in this case + //parent of new CtClass is root package - there is no way how to modify that + assertNull(RoleHandlerHelper.getRoleHandlerWrtParent(type)); + } + @Test public void elementAnnotationRoleHandlerTest() { Launcher launcher = new Launcher(); Factory factory = launcher.getFactory();