blob: eb5de3eebf439fbacb813478d108055ffd6a7729 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.util.xml;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ConstantFunction;
import com.intellij.util.NotNullFunction;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.containers.ConcurrentInstanceMap;
import com.intellij.util.xml.highlighting.DomElementsAnnotator;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.lang.reflect.Type;
import java.util.*;
/**
* @author peter
*
* @see com.intellij.util.xml.MergingFileDescription
*/
public class DomFileDescription<T> {
public static final ExtensionPointName<DomFileDescription> EP_NAME = ExtensionPointName.create("com.intellij.dom.fileDescription");
private static final Logger LOG = Logger.getInstance("#com.intellij.util.xml.DomFileDescription");
private final ConcurrentInstanceMap<ScopeProvider> myScopeProviders = new ConcurrentInstanceMap<ScopeProvider>();
protected final Class<T> myRootElementClass;
protected final String myRootTagName;
private final String[] myAllPossibleRootTagNamespaces;
private volatile boolean myInitialized;
private final Map<Class<? extends DomElement>,Class<? extends DomElement>> myImplementations = new HashMap<Class<? extends DomElement>, Class<? extends DomElement>>();
private final TypeChooserManager myTypeChooserManager = new TypeChooserManager();
private final List<DomReferenceInjector> myInjectors = new SmartList<DomReferenceInjector>();
private final Map<String, NotNullFunction<XmlTag,List<String>>> myNamespacePolicies = new ConcurrentHashMap<String, NotNullFunction<XmlTag, List<String>>>();
public DomFileDescription(final Class<T> rootElementClass, @NonNls final String rootTagName, @NonNls final String... allPossibleRootTagNamespaces) {
myRootElementClass = rootElementClass;
myRootTagName = rootTagName;
myAllPossibleRootTagNamespaces = allPossibleRootTagNamespaces;
}
public String[] getAllPossibleRootTagNamespaces() {
return myAllPossibleRootTagNamespaces;
}
/**
* Register an implementation class to provide additional functionality for DOM elements.
*
* @param domElementClass interface class.
* @param implementationClass abstract implementation class.
*
* @deprecated use dom.implementation extension point instead
* @see #initializeFileDescription()
*/
public final <T extends DomElement> void registerImplementation(Class<T> domElementClass, Class<? extends T> implementationClass) {
myImplementations.put(domElementClass, implementationClass);
}
/**
* @param namespaceKey namespace identifier
* @see com.intellij.util.xml.Namespace
* @param policy function that takes XML file root tag and returns (maybe empty) list of possible namespace URLs or DTD public ids. This
* function shouldn't use DOM since it may be not initialized for the file at the moment
* @deprecated use {@link #registerNamespacePolicy(String, String...)} or override {@link #getAllowedNamespaces(String, com.intellij.psi.xml.XmlFile)} instead
*/
protected final void registerNamespacePolicy(String namespaceKey, NotNullFunction<XmlTag,List<String>> policy) {
myNamespacePolicies.put(namespaceKey, policy);
}
/**
* @param namespaceKey namespace identifier
* @see com.intellij.util.xml.Namespace
* @param namespaces XML namespace or DTD public or system id value for the given namespaceKey
*/
public final void registerNamespacePolicy(String namespaceKey, final String... namespaces) {
registerNamespacePolicy(namespaceKey, new ConstantFunction<XmlTag, List<String>>(Arrays.asList(namespaces)));
}
/**
* Consider using {@link DomService#getXmlFileHeader(com.intellij.psi.xml.XmlFile)} when implementing this.
*/
@SuppressWarnings({"MethodMayBeStatic"})
@NotNull
public List<String> getAllowedNamespaces(@NotNull String namespaceKey, @NotNull XmlFile file) {
final NotNullFunction<XmlTag, List<String>> function = myNamespacePolicies.get(namespaceKey);
if (function instanceof ConstantFunction) {
return function.fun(null);
}
if (function != null) {
final XmlDocument document = file.getDocument();
if (document != null) {
final XmlTag tag = document.getRootTag();
if (tag != null) {
return function.fun(tag);
}
}
} else {
return Collections.singletonList(namespaceKey);
}
return Collections.emptyList();
}
/**
* @return some version. Override and change (e.g. <code>super.getVersion()+1</code>) when after some changes some files stopped being
* described by this description or vice versa, so that the
* {@link com.intellij.util.xml.DomService#getDomFileCandidates(Class, com.intellij.openapi.project.Project)} index is rebuilt correctly.
*/
public int getVersion() {
return myRootTagName.hashCode();
}
protected final void registerTypeChooser(final Type aClass, final TypeChooser typeChooser) {
myTypeChooserManager.registerTypeChooser(aClass, typeChooser);
}
public final TypeChooserManager getTypeChooserManager() {
return myTypeChooserManager;
}
protected final void registerReferenceInjector(DomReferenceInjector injector) {
myInjectors.add(injector);
}
public List<DomReferenceInjector> getReferenceInjectors() {
return myInjectors;
}
public boolean isAutomaticHighlightingEnabled() {
return true;
}
@Nullable
public Icon getFileIcon(@Iconable.IconFlags int flags) {
return null;
}
/**
* The right place to call
* {@link #registerNamespacePolicy(String, String...)}
* and {@link #registerTypeChooser(java.lang.reflect.Type, TypeChooser)}.
*/
protected void initializeFileDescription() {}
/**
* Create custom DOM annotator that will be used when error-highlighting DOM. The results will be collected to
* {@link com.intellij.util.xml.highlighting.DomElementsProblemsHolder}. The highlighting will be most probably done in an
* {@link com.intellij.util.xml.highlighting.BasicDomElementsInspection} instance.
* @return Annotator or null
*/
@Nullable
public DomElementsAnnotator createAnnotator() {
return null;
}
public final Map<Class<? extends DomElement>,Class<? extends DomElement>> getImplementations() {
if (!myInitialized) {
initializeFileDescription();
myInitialized = true;
}
return myImplementations;
}
@NotNull
public final Class<T> getRootElementClass() {
return myRootElementClass;
}
public final String getRootTagName() {
return myRootTagName;
}
public boolean isMyFile(@NotNull XmlFile file, @Nullable final Module module) {
final Namespace namespace = DomReflectionUtil.findAnnotationDFS(myRootElementClass, Namespace.class);
if (namespace != null) {
final String key = namespace.value();
Set<String> allNs = new HashSet<String>(getAllowedNamespaces(key, file));
if (allNs.isEmpty()) {
return false;
}
XmlFileHeader header = DomService.getInstance().getXmlFileHeader(file);
return allNs.contains(header.getPublicId()) || allNs.contains(header.getSystemId()) || allNs.contains(header.getRootTagNamespace());
}
return true;
}
public boolean acceptsOtherRootTagNames() {
return false;
}
/**
* Get dependency items (the same, as in {@link com.intellij.psi.util.CachedValue}) for file. On any dependency item change, the
* {@link #isMyFile(com.intellij.psi.xml.XmlFile, Module)} method will be invoked once more to ensure that the file description still
* accepts this file
* @param file XML file to get dependencies of
* @return dependency item set
*/
@NotNull
public Set<?> getDependencyItems(XmlFile file) {
return Collections.emptySet();
}
/**
* @param reference DOM reference
* @return element, whose all children will be searched for declaration
*/
@NotNull
public DomElement getResolveScope(GenericDomValue<?> reference) {
final DomElement annotation = getScopeFromAnnotation(reference);
if (annotation != null) return annotation;
return DomUtil.getRoot(reference);
}
/**
* @param element DOM element
* @return element, whose direct children names will be compared by name. Basically it's parameter element's parent (see {@link ParentScopeProvider}).
*/
@NotNull
public DomElement getIdentityScope(DomElement element) {
final DomElement annotation = getScopeFromAnnotation(element);
if (annotation != null) return annotation;
return element.getParent();
}
@Nullable
protected final DomElement getScopeFromAnnotation(final DomElement element) {
final Scope scope = element.getAnnotation(Scope.class);
if (scope != null) {
return myScopeProviders.get(scope.value()).getScope(element);
}
return null;
}
/**
* @see Stubbed
* @return false
*/
public boolean hasStubs() {
return false;
}
public int getStubVersion() {
return 0;
}
}