/// <summary> /// Creates a new model filter for the specified root object. /// </summary> /// <param name="root"></param> /// <param name="paths"></param> public ModelFilter(ModelInstance root, string[] paths) { // Store the model root this.root = root; // Delay model notifications while loading the model ModelEventScope.Perform(() => { // Subscribe to paths for each predicate ModelType rootType = root.Type; foreach (string p in paths) { // Get the path for this predicate ModelPath path = rootType.GetPath(p); // Skip this predicate if it is already being monitored if (pathModels.ContainsKey(path)) { continue; } // Subscribe to path change events path.Change += path_PathChanged; // Get the model for this path and add the path and model to the list of paths pathModels.Add(path, path.GetInstances(root)); } }); // Combine the models for each path into a universal model CombinePathModels(); }
/// <summary> /// Attempts to initialize a <see cref="ModelSource"/> with the specified root type and path. /// </summary> /// <param name="rootType">The root type name, which is required for instance paths</param> /// <param name="path">The source path, which is either an instance path or a static path</param> /// <returns>True if the source was created, otherwise false</returns> bool InitializeFromTypeAndPath(ModelType rootType, string path, out ModelProperty sourceProperty) { // Instance Path ModelPath instancePath = null; sourceProperty = null; // clean up any array indices string indexFreePath = arraySyntaxRegex.Replace(path, ""); try { if (rootType != null) { rootType.TryGetPath(indexFreePath, out instancePath); } } catch { } if (instancePath != null) { InitializeFromModelPath(instancePath, path, out sourceProperty); return(true); } // Static Path else if (path.Contains('.')) { // Store the source path var sourceModelType = ModelContext.Current.GetModelType(path.Substring(0, path.LastIndexOf('.'))); if (sourceModelType != null) { sourceProperty = sourceModelType.Properties[path.Substring(path.LastIndexOf('.') + 1)]; if (sourceProperty != null && sourceProperty.IsStatic) { this.Path = path; this.IsStatic = true; this.SourceProperty = sourceProperty.Name; this.SourceType = sourceProperty.DeclaringType.Name; return(true); } } } return(false); }
/// <summary> /// Initialize the source from a valid <see cref="ModelPath"/> /// </summary> /// <param name="instancePath"></param> void InitializeFromModelPath(ModelPath instancePath, string path, out ModelProperty sourceProperty) { this.Path = path; this.IsStatic = false; this.RootType = instancePath.RootType.Name; var tokens = tokenizer.Matches(path); this.steps = new SourceStep[tokens.Count]; var rootType = instancePath.RootType; int i = 0; sourceProperty = null; foreach (Match token in tokens) { sourceProperty = rootType.Properties[token.Groups["Property"].Value]; if (sourceProperty == null) { throw new ArgumentException(String.Format("Property {0} is not valid for type {1} in path {2}.", token.Groups["Property"].Value, rootType.Name, path)); } int index; if (!Int32.TryParse(token.Groups["Index"].Value, out index)) { index = -1; } steps[i] = new SourceStep() { Property = sourceProperty.Name, Index = index, DeclaringType = sourceProperty.DeclaringType, IsReferenceProperty = sourceProperty is ModelReferenceProperty }; rootType = sourceProperty is ModelReferenceProperty ? ((ModelReferenceProperty)sourceProperty).PropertyType : null; i++; } this.SourceProperty = sourceProperty.Name; this.SourceType = sourceProperty.DeclaringType.Name; }
/// <summary> /// Creates a new <see cref="ModelPath"/> instance for the specified root <see cref="ModelType"/> /// and path string. /// </summary> /// <param name="rootType"></param> /// <param name="path"></param> internal static ModelPath CreatePath(ModelType rootType, string path) { // Create the new path ModelPath newPath = new ModelPath() { RootType = rootType }; // Create a temporary root step var root = new ModelStep(newPath); var steps = new List <ModelStep>() { root }; var stack = new Stack <List <ModelStep> >(); int expectedTokenStart = 0; var tokenMatches = pathParser.Matches(path); int tokenIndex = 0; foreach (Match tokenMatch in tokenMatches) { // ensure there are no gaps between tokens except for whitespace if (tokenMatch.Index != expectedTokenStart && path.Substring(expectedTokenStart, tokenMatch.Index - expectedTokenStart - 1).Trim() != "") { return(null); // throw new ArgumentException("The specified path, '" + path + "', is not valid. Character " + (expectedTokenStart)); } expectedTokenStart = tokenMatch.Index + tokenMatch.Length; var token = tokenMatch.Value; switch (token[0]) { case '{': stack.Push(steps); break; case '}': steps = stack.Pop(); break; case ',': steps = stack.Peek(); break; case '.': break; case '<': var filter = rootType.Context.GetModelType(token.Substring(1, token.Length - 2)); foreach (var step in steps) { if (step.Property is ModelReferenceProperty && (((ModelReferenceProperty)step.Property).PropertyType == filter || ((ModelReferenceProperty)step.Property).PropertyType.IsSubType(filter))) { step.Filter = filter; } else { RemoveStep(step); } } break; default: // Get the property name for the next step var propertyName = token; // Track the next steps var nextSteps = new List <ModelStep>(); // Process each of the current steps foreach (var step in steps) { // Ensure the current step is a valid reference property if (step.Property != null && step.Property is ModelValueProperty) { return(null); // throw new ArgumentException("Property '" + step.Property.Name + "' is a value property and cannot have child properties specified."); } // Get the type of the current step var currentType = step.Property != null ? ((ModelReferenceProperty)step.Property).PropertyType : step.Path.RootType; if (step.Filter != null) { if (step.Filter != currentType && !currentType.IsSubType(step.Filter)) { return(null); // throw new ArgumentException("Filter type '" + step.Filter.Name + "' is not a valid subtype of '" + currentType.Name + "'."); } currentType = step.Filter; } // Process the current and all subtypes, honoring any specified type filters foreach (var type in currentType.GetDescendentsInclusive()) { // Get the current property var property = type.Properties[propertyName]; // Ensure the property is valid if (property == null || property.IsStatic || (property.DeclaringType != type && type != currentType && currentType.Properties[propertyName] != null)) { continue; } // Look ahead to see if this step is filtered filter = tokenIndex < tokenMatches.Count - 1 && tokenMatches[tokenIndex + 1].Value.StartsWith("<") ? rootType.Context.GetModelType(tokenMatches[tokenIndex + 1].Value.Substring(1, tokenMatches[tokenIndex + 1].Length - 2)) : null; // See if the step already exists for this property and filter or needs to be created var nextStep = step.NextSteps.Where(s => s.Property == property && s.Filter == filter).FirstOrDefault(); if (nextStep == null) { nextStep = new ModelStep(newPath) { Property = property, Filter = filter, PreviousStep = step.Property != null ? step : null }; step.NextSteps.Add(nextStep); } nextSteps.Add(nextStep); } // Remove steps that do not lead to the end of the path RemoveStep(step); } // Immediately exit if no steps were found matching the requested path if (nextSteps.Count == 0) { return(null); } steps = nextSteps; break; } ++tokenIndex; } // Throw an exception if there are unmatched property group delimiters if (stack.Count > 0) { throw new ArgumentException("Unclosed '{' in path: " + path, "path"); } // Return null if the path was not valid if (!root.NextSteps.Any()) { return(null); } // Otherwise, finish initializing and return the new path newPath.FirstSteps = root.NextSteps; newPath.Path = path; return(newPath); }
internal ModelStep(ModelPath path) { this.Path = path; this.NextSteps = new ModelStepList(); }
/// <summary> /// Initialize the source from a valid <see cref="ModelPath"/> /// </summary> /// <param name="instancePath"></param> void InitializeFromModelPath(ModelPath instancePath, string path, out ModelProperty sourceProperty) { this.Path = path; this.IsStatic = false; this.RootType = instancePath.RootType.Name; var tokens = tokenizer.Matches(path); this.steps = new SourceStep[tokens.Count]; var rootType = instancePath.RootType; int i = 0; sourceProperty = null; foreach(Match token in tokens) { sourceProperty = rootType.Properties[token.Groups["Property"].Value]; if (sourceProperty == null) throw new ArgumentException(String.Format("Property {0} is not valid for type {1} in path {2}.", token.Groups["Property"].Value, rootType.Name, path)); int index; if (!Int32.TryParse(token.Groups["Index"].Value, out index)) index = -1; steps[i] = new SourceStep() { Property = sourceProperty.Name, Index = index, DeclaringType = sourceProperty.DeclaringType.Name, IsReferenceProperty = sourceProperty is ModelReferenceProperty }; rootType = sourceProperty is ModelReferenceProperty ? ((ModelReferenceProperty)sourceProperty).PropertyType : null; i++; } this.SourceProperty = sourceProperty.Name; this.SourceType = sourceProperty.DeclaringType.Name; }
/// <summary> /// Creates a new <see cref="ModelSource"/> for the specified root type and path. /// </summary> /// <param name="path">The source model path</param> public ModelSource(ModelPath path) { ModelProperty property; InitializeFromModelPath(path, path.Path, out property); }
internal ModelPathChangeEvent(ModelInstance instance, ModelPath path) : base(instance) { this.path = path; }
/// <summary> /// Creates a new <see cref="ModelPath"/> instance for the specified root <see cref="ModelType"/> /// and path string. /// </summary> /// <param name="rootType"></param> /// <param name="path"></param> internal static ModelPath CreatePath(ModelType rootType, string path) { // Create the new path ModelPath newPath = new ModelPath() { RootType = rootType }; // Create a temporary root step var root = new ModelStep(newPath); var steps = new List<ModelStep>() { root }; var stack = new Stack<List<ModelStep>>(); int expectedTokenStart = 0; var tokenMatches = pathParser.Matches(path); int tokenIndex = 0; foreach (Match tokenMatch in tokenMatches) { // ensure there are no gaps between tokens except for whitespace if (tokenMatch.Index != expectedTokenStart && path.Substring(expectedTokenStart, tokenMatch.Index - expectedTokenStart - 1).Trim() != "" ) return null; // throw new ArgumentException("The specified path, '" + path + "', is not valid. Character " + (expectedTokenStart)); expectedTokenStart = tokenMatch.Index + tokenMatch.Length; var token = tokenMatch.Value; switch (token[0]) { case '{': stack.Push(steps); break; case '}': steps = stack.Pop(); break; case ',': steps = stack.Peek(); break; case '.': break; case '<': var filter = rootType.Context.GetModelType(token.Substring(1, token.Length - 2)); foreach (var step in steps) { if (step.Property is ModelReferenceProperty && (((ModelReferenceProperty)step.Property).PropertyType == filter || ((ModelReferenceProperty)step.Property).PropertyType.IsSubType(filter))) step.Filter = filter; else RemoveStep(step); } break; default: // Get the property name for the next step var propertyName = token; // Track the next steps var nextSteps = new List<ModelStep>(); // Process each of the current steps foreach (var step in steps) { // Ensure the current step is a valid reference property if (step.Property != null && step.Property is ModelValueProperty) return null; // throw new ArgumentException("Property '" + step.Property.Name + "' is a value property and cannot have child properties specified."); // Get the type of the current step var currentType = step.Property != null ? ((ModelReferenceProperty)step.Property).PropertyType : step.Path.RootType; if (step.Filter != null) { if (step.Filter != currentType && !currentType.IsSubType(step.Filter)) return null; // throw new ArgumentException("Filter type '" + step.Filter.Name + "' is not a valid subtype of '" + currentType.Name + "'."); currentType = step.Filter; } // Process the current and all subtypes, honoring any specified type filters foreach (var type in currentType.GetDescendentsInclusive()) { // Get the current property var property = type.Properties[propertyName]; // Ensure the property is valid if (property == null || property.IsStatic || (property.DeclaringType != type && type != currentType && currentType.Properties[propertyName] != null)) continue; // Look ahead to see if this step is filtered filter = tokenIndex < tokenMatches.Count - 1 && tokenMatches[tokenIndex + 1].Value.StartsWith("<") ? rootType.Context.GetModelType(tokenMatches[tokenIndex + 1].Value.Substring(1, tokenMatches[tokenIndex + 1].Length - 2)) : null; // See if the step already exists for this property and filter or needs to be created var nextStep = step.NextSteps.Where(s => s.Property == property && s.Filter == filter).FirstOrDefault(); if (nextStep == null) { nextStep = new ModelStep(newPath) { Property = property, Filter = filter, PreviousStep = step.Property != null ? step : null }; step.NextSteps.Add(nextStep); } nextSteps.Add(nextStep); } // Remove steps that do not lead to the end of the path RemoveStep(step); } // Immediately exit if no steps were found matching the requested path if (nextSteps.Count == 0) return null; steps = nextSteps; break; } ++tokenIndex; } // Throw an exception if there are unmatched property group delimiters if (stack.Count > 0) throw new ArgumentException("Unclosed '{' in path: " + path, "path"); // Return null if the path was not valid if (!root.NextSteps.Any()) return null; // Otherwise, finish initializing and return the new path newPath.FirstSteps = root.NextSteps; newPath.Path = path; return newPath; }