Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

review: feature: SpoonMetaModel can be build without spoon sources - using shadow classes #1907

Merged
merged 3 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/test/java/spoon/test/api/MetamodelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,27 @@
import spoon.reflect.visitor.filter.AnnotationFilter;
import spoon.reflect.visitor.filter.SuperInheritanceHierarchyFunction;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.test.metamodel.MetamodelConcept;
import spoon.test.metamodel.MetamodelProperty;
import spoon.test.metamodel.SpoonMetaModel;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

public class MetamodelTest {
Expand Down Expand Up @@ -164,4 +175,56 @@ public boolean matches(CtField candidate) {
}

}
@Test
public void testMetamodelWithoutSources() {
//contract: metamodel based on spoon sources delivers is same like metamodel based on shadow classes
SpoonMetaModel runtimeMM = new SpoonMetaModel();
Collection<MetamodelConcept> concepts = runtimeMM.getConcepts();

SpoonMetaModel sourceBasedMM = new SpoonMetaModel(new File("src/main/java"));
Map<String, MetamodelConcept> expectedConceptsByName = new HashMap<>();
sourceBasedMM.getConcepts().forEach(c -> {
expectedConceptsByName.put(c.getName(), c);
});
for (MetamodelConcept runtimeConcept : concepts) {
MetamodelConcept expectedConcept = expectedConceptsByName.remove(runtimeConcept.getName());
assertNotNull(expectedConcept);
assertConceptsEqual(expectedConcept, runtimeConcept);
}
assertEquals(0, expectedConceptsByName.size());
}

private void assertConceptsEqual(MetamodelConcept expectedConcept, MetamodelConcept runtimeConcept) {
assertEquals(expectedConcept.getName(), runtimeConcept.getName());
if (expectedConcept.getModelClass() == null) {
assertNull(runtimeConcept.getModelClass());
} else {
assertNotNull(runtimeConcept.getModelClass());
assertEquals(expectedConcept.getModelClass().getActualClass(), runtimeConcept.getModelClass().getActualClass());
}
assertEquals(expectedConcept.getModelInterface().getActualClass(), runtimeConcept.getModelInterface().getActualClass());
assertEquals(expectedConcept.getKind(), runtimeConcept.getKind());
assertEquals(expectedConcept.getSuperConcepts().size(), runtimeConcept.getSuperConcepts().size());
for (int i = 0; i < expectedConcept.getSuperConcepts().size(); i++) {
assertConceptsEqual(expectedConcept.getSuperConcepts().get(i), runtimeConcept.getSuperConcepts().get(i));
}
Map<CtRole, MetamodelProperty> expectedRoleToProperty = new HashMap(expectedConcept.getRoleToProperty());
for (Map.Entry<CtRole, MetamodelProperty> e : runtimeConcept.getRoleToProperty().entrySet()) {
MetamodelProperty runtimeProperty = e.getValue();
MetamodelProperty expectedProperty = expectedRoleToProperty.remove(e.getKey());
assertPropertiesEqual(expectedProperty, runtimeProperty);
}
assertEquals(0, expectedRoleToProperty.size());
}

private void assertPropertiesEqual(MetamodelProperty expectedProperty, MetamodelProperty runtimeProperty) {
assertSame(expectedProperty.getRole(), runtimeProperty.getRole());
assertEquals(expectedProperty.getName(), runtimeProperty.getName());
assertEquals(expectedProperty.getItemValueType().getActualClass(), runtimeProperty.getItemValueType().getActualClass());
assertEquals(expectedProperty.getOwnerConcept().getName(), runtimeProperty.getOwnerConcept().getName());
assertSame(expectedProperty.getValueContainerType(), runtimeProperty.getValueContainerType());
assertEquals(expectedProperty.getValueType(), runtimeProperty.getValueType());
assertEquals(expectedProperty.isDerived(), runtimeProperty.isDerived());
assertEquals(expectedProperty.isUnsettable(), runtimeProperty.isUnsettable());
}
}
45 changes: 37 additions & 8 deletions src/test/java/spoon/test/metamodel/SpoonMetaModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.function.Supplier;

