dev-time pattern injection

This commit is contained in:
Gregory Shrago
2010-07-01 22:30:24 +04:00
parent c283e6f52a
commit 273c3fc7ce
11 changed files with 205 additions and 66 deletions
@@ -16,6 +16,8 @@
package com.intellij.patterns.compiler;
import com.intellij.openapi.components.ServiceManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Gregory.Shrago
@@ -25,5 +27,19 @@ public abstract class PatternCompilerFactory {
return ServiceManager.getService(PatternCompilerFactory.class);
}
public abstract <T> PatternCompiler<T> getPatternCompiler(Class[] patternClasses);
/**
* Retrieves pattern classes registered via com.intellij.patterns.patternClass extension.
* @param alias or null
* @return pattern classes
*/
@NotNull
public abstract Class[] getPatternClasses(@Nullable final String alias);
@NotNull
public abstract <T> PatternCompiler<T> getPatternCompiler(@NotNull Class[] patternClasses);
@NotNull
public <T> PatternCompiler<T> getPatternCompiler(@Nullable final String alias) {
return getPatternCompiler(getPatternClasses(alias));
}
}
@@ -15,13 +15,51 @@
*/
package com.intellij.patterns.compiler;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.FactoryMap;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* @author Gregory.Shrago
*/
public class PatternCompilerFactoryImpl extends PatternCompilerFactory {
public static final ExtensionPointName<PatternClassBean> PATTERN_CLASS_EP =
new ExtensionPointName<PatternClassBean>("com.intellij.patterns.patternClass");
private final Map<String, Class[]> myClasses = new FactoryMap<String, Class[]>() {
@Override
protected Class[] create(String key) {
final ArrayList<Class> result = new ArrayList<Class>(1);
final List<String> typeList = key == null? null : Arrays.asList(key.split(",|\\s"));
for (PatternClassBean bean : PATTERN_CLASS_EP.getExtensions()) {
if (typeList == null || typeList.contains(bean.getAlias())) result.add(bean.getPatternClass());
}
return result.isEmpty()? ArrayUtil.EMPTY_CLASS_ARRAY : result.toArray(new Class[result.size()]);
}
};
private final Map<Class[], PatternCompiler> myCompilers = new FactoryMap<Class[], PatternCompiler>() {
@Override
protected PatternCompiler create(Class[] key) {
return new PatternCompilerImpl(key);
}
};
@NotNull
@Override
public <T> PatternCompiler<T> getPatternCompiler(Class[] patternClasses) {
return new PatternCompilerImpl<T>(patternClasses);
public Class[] getPatternClasses(String alias) {
return myClasses.get(alias);
}
@NotNull
@Override
public <T> PatternCompiler<T> getPatternCompiler(@NotNull Class[] patternClasses) {
return myCompilers.get(patternClasses);
}
}
@@ -18,31 +18,23 @@ package com.intellij.psi;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.CustomLoadingExtensionPointBean;
import com.intellij.openapi.extensions.AbstractExtensionPointBean;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.StandardPatterns;
import com.intellij.patterns.compiler.PatternCompiler;
import com.intellij.patterns.compiler.PatternCompilerFactory;
import com.intellij.util.xmlb.annotations.AbstractCollection;
import com.intellij.util.xmlb.annotations.Property;
import com.intellij.util.xmlb.annotations.Tag;
import com.intellij.util.xmlb.annotations.*;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class PsiReferenceProviderBean extends CustomLoadingExtensionPointBean {
public class PsiReferenceProviderBean extends AbstractExtensionPointBean {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.PsiReferenceProviderBean");
@Tag("className")
public String className;
@Tag("description")
public String description;
@Property(surroundWithTag = false)
@AbstractCollection(surroundWithTag = false, elementTag = "patternClass", elementValueAttribute = "")
public List<String> patternClasses = new ArrayList<String>();
@Property(surroundWithTag = false)
@AbstractCollection(surroundWithTag = false, elementTag = "pattern", elementValueAttribute = "")
public List<String> patterns = new ArrayList<String>();
@AbstractCollection(surroundWithTag = false)
public Info[] patterns;
public String getDescription() {
return description;
@@ -50,7 +42,7 @@ public class PsiReferenceProviderBean extends CustomLoadingExtensionPointBean {
public PsiReferenceProvider instantiate() {
try {
return (PsiReferenceProvider)instantiateExtension(className, ApplicationManager.getApplication().getPicoContainer());
return (PsiReferenceProvider)instantiate(className, ApplicationManager.getApplication().getPicoContainer());
}
catch (ClassNotFoundException e) {
LOG.error(e);
@@ -60,30 +52,28 @@ public class PsiReferenceProviderBean extends CustomLoadingExtensionPointBean {
@Nullable
public ElementPattern<PsiElement> createElementPattern() {
final ArrayList<Class> classes = new ArrayList<Class>();
for (String patternClass : patternClasses) {
try {
classes.add(Class.forName(patternClass, true, getLoaderForClass()));
}
catch (ClassNotFoundException e) {
LOG.error(e);
final PatternCompilerFactory factory = PatternCompilerFactory.getFactory();
if (patterns.length > 1) {
final ElementPattern[] result = new ElementPattern[this.patterns.length];
for (int i = 0, len = this.patterns.length; i < len; i++) {
result[i] = factory.getPatternCompiler(patterns[i].type).compileElementPattern(patterns[i].text);
}
return StandardPatterns.or(result);
}
final PatternCompiler<PsiElement> compiler =
PatternCompilerFactory.getFactory().getPatternCompiler(classes.toArray(new Class[classes.size()]));
if (patterns.size() > 1) {
final ElementPattern[] patterns = new ElementPattern[this.patterns.size()];
for (int i = 0, len = this.patterns.size(); i < len; i++) {
patterns[i] = compiler.compileElementPattern(this.patterns.get(i));
}
return StandardPatterns.or(patterns);
}
else if (!patterns.isEmpty()) {
return compiler.compileElementPattern(patterns.get(0));
else if (patterns.length == 1) {
return factory.<PsiElement>getPatternCompiler(patterns[0].type).compileElementPattern(patterns[0].text);
}
else {
LOG.error("At least one pattern should be specified");
return null;
}
}
@Tag("pattern")
public static class Info {
@Attribute("type")
public String type;
@Text
public String text;
}
}
@@ -39,18 +39,10 @@ public class PatternCompilerImpl<T> implements PatternCompiler<T> {
private static final Logger LOG = Logger.getInstance(PatternCompilerImpl.class.getName());
private final Class[] myPatternClasses;
private Set<Method> myStaticMethods;
public PatternCompilerImpl(final Class[] patternClasses) {
myPatternClasses = patternClasses;
}
private Set<Method> getStaticMethods() {
if (myStaticMethods == null) {
myStaticMethods = getStaticMethods(myPatternClasses);
}
return myStaticMethods;
myStaticMethods = getStaticMethods(patternClasses);
}
protected void preInvoke(Object target, String methodName, Object[] arguments) {
@@ -71,13 +63,12 @@ public class PatternCompilerImpl<T> implements PatternCompiler<T> {
@Override
public ElementPattern<T> compileElementPattern(final String text) {
final Set<Method> staticMethods = getStaticMethods();
return processElementPatternText(text, new Function<Frame, Object>() {
public Object fun(final Frame frame) {
try {
final Object[] args = frame.params.toArray();
preInvoke(frame.target, frame.methodName, args);
return invokeMethod(frame.target, frame.methodName, args, staticMethods);
return invokeMethod(frame.target, frame.methodName, args, myStaticMethods);
}
catch (Throwable throwable) {
throw new IllegalArgumentException(text, throwable);
@@ -338,12 +329,11 @@ public class PatternCompilerImpl<T> implements PatternCompiler<T> {
@Override
public String dumpContextDeclarations() {
final Set<Method> methods = getStaticMethods();
final StringBuilder sb = new StringBuilder();
final THashMap<Class, Collection<Class>> classes = new THashMap<Class, Collection<Class>>();
final THashSet<Class> missingClasses = new THashSet<Class>();
classes.put(Object.class, missingClasses);
for (Method method : methods) {
for (Method method : myStaticMethods) {
for (Class<?> type = method.getReturnType(); type != null && ElementPattern.class.isAssignableFrom(type); type = type.getSuperclass()) {
final Class<?> enclosingClass = type.getEnclosingClass();
if (enclosingClass != null) {
@@ -363,7 +353,7 @@ public class PatternCompilerImpl<T> implements PatternCompiler<T> {
if (aClass == Object.class) continue;
printClass(aClass, classes, sb);
}
for (Method method : methods) {
for (Method method : myStaticMethods) {
printMethodDeclaration(method, sb, classes);
}
for (Class aClass : missingClasses) {
@@ -259,6 +259,7 @@
<extensionPoint name="psi.referenceContributor" interface="com.intellij.psi.PsiReferenceContributor"/>
<extensionPoint name="psi.referenceProvider" beanClass="com.intellij.psi.PsiReferenceProviderBean"/>
<extensionPoint name="patterns.patternClass" beanClass="com.intellij.patterns.compiler.PatternClassBean"/>
<extensionPoint name="pom.declarationSearcher" interface="com.intellij.pom.PomDeclarationSearcher"/>
@@ -267,6 +267,8 @@
<webBrowserUrlProvider implementation="com.intellij.ide.browsers.HtmlWebBrowserUrlProvider" id="static" order="last"/>
<patterns.patternClass className="com.intellij.patterns.XmlPatterns" alias="xml"/>
<lang.smartEnterProcessor language="XML" implementationClass="com.intellij.codeInsight.completion.XmlSmartEnterProcessor"/>
<lang.smartEnterProcessor language="HTML" implementationClass="com.intellij.codeInsight.completion.XmlSmartEnterProcessor"/>
<lang.smartEnterProcessor language="XHTML" implementationClass="com.intellij.codeInsight.completion.XmlSmartEnterProcessor"/>
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="LanguageInjectionConfiguration">
<injection language="Groovy" injector-id="xml">
<display-name>IntelliJ IDEA pattern</display-name>
<place><![CDATA[xmlTag().withLocalName("place").withParent(xmlTag().withLocalName("injection").withParent(xmlTag().withLocalName("component")))]]></place>
<place><![CDATA[xmlTag().withLocalName("pattern").inside(xmlTag().withLocalName("idea-plugin"))]]></place>
</injection>
<injection language="XPath" injector-id="xml">
<display-name>JAXB attribute node</display-name>
<place><![CDATA[xmlAttribute().withLocalName(string().equalTo("node")).inside(xmlTag().withLocalName(string().equalTo("bindings")).withNamespace(string().equalTo("http://java.sun.com/xml/ns/jaxb")))]]></place>
+2
View File
@@ -250,6 +250,8 @@
<indexedRootsProvider implementation="org.jetbrains.plugins.groovy.dsl.StandardDslIndexedRootsProvider"/>
<fileBasedIndex implementation="org.jetbrains.plugins.groovy.dsl.GroovyDslFileIndex"/>
<patterns.patternClass className="org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns" alias="groovy"/>
<!-- control flow -->
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
@@ -15,26 +15,44 @@
*/
package org.intellij.plugins.intelliLang.inject.groovy;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolderEx;
import com.intellij.patterns.compiler.PatternCompilerImpl;
import com.intellij.patterns.compiler.PatternClassBean;
import com.intellij.patterns.compiler.PatternCompilerFactory;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.TextOccurenceProcessor;
import com.intellij.psi.search.UsageSearchContext;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.plugins.intelliLang.inject.InjectorUtils;
import org.intellij.plugins.intelliLang.inject.config.BaseInjection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Gregory.Shrago
*/
public class PatternEditorContextMembersProvider extends NonCodeMembersContributor {
public static final Key<AtomicNotNullLazyValue<PsiFile>> INJECTION_PARSED_CONTEXT = Key.create("INJECTION_PARSED_CONTEXT");
public static final Key<CachedValue<List<PsiElement>>> INJECTION_PARSED_CONTEXT = Key.create("INJECTION_PARSED_CONTEXT");
@Override
public void processDynamicElements(@NotNull PsiType qualifierType,
@@ -43,22 +61,95 @@ public class PatternEditorContextMembersProvider extends NonCodeMembersContribut
ResolveState state) {
final PsiFile file = place.getContainingFile().getOriginalFile();
final BaseInjection injection = file.getUserData(BaseInjection.INJECTION_KEY);
if (injection == null) return;
final PsiFile contextFile =
((UserDataHolderEx)file).putUserDataIfAbsent(INJECTION_PARSED_CONTEXT, new AtomicNotNullLazyValue<PsiFile>() {
@NotNull
@Override
protected PsiFile compute() {
return parseInjectionContext(injection, file.getProject());
}
}).getValue();
for (PsiElement cur = contextFile.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
if (cur instanceof PsiNamedElement && !ResolveUtil.processElement(processor, (PsiNamedElement)cur)) return;
final CachedValueProvider<List<PsiElement>> provider;
if (injection == null) {
provider = createDevProvider(file);
}
else {
provider = createPatternProvider(injection, file);
}
if (provider == null) return;
final List<PsiElement> roots =
((UserDataHolderEx)file).putUserDataIfAbsent(INJECTION_PARSED_CONTEXT,
CachedValuesManager.getManager(file.getProject()).createCachedValue(provider, false)).getValue();
for (PsiElement root : roots) {
for (PsiElement cur = root.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
if (cur instanceof PsiNamedElement && !ResolveUtil.processElement(processor, (PsiNamedElement)cur)) return;
}
}
}
private static PsiFile parseInjectionContext(@NotNull BaseInjection injection, Project project) {
final String text = new PatternCompilerImpl(InjectorUtils.findInjectionSupport(injection.getSupportId()).getPatternClasses()).dumpContextDeclarations();
private static CachedValueProvider<List<PsiElement>> createPatternProvider(final BaseInjection injection, final PsiFile file) {
return new CachedValueProvider<List<PsiElement>>() {
@Override
public Result<List<PsiElement>> compute() {
return new Result<List<PsiElement>>(Collections.<PsiElement>singletonList(
getRootByClasses(InjectorUtils.findInjectionSupport(injection.getSupportId()).getPatternClasses(), file.getProject())),
ArrayUtil.EMPTY_OBJECT_ARRAY);
}
};
}
private static PsiFile getRootByClasses(Class[] classes, Project project) {
final String text = PatternCompilerFactory.getFactory().getPatternCompiler(classes).dumpContextDeclarations();
return PsiFileFactory.getInstance(project).createFileFromText("context.groovy", GroovyFileType.GROOVY_FILE_TYPE, text);
}
@Nullable
private static CachedValueProvider<List<PsiElement>> createDevProvider(final PsiFile file) {
final SmartPsiElementPointer pointer = file.getUserData(FileContextUtil.INJECTED_IN_ELEMENT);
if (pointer == null) return null;
final PsiElement element = pointer.getElement();
if (!(element instanceof XmlText)) return null;
final XmlTag tag = ((XmlText)element).getParentTag();
final XmlTag parentTag = tag == null ? null : tag.getParentTag();
final String parentTagName = parentTag == null ? null : parentTag.getName();
final String name = tag == null ? null : tag.getName();
if ("place".equals(name) && "injection".equals(parentTagName) && parentTag != null) {
return new CachedValueProvider<List<PsiElement>>() {
@Override
public Result<List<PsiElement>> compute() {
return new Result<List<PsiElement>>(getRootsByClassNames(file, parentTag.getAttributeValue("injector-id")), parentTag.getContainingFile());
}
};
}
else if ("pattern".equals(name) && parentTag != null) {
return new CachedValueProvider<List<PsiElement>>() {
@Override
public Result<List<PsiElement>> compute() {
return new Result<List<PsiElement>>(getRootsByClassNames(file, tag.getAttributeValue("type")), parentTag.getContainingFile());
}
};
}
else return null;
}
private static List<PsiElement> getRootsByClassNames(PsiFile file, String type) {
final ArrayList<PsiElement> roots = new ArrayList<PsiElement>(1);
final Project project = file.getProject();
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiClass beanClass = psiFacade.findClass(PatternClassBean.class.getName(), GlobalSearchScope.allScope(project));
if (beanClass != null) {
final GlobalSearchScope scope =
GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), StdFileTypes.XML);
beanClass.getManager().getSearchHelper().processElementsWithWord(new TextOccurenceProcessor() {
@Override
public boolean execute(PsiElement element, int offsetInElement) {
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
final String className = tag == null ? null : tag.getAttributeValue("className");
if (className != null && tag.getLocalName().endsWith("patternClass")) {
ContainerUtil.addIfNotNull(psiFacade.findClass(className, GlobalSearchScope.allScope(project)), roots);
}
return true;
}
}, scope, "patterns.patternClass", UsageSearchContext.IN_FOREIGN_LANGUAGES, true);
}
final Class[] classes = PatternCompilerFactory.getFactory().getPatternClasses(type);
if (classes.length != 0) {
roots.add(getRootByClasses(classes, project));
}
return roots;
}
}
+2
View File
@@ -915,6 +915,8 @@
<patternDialectProvider implementation="com.intellij.packageDependencies.ui.PackagePatternProvider" id="package" order="before file" />
<scopeDescriptorProvider implementation="com.intellij.ide.util.scopeChooser.HierarchyScopeDescriptorProvider"/>
<patterns.patternClass className="com.intellij.patterns.PsiJavaPatterns" alias="java"/>
<customScopesProvider implementation="com.intellij.analysis.PackagesScopesProvider"/>
<highlightErrorFilter implementation="com.intellij.codeInsight.daemon.impl.analysis.JavadocErrorFilter"/>
+2
View File
@@ -63,5 +63,7 @@
<pom.declarationSearcher implementation="com.intellij.util.xml.impl.DomDeclarationSearcher"/>
<elementDescriptionProvider implementation="com.intellij.util.xml.impl.DefaultDomTargetDescriptionProvider" id="domDefault" order="last, before pomDefault"/>
<iconProvider implementation="com.intellij.util.xml.impl.DefaultDomTargetIconProvider" id="domDefault" order="last"/>
<patterns.patternClass className="com.intellij.patterns.DomPatterns" alias="dom"/>
</extensions>
</idea-plugin>