| /* |
| * Copyright 2005 Sascha Weinreuter |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.intellij.lang.xpath.xslt.context; |
| |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.UserDataCache; |
| import com.intellij.psi.util.CachedValueProvider; |
| import com.intellij.psi.util.CachedValuesManager; |
| import com.intellij.psi.util.ParameterizedCachedValue; |
| import com.intellij.psi.util.ParameterizedCachedValueProvider; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.intellij.lang.xpath.context.ContextType; |
| import org.intellij.lang.xpath.context.XPathVersion; |
| import org.intellij.lang.xpath.context.functions.Function; |
| import org.intellij.lang.xpath.context.functions.FunctionContext; |
| import org.intellij.lang.xpath.psi.XPath2Type; |
| import org.intellij.lang.xpath.psi.XPathType; |
| import org.intellij.lang.xpath.psi.impl.ResolveUtil; |
| import org.intellij.lang.xpath.xslt.XsltSupport; |
| import org.intellij.lang.xpath.xslt.psi.XsltElementFactory; |
| import org.intellij.lang.xpath.xslt.psi.XsltFunction; |
| import org.intellij.lang.xpath.xslt.psi.XsltStylesheet; |
| import org.intellij.lang.xpath.xslt.util.XsltCodeInsightUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.xml.namespace.QName; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| public class Xslt2ContextProvider extends XsltContextProviderBase { |
| public static final ContextType TYPE = ContextType.lookupOrCreate("XSLT2", XPathVersion.V2); |
| |
| private static final Key<ParameterizedCachedValue<Map<Pair<QName,Integer>,Function>,XmlFile>> FUNCTIONS = Key.create("XSLT_FUNCTIONS"); |
| |
| protected Xslt2ContextProvider(@NotNull XmlElement contextElement) { |
| super(contextElement); |
| } |
| |
| @NotNull |
| @Override |
| public ContextType getContextType() { |
| return TYPE; |
| } |
| |
| @Override |
| protected XPathType getTypeForTag(XmlTag tag, String attribute) { |
| if ("select".equals(attribute)) { |
| final String tagName = tag.getLocalName(); |
| |
| if ("sequence".equals(tagName)) { |
| final XPathType declaredType = XsltCodeInsightUtil.getDeclaredType(tag); |
| if (declaredType != null) { |
| return declaredType; |
| } |
| |
| if (XsltSupport.isFunction(tag.getParentTag())) { |
| final XsltFunction func = XsltElementFactory.getInstance().wrapElement(tag.getParentTag(), XsltFunction.class); |
| return func.getReturnType(); |
| } |
| return XPath2Type.SEQUENCE; |
| } else if ("value-of".equals(tagName) || "copy-of".equals(tagName) || "for-each".equals(tagName)) { |
| return XPath2Type.SEQUENCE; |
| } |
| } else if ("group-by".equals(attribute)) { |
| return XPath2Type.ITEM; |
| } |
| return super.getTypeForTag(tag, attribute); |
| } |
| |
| private static final UserDataCache<FunctionContext, XmlFile, Void> functionContextCache = |
| new UserDataCache<FunctionContext, XmlFile, Void>("xslt2FunctionContext") { |
| |
| @Override |
| protected FunctionContext compute(final XmlFile xmlFile, Void p) { |
| final FunctionContext base = Xslt2FunctionContext.getInstance(); |
| return new FunctionContext() { |
| @Override |
| public Map<Pair<QName, Integer>, Function> getFunctions() { |
| return ContainerUtil.union(base.getFunctions(), getCustomFunctions(xmlFile)); |
| } |
| |
| @Override |
| public boolean allowsExtensions() { |
| return base.allowsExtensions(); |
| } |
| |
| @Override |
| public Function resolve(QName name, int argCount) { |
| final Function f = base.resolve(name, argCount); |
| if (f == null) { |
| return resolveCustomFunction(xmlFile, name, argCount); |
| } |
| return f; |
| } |
| }; |
| } |
| }; |
| |
| @Override |
| @NotNull |
| public FunctionContext createFunctionContext() { |
| final XmlElement contextElement = getContextElement(); |
| return contextElement != null && contextElement.isValid() ? |
| functionContextCache.get((XmlFile)contextElement.getContainingFile(), null) : |
| Xslt2FunctionContext.getInstance(); |
| } |
| |
| private static final UserDataCache<ParameterizedCachedValue<Map<Pair<QName,Integer>,Function>,XmlFile>, XmlFile, Void> ourFunctionCacheProvider = |
| new UserDataCache<ParameterizedCachedValue<Map<Pair<QName,Integer>,Function>,XmlFile>, XmlFile, Void>() { |
| @Override |
| protected ParameterizedCachedValue<Map<Pair<QName,Integer>,Function>,XmlFile> compute(XmlFile file, Void p) { |
| return CachedValuesManager.getManager(file.getProject()).createParameterizedCachedValue(MyFunctionProvider.INSTANCE, false); |
| } |
| }; |
| |
| private static Map<Pair<QName, Integer>, Function> getCustomFunctions(XmlFile file) { |
| final XmlTag rootTag = file.getRootTag(); |
| // Simplified xslt syntax does not allow to declare custom functions (Saxon 9: "An html element must not contain an xsl:function element") |
| if (rootTag != null && XsltSupport.isXsltRootTag(rootTag)) { |
| return ourFunctionCacheProvider.get(FUNCTIONS, file, null).getValue(file); |
| } |
| return Collections.emptyMap(); |
| } |
| |
| @Nullable |
| private static Function resolveCustomFunction(final XmlFile file, final QName name, int argCount) { |
| final Map<Pair<QName, Integer>, Function> functions = getCustomFunctions(file); |
| final Function exactMatch = functions.get(Pair.create(name, argCount)); |
| if (exactMatch != null) { |
| return exactMatch; |
| } |
| |
| Function candidate = null; |
| for (Pair<QName, Integer> pair : functions.keySet()) { |
| if (pair.getFirst().equals(name)) { |
| candidate = functions.get(pair); |
| } |
| } |
| return candidate; |
| } |
| |
| private static class MyFunctionProvider implements ParameterizedCachedValueProvider<Map<Pair<QName, Integer>, Function>, XmlFile> { |
| private static ParameterizedCachedValueProvider<Map<Pair<QName,Integer>,Function>,XmlFile> INSTANCE = new MyFunctionProvider(); |
| |
| @Override |
| public CachedValueProvider.Result<Map<Pair<QName, Integer>, Function>> compute(XmlFile param) { |
| final XmlTag rootTag = param.getRootTag(); |
| assert rootTag != null; |
| |
| final Map<Pair<QName, Integer>, Function> candidates = new HashMap<Pair<QName, Integer>, Function>(); |
| final XsltFunction[] functions = XsltElementFactory.getInstance().wrapElement(rootTag, XsltStylesheet.class).getFunctions(); |
| for (XsltFunction function : functions) { |
| candidates.put(Pair.create(function.getQName(), function.getParameters().length), function); |
| } |
| |
| final Collection<XmlFile> data = ResolveUtil.getDependencies(param); |
| final Object[] dependencies; |
| if (data == null || data.size() == 0) { |
| dependencies = new Object[]{ param }; |
| } else { |
| data.add(param); |
| dependencies = ArrayUtil.toObjectArray(data); |
| } |
| return CachedValueProvider.Result.create(candidates, dependencies); |
| } |
| } |
| } |