package com.intellij.javascript.flex.mxml.schema;
import com.intellij.lang.ASTNode;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.flex.AnnotationBackedDescriptor;
import com.intellij.lang.javascript.psi.JSFile;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.XmlElementFactory;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.xml.SchemaPrefix;
import com.intellij.psi.impl.source.xml.SchemaPrefixReference;
import com.intellij.psi.impl.source.xml.TagNameReference;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MxmlTagNameReference extends TagNameReference {
public MxmlTagNameReference(ASTNode nameElement, boolean startTagFlag) {
super(nameElement, startTagFlag);
}
public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
final String newPackage = getNewPackage(element);
if (newPackage == null) {
return super.bindToElement(element);
}
else {
final XmlTag tag = getTagElement();
if (tag == null || !myStartTagFlag) return tag;
final String newNamespace = newPackage.isEmpty() ? "*" : newPackage + ".*";
String newPrefix = tag.getPrefixByNamespace(newNamespace);
if (newPrefix == null) {
final XmlFile xmlFile = (XmlFile)tag.getContainingFile();
newPrefix = FlexSchemaHandler.getUniquePrefix(newNamespace, xmlFile);
final XmlTag rootTag = xmlFile.getRootTag();
assert rootTag != null;
insertNamespaceDeclaration(rootTag, newNamespace, newPrefix);
}
final SchemaPrefixReference schemaPrefixReference = getSchemaPrefixReference(tag);
final SchemaPrefix schemaPrefix = schemaPrefixReference == null ? null : schemaPrefixReference.resolve();
final String oldPrefix = tag.getNamespacePrefix();
final String newLocalName = FileUtil.getNameWithoutExtension(((PsiFile)element).getName());
tag.setName(StringUtil.isEmpty(newPrefix) ? newLocalName : (newPrefix + ":" + newLocalName));
fixSubTagsPrefixes(tag, oldPrefix, newPrefix);
removeNamespaceDeclarationIfNotUsed(schemaPrefix);
return tag;
}
}
private static void fixSubTagsPrefixes(final XmlTag tag, final String oldPrefix, final String newPrefix) {
final XmlElementDescriptor descriptor = tag.getDescriptor();
if (!(descriptor instanceof ClassBackedElementDescriptor)) {
return;
}
for (final XmlTag subTag : tag.getSubTags()) {
if (Comparing.strEqual(subTag.getNamespacePrefix(), oldPrefix) && subTag.getDescriptor() == null) {
final String oldSubTagName = subTag.getName();
subTag.setName(StringUtil.isEmpty(newPrefix) ? subTag.getLocalName() : (newPrefix + ":" + subTag.getLocalName()));
final XmlElementDescriptor subTagDescriptor = descriptor.getElementDescriptor(subTag, tag);
if (!(subTagDescriptor instanceof AnnotationBackedDescriptor)) {
subTag.setName(oldSubTagName);
}
}
}
}
private static void removeNamespaceDeclarationIfNotUsed(final SchemaPrefix schemaPrefix) {
if (schemaPrefix == null) return;
final Ref<Boolean> hasUsagesRef = new Ref<>(false);
ReferencesSearch.search(schemaPrefix, GlobalSearchScope.fileScope(schemaPrefix.getContainingFile()))
.forEach(reference -> {
final TextRange range = schemaPrefix.getTextRange();
if (range != null
&& (reference.getElement().getTextRange().getStartOffset() + reference.getRangeInElement().getStartOffset()
== range.getStartOffset())
&& reference.getRangeInElement().getLength() == range.getLength()) {
// self reference
return true;
}
hasUsagesRef.set(true);
return false;
});
if (!hasUsagesRef.get()) {
final XmlAttribute attribute = schemaPrefix.getDeclaration();
MxmlLanguageTagsUtil.RemoveNamespaceDeclarationIntention.removeXmlAttribute(attribute);
}
}
@Nullable
private static SchemaPrefixReference getSchemaPrefixReference(final XmlTag tag) {
for (final PsiReference reference : tag.getReferences()) {
if (reference instanceof SchemaPrefixReference) {
return (SchemaPrefixReference)reference;
}
}
return null;
}
@Nullable
private static String getNewPackage(final PsiElement element) {
/*
if (element instanceof JSFile) {
final JSPackageStatement packageStatement = JSPsiImplUtils.findPackageStatement((JSFile)element);
if (packageStatement != null) {
final String qualifiedName = packageStatement.getQualifiedName();
return StringUtil.notNullize(qualifiedName);
}
}
*/
if (element instanceof JSFile || (element instanceof XmlFile && JavaScriptSupportLoader.isMxmlOrFxgFile((XmlFile)element))) {
final VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
if (virtualFile != null) {
final VirtualFile sourceRoot =
ProjectRootManager.getInstance(element.getProject()).getFileIndex().getSourceRootForFile(virtualFile);
if (sourceRoot != null) {
final String relPath = FileUtil.getRelativePath(sourceRoot.getPath(), virtualFile.getPath(), '/');
final int lastSlashIndex = relPath.lastIndexOf("/");
return relPath.substring(0, Math.max(0, lastSlashIndex)).replace("/", ".");
}
}
}
return null;
}
public static void insertNamespaceDeclaration(final @NotNull XmlTag tag, final @NotNull String namespace, final @NotNull String prefix) {
final XmlAttribute[] attributes = tag.getAttributes();
XmlAttribute anchor = null;
for (final XmlAttribute attribute : attributes) {
final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
if (attribute.isNamespaceDeclaration() || (descriptor != null && descriptor.isRequired())) {
anchor = attribute;
}
else {
break;
}
}
@NonNls final String qname = "xmlns" + (prefix.length() > 0 ? ":" + prefix : "");
final XmlAttribute attribute = XmlElementFactory.getInstance(tag.getProject()).createXmlAttribute(qname, namespace);
if (anchor == null) {
tag.add(attribute);
}
else {
tag.addAfter(attribute, anchor);
}
CodeStyleManager.getInstance(tag.getProject()).reformat(tag);
}
}