// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Ian MacLean (ian@maclean.ms)
// Scott Hernandez (ScottHernandez@hotmail.com)
// Gert Driesen (gert.driesen@ardatis.com)
// Giuseppe Greco (giuseppe.greco@agamura.com)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
namespace NAnt.Core {
///
/// Models a NAnt XML element in the build file.
///
///
///
/// Automatically validates attributes in the element based on attributes
/// applied to members in derived classes.
///
///
[Serializable()]
public abstract class Element {
#region Private Instance Fields
private Location _location = Location.UnknownLocation;
private Project _project;
[NonSerialized()]
private XmlNode _xmlNode;
private object _parent;
[NonSerialized()]
private XmlNamespaceManager _nsMgr;
#endregion Private Instance Fields
#region Private Static Fields
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endregion Private Static Fields
#region Protected Instance Constructors
///
/// Initializes a new instance of the class.
///
protected Element() {
}
///
/// Initializes a new instance of the class
/// from the specified element.
///
/// The element that should be used to create a new instance of the class.
protected Element(Element e) : this() {
_location = e._location;
_project = e._project;
_xmlNode = e._xmlNode;
_nsMgr = e._nsMgr;
}
#endregion Protected Instance Constructors
#region Public Instance Properties
///
/// Gets or sets the parent of the element.
///
///
/// The parent of the element.
///
///
/// This will be the parent , , or
/// depending on where the element is defined.
///
public object Parent {
get { return _parent; }
set { _parent = value; }
}
///
/// Gets the name of the XML element used to initialize this element.
///
///
/// The name of the XML element used to initialize this element.
///
public virtual string Name {
get { return Element.GetElementNameFromType(GetType()); }
}
///
/// Gets or sets the to which this element belongs.
///
///
/// The to which this element belongs.
///
public virtual Project Project {
get { return _project; }
set { _project = value; }
}
///
/// Gets the properties local to this and the
/// .
///
///
/// The properties local to this and the .
///
public virtual PropertyDictionary Properties {
get { return Project.Properties; }
}
///
/// Gets or sets the .
///
///
/// The .
///
///
/// The defines the current namespace
/// scope and provides methods for looking up namespace information.
///
public XmlNamespaceManager NamespaceManager {
get { return _nsMgr; }
set { _nsMgr = value; }
}
#endregion Public Instance Properties
#region Protected Instance Properties
///
/// Gets or sets the XML node of the element.
///
///
/// The XML node of the element.
///
protected virtual XmlNode XmlNode {
get { return _xmlNode; }
set { _xmlNode = value; }
}
///
/// Gets or sets the location in the build file where the element is
/// defined.
///
///
/// The location in the build file where the element is defined.
///
protected virtual Location Location {
get { return _location; }
set { _location = value; }
}
///
/// Gets a value indicating whether the element is performing additional
/// processing using the that was used to
/// initialize the element.
///
///
/// .
///
///
///
/// Elements that need to perform additional processing of the
/// that was used to initialize the element, should
/// override this property and return .
///
///
/// When , no build errors will be reported for
/// unknown nested build elements.
///
///
protected virtual bool CustomXmlProcessing {
get { return false; }
}
#endregion Protected Instance Properties
#region Public Instance Methods
///
/// Performs default initialization.
///
///
/// Derived classes that wish to add custom initialization should override
/// the method.
///
public void Initialize(XmlNode elementNode) {
Initialize(elementNode, Project.Properties, Project.TargetFramework);
}
///
/// Logs a message with the given priority.
///
/// The message priority at which the specified message is to be logged.
/// The message to be logged.
///
/// The actual logging is delegated to the project.
///
public virtual void Log(Level messageLevel, string message) {
if (Project != null) {
Project.Log(messageLevel, message);
}
}
///
/// Logs a message with the given priority.
///
/// The message priority at which the specified message is to be logged.
/// The message to log, containing zero or more format items.
/// An array containing zero or more objects to format.
///
/// The actual logging is delegated to the project.
///
public virtual void Log(Level messageLevel, string message, params object[] args) {
if (Project != null) {
Project.Log(messageLevel, message, args);
}
}
#endregion Public Instance Methods
#region Protected Instance Methods
///
/// Derived classes should override to this method to provide extra
/// initialization and validation not covered by the base class.
///
/// The XML node of the element to use for initialization.
protected virtual void InitializeElement(XmlNode elementNode) {
}
///
/// Copies all instance data of the to a given
/// .
///
protected void CopyTo(Element clone) {
clone._location = _location;
clone._nsMgr = _nsMgr;
clone._parent = _parent;
clone._project = _project;
if (_xmlNode != null) {
clone._xmlNode = _xmlNode.Clone();
}
}
#endregion Protected Instance Methods
#region Internal Instance Methods
///
/// Performs initialization using the given set of properties.
///
internal void Initialize(XmlNode elementNode, PropertyDictionary properties, FrameworkInfo framework) {
if (Project == null) {
throw new InvalidOperationException("Element has invalid Project property.");
}
// save position in buildfile for reporting useful error messages.
try {
_location = Project.LocationMap.GetLocation(elementNode);
} catch (ArgumentException ex) {
logger.Warn("Location of Element node could be located.", ex);
}
InitializeXml(elementNode, properties, framework);
// allow inherited classes a chance to do some custom initialization
InitializeElement(elementNode);
}
#endregion Internal Instance Methods
#region Protected Instance Methods
///
/// Initializes all build attributes and child elements.
///
protected virtual void InitializeXml(XmlNode elementNode, PropertyDictionary properties, FrameworkInfo framework) {
_xmlNode = elementNode;
AttributeConfigurator configurator = new AttributeConfigurator(
this, elementNode, properties, framework);
configurator.Initialize();
}
///
/// Locates the XML node for the specified attribute in the project
/// configuration node.
///
/// The name of attribute for which the XML configuration node should be located.
/// The framework to use to obtain framework specific information, or if no framework specific information should be used.
///
/// The XML configuration node for the specified attribute, or
/// if no corresponding XML node could be
/// located.
///
///
/// If there's a valid current framework, the configuration section for
/// that framework will first be searched. If no corresponding
/// configuration node can be located in that section, the framework-neutral
/// section of the project configuration node will be searched.
///
protected XmlNode GetAttributeConfigurationNode(FrameworkInfo framework, string attributeName) {
XmlNode attributeNode = null;
XmlNode nantSettingsNode = Project.ConfigurationNode;
string xpath = "";
int level = 0;
if (nantSettingsNode != null) {
#region Construct XPath expression for locating configuration node
Element parentElement = this as Element;
while (parentElement != null) {
if (parentElement is Task) {
xpath += " and parent::task[@name=\"" + parentElement.Name + "\"";
level++;
break;
}
// For now do not support framework configurable attributes
// on nested types.
/*
} else if (!(parentElement is Target)) {
if (parentElement.XmlNode != null) {
// perform lookup using name of the node
xpath += " and parent::element[@name=\"" + parentElement.XmlNode.Name + "\"";
} else {
// perform lookup using name of the element
xpath += " and parent::element[@name=\"" + parentElement.Name + "\"";
}
level++;
}
*/
parentElement = parentElement.Parent as Element;
}
xpath = "descendant::attribute[@name=\"" + attributeName + "\"" + xpath;
for (int counter = 0; counter < level; counter++) {
xpath += "]";
}
xpath += "]";
#endregion Construct XPath expression for locating configuration node
#region Retrieve framework-specific configuration node
if (framework != null) {
// locate framework node for current framework
XmlNode frameworkNode = nantSettingsNode.SelectSingleNode("frameworks/platform[@name=\""
+ Project.PlatformName + "\"]/framework[@name=\""
+ framework.Name + "\"]", NamespaceManager);
if (frameworkNode != null) {
// locate framework-specific configuration node
attributeNode = frameworkNode.SelectSingleNode(xpath,
NamespaceManager);
}
}
#endregion Retrieve framework-specific configuration node
#region Retrieve framework-neutral configuration node
if (attributeNode == null) {
// locate framework-neutral node
XmlNode frameworkNeutralNode = nantSettingsNode.SelectSingleNode(
"frameworks/tasks", NamespaceManager);
if (frameworkNeutralNode != null) {
// locate framework-neutral configuration node
attributeNode = frameworkNeutralNode.SelectSingleNode(xpath,
NamespaceManager);
}
}
#endregion Retrieve framework-neutral configuration node
}
return attributeNode;
}
#endregion Protected Instance Methods
#region Public Static Methods
public static Element InitializeBuildElement(Element parent, XmlNode childNode, Element buildElement, Type elementType) {
// if subtype of DataTypeBase
DataTypeBase dataType = buildElement as DataTypeBase;
if (dataType != null && dataType.CanBeReferenced && childNode.Attributes["refid"] != null ) {
dataType.RefID = childNode.Attributes["refid"].Value;
if (!StringUtils.IsNullOrEmpty(dataType.ID)) {
// throw exception because of id and ref
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1183")),
dataType.Location);
}
if (parent.Project.DataTypeReferences.Contains(dataType.RefID)) {
dataType = parent.Project.DataTypeReferences[dataType.RefID];
// clear any instance specific state
dataType.Reset();
} else {
// reference not found exception
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1184"), dataType.Name, dataType.RefID),
dataType.Location);
}
if (!elementType.IsAssignableFrom(dataType.GetType())) {
// see if we have a valid copy constructor
ConstructorInfo constructor = elementType.GetConstructor(new Type[] {dataType.GetType()});
if (constructor != null){
dataType = (DataTypeBase) constructor.Invoke(new object[] {dataType});
} else {
ElementNameAttribute dataTypeAttr = (ElementNameAttribute)
Attribute.GetCustomAttribute(dataType.GetType(), typeof(ElementNameAttribute));
ElementNameAttribute elementTypeAttr = (ElementNameAttribute)
Attribute.GetCustomAttribute(elementType, typeof(ElementNameAttribute));
// throw error wrong type definition
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1185"),
dataTypeAttr.Name, elementTypeAttr.Name), Location.UnknownLocation);
}
}
// re-initialize the object with current context
dataType.Project = parent.Project;
dataType.Parent = parent;
dataType.NamespaceManager = parent.NamespaceManager;
dataType.Location = parent.Project.LocationMap.GetLocation(childNode);
// return initialized data type
return dataType;
} else {
// initialize the object with context
buildElement.Project = parent.Project;
buildElement.Parent = parent;
buildElement.NamespaceManager = parent.NamespaceManager;
// initialize element from XML
buildElement.Initialize(childNode);
// return initialize build element
return buildElement;
}
}
#endregion Public Static Methods
#region Private Static Methods
///
/// Returns the of the
/// assigned to the specified
/// .
///
/// The of which the assigned should be retrieved.
///
/// The assigned to the specified
/// or a null reference is no
/// is assigned to the .
///
private static string GetElementNameFromType(Type type) {
if (type == null) {
throw new ArgumentNullException("type");
}
ElementNameAttribute elementNameAttribute = (ElementNameAttribute)
Attribute.GetCustomAttribute(type, typeof(ElementNameAttribute),
false);
if (elementNameAttribute != null) {
return elementNameAttribute.Name;
}
return null;
}
#endregion Private Static Methods
///
/// Configures an using meta-data provided by
/// assigned attributes.
///
public class AttributeConfigurator {
#region Public Instance Constructors
///
/// Initializes a new instance of the
/// class for the given .
///
/// The for which an should be created.
/// The to initialize the with.
/// The to use for property expansion.
/// The framework that the should target.
///
/// is .
/// -or-
/// is .
/// -or-
/// is .
///
public AttributeConfigurator(Element element, XmlNode elementNode, PropertyDictionary properties, FrameworkInfo targetFramework) {
if (element == null) {
throw new ArgumentNullException("element");
}
if (elementNode == null) {
throw new ArgumentNullException("elementNode");
}
if (properties == null) {
throw new ArgumentNullException("properties");
}
_element = element;
_elementXml = elementNode;
_properties = properties;
_targetFramework = targetFramework;
// collect a list of attributes, we will check to see if we use them all.
_unprocessedAttributes = new StringCollection();
foreach (XmlAttribute attribute in elementNode.Attributes) {
// skip non-nant namespace attributes
if (attribute.NamespaceURI.Length > 0 && !attribute.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant")) ) {
continue;
}
_unprocessedAttributes.Add(attribute.Name);
}
// create collection of node names
_unprocessedChildNodes = new StringCollection();
foreach (XmlNode childNode in elementNode) {
// skip non-nant namespace elements and special elements like comments, pis, text, etc.
if (!(childNode.NodeType == XmlNodeType.Element) || !childNode.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) {
continue;
}
// skip existing names as we only need unique names.
if (_unprocessedChildNodes.Contains(childNode.Name)) {
continue;
}
_unprocessedChildNodes.Add(childNode.Name);
}
}
#endregion Public Instance Constructors
#region Public Instance Properties
public Element Element {
get { return _element; }
}
public Location Location {
get { return Element.Location; }
}
public string Name {
get { return Element.Name; }
}
public Project Project {
get { return Element.Project; }
}
public XmlNode ElementXml {
get { return _elementXml; }
}
public PropertyDictionary Properties {
get { return _properties; }
}
public FrameworkInfo TargetFramework {
get { return _targetFramework; }
}
public StringCollection UnprocessedAttributes {
get { return _unprocessedAttributes; }
}
public StringCollection UnprocessedChildNodes {
get { return _unprocessedChildNodes; }
}
///
/// Gets the .
///
///
/// The .
///
///
/// The defines the current namespace
/// scope and provides methods for looking up namespace information.
///
public XmlNamespaceManager NamespaceManager {
get { return Element.NamespaceManager; }
}
#endregion Public Instance Properties
#region Public Instance Methods
public void Initialize() {
Type currentType = Element.GetType();
PropertyInfo[] propertyInfoArray = currentType.GetProperties(
BindingFlags.Public | BindingFlags.Instance);
// loop through all the properties in the derived class.
foreach (PropertyInfo propertyInfo in propertyInfoArray) {
if (InitializeAttribute(propertyInfo)) {
continue;
}
if (InitializeBuildElementCollection(propertyInfo)) {
continue;
}
if (InitializeChildElement(propertyInfo)) {
continue;
}
}
// also support child elements that are backed by methods
// we need this in order to support ordered child elements
InitializeOrderedChildElements();
// skip checking for anything in target
if(!(currentType.Equals(typeof(Target)) || currentType.IsSubclassOf(typeof(Target)))) {
// check if there are unused attributes
if (UnprocessedAttributes.Count > 0) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1027"),
UnprocessedAttributes[0], Element.XmlNode.Name),
Location);
}
if (!Element.CustomXmlProcessing) {
// check if there are unused nested build elements
if (UnprocessedChildNodes.Count > 0) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1032"), Element.Name, UnprocessedChildNodes[0]),
Location);
}
}
}
}
protected virtual bool InitializeAttribute(PropertyInfo propertyInfo) {
XmlNode attributeNode = null;
string attributeValue = null;
XmlNode frameworkAttributeNode = null;
#region Initialize property using framework configuration
FrameworkConfigurableAttribute frameworkAttribute = (FrameworkConfigurableAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(FrameworkConfigurableAttribute),
false);
if (frameworkAttribute != null) {
// locate XML configuration node for current attribute
frameworkAttributeNode = Element.GetAttributeConfigurationNode(
TargetFramework, frameworkAttribute.Name);
if (frameworkAttributeNode != null) {
// get the configured value
attributeValue = frameworkAttributeNode.InnerText;
if (frameworkAttribute.ExpandProperties && TargetFramework != null) {
try {
// expand attribute properties
attributeValue = TargetFramework.Project.Properties.ExpandProperties(
attributeValue, Location);
} catch (BuildException ex) {
// throw BuildException if required
if (frameworkAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1015"),
frameworkAttribute.Name, Name),
Location, ex);
}
// set value to null
attributeValue = null;
}
}
} else {
// check if the attribute is required
if (frameworkAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1015"),
frameworkAttribute.Name, Name), Location);
}
}
}
#endregion Initialize property using framework configuration
#region Initialize property with an assigned BuildAttribute
// process all BuildAttribute attributes
BuildAttributeAttribute buildAttribute = (BuildAttributeAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(BuildAttributeAttribute),
false);
if (buildAttribute != null) {
logger.Debug(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_FoundAttribute"),
buildAttribute.Name,
propertyInfo.DeclaringType.FullName));
if (ElementXml != null) {
// locate attribute in build file
attributeNode = ElementXml.Attributes[buildAttribute.Name];
}
if (attributeNode != null) {
// remove processed attribute name
UnprocessedAttributes.Remove(attributeNode.Name);
// if we don't process the xml then skip on
if (!buildAttribute.ProcessXml) {
logger.Debug(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_SkippingAttribute"),
buildAttribute.Name,
propertyInfo.DeclaringType.FullName));
// consider this property done
return true;
}
// get the configured value
attributeValue = attributeNode.Value;
if (buildAttribute.ExpandProperties) {
// expand attribute properites
attributeValue = Properties.ExpandProperties(attributeValue, Location);
}
// check if property is deprecated
ObsoleteAttribute obsoleteAttribute = (ObsoleteAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(ObsoleteAttribute));
// emit warning or error if attribute is deprecated
if (obsoleteAttribute != null) {
string obsoleteMessage = string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1014"),
buildAttribute.Name, Name, obsoleteAttribute.Message);
if (obsoleteAttribute.IsError) {
throw new BuildException(obsoleteMessage,
Location);
} else {
Element.Log(Level.Warning, Location.ToString()
+ " " + obsoleteMessage);
}
}
} else {
// check if attribute is required
if (buildAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1033"),
buildAttribute.Name, Name), Location);
}
}
}
#endregion Initialize property with an assigned BuildAttribute
if (attributeValue != null) {
// if attribute was not encountered in the build file, but
// still has a value, then it was configured in the framework
// section of the NAnt configuration file
if (attributeNode == null) {
attributeNode = frameworkAttributeNode;
}
logger.Debug(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_SettingValue"),
propertyInfo.Name,
attributeValue,
propertyInfo.DeclaringType.Name));
if (propertyInfo.CanWrite) {
// get the type of the property
Type propertyType = propertyInfo.PropertyType;
// validate attribute value with custom ValidatorAttribute(ors)
object[] validateAttributes = (ValidatorAttribute[])
Attribute.GetCustomAttributes(propertyInfo, typeof(ValidatorAttribute));
try {
foreach (ValidatorAttribute validator in validateAttributes) {
logger.Info(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_ValidatingElement"),
validator.GetType().Name, ElementXml.Name,
attributeNode.Name));
validator.Validate(attributeValue);
}
} catch (ValidationException ve) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
attributeValue, attributeNode.Name, ElementXml.Name), Location, ve);
}
// create an attribute setter for the type of the property
IAttributeSetter attributeSetter = CreateAttributeSetter(propertyType);
// set the property value
attributeSetter.Set(attributeNode, Element, propertyInfo, attributeValue);
// if a value is assigned to the property, we consider
// it done
return true;
}
}
// if a BuildAttribute was assigned to the property, then
// there's no need to try to initialize this property as a
// collection or nested element
return (buildAttribute != null);
}
protected virtual bool InitializeBuildElementCollection(PropertyInfo propertyInfo) {
BuildElementArrayAttribute buildElementArrayAttribute = null;
BuildElementCollectionAttribute buildElementCollectionAttribute = null;
// do build element arrays (assuming they are of a certain collection type.)
buildElementArrayAttribute = (BuildElementArrayAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(BuildElementArrayAttribute),
false);
buildElementCollectionAttribute = (BuildElementCollectionAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(BuildElementCollectionAttribute),
false);
if (buildElementArrayAttribute == null && buildElementCollectionAttribute == null) {
// continue trying to initialize property
return false;
}
if (!propertyInfo.PropertyType.IsArray && !(typeof(ICollection).IsAssignableFrom(propertyInfo.PropertyType))) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1031"), buildElementArrayAttribute.Name,
Name), Location);
}
Type elementType = null;
// determine type of child elements
if (buildElementArrayAttribute != null) {
elementType = buildElementArrayAttribute.ElementType;
} else {
elementType = buildElementCollectionAttribute.ElementType;
}
if (propertyInfo.PropertyType.IsArray) {
if (!propertyInfo.CanWrite) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1016"),
buildElementArrayAttribute.Name, Name),
Location);
}
if (elementType == null) {
elementType = propertyInfo.PropertyType.GetElementType();
}
} else {
if (!propertyInfo.CanRead) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1019"),
buildElementArrayAttribute.Name, Name),
Location);
}
if (elementType == null) {
// locate Add method with 1 parameter, type of that parameter is parameter type
foreach (MethodInfo method in propertyInfo.PropertyType.GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
if (method.Name == "Add" && method.GetParameters().Length == 1) {
ParameterInfo parameter = method.GetParameters()[0];
elementType = parameter.ParameterType;
break;
}
}
}
}
// make sure the element is strongly typed
if (elementType == null || !typeof(Element).IsAssignableFrom(elementType)) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1140"),
propertyInfo.PropertyType.FullName, propertyInfo.Name),
Location);
}
XmlNodeList collectionNodes = null;
if (buildElementCollectionAttribute != null) {
collectionNodes = ElementXml.SelectNodes("nant:"
+ buildElementCollectionAttribute.Name,
NamespaceManager);
if (collectionNodes.Count == 0 && buildElementCollectionAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1021"),
buildElementCollectionAttribute.Name, Name),
Location);
}
if (collectionNodes.Count == 1) {
// check if property is deprecated
ObsoleteAttribute obsoleteAttribute = (ObsoleteAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(ObsoleteAttribute));
// emit warning or error if attribute is deprecated
if (obsoleteAttribute != null) {
string obsoleteMessage = string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1034"),
buildElementCollectionAttribute.Name, Name,
obsoleteAttribute.Message);
if (obsoleteAttribute.IsError) {
throw new BuildException(obsoleteMessage,
Location);
} else {
Element.Log(Level.Warning, Location.ToString()
+ " " + obsoleteMessage);
}
}
// remove element from list of remaining items
UnprocessedChildNodes.Remove(collectionNodes[0].Name);
string elementName = buildElementCollectionAttribute.ChildElementName;
if (elementName == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1026"),
elementType.FullName, buildElementCollectionAttribute.Name,
Name), Location);
}
// get actual collection of element nodes
collectionNodes = collectionNodes[0].SelectNodes("nant:"
+ elementName, NamespaceManager);
// check if its required
if (collectionNodes.Count == 0 && buildElementCollectionAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1021"),
elementName, buildElementCollectionAttribute.Name),
Location);
}
} else if (collectionNodes.Count > 1) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1030"),
buildElementCollectionAttribute.Name,
Name), Location);
}
} else {
collectionNodes = ElementXml.SelectNodes("nant:"
+ buildElementArrayAttribute.Name,
NamespaceManager);
if (collectionNodes.Count > 0) {
// check if property is deprecated
ObsoleteAttribute obsoleteAttribute = (ObsoleteAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(ObsoleteAttribute));
// emit warning or error if attribute is deprecated
if (obsoleteAttribute != null) {
string obsoleteMessage = string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1034"),
buildElementArrayAttribute.Name, Name,
obsoleteAttribute.Message);
if (obsoleteAttribute.IsError) {
throw new BuildException(obsoleteMessage,
Location);
} else {
Element.Log(Level.Warning, Location.ToString()
+ " " + obsoleteMessage);
}
}
// remove element from list of remaining items
UnprocessedChildNodes.Remove(collectionNodes[0].Name);
} else if (buildElementArrayAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1035"),
buildElementArrayAttribute.Name, Name),
Location);
}
}
if (buildElementArrayAttribute != null) {
if (!buildElementArrayAttribute.ProcessXml) {
return true;
}
} else if (!buildElementCollectionAttribute.ProcessXml) {
return true;
}
// create new array of the required size - even if size is 0
Array list = Array.CreateInstance(elementType, collectionNodes.Count);
int arrayIndex = 0;
foreach (XmlNode childNode in collectionNodes) {
// skip non-nant namespace elements and special elements like comments, pis, text, etc.
if (!(childNode.NodeType == XmlNodeType.Element) || !childNode.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) {
continue;
}
// create and initialize child element (from XML or data type reference)
Element childElement = InitializeBuildElement(childNode,
elementType);
// set element in array
list.SetValue(childElement, arrayIndex);
// move to next index position in array
arrayIndex++;
}
if (propertyInfo.PropertyType.IsArray) {
try {
// set the member array to our newly created array
propertyInfo.SetValue(Element, list, null);
} catch (TargetInvocationException ex) {
if (ex.InnerException is BuildException) {
throw ex.InnerException;
}
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1012"),
elementType.FullName, propertyInfo.PropertyType.FullName,
propertyInfo.Name, Name), Location, ex);
}
} else {
MethodInfo addMethod = null;
// get array of public instance methods
MethodInfo[] addMethods = propertyInfo.PropertyType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
// search for a method called 'Add' which accepts a parameter
// to which the element type is assignable
foreach (MethodInfo method in addMethods) {
if (method.Name == "Add" && method.GetParameters().Length == 1) {
ParameterInfo parameter = method.GetParameters()[0];
if (parameter.ParameterType.IsAssignableFrom(elementType)) {
addMethod = method;
break;
}
}
}
if (addMethod == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1020"),
elementType.FullName,
propertyInfo.PropertyType.FullName, propertyInfo.Name, Name),
Location);
}
// if value of property is null, create new instance of collection
object collection = propertyInfo.GetValue(Element, BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
if (collection == null) {
if (!propertyInfo.CanWrite) {
if (buildElementArrayAttribute != null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1093"),
buildElementArrayAttribute.Name, Name),
Location);
} else {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1029"),
buildElementCollectionAttribute.Name, Name),
Location);
}
}
object instance = Activator.CreateInstance(
propertyInfo.PropertyType, BindingFlags.Public | BindingFlags.Instance,
null, null, CultureInfo.InvariantCulture);
propertyInfo.SetValue(Element, instance,
BindingFlags.Default, null, null, CultureInfo.InvariantCulture);
}
// add each element of the array to collection instance
foreach (object childElement in list) {
addMethod.Invoke(collection, BindingFlags.Default, null, new object[] {childElement}, CultureInfo.InvariantCulture);
}
}
return true;
}
protected virtual bool InitializeChildElement(PropertyInfo propertyInfo) {
// now do nested BuildElements
BuildElementAttribute buildElementAttribute = (BuildElementAttribute)
Attribute.GetCustomAttribute(propertyInfo, typeof(BuildElementAttribute),
false);
if (buildElementAttribute == null) {
return false;
}
// will hold the XML node
XmlNode nestedElementNode;
// when element is initialized from application configuration file,
// there's no DocumentElement
if (ElementXml.OwnerDocument.DocumentElement == null) {
nestedElementNode = ElementXml[buildElementAttribute.Name];
} else {
nestedElementNode = ElementXml[buildElementAttribute.Name, ElementXml.OwnerDocument.DocumentElement.NamespaceURI];
}
// check if its required
if (nestedElementNode == null && buildElementAttribute.Required) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1013"),
buildElementAttribute.Name, Name), Location);
}
if (nestedElementNode != null) {
//remove item from list. Used to account for each child xmlelement.
UnprocessedChildNodes.Remove(nestedElementNode.Name);
if (!buildElementAttribute.ProcessXml) {
return true;
}
// create the child build element; not needed directly. It will be assigned to the local property.
CreateChildBuildElement(propertyInfo, nestedElementNode,
Properties, TargetFramework);
// output warning to build log when multiple nested elements
// were specified in the build file, as NAnt will only process
// the first element it encounters
if (ElementXml.SelectNodes("nant:" + buildElementAttribute.Name, NamespaceManager).Count > 1) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1186"),
Name, buildElementAttribute.Name), Location);
}
}
return true;
}
protected virtual void InitializeOrderedChildElements() {
// first, we'll fill a hashtable with public methods that take
// a single argument and to which a BuildElementAttribute is
// assigned
Hashtable childElementMethods = new Hashtable();
// will hold list of required methods, in order to check missing
// required elements
Hashtable requiredMethods = new Hashtable();
MethodInfo[] methods = Element.GetType().GetMethods(BindingFlags.Public
| BindingFlags.Instance);
foreach (MethodInfo method in methods) {
ParameterInfo[] parameters = method.GetParameters();
// we're only interested in methods with one argument, meaning
// the element
if (parameters.Length != 1) {
continue;
}
// ignore methods that do not have a BuildElementAttribute
// assigned to it
object[] buildElementAttributes = method.GetCustomAttributes(
typeof(BuildElementAttribute), true);
if (buildElementAttributes.Length == 0) {
continue;
}
BuildElementAttribute buildElementAttribute = (BuildElementAttribute)
buildElementAttributes[0];
childElementMethods.Add(buildElementAttribute.Name, method);
if (buildElementAttribute.Required) {
requiredMethods.Add(buildElementAttribute.Name, method);
}
}
// keep track of nodes that were processed as ordered build
// elements
StringCollection processedNodes = new StringCollection();
foreach (XmlNode childNode in ElementXml.ChildNodes) {
string elementName = childNode.Name;
// skip childnodes that have already been processed
// (by other means)
if (!UnprocessedChildNodes.Contains(elementName)) {
continue;
}
// skip child nodes for which no init method exists
MethodInfo method = (MethodInfo) childElementMethods[elementName];
if (method == null) {
continue;
}
// mark node as processed (as an ordered build element)
if (!processedNodes.Contains(elementName)) {
processedNodes.Add(elementName);
}
// ensure method is marked processed
if (requiredMethods.ContainsKey(elementName)) {
requiredMethods.Remove(elementName);
}
// obtain buildelementattribute to check whether xml
// should be processed
BuildElementAttribute buildElementAttribute = (BuildElementAttribute)
Attribute.GetCustomAttribute(method, typeof(BuildElementAttribute),
false);
if (!buildElementAttribute.ProcessXml) {
continue;
}
// methods should have an argument of a type that derives
// from Element
Type childElementType = method.GetParameters()[0].ParameterType;
// create and initialize the element (from XML or datatype reference)
Element element = InitializeBuildElement(childNode,
childElementType);
try {
// invoke method, passing in the initialized element
method.Invoke(Element, BindingFlags.InvokeMethod, null,
new object[] {element}, CultureInfo.InvariantCulture);
} catch (TargetInvocationException ex) {
if (ex.InnerException != null) {
throw ex.InnerException;
}
throw;
}
}
// permanently mark nodes as processed
foreach (string node in processedNodes) {
UnprocessedChildNodes.Remove(node);
}
// finally check if there are methods that are required, and for
// which no element was specified in the build file
if (requiredMethods.Count > 0) {
foreach (DictionaryEntry entry in requiredMethods) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1021"), (string) entry.Key, Name), Location);
}
}
}
protected virtual Element InitializeBuildElement(XmlNode childNode, Type elementType) {
if (!typeof(Element).IsAssignableFrom(elementType)) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1187"),
childNode.Name, elementType.FullName), Location);
}
// create a child element
Element childElement = (Element) Activator.CreateInstance(
elementType, BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
// initialize the element
return Element.InitializeBuildElement(Element, childNode, childElement, elementType);
}
#endregion Public Instance Methods
#region Private Instance Methods
///
/// Creates a child using property set/get methods.
///
/// The instance that represents the property of the current class.
/// The used to initialize the new instance.
/// The collection of property values to use for macro expansion.
/// The from which to obtain framework-specific information.
/// The child.
private Element CreateChildBuildElement(PropertyInfo propInf, XmlNode xml, PropertyDictionary properties, FrameworkInfo framework) {
MethodInfo getter = null;
MethodInfo setter = null;
Element childElement = null;
Type elementType = null;
setter = propInf.GetSetMethod(true);
getter = propInf.GetGetMethod(true);
// if there is a getter, then get the current instance of the object, and use that
if (getter != null) {
try {
childElement = (Element) propInf.GetValue(Element, null);
} catch (InvalidCastException) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1188"),
propInf.Name, Element.GetType().FullName, propInf.PropertyType.FullName,
typeof(Element).FullName), Location);
}
if (childElement == null) {
if (setter == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1189"), propInf.Name,
Element.GetType().FullName), Location);
} else {
// fake the getter as null so we process the rest like there is no getter
getter = null;
logger.Info(string.Format(CultureInfo.InvariantCulture,"{0}_get() returned null; will go the route of set method to populate.", propInf.Name));
}
} else {
elementType = childElement.GetType();
}
}
// create a new instance of the object if there is not a get method. (or the get object returned null... see above)
if (getter == null && setter != null) {
elementType = setter.GetParameters()[0].ParameterType;
if (elementType.IsAbstract) {
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("String_AbstractType"), elementType.Name, propInf.Name, Name));
}
childElement = (Element) Activator.CreateInstance(elementType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, null , CultureInfo.InvariantCulture);
}
// initialize the child element
childElement = Element.InitializeBuildElement(Element, xml, childElement, elementType);
// check if we're dealing with a reference to a data type
DataTypeBase dataType = childElement as DataTypeBase;
if (dataType != null && xml.Attributes["refid"] != null) {
// references to data type should be always be set
if (setter == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1190"),
propInf.Name, this.GetType().FullName), Location);
}
// re-set the getter (for force the setter to be used)
getter = null;
}
// call the set method if we created the object
if (setter != null && getter == null) {
setter.Invoke(Element, new object[] {childElement});
}
// return the new/used object
return childElement;
}
///
/// Creates an for the given
/// .
///
/// The for which an should be created.
///
/// An for the given .
///
private IAttributeSetter CreateAttributeSetter(Type attributeType) {
if (AttributeSetters.ContainsKey(attributeType)) {
return (IAttributeSetter) AttributeSetters[attributeType];
}
IAttributeSetter attributeSetter = null;
if (attributeType.IsEnum) {
attributeSetter = new EnumAttributeSetter();
} else if (attributeType == typeof(Encoding)) {
attributeSetter = new EncodingAttributeSetter();
} else if (attributeType == typeof(FileInfo)) {
attributeSetter = new FileAttributeSetter();
} else if (attributeType == typeof(DirectoryInfo)) {
attributeSetter = new DirectoryAttributeSetter();
} else if (attributeType == typeof(PathSet)) {
attributeSetter = new PathSetAttributeSetter();
} else if (attributeType == typeof(Uri)) {
attributeSetter = new UriAttributeSetter();
} else {
attributeSetter = new ConvertableAttributeSetter();
}
if (attributeSetter != null) {
AttributeSetters.Add(attributeType, attributeSetter);
}
return attributeSetter;
}
#endregion Private Instance Methods
#region Private Instance Fields
///
/// Holds the that should be initialized.
///
private readonly Element _element;
///
/// Holds the that should be used to initialize
/// the .
///
private readonly XmlNode _elementXml;
///
/// Holds the dictionary that should be used for property
/// expansion.
///
private readonly PropertyDictionary _properties;
///
/// Holds the framework that should be targeted by the
/// that we're configuring, or
/// if there's no current target
/// framework.
///
private readonly FrameworkInfo _targetFramework;
///
/// Holds the names of the attributes that still need to be
/// processed.
///
private readonly StringCollection _unprocessedAttributes;
///
/// Holds the names of the child nodes that still need to be
/// processed.
///
private readonly StringCollection _unprocessedChildNodes;
#endregion Private Instance Fields
#region Private Static Fields
///
/// Holds the logger for the current class.
///
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
///
/// Holds the cache of instances.
///
private static Hashtable AttributeSetters = new Hashtable();
#endregion Private Static Fields
private class EnumAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
try {
object propertyValue;
// check for more specific type converter
TypeConverter tc = TypeDescriptor.GetConverter(property.PropertyType);
if (!(tc.GetType() == typeof(EnumConverter))) {
propertyValue = tc.ConvertFrom(value);
} else {
propertyValue = Enum.Parse(property.PropertyType, value);
}
property.SetValue(parent, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (FormatException) {
throw CreateBuildException(attributeNode, parent,
property, value);
} catch (ArgumentException) {
throw CreateBuildException(attributeNode, parent,
property, value);
}
}
private BuildException CreateBuildException(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
StringBuilder sb = new StringBuilder();
foreach (object field in Enum.GetValues(property.PropertyType)) {
if (sb.Length > 0) {
sb.Append(", ");
}
sb.Append(field.ToString());
}
string message = string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1023"), value, attributeNode.Name,
parent.Name, sb.ToString());
return new BuildException(message, parent.Location);
}
}
private class EncodingAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
string encodingName = StringUtils.ConvertEmptyToNull(value);
if (encodingName == null) {
return;
}
Encoding encoding = null;
try {
encoding = System.Text.Encoding.GetEncoding(
encodingName);
} catch (ArgumentException) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1191"),
encodingName), parent.Location);
} catch (NotSupportedException) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1192"),
encodingName), parent.Location);
}
try {
property.SetValue(parent, encoding, BindingFlags.Public |
BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
}
}
private class FileAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
string path = StringUtils.ConvertEmptyToNull(value);
if (path != null) {
object propertyValue;
try {
propertyValue = new FileInfo(parent.Project.GetFullPath(value));
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
try {
property.SetValue(parent, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
} else {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1193"),
attributeNode.Name, parent.Name), parent.Location);
}
}
}
private class DirectoryAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
string path = StringUtils.ConvertEmptyToNull(value);
if (path != null) {
try {
object propertyValue = new DirectoryInfo(
parent.Project.GetFullPath(value));
property.SetValue(parent, propertyValue,
BindingFlags.Public | BindingFlags.Instance,
null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
} else {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1193"),
attributeNode.Name, parent.Name), parent.Location);
}
}
}
private class PathSetAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
try {
PathSet propertyValue = new PathSet(parent.Project, value);
property.SetValue(parent, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
}
}
private class UriAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
string uri = StringUtils.ConvertEmptyToNull(value);
if (uri != null) {
Uri propertyValue;
// if uri does not contain a scheme, we'll consider it
// to be a normal path and as such we need to resolve
// it to an absolute path (relative to project base
// directory
if (value.IndexOf(Uri.SchemeDelimiter) == -1) {
uri = parent.Project.GetFullPath(value);
}
try {
propertyValue = new Uri(uri);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
try {
property.SetValue(parent, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
} else {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1193"),
attributeNode.Name, parent.Name), parent.Location);
}
}
}
private class ConvertableAttributeSetter : IAttributeSetter {
public void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value) {
try {
object propertyValue = Convert.ChangeType(value, property.PropertyType, CultureInfo.InvariantCulture);
property.SetValue(parent, propertyValue, BindingFlags.Public | BindingFlags.Instance, null, null, CultureInfo.InvariantCulture);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA1022"),
value, attributeNode.Name, parent.Name), parent.Location, ex);
}
}
}
///
/// Internal interface used for setting element attributes.
///
private interface IAttributeSetter {
void Set(XmlNode attributeNode, Element parent, PropertyInfo property, string value);
}
}
}
}