|
From: <jer...@us...> - 2009-12-29 03:50:22
|
Revision: 315
http://structuremap.svn.sourceforge.net/structuremap/?rev=315&view=rev
Author: jeremydmiller
Date: 2009-12-29 03:50:13 +0000 (Tue, 29 Dec 2009)
Log Message:
-----------
marking more things [Obsolete]. Depending on what happens with the migration to other projects, this might be the 2.5.4 release
Modified Paths:
--------------
trunk/Source/StructureMap/Configuration/DSL/Expressions/CreatePluginFamilyExpression.cs
trunk/Source/StructureMap/Extensions.cs
trunk/Source/StructureMap/Graph/Plugin.cs
trunk/Source/StructureMap/Graph/SetterPropertyCollection.cs
trunk/Source/StructureMap/Pipeline/ConfiguredInstance.Expressions.cs
trunk/Source/StructureMap/Pipeline/EnumerableInstance.cs
trunk/Source/StructureMap/Pipeline/PropertyExpression.cs
trunk/Source/StructureMap/Pipeline/SmartInstance.cs
trunk/Source/StructureMap/TypeExtensions.cs
trunk/Source/StructureMap.Testing/Configuration/DSL/InjectArrayTester.cs
trunk/Source/StructureMap.Testing/Graph/PluginTester.cs
trunk/Source/StructureMap.Testing/ObjectFactoryInitializeTester.cs
Modified: trunk/Source/StructureMap/Configuration/DSL/Expressions/CreatePluginFamilyExpression.cs
===================================================================
--- trunk/Source/StructureMap/Configuration/DSL/Expressions/CreatePluginFamilyExpression.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Configuration/DSL/Expressions/CreatePluginFamilyExpression.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -374,6 +374,14 @@
return instance;
}
+ public SmartInstance<PLUGGEDTYPE> Add<PLUGGEDTYPE>()
+ {
+ var instance = new SmartInstance<PLUGGEDTYPE>();
+ Add(instance);
+
+ return instance;
+ }
+
/// <summary>
/// Add an Instance to this type created by a Lambda
/// </summary>
Modified: trunk/Source/StructureMap/Extensions.cs
===================================================================
--- trunk/Source/StructureMap/Extensions.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Extensions.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -15,6 +15,16 @@
return enumerable;
}
+ public static bool IsEmpty(this string value)
+ {
+ return string.IsNullOrEmpty(value);
+ }
+
+ public static bool IsNotEmpty(this string value)
+ {
+ return !value.IsEmpty();
+ }
+
public static string ToFormat(this string template, params object[] parameters)
{
return string.Format(template, parameters);
Modified: trunk/Source/StructureMap/Graph/Plugin.cs
===================================================================
--- trunk/Source/StructureMap/Graph/Plugin.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Graph/Plugin.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -1,8 +1,10 @@
using System;
+using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using StructureMap.Pipeline;
using StructureMap.TypeRules;
+using System.Linq;
namespace StructureMap.Graph
{
@@ -75,11 +77,21 @@
return FindArgumentNameForType(typeof (T));
}
+ public string FindArgumentNameForEnumerableOf(Type type)
+ {
+ var enumerableTypes = EnumerableInstance.OpenEnumerableTypes.Select(x => x.MakeGenericType(type)).Union(new []{type.MakeArrayType()});
+ return enumerableTypes.Select(t =>
+ {
+ return _constructor.FindFirstConstructorArgumentOfType(t) ??
+ _setters.FindFirstWriteablePropertyOfType(t);
+ }).FirstOrDefault(x => x != null);
+ }
+
public string FindArgumentNameForType(Type type)
{
string returnValue =
_constructor.FindFirstConstructorArgumentOfType(type) ??
- _setters.FindFirstConstructorArgumentOfType(type);
+ _setters.FindFirstWriteablePropertyOfType(type);
if (returnValue == null)
{
Modified: trunk/Source/StructureMap/Graph/SetterPropertyCollection.cs
===================================================================
--- trunk/Source/StructureMap/Graph/SetterPropertyCollection.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Graph/SetterPropertyCollection.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -107,7 +107,7 @@
return returnValue;
}
- public string FindFirstConstructorArgumentOfType(Type type)
+ public string FindFirstWriteablePropertyOfType(Type type)
{
foreach (SetterProperty setterProperty in this)
{
Modified: trunk/Source/StructureMap/Pipeline/ConfiguredInstance.Expressions.cs
===================================================================
--- trunk/Source/StructureMap/Pipeline/ConfiguredInstance.Expressions.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Pipeline/ConfiguredInstance.Expressions.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -6,12 +6,19 @@
{
public partial class ConfiguredInstance
{
+ [Obsolete("Change to Named()")]
public ConfiguredInstance WithName(string instanceKey)
{
Name = instanceKey;
return this;
}
+ public ConfiguredInstance Named(string instanceKey)
+ {
+ Name = instanceKey;
+ return this;
+ }
+
/// <summary>
/// Register an Action to perform on the object created by this Instance
/// before it is returned to the caller
@@ -202,6 +209,7 @@
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
+ [Obsolete("Change to DependencyExpression<CTORTYPE> instead")]
public PropertyExpression<ConfiguredInstance> WithCtorArg(string propertyName)
{
return new PropertyExpression<ConfiguredInstance>(this, propertyName);
Modified: trunk/Source/StructureMap/Pipeline/EnumerableInstance.cs
===================================================================
--- trunk/Source/StructureMap/Pipeline/EnumerableInstance.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Pipeline/EnumerableInstance.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -13,6 +13,14 @@
typeof (List<>)
};
+ public static IEnumerable<Type> OpenEnumerableTypes
+ {
+ get
+ {
+ return _enumerableTypes;
+ }
+ }
+
private readonly IEnumerable<Instance> _children;
private readonly IEnumerableCoercion _coercion;
private readonly string _description;
@@ -83,5 +91,7 @@
return type.IsGenericType && type.GetGenericTypeDefinition().IsIn(_enumerableTypes);
}
+
+
}
}
\ No newline at end of file
Modified: trunk/Source/StructureMap/Pipeline/PropertyExpression.cs
===================================================================
--- trunk/Source/StructureMap/Pipeline/PropertyExpression.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Pipeline/PropertyExpression.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -6,6 +6,7 @@
/// <summary>
/// Defines the value of a primitive argument to a constructur argument
/// </summary>
+ [Obsolete("Change to DependencyExpression<CTORTYPE> instead")]
public class PropertyExpression<T> where T : ConstructorInstance
{
private readonly ConstructorInstance _instance;
Modified: trunk/Source/StructureMap/Pipeline/SmartInstance.cs
===================================================================
--- trunk/Source/StructureMap/Pipeline/SmartInstance.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/Pipeline/SmartInstance.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Configuration;
using System.Diagnostics;
using System.Linq.Expressions;
using StructureMap.Configuration.DSL.Expressions;
@@ -26,6 +27,7 @@
/// </summary>
/// <param name="instanceKey"></param>
/// <returns></returns>
+ [Obsolete("Please change to Named(instanceKey)")]
public SmartInstance<T> WithName(string instanceKey)
{
Name = instanceKey;
@@ -33,6 +35,17 @@
}
/// <summary>
+ /// Sets the name of this Instance
+ /// </summary>
+ /// <param name="instanceKey"></param>
+ /// <returns></returns>
+ public SmartInstance<T> Named(string instanceKey)
+ {
+ Name = instanceKey;
+ return this;
+ }
+
+ /// <summary>
/// Register an Action to perform on the object created by this Instance
/// before it is returned to the caller
/// </summary>
@@ -132,6 +145,7 @@
/// </summary>
/// <param name="argumentName"></param>
/// <returns></returns>
+ [Obsolete("Use CtorDependency() instead")]
public PropertyExpression<SmartInstance<T>> WithCtorArg(string argumentName)
{
return new PropertyExpression<SmartInstance<T>>(this, argumentName);
@@ -187,12 +201,25 @@
/// </summary>
/// <typeparam name="CTORTYPE"></typeparam>
/// <returns></returns>
+ [Obsolete("Change to Ctor<>()")]
public DependencyExpression<CTORTYPE> CtorDependency<CTORTYPE>()
{
string constructorArg = getArgumentNameForType<CTORTYPE>();
return CtorDependency<CTORTYPE>(constructorArg);
}
+ /// <summary>
+ /// Inline definition of a constructor dependency. Select the constructor argument by type. Do not
+ /// use this method if there is more than one constructor arguments of the same type
+ /// </summary>
+ /// <typeparam name="CTORTYPE"></typeparam>
+ /// <returns></returns>
+ public DependencyExpression<CTORTYPE> Ctor<CTORTYPE>()
+ {
+ string constructorArg = getArgumentNameForType<CTORTYPE>();
+ return CtorDependency<CTORTYPE>(constructorArg);
+ }
+
private string getArgumentNameForType<CTORTYPE>()
{
Plugin plugin = PluginCache.GetPlugin(getConcreteType(null));
@@ -206,17 +233,31 @@
/// <typeparam name="CTORTYPE"></typeparam>
/// <param name="constructorArg"></param>
/// <returns></returns>
+ [Obsolete("Use Ctor<CTORTYPE>(constructorArg)")]
public DependencyExpression<CTORTYPE> CtorDependency<CTORTYPE>(string constructorArg)
{
return new DependencyExpression<CTORTYPE>(this, constructorArg);
}
/// <summary>
+ /// Inline definition of a constructor dependency. Select the constructor argument by type and constructor name.
+ /// Use this method if there is more than one constructor arguments of the same type
+ /// </summary>
+ /// <typeparam name="CTORTYPE"></typeparam>
+ /// <param name="constructorArg"></param>
+ /// <returns></returns>
+ public DependencyExpression<CTORTYPE> Ctor<CTORTYPE>(string constructorArg)
+ {
+ return new DependencyExpression<CTORTYPE>(this, constructorArg);
+ }
+
+ /// <summary>
/// Inline definition of a setter dependency. The property name is specified with an Expression
/// </summary>
/// <typeparam name="SETTERTYPE"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
+ [Obsolete("Use Setter()")]
public DependencyExpression<SETTERTYPE> SetterDependency<SETTERTYPE>(
Expression<Func<T, SETTERTYPE>> expression)
{
@@ -224,23 +265,51 @@
return new DependencyExpression<SETTERTYPE>(this, propertyName);
}
+
/// <summary>
+ /// Inline definition of a setter dependency. The property name is specified with an Expression
+ /// </summary>
+ /// <typeparam name="SETTERTYPE"></typeparam>
+ /// <param name="expression"></param>
+ /// <returns></returns>
+ public DependencyExpression<SETTERTYPE> Setter<SETTERTYPE>(
+ Expression<Func<T, SETTERTYPE>> expression)
+ {
+ string propertyName = ReflectionHelper.GetProperty(expression).Name;
+ return new DependencyExpression<SETTERTYPE>(this, propertyName);
+ }
+
+ /// <summary>
/// Inline definition of a setter dependency. Only use this method if there
/// is only a single property of the SETTERTYPE
/// </summary>
/// <typeparam name="SETTERTYPE"></typeparam>
/// <returns></returns>
+ [Obsolete("Use Setter<>()")]
public DependencyExpression<SETTERTYPE> SetterDependency<SETTERTYPE>()
{
return CtorDependency<SETTERTYPE>();
}
+
/// <summary>
+ /// Inline definition of a setter dependency. Only use this method if there
+ /// is only a single property of the SETTERTYPE
+ /// </summary>
+ /// <typeparam name="SETTERTYPE"></typeparam>
+ /// <returns></returns>
+ public DependencyExpression<SETTERTYPE> Setter<SETTERTYPE>()
+ {
+ return CtorDependency<SETTERTYPE>();
+ }
+
+ /// <summary>
/// Inline definition of a dependency on an Array of the CHILD type. I.e. CHILD[].
/// This method can be used for either constructor arguments or setter properties
/// </summary>
/// <typeparam name="CHILD"></typeparam>
/// <returns></returns>
+ [Obsolete("Use EnumerableOf<>")]
public ArrayDefinitionExpression<CHILD> TheArrayOf<CHILD>()
{
if (typeof (CHILD).IsArray)
@@ -249,8 +318,8 @@
}
Plugin plugin = PluginCache.GetPlugin(typeof (T));
- string propertyName = plugin.FindArgumentNameForType(typeof (CHILD).MakeArrayType());
- Debug.WriteLine("Property Name: " + propertyName);
+ string propertyName = plugin.FindArgumentNameForEnumerableOf(typeof (CHILD));
+
return TheArrayOf<CHILD>(propertyName);
}
@@ -261,11 +330,53 @@
/// <typeparam name="CHILD"></typeparam>
/// <param name="ctorOrPropertyName"></param>
/// <returns></returns>
+ [Obsolete("Use EnumerableOf<>")]
public ArrayDefinitionExpression<CHILD> TheArrayOf<CHILD>(string ctorOrPropertyName)
{
+ if (ctorOrPropertyName.IsEmpty())
+ {
+ throw new StructureMapException(302, typeof(CHILD).FullName, typeof(T).FullName);
+ }
return new ArrayDefinitionExpression<CHILD>(this, ctorOrPropertyName);
}
+
+ /// <summary>
+ /// Inline definition of a dependency on an Array of the CHILD type. I.e. CHILD[].
+ /// This method can be used for either constructor arguments or setter properties
+ /// </summary>
+ /// <typeparam name="CHILD"></typeparam>
+ /// <returns></returns>
+ public ArrayDefinitionExpression<CHILD> EnumerableOf<CHILD>()
+ {
+ if (typeof(CHILD).IsArray)
+ {
+ throw new ApplicationException("Please specify the element type in the call to TheArrayOf");
+ }
+
+ Plugin plugin = PluginCache.GetPlugin(typeof(T));
+ string propertyName = plugin.FindArgumentNameForEnumerableOf(typeof(CHILD));
+
+ return TheArrayOf<CHILD>(propertyName);
+ }
+
+ /// <summary>
+ /// Inline definition of a dependency on an Array of the CHILD type and the specified setter property or constructor argument name. I.e. CHILD[].
+ /// This method can be used for either constructor arguments or setter properties
+ /// </summary>
+ /// <typeparam name="CHILD"></typeparam>
+ /// <param name="ctorOrPropertyName"></param>
+ /// <returns></returns>
+ public ArrayDefinitionExpression<CHILD> EnumerableOf<CHILD>(string ctorOrPropertyName)
+ {
+ if (ctorOrPropertyName.IsEmpty())
+ {
+ throw new StructureMapException(302, typeof(CHILD).FullName, typeof(T).FullName);
+ }
+ return new ArrayDefinitionExpression<CHILD>(this, ctorOrPropertyName);
+ }
+
+
#region Nested type: ArrayDefinitionExpression
/// <summary>
@@ -333,6 +444,32 @@
}
/// <summary>
+ /// Sets the value of the constructor argument to the key/value in the
+ /// AppSettings
+ /// </summary>
+ /// <param name="appSettingKey">The key in appSettings for the value to use.</param>
+ /// <returns></returns>
+ public SmartInstance<T> EqualToAppSetting(string appSettingKey)
+ {
+ return EqualToAppSetting(appSettingKey, null);
+ }
+
+ /// <summary>
+ /// Sets the value of the constructor argument to the key/value in the
+ /// AppSettings when it exists. Otherwise uses the provided default value.
+ /// </summary>
+ /// <param name="appSettingKey">The key in appSettings for the value to use.</param>
+ /// <param name="defaultValue">The value to use if an entry for <paramref name="appSettingKey"/> does not exist in the appSettings section.</param>
+ /// <returns></returns>
+ public SmartInstance<T> EqualToAppSetting(string appSettingKey, string defaultValue)
+ {
+ string propertyValue = ConfigurationManager.AppSettings[appSettingKey];
+ if (propertyValue == null) propertyValue = defaultValue;
+ _instance.SetValue(_propertyName, propertyValue);
+ return _instance;
+ }
+
+ /// <summary>
/// Nested Closure to define a child dependency inline
/// </summary>
/// <param name="action"></param>
@@ -345,6 +482,12 @@
return _instance;
}
+ public SmartInstance<T> Is(Func<IContext, CHILD> func)
+ {
+ var child = new LambdaInstance<CHILD>(func);
+ return Is(child);
+ }
+
/// <summary>
/// Shortcut to set an inline dependency to an Instance
/// </summary>
@@ -363,10 +506,24 @@
/// <returns></returns>
public SmartInstance<T> Is(CHILD value)
{
- return Is(new ObjectInstance(value));
+ _instance.SetValue(_propertyName, value);
+ return _instance;
}
+
/// <summary>
+ /// Shortcut to set an inline dependency to a designated object
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ [Obsolete("Change to Is()")]
+ public SmartInstance<T> EqualTo(CHILD value)
+ {
+ _instance.SetValue(_propertyName, value);
+ return _instance;
+ }
+
+ /// <summary>
/// Set an Inline dependency to the Default Instance of the Property type
/// Used mostly to force an optional Setter property to be filled by
/// StructureMap
@@ -386,6 +543,20 @@
{
return Is(new SmartInstance<CONCRETETYPE>());
}
+
+
+ /// <summary>
+ /// Shortcut method to define a child dependency inline and configure
+ /// the child dependency
+ /// </summary>
+ /// <typeparam name="CONCRETETYPE"></typeparam>
+ /// <returns></returns>
+ public SmartInstance<T> Is<CONCRETETYPE>(Action<SmartInstance<CONCRETETYPE>> configure) where CONCRETETYPE : CHILD
+ {
+ var instance = new SmartInstance<CONCRETETYPE>();
+ configure(instance);
+ return Is(instance);
+ }
}
#endregion
Modified: trunk/Source/StructureMap/TypeExtensions.cs
===================================================================
--- trunk/Source/StructureMap/TypeExtensions.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap/TypeExtensions.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -45,6 +45,7 @@
{
public static class TypeExtensions
{
+
public static bool Closes(this Type type, Type openType)
{
Type baseType = type.BaseType;
Modified: trunk/Source/StructureMap.Testing/Configuration/DSL/InjectArrayTester.cs
===================================================================
--- trunk/Source/StructureMap.Testing/Configuration/DSL/InjectArrayTester.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap.Testing/Configuration/DSL/InjectArrayTester.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
using NUnit.Framework;
using StructureMap.Pipeline;
+using System.Linq;
namespace StructureMap.Testing.Configuration.DSL
{
@@ -33,6 +35,24 @@
public string Name { get { return _name; } }
}
+ public class ProcessorWithList
+ {
+ private readonly IList<IHandler> _handlers;
+ private readonly string _name;
+
+ public ProcessorWithList(IList<IHandler> handlers, string name)
+ {
+ _handlers = handlers;
+ _name = name;
+ }
+
+
+ public IList<IHandler> Handlers { get { return _handlers; } }
+
+
+ public string Name { get { return _name; } }
+ }
+
public class Processor2
{
private readonly IHandler[] _first;
@@ -86,6 +106,24 @@
}
[Test]
+ public void get_a_configured_list()
+ {
+ var container = new Container(x =>
+ {
+ x.ForRequestedType<Processor>().TheDefault.Is
+ .OfConcreteType<Processor>()
+ .TheArrayOf<IHandler>().Contains(
+ new SmartInstance<Handler1>(),
+ new SmartInstance<Handler2>(),
+ new SmartInstance<Handler3>()
+ )
+ .WithCtorArg("name").EqualTo("Jeremy");
+ });
+
+ container.GetInstance<Processor>().Handlers.Select(x => x.GetType()).ShouldHaveTheSameElementsAs(typeof(Handler1), typeof(Handler2), typeof(Handler3));
+ }
+
+ [Test]
public void InjectPropertiesByName()
{
var container = new Container(r =>
Modified: trunk/Source/StructureMap.Testing/Graph/PluginTester.cs
===================================================================
--- trunk/Source/StructureMap.Testing/Graph/PluginTester.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap.Testing/Graph/PluginTester.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using NUnit.Framework;
@@ -405,6 +406,64 @@
}
}
+
+ [TestFixture]
+ public class when_finding_property_name_of_enumerable_type
+ {
+ private Plugin plugin;
+
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ plugin = new Plugin(typeof (ClassWithEnumerables));
+ }
+
+ [Test]
+ public void smoke_test()
+ {
+ new Plugin(typeof (ClassThatUsesValidators)).FindArgumentNameForEnumerableOf(typeof (IValidator)).
+ ShouldEqual("validators");
+ }
+
+ [Test]
+ public void array_in_ctor()
+ {
+ plugin.FindArgumentNameForEnumerableOf(typeof (IEngine)).ShouldEqual("engines");
+ }
+
+ [Test]
+ public void enumerable_in_ctor()
+ {
+ plugin.FindArgumentNameForEnumerableOf(typeof (IAutomobile)).ShouldEqual("autos");
+ }
+
+ [Test]
+ public void ilist_as_setter()
+ {
+ plugin.FindArgumentNameForEnumerableOf(typeof (IWidget)).ShouldEqual("Widgets");
+ }
+
+ [Test]
+ public void list_as_setter()
+ {
+ plugin.FindArgumentNameForEnumerableOf(typeof (Rule)).ShouldEqual("Rules");
+ }
+ }
+
+
+ public class ClassWithEnumerables
+ {
+ public ClassWithEnumerables(IEngine[] engines, IEnumerable<IAutomobile> autos)
+ {
+
+ }
+
+ public IList<IWidget> Widgets { get; set; }
+ public List<Rule> Rules { get; set; }
+ }
+
public class LotsOfStuff
{
public LotsOfStuff(IEngine engine, IEngine[] engines, string name, int age, BreedEnum breed)
@@ -432,6 +491,8 @@
{
}
+
+
[Pluggable("Pushrod")]
public class PushrodEngine : IEngine
{
Modified: trunk/Source/StructureMap.Testing/ObjectFactoryInitializeTester.cs
===================================================================
--- trunk/Source/StructureMap.Testing/ObjectFactoryInitializeTester.cs 2009-12-29 02:55:12 UTC (rev 314)
+++ trunk/Source/StructureMap.Testing/ObjectFactoryInitializeTester.cs 2009-12-29 03:50:13 UTC (rev 315)
@@ -11,8 +11,9 @@
{
public InitializeRegistry()
{
- InstanceOf<IWidget>().Is.OfConcreteType<ColorWidget>().WithCtorArg("color").EqualTo("Green").WithName(
- "Green");
+ For<IWidget>().Add<ColorWidget>()
+ .Ctor<string>("color").Is("Green")
+ .Named("Green");
}
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|