/* * Copyright (C) 2012 The Android Open Source Project * * 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.motorola.studio.android.model.resources; import java.io.IOException; import java.io.StringWriter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.eclipse.jface.text.IDocument; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS; import com.motorola.studio.android.common.exception.AndroidException; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.model.resources.parser.AbstractResourceFileParser; import com.motorola.studio.android.model.resources.types.AbstractResourceNode; import com.motorola.studio.android.model.resources.types.AbstractResourceNode.NodeType; import com.motorola.studio.android.model.resources.types.AbstractSimpleNameResourceNode; import com.motorola.studio.android.model.resources.types.ResourcesNode; import com.motorola.studio.android.model.resources.types.UnknownNode; /** * Class that represents a resource file */ @SuppressWarnings("deprecation") public class ResourceFile extends AbstractResourceFileParser { /** * Adds a resource entry to the resources file * * @param node The entry to be added * @return true if the entry has been added or false otherwise */ public boolean addResourceEntry(AbstractResourceNode node) { boolean added = false; if (!rootNodes.contains(node)) { rootNodes.add(node); added = true; } return added; } /** * Removes a resource entry from the resources file * * @param node the entry to be removed * @return true if the entry has been removed or false otherwise */ public boolean removeResourceEntry(AbstractResourceNode node) { boolean removed = false; if (rootNodes.contains(node)) { rootNodes.remove(node); removed = true; } return removed; } /** * Retrieves an array containing all root nodes of the resources file. * If the file is well-formed, only the <resources> node must be present * in the array. * * @return an array containing all root nodes of the resources file. */ public AbstractResourceNode[] getResourceEntries() { AbstractResourceNode[] nodes = new AbstractResourceNode[rootNodes.size()]; nodes = rootNodes.toArray(nodes); return nodes; } /** * Retrieves the <resources> main node * * @return the <resources> main node or null if it does not exist. */ public ResourcesNode getResourcesNode() { ResourcesNode resourcesNode = null; for (AbstractResourceNode node : rootNodes) { if (node.getNodeType() == NodeType.Resources) { resourcesNode = (ResourcesNode) node; break; } } return resourcesNode; } /** * Retrieves an IDocument object containing the xml content for the file * * @return an IDocument object containing the xml content for the file */ public IDocument getContent() throws AndroidException { IDocument document = null; DocumentBuilder documentBuilder = null; try { documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { StudioLogger.error(ResourceFile.class, CodeUtilsNLS.EXC_ResourceFile_ErrorCreatingTheDocumentBuilder, e); throw new AndroidException( CodeUtilsNLS.EXC_ResourceFile_ErrorCreatingTheDocumentBuilder); } Document xmlDocument = documentBuilder.newDocument(); for (AbstractResourceNode node : rootNodes) { addNode(xmlDocument, null, node); } document = new org.eclipse.jface.text.Document(getXmlContent(xmlDocument)); return document; } /** * Recursive function to build a XML file from AbstractResourceNode objects * * @param xmlDocument The XML Document * @param xmlParentNode The XML parent node * @param nodeToAdd The AndroidManifestNode to be added */ private void addNode(Document xmlDocument, Node xmlParentNode, AbstractResourceNode nodeToAdd) { Node xmlNode = xmlDocument.createElement(nodeToAdd.getNodeName()); String[] attributes = nodeToAdd.getAttributes(); String[] unknownAttributes = nodeToAdd.getUnknownAttributes(); AbstractResourceNode[] children = nodeToAdd.getChildNodes(); AbstractResourceNode[] unknownChildren = nodeToAdd.getUnknownChildNodes(); // Sets the node value if (nodeToAdd instanceof AbstractSimpleNameResourceNode) { AbstractSimpleNameResourceNode asnrNode = (AbstractSimpleNameResourceNode) nodeToAdd; if (asnrNode.getNodeValue() != null) { xmlNode.appendChild(xmlDocument.createTextNode(asnrNode.getNodeValue())); } } else if (nodeToAdd.getNodeType() == NodeType.Unknown) { UnknownNode unknownNode = (UnknownNode) nodeToAdd; if (unknownNode.getNodeValue() != null) { xmlNode.appendChild(xmlDocument.createTextNode(unknownNode.getNodeValue())); } } // Adds valid attributes if (attributes.length > 0) { NamedNodeMap xmlAttributes = xmlNode.getAttributes(); for (String attrName : attributes) { Attr attr = xmlDocument.createAttribute(attrName); attr.setValue(nodeToAdd.getAttributeValue(attrName)); xmlAttributes.setNamedItem(attr); } } // Adds invalid attributes if (unknownAttributes.length > 0) { NamedNodeMap xmlAttributes = xmlNode.getAttributes(); for (String attrName : unknownAttributes) { Attr attr = xmlDocument.createAttribute(attrName); attr.setValue(nodeToAdd.getUnknownAttributeValue(attrName)); xmlAttributes.setNamedItem(attr); } } // Adds known child nodes for (AbstractResourceNode child : children) { addNode(xmlDocument, xmlNode, child); } // Adds unknown child nodes for (AbstractResourceNode child : unknownChildren) { addNode(xmlDocument, xmlNode, child); } if (xmlParentNode == null) { xmlDocument.appendChild(xmlNode); } else { xmlParentNode.appendChild(xmlNode); } } /** * Creates the XML content from a XML Document * * @param xmlDocument The XML Document * @return a String object containing the XML content */ private String getXmlContent(Document xmlDocument) throws AndroidException { // Despite Xerces is deprecated, its formatted xml source output works // better than W3C xml output classes OutputFormat outputFormat = new OutputFormat(); XMLSerializer xmlSerializer = new XMLSerializer(); StringWriter writer = null; String content = null; try { writer = new StringWriter(); outputFormat.setEncoding("UTF-8"); outputFormat.setLineSeparator(System.getProperty("line.separator")); outputFormat.setIndenting(true); xmlSerializer.setOutputCharStream(writer); xmlSerializer.setOutputFormat(outputFormat); xmlSerializer.serialize(xmlDocument); content = writer.toString(); } catch (IOException e) { StudioLogger.error(ResourceFile.class, CodeUtilsNLS.EXC_ResourceFile_ErrorFormattingTheXMLOutput, e); throw new AndroidException(CodeUtilsNLS.EXC_ResourceFile_ErrorFormattingTheXMLOutput); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { StudioLogger .error("Could not close stream while retrieving resource xml content. " + e.getMessage()); } } } return content; } /** * Returns a new resource name to create a new resource entry. This method grants that you are not * creating a resource with a duplicated name. * * @param baseName The initial resource name * @return The baseName value if a resource with this name does not exist or a new suggested name otherwise */ public String getNewResourceName(String baseName) { int count = 0; String newName = baseName; boolean found = true; if (getResourcesNode() != null) { while (found) { found = false; for (AbstractResourceNode resNode : getResourcesNode().getChildNodes()) { newName = baseName + (count == 0 ? "" : "_" + Integer.toString(count)); if (resNode instanceof AbstractSimpleNameResourceNode) { AbstractSimpleNameResourceNode validResNode = (AbstractSimpleNameResourceNode) resNode; if (validResNode.getName().equals(newName)) { found = true; count++; break; } } } } } return newName; } }