#if XFORMS
namespace Caliburn.Micro.Core.Xamarin.Forms
#else
namespace Caliburn.Micro
#endif
{
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Collections.Generic;
using Caliburn.Micro.Core;
#if WinRT
using Windows.UI.Xaml;
#endif
#if XFORMS
using UIElement = global::Xamarin.Forms.Element;
#endif
///
/// A strategy for determining which view model to use for a given view.
///
public static class ViewModelLocator
{
#if ANDROID
const string DefaultViewSuffix = "Activity";
#elif IOS
const string DefaultViewSuffix = "ViewController";
#else
const string DefaultViewSuffix = "View";
#endif
static readonly ILog Log = LogManager.GetLog(typeof(ViewModelLocator));
//These fields are used for configuring the default type mappings. They can be changed using ConfigureTypeMappings().
static string defaultSubNsViews;
static string defaultSubNsViewModels;
static bool useNameSuffixesInMappings;
static string nameFormat;
static string viewModelSuffix;
static readonly List ViewSuffixList = new List();
static bool includeViewSuffixInVmNames;
///
/// Used to transform names.
///
public static readonly NameTransformer NameTransformer = new NameTransformer();
///
/// The name of the capture group used as a marker for rules that return interface types
///
public static string InterfaceCaptureGroupName = "isinterface";
static ViewModelLocator() {
var configuration = new TypeMappingConfiguration();
#if ANDROID
configuration.DefaultSubNamespaceForViews = "Activities";
configuration.ViewSuffixList.Add("Activity");
configuration.IncludeViewSuffixInViewModelNames = false;
#elif IOS
configuration.DefaultSubNamespaceForViews = "ViewControllers";
configuration.ViewSuffixList.Add("ViewController");
configuration.IncludeViewSuffixInViewModelNames = false;
#endif
ConfigureTypeMappings(configuration);
}
///
/// Specifies how type mappings are created, including default type mappings. Calling this method will
/// clear all existing name transformation rules and create new default type mappings according to the
/// configuration.
///
/// An instance of TypeMappingConfiguration that provides the settings for configuration
public static void ConfigureTypeMappings(TypeMappingConfiguration config)
{
if (String.IsNullOrEmpty(config.DefaultSubNamespaceForViews))
{
throw new ArgumentException("DefaultSubNamespaceForViews field cannot be blank.");
}
if (String.IsNullOrEmpty(config.DefaultSubNamespaceForViewModels))
{
throw new ArgumentException("DefaultSubNamespaceForViewModels field cannot be blank.");
}
if (String.IsNullOrEmpty(config.NameFormat))
{
throw new ArgumentException("NameFormat field cannot be blank.");
}
NameTransformer.Clear();
ViewSuffixList.Clear();
defaultSubNsViews = config.DefaultSubNamespaceForViews;
defaultSubNsViewModels = config.DefaultSubNamespaceForViewModels;
nameFormat = config.NameFormat;
useNameSuffixesInMappings = config.UseNameSuffixesInMappings;
viewModelSuffix = config.ViewModelSuffix;
ViewSuffixList.AddRange(config.ViewSuffixList);
includeViewSuffixInVmNames = config.IncludeViewSuffixInViewModelNames;
SetAllDefaults();
}
private static void SetAllDefaults()
{
if (useNameSuffixesInMappings)
{
//Add support for all view suffixes
ViewSuffixList.Apply(AddDefaultTypeMapping);
}
else
{
AddSubNamespaceMapping(defaultSubNsViews, defaultSubNsViewModels);
}
}
///
/// Adds a default type mapping using the standard namespace mapping convention
///
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddDefaultTypeMapping(string viewSuffix = DefaultViewSuffix)
{
if (!useNameSuffixesInMappings)
{
return;
}
//Check for . construct
AddNamespaceMapping(String.Empty, String.Empty, viewSuffix);
//Check for .Views.. construct
AddSubNamespaceMapping(defaultSubNsViews, defaultSubNsViewModels, viewSuffix);
}
///
/// Adds a standard type mapping based on namespace RegEx replace and filter patterns
///
/// RegEx replace pattern for source namespace
/// RegEx filter pattern for source namespace
/// Array of RegEx replace values for target namespaces
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddTypeMapping(string nsSourceReplaceRegEx, string nsSourceFilterRegEx, string[] nsTargetsRegEx, string viewSuffix = DefaultViewSuffix)
{
var replist = new List();
Action func;
const string basegrp = "${basename}";
var interfacegrp = "${" + InterfaceCaptureGroupName + "}";
if (useNameSuffixesInMappings)
{
if (viewModelSuffix.Contains(viewSuffix) || !includeViewSuffixInVmNames)
{
var nameregex = String.Format(nameFormat, basegrp, viewModelSuffix);
func = t =>
{
replist.Add(t + "I" + nameregex + interfacegrp);
replist.Add(t + "I" + basegrp + interfacegrp);
replist.Add(t + nameregex);
replist.Add(t + basegrp);
};
}
else
{
var nameregex = String.Format(nameFormat, basegrp, "${suffix}" + viewModelSuffix);
func = t =>
{
replist.Add(t + "I" + nameregex + interfacegrp);
replist.Add(t + nameregex);
};
}
}
else
{
func = t =>
{
replist.Add(t + "I" + basegrp + interfacegrp);
replist.Add(t + basegrp);
};
}
nsTargetsRegEx.ToList().Apply(t => func(t));
string suffix = useNameSuffixesInMappings ? viewSuffix : String.Empty;
var srcfilterregx = String.IsNullOrEmpty(nsSourceFilterRegEx)
? null
: String.Concat(nsSourceFilterRegEx, String.Format(nameFormat, RegExHelper.NameRegEx, suffix), "$");
var rxbase = RegExHelper.GetNameCaptureGroup("basename");
var rxsuffix = RegExHelper.GetCaptureGroup("suffix", suffix);
//Add a dummy capture group -- place after the "$" so it can never capture anything
var rxinterface = RegExHelper.GetCaptureGroup(InterfaceCaptureGroupName, String.Empty);
NameTransformer.AddRule(
String.Concat(nsSourceReplaceRegEx, String.Format(nameFormat, rxbase, rxsuffix), "$", rxinterface),
replist.ToArray(),
srcfilterregx
);
}
///
/// Adds a standard type mapping based on namespace RegEx replace and filter patterns
///
/// RegEx replace pattern for source namespace
/// RegEx filter pattern for source namespace
/// RegEx replace value for target namespace
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddTypeMapping(string nsSourceReplaceRegEx, string nsSourceFilterRegEx, string nsTargetRegEx, string viewSuffix = DefaultViewSuffix)
{
AddTypeMapping(nsSourceReplaceRegEx, nsSourceFilterRegEx, new[] { nsTargetRegEx }, viewSuffix);
}
///
/// Adds a standard type mapping based on simple namespace mapping
///
/// Namespace of source type
/// Namespaces of target type as an array
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddNamespaceMapping(string nsSource, string[] nsTargets, string viewSuffix = DefaultViewSuffix)
{
//need to terminate with "." in order to concatenate with type name later
var nsencoded = RegExHelper.NamespaceToRegEx(nsSource + ".");
//Start pattern search from beginning of string ("^")
//unless original string was blank (i.e. special case to indicate "append target to source")
if (!String.IsNullOrEmpty(nsSource))
{
nsencoded = "^" + nsencoded;
}
//Capture namespace as "origns" in case we need to use it in the output in the future
var nsreplace = RegExHelper.GetCaptureGroup("origns", nsencoded);
var nsTargetsRegEx = nsTargets.Select(t => t + ".").ToArray();
AddTypeMapping(nsreplace, null, nsTargetsRegEx, viewSuffix);
}
///
/// Adds a standard type mapping based on simple namespace mapping
///
/// Namespace of source type
/// Namespace of target type
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddNamespaceMapping(string nsSource, string nsTarget, string viewSuffix = DefaultViewSuffix)
{
AddNamespaceMapping(nsSource, new[] { nsTarget }, viewSuffix);
}
///
/// Adds a standard type mapping by substituting one subnamespace for another
///
/// Subnamespace of source type
/// Subnamespaces of target type as an array
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddSubNamespaceMapping(string nsSource, string[] nsTargets, string viewSuffix = DefaultViewSuffix)
{
//need to terminate with "." in order to concatenate with type name later
var nsencoded = RegExHelper.NamespaceToRegEx(nsSource + ".");
string rxbeforetgt, rxaftersrc, rxaftertgt;
string rxbeforesrc = rxbeforetgt = rxaftersrc = rxaftertgt = String.Empty;
if (!String.IsNullOrEmpty(nsSource))
{
if (!nsSource.StartsWith("*"))
{
rxbeforesrc = RegExHelper.GetNamespaceCaptureGroup("nsbefore");
rxbeforetgt = @"${nsbefore}";
}
if (!nsSource.EndsWith("*"))
{
rxaftersrc = RegExHelper.GetNamespaceCaptureGroup("nsafter");
rxaftertgt = "${nsafter}";
}
}
var rxmid = RegExHelper.GetCaptureGroup("subns", nsencoded);
var nsreplace = String.Concat(rxbeforesrc, rxmid, rxaftersrc);
var nsTargetsRegEx = nsTargets.Select(t => String.Concat(rxbeforetgt, t, ".", rxaftertgt)).ToArray();
AddTypeMapping(nsreplace, null, nsTargetsRegEx, viewSuffix);
}
///
/// Adds a standard type mapping by substituting one subnamespace for another
///
/// Subnamespace of source type
/// Subnamespace of target type
/// Suffix for type name. Should be "View" or synonym of "View". (Optional)
public static void AddSubNamespaceMapping(string nsSource, string nsTarget, string viewSuffix = DefaultViewSuffix)
{
AddSubNamespaceMapping(nsSource, new[] { nsTarget }, viewSuffix);
}
///
/// Makes a type name into an interface name.
///
/// The part.
///
public static string MakeInterface(string typeName)
{
var suffix = string.Empty;
if (typeName.Contains("[["))
{
//generic type
var genericParStart = typeName.IndexOf("[[");
suffix = typeName.Substring(genericParStart);
typeName = typeName.Remove(genericParStart);
}
var index = typeName.LastIndexOf(".");
return typeName.Insert(index + 1, "I") + suffix;
}
///
/// Transforms a View type name into all of its possible ViewModel type names. Accepts a flag
/// to include or exclude interface types.
///
/// Enumeration of transformed names
/// Arguments:
/// typeName = The name of the View type being resolved to its companion ViewModel.
/// includeInterfaces = Flag to indicate if interface types are included
///
public static Func> TransformName = (typeName, includeInterfaces) =>
{
Func getReplaceString;
if (includeInterfaces)
{
getReplaceString = r => r;
}
else
{
var interfacegrpregex = @"\${" + InterfaceCaptureGroupName + @"}$";
getReplaceString = r => Regex.IsMatch(r, interfacegrpregex) ? String.Empty : r;
}
return NameTransformer.Transform(typeName, getReplaceString).Where(n => n != String.Empty);
};
///
/// Determines the view model type based on the specified view type.
///
/// The view model type.
///
/// Pass the view type and receive a view model type. Pass true for the second parameter to search for interfaces.
///
public static Func LocateTypeForViewType = (viewType, searchForInterface) =>
{
var typeName = viewType.FullName;
var viewModelTypeList = TransformName(typeName, searchForInterface).ToList();
var viewModelType = AssemblySource.FindTypeByNames(viewModelTypeList);
if (viewModelType == null)
{
Log.Warn("View Model not found. Searched: {0}.", string.Join(", ", viewModelTypeList.ToArray()));
}
return viewModelType;
};
///
/// Locates the view model for the specified view type.
///
/// The view model.
///
/// Pass the view type as a parameter and receive a view model instance.
///
public static Func LocateForViewType = viewType =>
{
var viewModelType = LocateTypeForViewType(viewType, false);
if (viewModelType != null)
{
var viewModel = IoC.GetInstance(viewModelType, null);
if (viewModel != null)
{
return viewModel;
}
}
viewModelType = LocateTypeForViewType(viewType, true);
return viewModelType != null
? IoC.GetInstance(viewModelType, null)
: null;
};
///
/// Locates the view model for the specified view instance.
///
/// The view model.
///
/// Pass the view instance as a parameters and receive a view model instance.
///
public static Func