| package org.jetbrains.android.refactoring; |
| |
| import com.android.SdkConstants; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.XmlElementFactory; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlChildRole; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.containers.HashMap; |
| import org.jetbrains.android.dom.converters.AndroidResourceReferenceBase; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| public class LayoutUsageData { |
| private final Project myProject; |
| private final XmlTag myIncludeTag; |
| private final AndroidResourceReferenceBase myReference; |
| |
| public LayoutUsageData(@NotNull Project project, |
| @NotNull XmlTag includeTag, |
| @NotNull AndroidResourceReferenceBase reference) { |
| myProject = project; |
| myIncludeTag = includeTag; |
| myReference = reference; |
| } |
| |
| @NotNull |
| public AndroidResourceReferenceBase getReference() { |
| return myReference; |
| } |
| |
| public void inline(@NotNull XmlTag layoutRootTag) throws AndroidRefactoringErrorException { |
| final XmlTag parent = myIncludeTag.getParentTag(); |
| |
| if ("merge".equals(layoutRootTag.getName()) && parent != null) { |
| inlineMultiTags(myIncludeTag, parent, layoutRootTag, myProject); |
| } |
| else { |
| inlineSingleTag(myIncludeTag, parent, layoutRootTag); |
| } |
| } |
| |
| private static void inlineSingleTag(XmlTag includeTag, XmlTag includeParentTag, XmlTag layoutRootTag) { |
| final Map<String, String> attributesToAdd = new HashMap<String, String>(); |
| |
| for (XmlAttribute attribute : includeTag.getAttributes()) { |
| final String namespace = attribute.getNamespace(); |
| |
| if (SdkConstants.NS_RESOURCES.equals(namespace)) { |
| attributesToAdd.put(attribute.getLocalName(), attribute.getValue()); |
| } |
| } |
| final XmlTag newTag = (XmlTag)includeTag.replace(layoutRootTag.copy()); |
| final List<XmlAttribute> toDelete = new ArrayList<XmlAttribute>(); |
| |
| for (XmlAttribute attribute : newTag.getAttributes()) { |
| if (attribute.isNamespaceDeclaration()) { |
| final String localName = attribute.getLocalName(); |
| final String prefix = localName.equals(attribute.getName()) ? "" : localName; |
| final String namespace = attribute.getValue(); |
| |
| if (includeParentTag != null && namespace.equals(includeParentTag.getNamespaceByPrefix(prefix))) { |
| toDelete.add(attribute); |
| } |
| } |
| } |
| |
| for (XmlAttribute attribute : toDelete) { |
| attribute.delete(); |
| } |
| |
| for (Map.Entry<String, String> entry : attributesToAdd.entrySet()) { |
| final String localName = entry.getKey(); |
| final String value = entry.getValue(); |
| newTag.setAttribute(localName, SdkConstants.NS_RESOURCES, value); |
| } |
| CodeStyleManager.getInstance(newTag.getManager()).reformat(newTag); |
| } |
| |
| private static void inlineMultiTags(XmlTag includeTag, XmlTag includeTagParent, XmlTag mergeTag, Project project) |
| throws AndroidRefactoringErrorException { |
| final Map<String, String> namespacesFromParent = includeTagParent.getLocalNamespaceDeclarations(); |
| final Map<String, String> namespacesToAddToParent = new HashMap<String, String>(); |
| final Map<String, String> namespacesToAddToEachTag = new HashMap<String, String>(); |
| |
| for (Map.Entry<String, String> entry : mergeTag.getLocalNamespaceDeclarations().entrySet()) { |
| final String prefix = entry.getKey(); |
| final String namespace = entry.getValue(); |
| final String declaredNamespace = namespacesFromParent.get(prefix); |
| |
| if (declaredNamespace != null && !declaredNamespace.equals(namespace)) { |
| namespacesToAddToEachTag.put(prefix, namespace); |
| } |
| else { |
| namespacesToAddToParent.put(prefix, namespace); |
| } |
| } |
| final XmlTag mergeTagCopy = (XmlTag)mergeTag.copy(); |
| final XmlElementFactory xmlElementFactory = XmlElementFactory.getInstance(project); |
| |
| for (XmlTag subtag : mergeTagCopy.getSubTags()) { |
| final XmlAttribute[] attributes = subtag.getAttributes(); |
| final XmlAttribute firstAttribute = attributes.length > 0 ? attributes[0] : null; |
| |
| for (Map.Entry<String, String> entry : namespacesToAddToEachTag.entrySet()) { |
| final String prefix = entry.getKey(); |
| final String namespace = entry.getValue(); |
| |
| if (!subtag.getLocalNamespaceDeclarations().containsKey(prefix)) { |
| final XmlAttribute xmlnsAttr = xmlElementFactory.createXmlAttribute("xmlns:" + prefix, namespace); |
| |
| if (firstAttribute != null) { |
| subtag.addBefore(xmlnsAttr, firstAttribute); |
| } |
| else { |
| subtag.add(xmlnsAttr); |
| } |
| } |
| } |
| } |
| replaceByTagContent(project, includeTag, mergeTagCopy); |
| addNamespaceAttributes(includeTagParent, namespacesToAddToParent, project); |
| } |
| |
| private static void addNamespaceAttributes(XmlTag tag, Map<String, String> namespaces, Project project) { |
| final XmlAttribute[] parentAttributes = tag.getAttributes(); |
| final XmlAttribute firstParentAttribute = parentAttributes.length > 0 ? parentAttributes[0] : null; |
| final XmlElementFactory factory = XmlElementFactory.getInstance(project); |
| |
| for (Map.Entry<String, String> entry : namespaces.entrySet()) { |
| final String prefix = entry.getKey(); |
| final String namespace = entry.getValue(); |
| |
| if (!namespace.equals(tag.getNamespaceByPrefix(prefix))) { |
| final XmlAttribute xmlnsAttr = factory.createXmlAttribute("xmlns:" + prefix, namespace); |
| |
| if (firstParentAttribute != null) { |
| tag.addBefore(xmlnsAttr, firstParentAttribute); |
| } |
| else { |
| tag.add(xmlnsAttr); |
| } |
| } |
| } |
| } |
| |
| private static void replaceByTagContent(Project project, XmlTag tagToReplace, XmlTag tagToInline) |
| throws AndroidRefactoringErrorException { |
| final ASTNode node = tagToInline.getNode(); |
| |
| if (node == null) { |
| throw new AndroidRefactoringErrorException(); |
| } |
| final ASTNode startTagEnd = XmlChildRole.START_TAG_END_FINDER.findChild(node); |
| final ASTNode closingTagStart = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(node); |
| |
| if (startTagEnd == null || closingTagStart == null) { |
| throw new AndroidRefactoringErrorException(); |
| } |
| final int contentStart = startTagEnd.getTextRange().getEndOffset(); |
| final int contentEnd = closingTagStart.getTextRange().getStartOffset(); |
| |
| if (contentStart < 0 || contentEnd < 0 || contentStart >= contentEnd) { |
| throw new AndroidRefactoringErrorException(); |
| } |
| final PsiFile file = tagToInline.getContainingFile(); |
| |
| if(file == null) { |
| throw new AndroidRefactoringErrorException(); |
| } |
| final String textToInline = file.getText(). |
| substring(contentStart, contentEnd).trim(); |
| final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); |
| final Document document = documentManager.getDocument(tagToReplace.getContainingFile()); |
| |
| if (document == null) { |
| throw new AndroidRefactoringErrorException(); |
| } |
| final TextRange range = tagToReplace.getTextRange(); |
| document.replaceString(range.getStartOffset(), range.getEndOffset(), textToInline); |
| documentManager.commitDocument(document); |
| } |
| } |