/*
* 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.manifest.dom;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import com.motorola.studio.android.common.CommonPlugin;
import com.motorola.studio.android.common.utilities.i18n.UtilitiesNLS;
/**
* Abstract class that represents a xml node on AndroidManifest.xml file.
*/
public abstract class AndroidManifestNode
{
/**
* List that contains the node properties
*/
protected static final List<String> defaultProperties = new LinkedList<String>();
/**
* Array that contains all node properties
*/
private String[] ALL_PROPERTIES = null;
/**
* Enumeration to identify all types of xml nodes
*/
public enum NodeType
{
Action, Activity, ActivityAlias, Application, Category, Data, GrantUriPermission,
Instrumentation, IntentFilter, Manifest, MetaData, Permission, PermissionGroup,
PermissionTree, Provider, Receiver, Service, UsesLibrary, UsesPermission, UsesSdk, Comment,
Unknown, UsesFeature
}
/**
* Retrieves the node name from its type. This name is the same as shown on AndroidManifest.xml file
*
* @param nodeType The node type
*
* @return the node name
*/
public static String getNodeName(NodeType nodeType)
{
String nodeName;
switch (nodeType)
{
case Action:
nodeName = "action";
break;
case Activity:
nodeName = "activity";
break;
case ActivityAlias:
nodeName = "activity-alias";
break;
case Application:
nodeName = "application";
break;
case Category:
nodeName = "category";
break;
case Data:
nodeName = "data";
break;
case GrantUriPermission:
nodeName = "grant-uri-permission";
break;
case Instrumentation:
nodeName = "instrumentation";
break;
case IntentFilter:
nodeName = "intent-filter";
break;
case Manifest:
nodeName = "manifest";
break;
case MetaData:
nodeName = "meta-data";
break;
case Permission:
nodeName = "permission";
break;
case PermissionGroup:
nodeName = "permission-group";
break;
case PermissionTree:
nodeName = "permission-tree";
break;
case Provider:
nodeName = "provider";
break;
case Receiver:
nodeName = "receiver";
break;
case Service:
nodeName = "service";
break;
case UsesLibrary:
nodeName = "uses-library";
break;
case UsesPermission:
nodeName = "uses-permission";
break;
case UsesSdk:
nodeName = "uses-sdk";
break;
case Comment:
nodeName = "comment";
break;
case UsesFeature:
nodeName = "uses-feature";
break;
default:
nodeName = "unknown";
}
return nodeName;
}
/**
* All valid children nodes
*/
protected final List<AndroidManifestNode> children = new LinkedList<AndroidManifestNode>();
/**
* All valid parent nodes
*/
protected AndroidManifestNode parent = null;
/**
* All valid node properties
*/
protected final Map<String, String> properties = new HashMap<String, String>();
/**
* All invalid children nodes
*/
protected final List<AndroidManifestNode> unknownChildren =
new LinkedList<AndroidManifestNode>();
/**
* All invalid children nodes
*/
protected final Map<String, String> unknownProperties = new HashMap<String, String>();
/**
* Checks if a node type can be a child of this node
*
* @param nodeType The node type
* @return true if the type is accept as child or false otherwise
*/
protected abstract boolean canContains(NodeType nodeType);
/**
* Checks if the node is valid, i.e., contains all required information to be
* valid on AndroidManifest.xml file
*
* @return true if the node is valid or false otherwise
*/
protected abstract boolean isNodeValid();
/**
* Retrieves the node type
*
* @return the node type
*/
public abstract NodeType getNodeType();
/**
* Retrieves all node properties, ready to be written to the AndroidManifest.xml file
*
* @return all node properties, ready to be written to the AndroidManifest.xml file
*/
public abstract Map<String, String> getNodeProperties();
/**
* Retrieves the specific node errors. These errors are related to the
* manifest model, excluding those related to unknown child nodes and
* unknown attributes.
* For example, this method can return an error related to the lack of
* a required child node.
*
* @return the specific node errors.
*/
protected abstract List<IStatus> getSpecificNodeProblems();
/**
* Default constructor
*/
protected AndroidManifestNode()
{
// Do nothing
}
/**
* Retrieves the node name from its type. This name is the same as shown on AndroidManifest.xml file
*
* @return the node name
*/
public String getNodeName()
{
return getNodeName(getNodeType());
}
/**
* Adds a child to the node. If the node is accepted as valid child, it will
* be treated this way. Otherwise, the node is treated as unknown.
*
* @param child The child node to be added
*/
public void addChild(AndroidManifestNode child)
{
Assert.isLegal(child != null);
if (canContains(child.getNodeType()))
{
children.add(child);
}
else
{
unknownChildren.add(child);
}
// Set the parent
child.setParent(this);
}
/**
* Adds a parent to the node.
*
* @param parent The parent node to be added
*/
public void setParent(AndroidManifestNode parent)
{
Assert.isLegal(parent != null);
this.parent = parent;
}
/**
* Gets the parent of the node.
*/
public AndroidManifestNode getParent()
{
return parent;
}
/**
* Retrieves all valid children nodes
*
* @return all valid children nodes
*/
public AndroidManifestNode[] getChildren()
{
AndroidManifestNode[] childrenArray = new AndroidManifestNode[children.size()];
childrenArray = children.toArray(childrenArray);
return childrenArray;
}
/**
* Retrieves all unknown children nodes
*
* @return all unknown children nodes
*/
public AndroidManifestNode[] getUnkownChildren()
{
AndroidManifestNode[] unknownChildrenArray =
new AndroidManifestNode[unknownChildren.size()];
unknownChildrenArray = unknownChildren.toArray(unknownChildrenArray);
return unknownChildrenArray;
}
/**
* Adds an unknown property to the node
*
* @param property The property name
* @param value The property value
* @return true if the property has been added or false otherwise
*/
public boolean addUnknownProperty(String property, String value)
{
boolean added = false;
if ((property != null) && (property.trim().length() > 0) && (value != null)
&& canAddUnknownProperty(property))
{
unknownProperties.put(property, value);
}
return added;
}
/**
* Checks if an unknown property can be added, based on the valid properties
*
* @param allProperties the array containing all valid property names
* @param property the property to be checked
* @return true if the unknown property can be added or false otherwise
*/
protected boolean canAddUnknownProperty(String property)
{
boolean canAdd = true;
if (ALL_PROPERTIES == null)
{
ALL_PROPERTIES = new String[defaultProperties.size()];
ALL_PROPERTIES = defaultProperties.toArray(ALL_PROPERTIES);
}
for (String prop : ALL_PROPERTIES)
{
if (prop.trim().equalsIgnoreCase(property))
{
canAdd = false;
break;
}
}
return canAdd;
}
/**
* Retrieves all unknown node properties, ready to be written to the AndroidManifest.xml file
*
* @return all unknown node properties, ready to be written to the AndroidManifest.xml file
*/
public Map<String, String> getNodeUnknownProperties()
{
return unknownProperties;
}
/**
* Retrieves all children nodes from a specific type
*
* @param type The children type
*
* @return all children nodes from the specific type
*/
protected AndroidManifestNode[] getAllChildrenFromType(NodeType type)
{
List<AndroidManifestNode> nodes = new LinkedList<AndroidManifestNode>();
for (AndroidManifestNode node : children)
{
if (node.getNodeType() == type)
{
nodes.add(node);
}
}
AndroidManifestNode[] arrayNodes = new AndroidManifestNode[nodes.size()];
arrayNodes = nodes.toArray(arrayNodes);
return arrayNodes;
}
/**
* Retrieves all node errors
*
* @return an IStatus array containing all node errors
*/
public IStatus[] getNodeErrors()
{
List<IStatus> nodeErrors = new LinkedList<IStatus>();
if ((getNodeType() != NodeType.Unknown) && (getNodeType() != NodeType.Comment))
{
// Adds specific node errors
List<IStatus> specificErrors = getSpecificNodeProblems();
if ((specificErrors != null) && !specificErrors.isEmpty())
{
nodeErrors.addAll(specificErrors);
}
}
return nodeErrors.toArray(new IStatus[0]);
}
/**
* Retrieves all node warnings
*
* @return an IStatus array containing all node warnings
*/
public IStatus[] getNodeWarnings()
{
List<IStatus> nodeWarnings = new LinkedList<IStatus>();
if ((getNodeType() != NodeType.Unknown) && (getNodeType() != NodeType.Comment))
{
String thisNode = "<" + getNodeName() + ">";
// Adds errors about unknown properties
for (String attribute : getNodeUnknownProperties().keySet())
{
String errMsg =
NLS.bind(
UtilitiesNLS.WARN_AndroidManifestNode_TheNodeContainsAnInvalidAttribute,
thisNode, attribute);
nodeWarnings.add(new Status(IStatus.WARNING, CommonPlugin.PLUGIN_ID, errMsg));
}
}
return nodeWarnings.toArray(new IStatus[0]);
}
/**
* Retrieves the errors for this node and for its children
*
* @return an IStatus array containing all node errors
*/
public IStatus[] getRecursiveNodeErrors()
{
List<IStatus> nodeErrors = new LinkedList<IStatus>();
IStatus[] thisNodeErrors = getNodeErrors();
if (thisNodeErrors != null)
{
for (IStatus status : thisNodeErrors)
{
nodeErrors.add(status);
}
}
for (AndroidManifestNode node : getChildren())
{
IStatus[] childrenErrors = node.getNodeErrors();
if (childrenErrors != null)
{
for (IStatus status : childrenErrors)
{
nodeErrors.add(status);
}
}
}
return nodeErrors.toArray(new IStatus[0]);
}
/**
* Checks if a know property can be added or updated, based on the valid properties.
*
* @param property the property to be checked
* @return true if the property can be added/updated or false otherwise
*/
protected boolean canAddOrUpdateProperty(String property)
{
boolean canAdd = false;
if (ALL_PROPERTIES == null)
{
ALL_PROPERTIES = new String[defaultProperties.size()];
ALL_PROPERTIES = defaultProperties.toArray(ALL_PROPERTIES);
}
for (String prop : ALL_PROPERTIES)
{
if (prop.trim().equalsIgnoreCase(property))
{
canAdd = true;
break;
}
}
return canAdd;
}
/**
* Adds a known, valid property to the node
*
* @param property The property name
* @param value The property value
* @return true if the property has been added or false otherwise
*/
public boolean addOrUpdateKnownProperty(String property, String value)
{
boolean added = false;
if ((property != null) && (property.trim().length() > 0) && (value != null)
&& canAddOrUpdateProperty(property))
{
properties.put(property, value);
}
return added;
}
}