package de.plushnikov.intellij.plugin.provider; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeElement; import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.impl.source.PsiExtensibleClass; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.containers.ContainerUtil; import de.plushnikov.intellij.plugin.agent.transformer.ModifierVisibilityClassFileTransformer; import de.plushnikov.intellij.plugin.extension.LombokProcessorExtensionPoint; import de.plushnikov.intellij.plugin.processor.Processor; import de.plushnikov.intellij.plugin.processor.ValProcessor; import de.plushnikov.intellij.plugin.processor.modifier.ModifierProcessor; import de.plushnikov.intellij.plugin.settings.ProjectSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; /** * Provides support for lombok generated elements * * @author Plushnikov Michail */ public class LombokAugmentProvider extends PsiAugmentProvider { private static final Logger log = Logger.getInstance(LombokAugmentProvider.class.getName()); private final ValProcessor valProcessor = new ValProcessor(); private final Collection<ModifierProcessor> modifierProcessors; public LombokAugmentProvider() { log.debug("LombokAugmentProvider created"); modifierProcessors = Arrays.asList(getModifierProcessors()); } /** * Support method required by patcher project and {@link ModifierVisibilityClassFileTransformer}. * Provides a simple way to inject modifiers into older versions of IntelliJ. Return of the null value is dictated by legacy IntelliJ API. * * @param modifierList PsiModifierList that is being queried * @param name String name of the PsiModifier * @return {@code Boolean.TRUE} if modifier exists (explicitly set by modifier transformers of the plugin), {@code null} otherwise. */ public Boolean hasModifierProperty(@NotNull PsiModifierList modifierList, @NotNull final String name) { if (DumbService.isDumb(modifierList.getProject())) { return null; } final Set<String> modifiers = this.transformModifiers(modifierList, Collections.<String>emptySet()); if (modifiers.contains(name)) { return Boolean.TRUE; } return null; } @NotNull //@Override //May cause issues with older versions of IDEA SDK that are currently supported protected Set<String> transformModifiers(@NotNull PsiModifierList modifierList, @NotNull final Set<String> modifiers) { // make copy of original modifiers Set<String> result = ContainerUtil.newHashSet(modifiers); // Loop through all available processors and give all of them a chance to respond for (ModifierProcessor processor : modifierProcessors) { if (processor.isSupported(modifierList)) { processor.transformModifiers(modifierList, result); } } return result; } @Nullable //@Override //May cause issues with older versions of IDEA SDK that are currently supported protected PsiType inferType(PsiTypeElement typeElement) { if (null == typeElement || DumbService.isDumb(typeElement.getProject()) || !valProcessor.isEnabled(typeElement.getProject())) { return null; } return valProcessor.inferType(typeElement); } @NotNull @Override public <Psi extends PsiElement> List<Psi> getAugments(@NotNull PsiElement element, @NotNull final Class<Psi> type) { final List<Psi> emptyResult = Collections.emptyList(); // skip processing during index rebuild final Project project = element.getProject(); if (DumbService.isDumb(project)) { return emptyResult; } // Expecting that we are only augmenting an PsiClass // Don't filter !isPhysical elements or code auto completion will not work if (!(element instanceof PsiExtensibleClass) || !element.isValid()) { return emptyResult; } // Skip processing of Annotations and Interfaces if (((PsiClass) element).isAnnotationType() || ((PsiClass) element).isInterface()) { return emptyResult; } // skip processing if plugin is disabled if (!ProjectSettings.isLombokEnabledInProject(project)) { return emptyResult; } final PsiClass psiClass = (PsiClass) element; if (type == PsiField.class) { return CachedValuesManager.getCachedValue(element, new FieldLombokCachedValueProvider<Psi>(type, psiClass)); } else if (type == PsiMethod.class) { return CachedValuesManager.getCachedValue(element, new MethodLombokCachedValueProvider<Psi>(type, psiClass)); } else if (type == PsiClass.class) { return CachedValuesManager.getCachedValue(element, new ClassLombokCachedValueProvider<Psi>(type, psiClass)); } else { return emptyResult; } } private ModifierProcessor[] getModifierProcessors() { return LombokProcessorExtensionPoint.EP_NAME_MODIFIER_PROCESSOR.getExtensions(); } private static class FieldLombokCachedValueProvider<Psi extends PsiElement> extends LombokCachedValueProvider<Psi> { FieldLombokCachedValueProvider(Class<Psi> type, PsiClass psiClass) { super(type, psiClass); } } private static class MethodLombokCachedValueProvider<Psi extends PsiElement> extends LombokCachedValueProvider<Psi> { MethodLombokCachedValueProvider(Class<Psi> type, PsiClass psiClass) { super(type, psiClass); } } private static class ClassLombokCachedValueProvider<Psi extends PsiElement> extends LombokCachedValueProvider<Psi> { ClassLombokCachedValueProvider(Class<Psi> type, PsiClass psiClass) { super(type, psiClass); } } private static class LombokCachedValueProvider<Psi extends PsiElement> implements CachedValueProvider<List<Psi>> { private final Class<Psi> type; private final PsiClass psiClass; LombokCachedValueProvider(Class<Psi> type, PsiClass psiClass) { this.type = type; this.psiClass = psiClass; } @Nullable @Override public Result<List<Psi>> compute() { if (log.isDebugEnabled()) { log.debug(String.format("Process call for type: %s class: %s", type, psiClass.getQualifiedName())); } final List<Psi> result = new ArrayList<Psi>(); final Collection<Processor> lombokProcessors = LombokProcessorProvider.getInstance(psiClass.getProject()).getLombokProcessors(type); for (Processor processor : lombokProcessors) { result.addAll((Collection<Psi>) processor.process(psiClass)); } return new Result<List<Psi>>(result, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); } } }