import spoon.Launcher;
import spoon.Metamodel;
import spoon.SpoonAPI;
import spoon.SpoonException;
import spoon.reflect.annotations.PropertyGetter;
Expand All @@ -42,10 +43,13 @@
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
import spoon.reflect.factory.FactoryImpl;
import spoon.reflect.path.CtRole;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.AllTypeMembersFunction;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.DefaultCoreFactory;
import spoon.support.StandardEnvironment;
import spoon.support.compiler.FileSystemFolder;
import spoon.support.visitor.ClassTypingContext;

Expand Down Expand Up @@ -103,6 +107,18 @@ public SpoonMetaModel(Factory factory) {
}
});
}

/**
* creates a {@link SpoonMetaModel} in runtime mode when spoon sources are not available
*/
public SpoonMetaModel() {
this.factory = new FactoryImpl(new DefaultCoreFactory(), new StandardEnvironment());
for (CtType<?> iface : Metamodel.getAllMetamodelInterfaces()) {
if (iface instanceof CtInterface) {
getOrCreateConcept(iface);
}
}
}

/**
* @return all {@link MetamodelConcept}s of spoon meta model
Expand Down Expand Up @@ -130,15 +146,26 @@ public static String getConceptName(CtType<?> type) {
*/
public static CtClass<?> getImplementationOfInterface(CtInterface<?> iface) {
String impl = replaceApiToImplPackage(iface.getQualifiedName()) + CLASS_SUFFIX;
return (CtClass<?>) iface.getFactory().Type().get(impl);
return (CtClass<?>) getType(impl, iface);
}

private static CtType<?> getType(String qualifiedName, CtElement anElement) {
Class aClass;
try {
aClass = anElement.getClass().getClassLoader().loadClass(qualifiedName);
} catch (ClassNotFoundException e) {
//OK, that interface has no implementation class
return null;
}
return anElement.getFactory().Type().get(aClass);
}

private static final String modelApiPackage = "spoon.reflect";
private static final String modelApiImplPackage = "spoon.support.reflect";

private static String replaceApiToImplPackage(String modelInterfaceQName) {
if (modelInterfaceQName.startsWith(modelApiPackage) == false) {
throw new SpoonException("The qualified name doesn't belong to Spoon model API package: " + modelApiPackage);
throw new SpoonException("The qualified name " + modelInterfaceQName + " doesn't belong to Spoon model API package: " + modelApiPackage);
}
return modelApiImplPackage + modelInterfaceQName.substring(modelApiPackage.length());
}
Expand All @@ -153,7 +180,7 @@ public static CtInterface<?> getInterfaceOfImplementation(CtClass<?> impl) {
}
iface = iface.substring(0, iface.length() - CLASS_SUFFIX.length());
iface = iface.replace("spoon.support.reflect", "spoon.reflect");
return (CtInterface<?>) impl.getFactory().Type().get(iface);
return (CtInterface<?>) getType(iface, impl);
}

private static Factory createFactory(File spoonJavaSourcesDirectory) {
Expand Down Expand Up @@ -248,6 +275,7 @@ private void addFieldsOfType(MetamodelConcept mmConcept, CtType<?> ctType) {

private static Set<String> EXPECTED_TYPES_NOT_IN_CLASSPATH = new HashSet<>(Arrays.asList(
"java.lang.Cloneable",
"java.lang.Object",
"spoon.processing.FactoryAccessor",
"spoon.reflect.visitor.CtVisitable",
"spoon.reflect.visitor.chain.CtQueryable",
Expand All @@ -265,13 +293,14 @@ private void addFieldsOfSuperType(MetamodelConcept concept, CtTypeReference<?> s
if (superTypeRef == null) {
return;
}
CtType<?> superType = superTypeRef.getDeclaration();
if (superType == null) {
if (EXPECTED_TYPES_NOT_IN_CLASSPATH.contains(superTypeRef.getQualifiedName()) == false) {
throw new SpoonException("Cannot create spoon meta model. The class " + superTypeRef.getQualifiedName() + " is missing class path");
}
if (EXPECTED_TYPES_NOT_IN_CLASSPATH.contains(superTypeRef.getQualifiedName())) {
//ignore classes which are not part of spoon model
return;
}
CtType<?> superType = superTypeRef.getTypeDeclaration();
if (superType == null) {
throw new SpoonException("Cannot create spoon meta model. The class " + superTypeRef.getQualifiedName() + " is missing class path");
}
//call getOrCreateConcept recursively for super concepts
MetamodelConcept superConcept = getOrCreateConcept(superType);
if (superConcept != concept) {
Expand Down