blob: 66507138ed2d682c0f13368448a36a6e5421187a [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.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.*;
import com.intellij.util.xml.reflect.DomCollectionChildDescription;
import com.intellij.util.xml.reflect.DomFixedChildDescription;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author peter
*/
public class DomImplUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.xml.impl.DomImplUtil");
private DomImplUtil() {
}
public static void assertValidity(DomElement element, String msg) {
if (element instanceof DomFileElementImpl) {
final String s = ((DomFileElementImpl)element).checkValidity();
if (s != null) {
throw new AssertionError(s);
}
return;
}
final DomInvocationHandler handler = DomManagerImpl.getDomInvocationHandler(element);
assert handler != null;
try {
handler.assertValid();
}
catch (AssertionError e) {
throw new AssertionError(msg + e.getMessage());
}
}
public static boolean isTagValueGetter(final JavaMethod method) {
if (!isGetter(method)) {
return false;
}
if (hasTagValueAnnotation(method)) {
return true;
}
if ("getValue".equals(method.getName())) {
if (method.getAnnotation(SubTag.class) != null) return false;
if (method.getAnnotation(SubTagList.class) != null) return false;
if (method.getAnnotation(Convert.class) != null || method.getAnnotation(Resolve.class) != null) {
return !ReflectionUtil.isAssignable(GenericDomValue.class, method.getReturnType());
}
if (ReflectionUtil.isAssignable(DomElement.class, method.getReturnType())) return false;
return true;
}
return false;
}
private static boolean hasTagValueAnnotation(final JavaMethod method) {
return method.getAnnotation(TagValue.class) != null;
}
public static boolean isGetter(final JavaMethod method) {
@NonNls final String name = method.getName();
final boolean isGet = name.startsWith("get");
final boolean isIs = !isGet && name.startsWith("is");
if (!isGet && !isIs) {
return false;
}
if (method.getGenericParameterTypes().length != 0) {
return false;
}
final Type returnType = method.getGenericReturnType();
if (isGet) {
return returnType != void.class;
}
return DomReflectionUtil.canHaveIsPropertyGetterPrefix(returnType);
}
public static boolean isTagValueSetter(final JavaMethod method) {
boolean setter = method.getName().startsWith("set") && method.getGenericParameterTypes().length == 1 && method.getReturnType() == void.class;
return setter && (hasTagValueAnnotation(method) || "setValue".equals(method.getName()));
}
@Nullable
public static DomNameStrategy getDomNameStrategy(final Class<?> rawType, boolean isAttribute) {
Class aClass = null;
if (isAttribute) {
NameStrategyForAttributes annotation = DomReflectionUtil.findAnnotationDFS(rawType, NameStrategyForAttributes.class);
if (annotation != null) {
aClass = annotation.value();
}
}
if (aClass == null) {
NameStrategy annotation = DomReflectionUtil.findAnnotationDFS(rawType, NameStrategy.class);
if (annotation != null) {
aClass = annotation.value();
}
}
if (aClass != null) {
if (HyphenNameStrategy.class.equals(aClass)) return DomNameStrategy.HYPHEN_STRATEGY;
if (JavaNameStrategy.class.equals(aClass)) return DomNameStrategy.JAVA_STRATEGY;
try {
return (DomNameStrategy)aClass.newInstance();
}
catch (InstantiationException e) {
LOG.error(e);
}
catch (IllegalAccessException e) {
LOG.error(e);
}
}
return null;
}
public static List<XmlTag> findSubTags(@NotNull final XmlTag tag, final EvaluatedXmlName name, final XmlFile file) {
if (!tag.isValid()) {
throw new AssertionError("Invalid tag");
}
final XmlTag[] tags = tag.getSubTags();
if (tags.length == 0) {
return Collections.emptyList();
}
return ContainerUtil.findAll(tags, new Condition<XmlTag>() {
@Override
public boolean value(XmlTag childTag) {
try {
return isNameSuitable(name, childTag.getLocalName(), childTag.getName(), childTag.getNamespace(), file);
}
catch (PsiInvalidElementAccessException e) {
if (!childTag.isValid()) {
LOG.error("tag.getSubTags() returned invalid, " +
"tag=" + tag + ", " +
"containing file: " + tag.getContainingFile() +
"subTag.parent=" + childTag.getNode().getTreeParent());
return false;
}
throw e;
}
}
});
}
public static List<XmlTag> findSubTags(final XmlTag[] tags, final EvaluatedXmlName name, final XmlFile file) {
if (tags.length == 0) {
return Collections.emptyList();
}
return ContainerUtil.findAll(tags, new Condition<XmlTag>() {
@Override
public boolean value(XmlTag childTag) {
return isNameSuitable(name, childTag, file);
}
});
}
public static boolean isNameSuitable(final XmlName name, final XmlTag tag, @NotNull final DomInvocationHandler handler, final XmlFile file) {
return isNameSuitable(handler.createEvaluatedXmlName(name), tag, file);
}
private static boolean isNameSuitable(final EvaluatedXmlName evaluatedXmlName, final XmlTag tag, final XmlFile file) {
return isNameSuitable(evaluatedXmlName, tag.getLocalName(), tag.getName(), tag.getNamespace(), file);
}
public static boolean isNameSuitable(final EvaluatedXmlName evaluatedXmlName, final String localName, final String qName, final String namespace,
final XmlFile file) {
final String localName1 = evaluatedXmlName.getXmlName().getLocalName();
return (localName1.equals(localName) || localName1.equals(qName)) && evaluatedXmlName.isNamespaceAllowed(namespace, file,
!localName1.equals(qName));
}
@Nullable
public static XmlName createXmlName(@NotNull String name, Type type, @Nullable JavaMethod javaMethod) {
final Class<?> aClass = getErasure(type);
if (aClass == null) return null;
String key = getNamespaceKey(aClass);
if (key == null && javaMethod != null) {
for (final Method method : javaMethod.getHierarchy()) {
final String key1 = getNamespaceKey(method.getDeclaringClass());
if (key1 != null) {
return new XmlName(name, key1);
}
}
}
return new XmlName(name, key);
}
@Nullable
private static Class<?> getErasure(Type type) {
if (type instanceof Class) {
return (Class)type;
}
if (type instanceof ParameterizedType) {
return getErasure(((ParameterizedType)type).getRawType());
}
if (type instanceof TypeVariable) {
for (final Type bound : ((TypeVariable)type).getBounds()) {
final Class<?> aClass = getErasure(bound);
if (aClass != null) {
return aClass;
}
}
}
if (type instanceof WildcardType) {
final WildcardType wildcardType = (WildcardType)type;
for (final Type bound : wildcardType.getUpperBounds()) {
final Class<?> aClass = getErasure(bound);
if (aClass != null) {
return aClass;
}
}
}
return null;
}
@Nullable
private static String getNamespaceKey(@NotNull Class<?> type) {
final Namespace namespace = DomReflectionUtil.findAnnotationDFS(type, Namespace.class);
return namespace != null ? namespace.value() : null;
}
@Nullable
public static XmlName createXmlName(@NotNull final String name, final JavaMethod method) {
return createXmlName(name, method.getGenericReturnType(), method);
}
public static List<XmlTag> getCustomSubTags(final DomInvocationHandler handler, final XmlTag[] subTags, final XmlFile file) {
if (subTags.length == 0) {
return Collections.emptyList();
}
final DomGenericInfoEx info = handler.getGenericInfo();
final Set<XmlName> usedNames = new THashSet<XmlName>();
List<? extends DomCollectionChildDescription> collectionChildrenDescriptions = info.getCollectionChildrenDescriptions();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = collectionChildrenDescriptions.size(); i < size; i++) {
DomCollectionChildDescription description = collectionChildrenDescriptions.get(i);
usedNames.add(description.getXmlName());
}
List<? extends DomFixedChildDescription> fixedChildrenDescriptions = info.getFixedChildrenDescriptions();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = fixedChildrenDescriptions.size(); i < size; i++) {
DomFixedChildDescription description = fixedChildrenDescriptions.get(i);
usedNames.add(description.getXmlName());
}
return ContainerUtil.findAll(subTags, new Condition<XmlTag>() {
@Override
public boolean value(final XmlTag tag) {
if (StringUtil.isEmpty(tag.getName())) return false;
for (final XmlName name : usedNames) {
if (isNameSuitable(name, tag, handler, file)) {
return false;
}
}
return true;
}
});
}
static XmlFile getFile(DomElement domElement) {
if (domElement instanceof DomFileElement) {
return ((DomFileElement)domElement).getFile();
}
DomInvocationHandler handler = DomManagerImpl.getDomInvocationHandler(domElement);
assert handler != null : domElement;
while (handler != null && !(handler instanceof DomRootInvocationHandler) && handler.getXmlTag() == null) {
handler = handler.getParentHandler();
}
if (handler instanceof DomRootInvocationHandler) {
return ((DomRootInvocationHandler)handler).getParent().getFile();
}
assert handler != null;
XmlTag tag = handler.getXmlTag();
while (true) {
final PsiElement parentTag = PhysicalDomParentStrategy.getParentTagCandidate(tag);
if (!(parentTag instanceof XmlTag)) {
return (XmlFile)tag.getContainingFile();
}
tag = (XmlTag)parentTag;
}
}
}