package de.plushnikov.intellij.plugin; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.intellij.pom.PomNamedTarget; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiReferenceList; import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.SortedList; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; /** * Base test case for testing that the Lombok plugin parses the Lombok annotations correctly. */ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightCodeInsightTestCase { private static final Logger LOG = Logger.getLogger(AbstractLombokParsingTestCase.class); protected boolean shouldCompareAnnotations() { return false; } protected boolean shouldCompareModifiers() { return true; } protected boolean shouldCompareCodeBlocks() { return true; } public void doTest() throws IOException { doTest(false); } public void doTest(final boolean lowercaseFirstLetter) throws IOException { final String fileName = getTestName(lowercaseFirstLetter).replace('$', '/') + ".java"; final String beforeFileName = "before/" + fileName; final String afterFileName = "after/" + fileName; doTest(beforeFileName, afterFileName); } protected void doTest(final String beforeFileName, final String afterFileName) throws IOException { final PsiFile psiDelombokFile = loadToPsiFile(afterFileName); final PsiFile psiLombokFile = loadToPsiFile(beforeFileName); if (!(psiLombokFile instanceof PsiJavaFile) || !(psiDelombokFile instanceof PsiJavaFile)) { fail("The test file type is not supported"); } final PsiJavaFile beforeFile = (PsiJavaFile) psiLombokFile; final PsiJavaFile afterFile = (PsiJavaFile) psiDelombokFile; PsiClass[] beforeClasses = beforeFile.getClasses(); PsiClass[] afterClasses = afterFile.getClasses(); compareClasses(beforeClasses, afterClasses); } private void compareClasses(PsiClass[] beforeClasses, PsiClass[] afterClasses) { String before = "Before classes: " + Arrays.toString(beforeClasses); String after = "After classes: " + Arrays.toString(afterClasses); assertEquals("Class counts are different " + before + " <> " + after, afterClasses.length, beforeClasses.length); for (PsiClass afterClass : afterClasses) { boolean compared = false; for (PsiClass beforeClass : beforeClasses) { if (Objects.equal(afterClass.getName(), beforeClass.getName())) { compareTwoClasses(beforeClass, afterClass); compared = true; } } assertTrue("Class names are not equal, class (" + afterClass.getName() + ") not found", compared); } } private void compareTwoClasses(PsiClass beforeClass, PsiClass afterClass) { LOG.info("Comparing classes " + beforeClass.getName() + " with " + afterClass.getName()); PsiModifierList beforeFieldModifierList = beforeClass.getModifierList(); PsiModifierList afterFieldModifierList = afterClass.getModifierList(); compareContainingClasses(beforeClass, afterClass); compareModifiers(beforeFieldModifierList, afterFieldModifierList); compareFields(beforeClass, afterClass); compareMethods(beforeClass, afterClass); compareConstructors(beforeClass, afterClass); compareInnerClasses(beforeClass, afterClass); LOG.debug("Compared classes IntelliJ " + beforeClass.getName() + " with " + afterClass.getName()); } private void compareFields(PsiClass beforeClass, PsiClass afterClass) { PsiField[] beforeClassFields = beforeClass.getFields(); PsiField[] afterClassFields = afterClass.getFields(); LOG.debug("IntelliJ fields for class " + beforeClass.getName() + ": " + Arrays.toString(beforeClassFields)); LOG.debug("Theirs fields for class " + afterClass.getName() + ": " + Arrays.toString(afterClassFields)); assertEquals("Field counts are different for Class " + beforeClass.getName(), afterClassFields.length, beforeClassFields.length); for (PsiField afterField : afterClassFields) { boolean compared = false; final PsiModifierList afterFieldModifierList = afterField.getModifierList(); for (PsiField beforeField : beforeClassFields) { if (Objects.equal(afterField.getName(), beforeField.getName())) { final PsiModifierList beforeFieldModifierList = beforeField.getModifierList(); compareModifiers(beforeFieldModifierList, afterFieldModifierList); compareType(beforeField.getType(), afterField.getType(), afterField); compareInitializers(beforeField.getInitializer(), afterField.getInitializer()); compared = true; } } assertTrue("Fieldnames are not equal, Field (" + afterField.getName() + ") not found", compared); } } private void compareInitializers(PsiExpression beforeInitializer, PsiExpression afterInitializer) { String beforeInitializerText = null == beforeInitializer ? "" : beforeInitializer.getText(); String afterInitializerText = null == afterInitializer ? "" : afterInitializer.getText(); assertEquals("Initializers are not equals ", afterInitializerText, beforeInitializerText); } private void compareType(PsiType beforeType, PsiType afterType, PomNamedTarget whereTarget) { if (null != beforeType && null != afterType) { final String afterText = stripJavaLang(afterType.getCanonicalText()); final String beforeText = stripJavaLang(beforeType.getCanonicalText()); assertEquals(String.format("Types are not equal for element: %s", whereTarget.getName()), afterText, beforeText); } } private String stripJavaLang(String canonicalText) { final String prefix = "java.lang."; if (canonicalText.startsWith(prefix)) { canonicalText = canonicalText.substring(prefix.length()); } return canonicalText; } private void compareModifiers(PsiModifierList beforeModifierList, PsiModifierList afterModifierList) { assertNotNull(beforeModifierList); assertNotNull(afterModifierList); if (shouldCompareModifiers()) { for (String modifier : PsiModifier.MODIFIERS) { boolean haveSameModifiers = afterModifierList.hasModifierProperty(modifier) == beforeModifierList.hasModifierProperty(modifier); final PsiMethod afterModifierListParent = PsiTreeUtil.getParentOfType(afterModifierList, PsiMethod.class); assertTrue(modifier + " Modifier is not equal for " + (null == afterModifierListParent ? "..." : afterModifierListParent.getText()), haveSameModifiers); } } if (shouldCompareAnnotations()) { Collection<String> beforeAnnotations = Lists.newArrayList(Collections2.transform(Arrays.asList(beforeModifierList.getAnnotations()), new QualifiedNameFunction())); Collection<String> afterAnnotations = Lists.newArrayList(Collections2.transform(Arrays.asList(afterModifierList.getAnnotations()), new QualifiedNameFunction())); Iterables.removeIf(beforeAnnotations, Predicates.containsPattern("lombok.*")); assertThat("Annotations are different", beforeAnnotations, equalTo(afterAnnotations)); } } private void compareMethods(PsiClass beforeClass, PsiClass afterClass) { PsiMethod[] beforeMethods = beforeClass.getMethods(); PsiMethod[] afterMethods = afterClass.getMethods(); assertEquals("Methods are different for Class: " + beforeClass.getName(), Arrays.toString(toList(afterMethods)), Arrays.toString(toList(beforeMethods))); for (PsiMethod afterMethod : afterMethods) { final Collection<PsiMethod> matchedMethods = filterMethods(beforeMethods, afterMethod); if (matchedMethods.isEmpty()) { fail("Method names are not equal, Method: (" + afterMethod.getName() + ") not found in class : " + beforeClass.getName()); } for (PsiMethod beforeMethod : matchedMethods) { compareMethod(beforeClass, afterClass, afterMethod, beforeMethod); } } } private void compareMethod(PsiClass beforeClass, PsiClass afterClass, PsiMethod afterMethod, PsiMethod beforeMethod) { final PsiModifierList afterModifierList = afterMethod.getModifierList(); PsiModifierList beforeModifierList = beforeMethod.getModifierList(); compareModifiers(beforeModifierList, afterModifierList); compareType(beforeMethod.getReturnType(), afterMethod.getReturnType(), afterMethod); compareParams(beforeMethod.getParameterList(), afterMethod.getParameterList()); compareThrows(beforeMethod.getThrowsList(), afterMethod.getThrowsList(), afterMethod); if (shouldCompareCodeBlocks()) { final PsiCodeBlock beforeMethodBody = beforeMethod.getBody(); final PsiCodeBlock afterMethodBody = afterMethod.getBody(); if (null != beforeMethodBody && null != afterMethodBody) { boolean codeBlocksAreEqual = beforeMethodBody.textMatches(afterMethodBody); if (!codeBlocksAreEqual) { String text1 = beforeMethodBody.getText().replaceAll("java\\.lang\\.", "").replaceAll("\\s+", ""); String text2 = afterMethodBody.getText().replaceAll("java\\.lang\\.", "").replaceAll("\\s+", ""); assertEquals("Methods not equal, Method: (" + afterMethod.getName() + ") Class:" + afterClass.getName(), text2, text1); } } else { if (null != afterMethodBody) { fail("MethodCodeBlocks is null: Method: (" + beforeMethod.getName() + ") Class:" + beforeClass.getName()); } } } } private Collection<PsiMethod> filterMethods(PsiMethod[] beforeMethods, PsiMethod compareMethod) { Collection<PsiMethod> result = new ArrayList<PsiMethod>(); for (PsiMethod psiMethod : beforeMethods) { final PsiParameterList compareMethodParameterList = compareMethod.getParameterList(); final PsiParameterList psiMethodParameterList = psiMethod.getParameterList(); if (compareMethod.getName().equals(psiMethod.getName()) && compareMethodParameterList.getParametersCount() == psiMethodParameterList.getParametersCount()) { final Collection<String> typesOfCompareMethod = mapToTypeString(compareMethodParameterList); final Collection<String> typesOfPsiMethod = mapToTypeString(psiMethodParameterList); if (typesOfCompareMethod.equals(typesOfPsiMethod)) { result.add(psiMethod); } } } return result; } @NotNull private Collection<String> mapToTypeString(PsiParameterList compareMethodParameterList) { Collection<String> result = new ArrayList<String>(); final PsiParameter[] compareMethodParameterListParameters = compareMethodParameterList.getParameters(); for (PsiParameter compareMethodParameterListParameter : compareMethodParameterListParameters) { result.add(stripJavaLang(compareMethodParameterListParameter.getType().getCanonicalText())); } return result; } private String[] toList(PsiMethod[] beforeMethods) { SortedList<String> result = new SortedList<String>(String.CASE_INSENSITIVE_ORDER); for (PsiMethod method : beforeMethods) { result.add(method.getName()); } return result.toArray(new String[result.size()]); } private void compareThrows(PsiReferenceList beforeThrows, PsiReferenceList afterThrows, PsiMethod psiMethod) { PsiClassType[] beforeTypes = beforeThrows.getReferencedTypes(); PsiClassType[] afterTypes = afterThrows.getReferencedTypes(); assertEquals("Throws counts are different for Method :" + psiMethod.getName(), beforeTypes.length, afterTypes.length); for (PsiClassType beforeType : beforeTypes) { boolean found = false; for (PsiClassType afterType : afterTypes) { if (beforeType.equals(afterType)) { found = true; break; } } assertTrue("Expected throw: " + beforeType.getClassName() + " not found on " + psiMethod.getName(), found); } } private void compareConstructors(PsiClass beforeClass, PsiClass afterClass) { PsiMethod[] beforeConstructors = beforeClass.getConstructors(); PsiMethod[] afterConstructors = afterClass.getConstructors(); LOG.debug("IntelliJ constructors for class " + beforeClass.getName() + ": " + Arrays.toString(beforeConstructors)); LOG.debug("Theirs constructors for class " + afterClass.getName() + ": " + Arrays.toString(afterConstructors)); assertEquals("Constructor counts are different for Class: " + beforeClass.getName(), afterConstructors.length, beforeConstructors.length); for (PsiMethod afterConstructor : afterConstructors) { boolean compared = false; final PsiModifierList theirsFieldModifierList = afterConstructor.getModifierList(); for (PsiMethod beforeConstructor : beforeConstructors) { if (afterConstructor.getName().equals(beforeConstructor.getName()) && afterConstructor.getParameterList().getParametersCount() == beforeConstructor.getParameterList().getParametersCount()) { PsiModifierList intellijConstructorModifierList = beforeConstructor.getModifierList(); compareModifiers(intellijConstructorModifierList, theirsFieldModifierList); compareType(beforeConstructor.getReturnType(), afterConstructor.getReturnType(), afterConstructor); compareParams(beforeConstructor.getParameterList(), afterConstructor.getParameterList()); compared = true; break; } } assertTrue("Constructor names are not equal, Method: (" + afterConstructor.getName() + ") not found in class : " + beforeClass.getName(), compared); } } private void compareContainingClasses(PsiClass intellij, PsiClass theirs) { PsiClass intellijContainingClass = intellij.getContainingClass(); PsiClass theirsContainingClass = theirs.getContainingClass(); String intellijContainingClassName = intellijContainingClass == null ? null : intellijContainingClass.toString(); String theirsContainingClassName = theirsContainingClass == null ? null : theirsContainingClass.toString(); LOG.debug("IntelliJ containing class for class " + intellij.getName() + ": " + intellijContainingClassName); LOG.debug("Theirs containing class for class " + theirs.getName() + ": " + theirsContainingClassName); assertEquals("Containing classes different for class: " + intellij.getName(), intellijContainingClassName, theirsContainingClassName); } private void compareInnerClasses(PsiClass intellij, PsiClass theirs) { PsiClass[] intellijClasses = intellij.getInnerClasses(); PsiClass[] theirsClasses = theirs.getInnerClasses(); LOG.debug("IntelliJ inner classes for class " + intellij.getName() + ": " + Arrays.toString(intellijClasses)); LOG.debug("Theirs inner classes for class " + theirs.getName() + ": " + Arrays.toString(theirsClasses)); compareClasses(intellijClasses, theirsClasses); } private void compareParams(PsiParameterList intellij, PsiParameterList theirs) { assertEquals(theirs.getParametersCount(), intellij.getParametersCount()); PsiParameter[] intellijParameters = intellij.getParameters(); PsiParameter[] theirsParameters = theirs.getParameters(); for (int i = 0; i < intellijParameters.length; i++) { PsiParameter intellijParameter = intellijParameters[i]; PsiParameter theirsParameter = theirsParameters[i]; compareType(intellijParameter.getType(), theirsParameter.getType(), theirsParameter); } } private static class QualifiedNameFunction implements Function<PsiAnnotation, String> { @Override public String apply(PsiAnnotation psiAnnotation) { return psiAnnotation.getQualifiedName(); } } }