/// <summary> /// Given a targeted binding expression (in the form "foo->bar"), this method extracts the target name, target type, and expression text. /// </summary> private Boolean GetExpressionTargetInfo(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, XObject source, ref String expText, out String expTarget, out Type expTargetType) { const string TargetExpressionDelimiter = "->"; var expOriginal = expText; var delimiterIndex = expText.IndexOf(TargetExpressionDelimiter); if (delimiterIndex >= 0) { var expPartTarget = expText.Substring(0, delimiterIndex); var expPartText = expText.Substring(delimiterIndex + TargetExpressionDelimiter.Length); var matchCandidates = (from element in dataSourceWrapperInfo.DataSourceDefinition.Definition.Descendants() where (String)element.Attribute("Name") == expPartTarget select element).ToList(); if (matchCandidates.Count == 0) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsNotFound.Format(expPartTarget)); } if (matchCandidates.Count > 1) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsAmbiguous.Format(expPartTarget)); } var match = matchCandidates.Single(); var matchName = match.Name.LocalName; expText = expPartText; expTargetType = ExpressionCompiler.GetPlaceholderType(dataSourceWrapperInfo.DataSourceType, matchName); if (expTargetType == null && !state.GetKnownType(matchName, out expTargetType)) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsUnrecognizedType.Format(expOriginal, matchName)); } expTarget = String.Format("__UPF_GetElementByName<{0}>(\"{1}\").", GetCSharpTypeName(expTargetType), expPartTarget); return(true); } expTarget = default(String); expTargetType = dataSourceWrapperInfo.DataSourceType; return(false); }
/// <summary> /// Gets a collection of <see cref="DataSourceDefinition"/> instances for any component templates which /// are currently registered with the Ultraviolet Presentation Foundation. /// </summary> /// <returns>A collection of <see cref="DataSourceDefinition"/> instances which represent UPF component template definitions.</returns> private static IEnumerable<DataSourceDefinition> RetrieveTemplateDefinitions(ExpressionCompilerState state) { if (state.ComponentTemplateManager == null) return Enumerable.Empty<DataSourceDefinition>(); var templateDefs = from template in state.ComponentTemplateManager select DataSourceDefinition.FromComponentTemplate(template.Key, template.Value.Root.Element("View")); return templateDefs.ToList(); }
/// <summary> /// Writes the source code for an individual data source wrapper class. /// </summary> private static void WriteSourceCodeForDataSourceWrapperClass(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, DataSourceWrapperWriter writer) { // Class declaration writer.WriteLine("[System.CLSCompliant(false)]"); writer.WriteLine("[System.CodeDom.Compiler.GeneratedCode(\"UPF Binding Expression Compiler\", \"{0}\")]", FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); writer.WriteLine("public sealed partial class {0} : {1}", dataSourceWrapperInfo.DataSourceWrapperName, writer.GetCSharpTypeName(typeof(CompiledDataSourceWrapper))); writer.WriteLine("{"); // Constructors writer.WriteLine("#region Constructors"); writer.WriteConstructor(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // IDataSourceWrapper writer.WriteLine("#region IDataSourceWrapper"); writer.WriteIDataSourceWrapperImplementation(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // Methods writer.WriteLine("#region Methods"); var methods = dataSourceWrapperInfo.DataSourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var method in methods) { if (!NeedsWrapper(method)) continue; writer.WriteWrapperMethod(method); } writer.WriteLine("#endregion"); writer.WriteLine(); // Properties writer.WriteLine("#region Properties"); var properties = dataSourceWrapperInfo.DataSourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var property in properties) { if (!NeedsWrapper(property)) continue; writer.WriteWrapperProperty(property); } writer.WriteLine("#endregion"); writer.WriteLine(); // Fields writer.WriteLine("#region Fields"); var fields = dataSourceWrapperInfo.DataSourceType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var field in fields) { if (!NeedsWrapper(field)) continue; writer.WriteWrapperProperty(field); } writer.WriteLine("#endregion"); writer.WriteLine(); // Expressions writer.WriteLine("#region Expressions"); for (int i = 0; i < dataSourceWrapperInfo.Expressions.Count; i++) { var expressionInfo = dataSourceWrapperInfo.Expressions[i]; writer.WriteExpressionProperty(state, dataSourceWrapperInfo, expressionInfo, i); } writer.WriteLine("#endregion"); // Class complete writer.WriteLine("}"); }
/// <summary> /// Writes a property which wraps a binding expression. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceWrapperInfo">A <see cref="DataSourceWrapperInfo"/> describing the data source for which to write an expression property.</param> /// <param name="expressionInfo">The binding expression for which to write a property.</param> /// <param name="id">The expression's identifier within the view model.</param> public void WriteExpressionProperty(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, BindingExpressionInfo expressionInfo, Int32 id) { var isDependencyProperty = false; var isSimpleDependencyProperty = false; var expText = BindingExpressions.GetBindingMemberPathPart(expressionInfo.Expression); var expTarget = default(String); var expTargetType = dataSourceWrapperInfo.DataSourceType; var expFormatString = BindingExpressions.GetBindingFormatStringPart(expressionInfo.Expression); var targeted = GetExpressionTargetInfo(state, dataSourceWrapperInfo, expressionInfo.Source, ref expText, out expTarget, out expTargetType); var dprop = DependencyProperty.FindByName(expText, expTargetType); var dpropField = default(FieldInfo); if (dprop != null) { isDependencyProperty = true; dpropField = (from prop in dprop.OwnerType.GetFields(BindingFlags.Public | BindingFlags.Static) where prop.FieldType == typeof(DependencyProperty) && prop.GetValue(null) == dprop select prop).SingleOrDefault(); if (dpropField == null) { throw new BindingExpressionCompilationErrorException(expressionInfo.Source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, PresentationStrings.CannotFindDependencyPropertyField.Format(dprop.OwnerType.Name, dprop.Name)); } if (String.IsNullOrEmpty(expFormatString) && !targeted) { isSimpleDependencyProperty = true; } } WriteLine("[{0}(@\"{1}\", SimpleDependencyPropertyOwner = {2}, SimpleDependencyPropertyName = {3})]", typeof(CompiledBindingExpressionAttribute).FullName, expressionInfo.Expression.Replace("\"", "\"\""), isSimpleDependencyProperty ? "typeof(" + GetCSharpTypeName(dprop.OwnerType) + ")" : "null", isSimpleDependencyProperty ? "\"" + dprop.Name + "\"" : "null"); WriteLine("public {0} __UPF_Expression{1}", GetCSharpTypeName(expressionInfo.Type), id); WriteLine("{"); if (expressionInfo.GenerateGetter) { expressionInfo.GetterLineStart = LineCount; var getexp = default(String); if (isDependencyProperty) { getexp = String.Format("{0}GetValue<{1}>({2}.{3})", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); } else { getexp = String.Format("{0}{1}", expTarget, expText); } var hasFormatString = !String.IsNullOrEmpty(expFormatString); expFormatString = hasFormatString ? String.Format("\"{{0:{0}}}\"", expFormatString) : "null"; if (IsStringType(expressionInfo.Type) || (expressionInfo.Type == typeof(Object) && hasFormatString)) { WriteLine("get"); WriteLine("{"); WriteLine("var value = {0};", getexp); WriteLine("return ({0})__UPF_ConvertToString(value, {1});", GetCSharpTypeName(expressionInfo.Type), expFormatString); WriteLine("}"); } else { WriteLine("get {{ return ({0})({1}); }}", GetCSharpTypeName(expressionInfo.Type), getexp); } expressionInfo.GetterLineEnd = LineCount - 1; } if (dprop != null && dprop.IsReadOnly) { expressionInfo.GenerateSetter = false; } if (expressionInfo.GenerateSetter) { var targetTypeName = expressionInfo.CS0266TargetType; var targetTypeSpecified = !String.IsNullOrEmpty(targetTypeName); expressionInfo.SetterLineStart = LineCount; if (isDependencyProperty) { if (IsStringType(expressionInfo.Type)) { WriteLine("set"); WriteLine("{"); WriteLine("var current = {0}GetValue<{1}>({2}.{3});", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); WriteLine("{0}SetValue<{1}>({2}.{3}, __UPF_ConvertFromString(value.ToString(), current));", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); WriteLine("}"); } else { if (expressionInfo.NullableFixup) { WriteLine(targetTypeSpecified ? "set {{ {0}SetValue<{1}>({2}.{3}, ({4})(value ?? default({1}))); }}" : "set {{ {0}SetValue<{1}>({2}.{3}, value ?? default({1})); }}", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name, targetTypeName); } else { WriteLine(targetTypeSpecified ? "set {{ {0}SetValue<{1}>({2}.{3}, ({4})(value)); }}" : "set {{ {0}SetValue<{1}>({2}.{3}, value); }}", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name, targetTypeName); } } } else { if (expressionInfo.Type == typeof(String) || expressionInfo.Type == typeof(VersionedStringSource)) { WriteLine("set"); WriteLine("{"); WriteLine("var current = {0}{1};", expTarget, expText); WriteLine("{0}{1} = __UPF_ConvertFromString(value.ToString(), current);", expTarget, expText); WriteLine("}"); } else { if (expressionInfo.NullableFixup) { WriteLine(targetTypeSpecified ? "set {{ {0}{1} = ({3})(value ?? default({2})); }}" : "set {{ {0}{1} = value ?? default({2}); }}", expTarget, expText, GetCSharpTypeName(Nullable.GetUnderlyingType(expressionInfo.Type)), targetTypeName); } else { WriteLine(targetTypeSpecified ? "set {{ {0}{1} = ({2})(value); }}" : "set {{ {0}{1} = value; }}", expTarget, expText, targetTypeName); } } } expressionInfo.SetterLineEnd = LineCount - 1; } WriteLine("}"); }
/// <summary> /// Gets the working directory for the specified compilation. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <returns>The working directory for the specified compilation.</returns> private static String GetWorkingDirectory(ExpressionCompilerState state) { return state.WorkInTemporaryDirectory ? Path.Combine(Path.GetTempPath(), "UV_CompiledExpressions") : "UV_CompiledExpressions"; }
/// <summary> /// Writes any compiler errors to the working directory. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="models">The list of models which were compiled.</param> /// <param name="results">The results of the previous compilation pass.</param> private static void WriteErrorsToWorkingDirectory(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, CompilerResults results) { var logpath = Path.Combine(GetWorkingDirectory(state), "Compilation Errors.txt"); try { File.Delete(logpath); } catch(DirectoryNotFoundException) { } var logdir = Path.GetDirectoryName(logpath); Directory.CreateDirectory(logdir); // NOTE: Under Mono we seem to get warnings even when "Treat Warnings as Errors" is turned off. var trueErrors = results.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).ToList(); if (trueErrors.Count > 0) { var filesWithErrors = trueErrors.Select(x => x.FileName).Where(x => !String.IsNullOrEmpty(x)).Distinct(); var filesWithErrorsPretty = new Dictionary<String, String> { { String.Empty, String.Empty } }; foreach (var fileWithErrors in filesWithErrors) { var modelNameForFile = models.Where(x => x.UniqueID.ToString() == Path.GetFileNameWithoutExtension(fileWithErrors)) .Select(x => x.DataSourceWrapperName).SingleOrDefault(); var prettyFileName = Path.ChangeExtension(modelNameForFile, "cs"); filesWithErrorsPretty[fileWithErrors] = prettyFileName; var fileWithErrorsSrc = Path.GetFullPath(fileWithErrors); var fileWithErrorsDst = Path.Combine(GetWorkingDirectory(state), prettyFileName); File.Copy(fileWithErrorsSrc, fileWithErrorsDst, true); } var errorStrings = trueErrors.Select(x => String.Format("{0}\t{1}\t{2}\t{3}", x.ErrorNumber, x.ErrorText, filesWithErrorsPretty[x.FileName ?? String.Empty], x.Line)); File.WriteAllLines(logpath, Enumerable.Union(new[] { "Code\tDescription\tFile\tLine" }, errorStrings)); } }
/// <summary> /// Deletes the compiler's working directory. /// </summary> /// <param name="state">The expression compiler's current state.</param> private static void DeleteWorkingDirectory(ExpressionCompilerState state) { try { Directory.Delete(GetWorkingDirectory(state), true); } catch (IOException) { } }
/// <summary> /// Writes the source code for the specified data source wrapper. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceWrapperInfo">The <see cref="DataSourceWrapperInfo"/> that describes the data source wrapper being generated.</param> private static void WriteSourceCodeForDataSourceWrapper(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo) { using (var writer = new DataSourceWrapperWriter()) { // Using statements var imports = Enumerable.Union(new[] { "System" }, dataSourceWrapperInfo.Imports).Distinct().OrderBy(x => x); foreach (var import in imports) { writer.WriteLine("using {0};", import); } writer.WriteLine(); // Namespace and class declaration var @namespace = dataSourceWrapperInfo.DataSourceDefinition.DataSourceWrapperNamespace; writer.WriteLine("namespace " + @namespace); writer.WriteLine("{"); writer.WriteLine("#pragma warning disable 1591"); writer.WriteLine("#pragma warning disable 0184"); writer.WriteLine("[System.CLSCompliant(false)]"); writer.WriteLine("[System.CodeDom.Compiler.GeneratedCode(\"UPF Binding Expression Compiler\", \"{0}\")]", FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); writer.WriteLine("public sealed partial class {0} : {1}", dataSourceWrapperInfo.DataSourceWrapperName, writer.GetCSharpTypeName(typeof(CompiledDataSourceWrapper))); writer.WriteLine("{"); // Constructors writer.WriteLine("#region Constructors"); writer.WriteConstructor(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // IDataSourceWrapper writer.WriteLine("#region IDataSourceWrapper"); writer.WriteIDataSourceWrapperImplementation(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // Methods writer.WriteLine("#region Methods"); var methods = dataSourceWrapperInfo.DataSourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var method in methods) { if (!NeedsWrapper(method)) continue; writer.WriteWrapperMethod(method); } writer.WriteLine("#endregion"); writer.WriteLine(); // Properties writer.WriteLine("#region Properties"); var properties = dataSourceWrapperInfo.DataSourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var property in properties) { if (!NeedsWrapper(property)) continue; writer.WriteWrapperProperty(property); } writer.WriteLine("#endregion"); writer.WriteLine(); // Fields writer.WriteLine("#region Fields"); var fields = dataSourceWrapperInfo.DataSourceType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var field in fields) { if (!NeedsWrapper(field)) continue; writer.WriteWrapperProperty(field); } writer.WriteLine("#endregion"); writer.WriteLine(); // Expressions writer.WriteLine("#region Expressions"); for (int i = 0; i < dataSourceWrapperInfo.Expressions.Count; i++) { var expressionInfo = dataSourceWrapperInfo.Expressions[i]; writer.WriteExpressionProperty(state, dataSourceWrapperInfo, expressionInfo, i); } writer.WriteLine("#endregion"); // Source code generation complete writer.WriteLine("}"); writer.WriteLine("#pragma warning restore 0184"); writer.WriteLine("#pragma warning restore 1591"); writer.WriteLine("}"); dataSourceWrapperInfo.DataSourceWrapperSourceCode = writer.ToString(); } }
/// <summary> /// Compiles the specified collection of view models. /// </summary> private static BindingExpressionCompilationResult CompileViewModels(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, String output) { DeleteWorkingDirectory(state); var referencedAssemblies = GetDefaultReferencedAssemblies(); var expressionVerificationResult = PerformExpressionVerificationCompilationPass(state, models, referencedAssemblies); if (expressionVerificationResult.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).Any()) { if (state.WriteErrorsToFile) WriteErrorsToWorkingDirectory(state, models, expressionVerificationResult); return BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedExpressionValidationPass, CreateBindingExpressionCompilationErrors(state, models, expressionVerificationResult.Errors)); } var setterEliminationPassResult = PerformSetterEliminationCompilationPass(state, models, referencedAssemblies); var conversionFixupPassResult = PerformConversionFixupCompilationPass(state, models, referencedAssemblies, setterEliminationPassResult); var finalPassResult = PerformFinalCompilationPass(state, output, models, referencedAssemblies, conversionFixupPassResult); if (finalPassResult.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).Any()) { if (state.WriteErrorsToFile) WriteErrorsToWorkingDirectory(state, models, finalPassResult); return BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedFinalPass, CreateBindingExpressionCompilationErrors(state, models, finalPassResult.Errors)); } return BindingExpressionCompilationResult.CreateSucceeded(); }
/// <summary> /// Creates a new instance of <see cref="ExpressionCompilerState"/> from the specified set of compiler options. /// </summary> private ExpressionCompilerState CreateCompilerState(UltravioletContext uv, BindingExpressionCompilerOptions options) { var compiler = CreateCodeProvider(); var state = new ExpressionCompilerState(uv, compiler); state.WorkInTemporaryDirectory = options.WorkInTemporaryDirectory; state.WriteErrorsToFile = options.WriteErrorsToFile; return state; }
/// <summary> /// Given a targeted binding expression (in the form "foo->bar"), this method extracts the target name, target type, and expression text. /// </summary> private Boolean GetExpressionTargetInfo(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, XObject source, ref String expText, out String expTarget, out Type expTargetType) { const string TargetExpressionDelimiter = "->"; var expOriginal = expText; var delimiterIndex = expText.IndexOf(TargetExpressionDelimiter); if (delimiterIndex >= 0) { var expPartTarget = expText.Substring(0, delimiterIndex); var expPartText = expText.Substring(delimiterIndex + TargetExpressionDelimiter.Length); var matchCandidates = (from element in dataSourceWrapperInfo.DataSourceDefinition.Definition.Descendants() where (String)element.Attribute("Name") == expPartTarget select element).ToList(); if (matchCandidates.Count == 0) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsNotFound.Format(expPartTarget)); } if (matchCandidates.Count > 1) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsAmbiguous.Format(expPartTarget)); } var match = matchCandidates.Single(); var matchName = match.Name.LocalName; expText = expPartText; expTargetType = ExpressionCompiler.GetPlaceholderType(dataSourceWrapperInfo.DataSourceType, matchName); if (expTargetType == null && !state.GetKnownType(matchName, out expTargetType)) { throw new BindingExpressionCompilationErrorException(source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, CompilerStrings.ExpressionTargetIsUnrecognizedType.Format(expOriginal, matchName)); } expTarget = String.Format("__UPF_GetElementByName<{0}>(\"{1}\").", GetCSharpTypeName(expTargetType), expPartTarget); return true; } expTarget = default(String); expTargetType = dataSourceWrapperInfo.DataSourceType; return false; }
/// <summary> /// Writes a property which wraps a binding expression. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceWrapperInfo">A <see cref="DataSourceWrapperInfo"/> describing the data source for which to write an expression property.</param> /// <param name="expressionInfo">The binding expression for which to write a property.</param> /// <param name="id">The expression's identifier within the view model.</param> public void WriteExpressionProperty(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, BindingExpressionInfo expressionInfo, Int32 id) { var isDependencyProperty = false; var isSimpleDependencyProperty = false; var expText = BindingExpressions.GetBindingMemberPathPart(expressionInfo.Expression); var expTarget = default(String); var expTargetType = dataSourceWrapperInfo.DataSourceType; var expFormatString = BindingExpressions.GetBindingFormatStringPart(expressionInfo.Expression); var targeted = GetExpressionTargetInfo(state, dataSourceWrapperInfo, expressionInfo.Source, ref expText, out expTarget, out expTargetType); var dprop = DependencyProperty.FindByName(expText, expTargetType); var dpropField = default(FieldInfo); if (dprop != null) { isDependencyProperty = true; dpropField = (from prop in dprop.OwnerType.GetFields(BindingFlags.Public | BindingFlags.Static) where prop.FieldType == typeof(DependencyProperty) && prop.GetValue(null) == dprop select prop).SingleOrDefault(); if (dpropField == null) { throw new BindingExpressionCompilationErrorException(expressionInfo.Source, dataSourceWrapperInfo.DataSourceDefinition.DefinitionPath, PresentationStrings.CannotFindDependencyPropertyField.Format(dprop.OwnerType.Name, dprop.Name)); } if (String.IsNullOrEmpty(expFormatString) && !targeted) { isSimpleDependencyProperty = true; } } WriteLine("[{0}(@\"{1}\", SimpleDependencyPropertyOwner = {2}, SimpleDependencyPropertyName = {3})]", typeof(CompiledBindingExpressionAttribute).FullName, expressionInfo.Expression.Replace("\"", "\"\""), isSimpleDependencyProperty ? "typeof(" + GetCSharpTypeName(dprop.OwnerType) + ")" : "null", isSimpleDependencyProperty ? "\"" + dprop.Name + "\"" : "null"); WriteLine("public {0} __UPF_Expression{1}", GetCSharpTypeName(expressionInfo.Type), id); WriteLine("{"); if (expressionInfo.GenerateGetter) { expressionInfo.GetterLineStart = LineCount; var getexp = default(String); if (isDependencyProperty) { getexp = String.Format("{0}GetValue<{1}>({2}.{3})", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); } else { getexp = String.Format("{0}{1}", expTarget, expText); } var hasFormatString = !String.IsNullOrEmpty(expFormatString); expFormatString = hasFormatString ? String.Format("\"{{0:{0}}}\"", expFormatString) : "null"; if (IsStringType(expressionInfo.Type) || (expressionInfo.Type == typeof(Object) && hasFormatString)) { WriteLine("get"); WriteLine("{"); WriteLine("var value = {0};", getexp); WriteLine("return ({0})__UPF_ConvertToString(value, {1});", GetCSharpTypeName(expressionInfo.Type), expFormatString); WriteLine("}"); } else { WriteLine("get {{ return ({0})({1}); }}", GetCSharpTypeName(expressionInfo.Type), getexp); } expressionInfo.GetterLineEnd = LineCount - 1; } if (dprop != null && dprop.IsReadOnly) expressionInfo.GenerateSetter = false; if (expressionInfo.GenerateSetter) { var targetTypeName = expressionInfo.CS0266TargetType; var targetTypeSpecified = !String.IsNullOrEmpty(targetTypeName); expressionInfo.SetterLineStart = LineCount; if (isDependencyProperty) { if (IsStringType(expressionInfo.Type)) { WriteLine("set"); WriteLine("{"); WriteLine("var current = {0}GetValue<{1}>({2}.{3});", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); WriteLine("{0}SetValue<{1}>({2}.{3}, __UPF_ConvertFromString(value, current));", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name); WriteLine("}"); } else { if (expressionInfo.NullableFixup) { WriteLine(targetTypeSpecified ? "set {{ {0}SetValue<{1}>({2}.{3}, ({4})(value ?? default({1}))); }}" : "set {{ {0}SetValue<{1}>({2}.{3}, value ?? default({1})); }}", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name, targetTypeName); } else { WriteLine(targetTypeSpecified ? "set {{ {0}SetValue<{1}>({2}.{3}, ({4})(value)); }}" : "set {{ {0}SetValue<{1}>({2}.{3}, value); }}", expTarget, GetCSharpTypeName(dprop.PropertyType), GetCSharpTypeName(dprop.OwnerType), dpropField.Name, targetTypeName); } } } else { if (IsStringType(expressionInfo.Type)) { WriteLine("set"); WriteLine("{"); WriteLine("var current = {0}{1};", expTarget, expText); WriteLine("{0}{1} = __UPF_ConvertFromString(value, current);", expTarget, expText); WriteLine("}"); } else { if (expressionInfo.NullableFixup) { WriteLine(targetTypeSpecified ? "set {{ {0}{1} = ({3})(value ?? default({2})); }}" : "set {{ {0}{1} = value ?? default({2}); }}", expTarget, expText, GetCSharpTypeName(Nullable.GetUnderlyingType(expressionInfo.Type)), targetTypeName); } else { WriteLine(targetTypeSpecified ? "set {{ {0}{1} = ({2})(value); }}" : "set {{ {0}{1} = value; }}", expTarget, expText, targetTypeName); } } } expressionInfo.SetterLineEnd = LineCount - 1; } WriteLine("}"); }
/// <summary> /// Gets a collection of <see cref="DataSourceDefinition"/> instances for any component templates which /// are currently registered with the Ultraviolet Presentation Foundation. /// </summary> /// <returns>A collection of <see cref="DataSourceDefinition"/> instances which represent UPF component template definitions.</returns> private static IEnumerable<DataSourceDefinition> RetrieveTemplateDefinitions(ExpressionCompilerState state) { if (state.ComponentTemplateManager == null) return Enumerable.Empty<DataSourceDefinition>(); var templateDefs = from template in state.ComponentTemplateManager select DataSourceDefinition.FromComponentTemplate(template.Key, template.Value.Root.Element("View")); var templateDefsList = templateDefs.ToList(); foreach (var templateDef in templateDefsList) UvmlLoader.AddUvmlAnnotations(templateDef.DataSourceWrapperName, templateDef.Definition); return templateDefsList; }
/// <summary> /// Creates a new <see cref="DataSourceWrapperInfo"/> instance which represents a particular framework template. /// </summary> private static DataSourceWrapperInfo GetDataSourceWrapperInfoForFrameworkTemplate(ExpressionCompilerState state, IEnumerable<String> references, IEnumerable<String> imports, DataSourceDefinition definition) { var dataSourceWrappedTypeAttr = definition.Definition.Attribute("ViewModelType"); if (dataSourceWrappedTypeAttr == null) { throw new BindingExpressionCompilationErrorException(definition.Definition, definition.DefinitionPath, PresentationStrings.TemplateMustSpecifyViewModelType); } var dataSourceWrappedType = Type.GetType(dataSourceWrappedTypeAttr.Value, false); if (dataSourceWrappedType == null) { throw new BindingExpressionCompilationErrorException(dataSourceWrappedTypeAttr, definition.DefinitionPath, PresentationStrings.ViewModelTypeNotFound.Format(dataSourceWrappedTypeAttr.Value)); } var dataSourceDefinition = definition; var dataSourceWrapperName = definition.DataSourceWrapperName; var expressions = new List<BindingExpressionInfo>(); foreach (var element in dataSourceDefinition.Definition.Elements()) { FindBindingExpressionsInDataSource(state, dataSourceDefinition, dataSourceWrappedType, element, expressions); } expressions = CollapseDataSourceExpressions(expressions); return new DataSourceWrapperInfo() { References = references, Imports = imports, DataSourceDefinition = dataSourceDefinition, DataSourceType = dataSourceWrappedType, DataSourceWrapperName = dataSourceWrapperName, Expressions = expressions }; }
/// <summary> /// Creates an instance of <see cref="DataSourceWrapperInfo"/> for each of the specified data source definitions. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceDefinitions">The collection of <see cref="DataSourceDefinition"/> objects for which to create <see cref="DataSourceWrapperInfo"/> instances.</param> /// <returns>A collection containing the <see cref="DataSourceWrapperInfo"/> instances which were created.</returns> private static IEnumerable<DataSourceWrapperInfo> RetrieveDataSourceWrapperInfos(ExpressionCompilerState state, IEnumerable<DataSourceDefinition> dataSourceDefinitions) { var dataSourceWrapperInfos = new ConcurrentBag<DataSourceWrapperInfo>(); Parallel.ForEach(dataSourceDefinitions, viewDefinition => { var dataSourceWrapperInfo = GetDataSourceWrapperInfo(state, viewDefinition); if (dataSourceWrapperInfo == null) return; dataSourceWrapperInfos.Add(dataSourceWrapperInfo); }); return dataSourceWrapperInfos; }
/// <summary> /// Creates a new instance of <see cref="DataSourceWrapperInfo"/> that represents the specified data source wrapper. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceDefinition">The data source definition for which to retrieve data source wrapper info.</param> /// <returns>The <see cref="DataSourceWrapperInfo"/> that was created to represent the specified data source.</returns> private static DataSourceWrapperInfo GetDataSourceWrapperInfo(ExpressionCompilerState state, DataSourceDefinition dataSourceDefinition) { var dataSourceWrappedType = dataSourceDefinition.TemplatedControl; if (dataSourceWrappedType == null) { var definedDataSourceTypeAttr = dataSourceDefinition.Definition.Attribute("ViewModelType"); var definedDataSourceTypeName = (String)definedDataSourceTypeAttr; if (definedDataSourceTypeName == null) return null; var typeNameCommaIx = definedDataSourceTypeName.IndexOf(','); if (typeNameCommaIx < 0) { throw new BindingExpressionCompilationErrorException(definedDataSourceTypeAttr, dataSourceDefinition.DefinitionPath, CompilerStrings.ViewModelTypeIsNotFullyQualified.Format(definedDataSourceTypeName)); } var definedDataSourceType = Type.GetType(definedDataSourceTypeName); if (definedDataSourceType == null) { throw new BindingExpressionCompilationErrorException(definedDataSourceTypeAttr, dataSourceDefinition.DefinitionPath, PresentationStrings.ViewModelTypeNotFound.Format(definedDataSourceTypeName)); } dataSourceWrappedType = definedDataSourceType; } var dataSourceWrapperName = dataSourceDefinition.DataSourceWrapperName; var dataSourceWrapperExpressions = new List<BindingExpressionInfo>(); foreach (var element in dataSourceDefinition.Definition.Elements()) { FindBindingExpressionsInDataSource(state, dataSourceDefinition, dataSourceWrappedType, element, dataSourceWrapperExpressions); } dataSourceWrapperExpressions = CollapseDataSourceExpressions(dataSourceWrapperExpressions); var dataSourceReferences = new List<String>(); var dataSourceImports = new List<String>(); var xmlRoot = dataSourceDefinition.Definition.Parent; var xmlDirectives = xmlRoot.Elements("Directive"); foreach (var xmlDirective in xmlDirectives) { var xmlDirectiveType = (String)xmlDirective.Attribute("Type"); if (String.IsNullOrEmpty(xmlDirectiveType)) { throw new BindingExpressionCompilationErrorException(xmlDirective, dataSourceDefinition.DefinitionPath, CompilerStrings.ViewDirectiveMustHaveType); } var xmlDirectiveTypeName = xmlDirectiveType.ToLowerInvariant(); var xmlDirectiveValue = xmlDirective.Value.Trim(); switch (xmlDirectiveTypeName) { case "import": { if (String.IsNullOrEmpty(xmlDirectiveValue)) { throw new BindingExpressionCompilationErrorException(xmlDirective, dataSourceDefinition.DefinitionPath, CompilerStrings.ViewDirectiveHasInvalidValue); } dataSourceImports.Add(xmlDirective.Value.Trim()); } break; case "reference": { if (String.IsNullOrEmpty(xmlDirectiveValue)) { throw new BindingExpressionCompilationErrorException(xmlDirective, dataSourceDefinition.DefinitionPath, CompilerStrings.ViewDirectiveHasInvalidValue); } dataSourceReferences.Add(xmlDirective.Value.Trim()); } break; default: throw new BindingExpressionCompilationErrorException(xmlDirective, dataSourceDefinition.DefinitionPath, CompilerStrings.ViewDirectiveNotRecognized.Format(xmlDirectiveTypeName)); } } return new DataSourceWrapperInfo() { References = dataSourceReferences, Imports = dataSourceImports, DataSourceDefinition = dataSourceDefinition, DataSourceType = dataSourceWrappedType, DataSourceWrapperName = dataSourceWrapperName, Expressions = dataSourceWrapperExpressions, }; }
/// <summary> /// Performs the first compilation pass, which generates expression getters in order to verify that the expressions are valid code. /// </summary> private static CompilerResults PerformExpressionVerificationCompilationPass(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, ConcurrentBag<String> referencedAssemblies) { Parallel.ForEach(models, model => { referencedAssemblies.Add(model.DataSourceType.Assembly.Location); foreach (var reference in model.References) { referencedAssemblies.Add(reference); } foreach (var expression in model.Expressions) { expression.GenerateGetter = true; expression.GenerateSetter = false; } WriteSourceCodeForDataSourceWrapper(state, model); }); return CompileDataSourceWrapperSources(state, null, models, referencedAssemblies); }
/// <summary> /// Searches the specified XML element tree for binding expressions and adds them to the specified collection. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="dataSourceDefinition">The data source definition for the data source which is being compiled.</param> /// <param name="dataSourceWrappedType">The type for which a data source wrapper is being compiled.</param> /// <param name="element">The root of the XML element tree to search.</param> /// <param name="expressions">The list to populate with any binding expressions that are found.</param> private static void FindBindingExpressionsInDataSource(ExpressionCompilerState state, DataSourceDefinition dataSourceDefinition, Type dataSourceWrappedType, XElement element, List<BindingExpressionInfo> expressions) { var elementName = element.Name.LocalName; var elementType = GetPlaceholderType(dataSourceWrappedType, elementName); if (elementType != null || state.GetKnownType(elementName, out elementType)) { var attrs = element.Attributes(); foreach (var attr in attrs) { var attrValue = attr.Value; if (!BindingExpressions.IsBindingExpression(attrValue)) continue; var dprop = FindDependencyOrAttachedPropertyByName(state, attr.Name.LocalName, elementType); if (dprop == null) { throw new BindingExpressionCompilationErrorException(attr, dataSourceDefinition.DefinitionPath, CompilerStrings.OnlyDependencyPropertiesCanBeBound.Format(attr.Name.LocalName)); } expressions.Add(new BindingExpressionInfo(attr, attrValue, dprop.PropertyType) { GenerateGetter = true }); } if (element.Nodes().Count() == 1) { var singleChild = element.Nodes().Single(); if (singleChild.NodeType == XmlNodeType.Text) { var elementValue = ((XText)singleChild).Value; if (BindingExpressions.IsBindingExpression(elementValue)) { String defaultProperty; if (!state.GetElementDefaultProperty(elementType, out defaultProperty)) { throw new BindingExpressionCompilationErrorException(singleChild, dataSourceDefinition.DefinitionPath, CompilerStrings.ElementDoesNotHaveDefaultProperty.Format(elementType.Name)); } var dprop = FindDependencyOrAttachedPropertyByName(state, defaultProperty, elementType); if (dprop == null) { throw new BindingExpressionCompilationErrorException(singleChild, dataSourceDefinition.DefinitionPath, CompilerStrings.OnlyDependencyPropertiesCanBeBound.Format(defaultProperty)); } expressions.Add(new BindingExpressionInfo(singleChild, elementValue, dprop.PropertyType) { GenerateGetter = true }); } } } } var children = element.Elements(); foreach (var child in children) { FindBindingExpressionsInDataSource(state, dataSourceDefinition, dataSourceWrappedType, child, expressions); } }
/// <summary> /// Performs the second compilation pass, which generates setters in order to determine which expressions support two-way bindings. /// </summary> private static CompilerResults PerformSetterEliminationCompilationPass(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, ConcurrentBag<String> referencedAssemblies) { Parallel.ForEach(models, model => { foreach (var expression in model.Expressions) { expression.GenerateGetter = true; expression.GenerateSetter = true; } WriteSourceCodeForDataSourceWrapper(state, model); }); return CompileDataSourceWrapperSources(state, null, models, referencedAssemblies); }
/// <summary> /// Writes the source code of the specified collection of wrappers to the working directory. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="models">The list of models which were compiled.</param> private static void WriteCompiledFilesToWorkingDirectory(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models) { var workingDirectory = GetWorkingDirectory(state); Directory.CreateDirectory(workingDirectory); foreach (var model in models) { var path = Path.ChangeExtension(Path.Combine(workingDirectory, model.DataSourceWrapperName), "cs"); File.WriteAllText(path, model.DataSourceWrapperSourceCode); } }
/// <summary> /// Performs the third compilation pass, which attempts to fix any errors caused by non-implicit conversions and nullable types that need to be cast to non-nullable types. /// </summary> private static CompilerResults PerformConversionFixupCompilationPass(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, ConcurrentBag<String> referencedAssemblies, CompilerResults setterEliminationResult) { var errors = setterEliminationResult.Errors.Cast<CompilerError>().ToList(); var fixableErrorNumbers = new List<String> { "CS0266", "CS1502", "CS1503" }; Parallel.ForEach(models, model => { var dataSourceWrapperFilename = Path.GetFileName(GetWorkingFileForDataSourceWrapper(model)); var dataSourceWrapperErrors = errors.Where(x => Path.GetFileName(x.FileName) == dataSourceWrapperFilename).ToList(); foreach (var expression in model.Expressions) { var setterErrors = dataSourceWrapperErrors.Where(x => x.Line >= expression.SetterLineStart && x.Line <= expression.SetterLineEnd).ToList(); var setterIsNullable = Nullable.GetUnderlyingType(expression.Type) != null; expression.GenerateSetter = !setterErrors.Any() || (setterIsNullable && setterErrors.All(x => fixableErrorNumbers.Contains(x.ErrorNumber))); expression.NullableFixup = setterIsNullable; if (setterErrors.Count == 1 && setterErrors.Single().ErrorNumber == "CS0266") { var error = setterErrors.Single(); var match = regexCS0266.Match(error.ErrorText); expression.CS0266SourceType = match.Groups["source"].Value; expression.CS0266TargetType = match.Groups["target"].Value; expression.GenerateSetter = true; } } WriteSourceCodeForDataSourceWrapper(state, model); }); return CompileDataSourceWrapperSources(state, null, models, referencedAssemblies); }
/// <summary> /// Attempts to find the dependency or attached property with the specified name. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="name">The name of the dependency or attached property to retrieve.</param> /// <param name="ownerType">The type that references the dependency or attached property.</param> /// <returns>The <see cref="DependencyProperty"/> referred to by the specified name, or <c>null</c> if there is no such dependency property.</returns> private static DependencyProperty FindDependencyOrAttachedPropertyByName(ExpressionCompilerState state, String name, Type ownerType) { String container; String property; if (IsAttachedProperty(name, out container, out property)) { Type containerType; if (!state.GetKnownType(container, out containerType)) return null; return DependencyProperty.FindByName(property, containerType); } return DependencyProperty.FindByName(name, ownerType); }
/// <summary> /// Performs the final compilation pass, which removes invalid expression setters based on the results of the previous pass. /// </summary> private static CompilerResults PerformFinalCompilationPass(ExpressionCompilerState state, String output, IEnumerable<DataSourceWrapperInfo> models, ConcurrentBag<String> referencedAssemblies, CompilerResults nullableFixupResult) { var errors = nullableFixupResult.Errors.Cast<CompilerError>().ToList(); Parallel.ForEach(models, model => { var dataSourceWrapperFilename = Path.GetFileName(GetWorkingFileForDataSourceWrapper(model)); var dataSourceWrapperErrors = errors.Where(x => Path.GetFileName(x.FileName) == dataSourceWrapperFilename).ToList(); foreach (var expression in model.Expressions) { if (expression.GenerateSetter && dataSourceWrapperErrors.Any(x => x.Line >= expression.SetterLineStart && x.Line <= expression.SetterLineEnd)) { expression.GenerateSetter = false; } } WriteSourceCodeForDataSourceWrapper(state, model); }); return CompileDataSourceWrapperSources(state, output, models, referencedAssemblies); }
/// <summary> /// Converts a <see cref="CompilerErrorCollection"/> to a collection of <see cref="BindingExpressionCompilationError"/> objects. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="models">The list of models which were compiled.</param> /// <param name="errors">The collection of errors produced during compilation.</param> /// <returns>A list containing the converted errors.</returns> private static List<BindingExpressionCompilationError> CreateBindingExpressionCompilationErrors(ExpressionCompilerState state, IEnumerable<DataSourceWrapperInfo> models, CompilerErrorCollection errors) { var result = new List<BindingExpressionCompilationError>(); var workingDirectory = GetWorkingDirectory(state); var errorsByFile = errors.Cast<CompilerError>() .Where(x => !x.IsWarning).GroupBy(x => Path.GetFileName(x.FileName)).ToDictionary(x => x.Key, x => x.ToList()); foreach (var model in models) { var dataSourceWrapperFilename = Path.GetFileName(GetWorkingFileForDataSourceWrapper(model)); var dataSourceErrors = default(List<CompilerError>); if (errorsByFile.TryGetValue(dataSourceWrapperFilename, out dataSourceErrors)) { foreach (var dataSourceError in dataSourceErrors) { var fullPathToFile = model.DataSourceWrapperName; if (state.WriteErrorsToFile) { fullPathToFile = Path.GetFullPath(Path.Combine(workingDirectory, Path.ChangeExtension(model.DataSourceWrapperName, "cs"))); } result.Add(new BindingExpressionCompilationError(fullPathToFile, dataSourceError.Line, dataSourceError.Column, dataSourceError.ErrorNumber, dataSourceError.ErrorText)); } } } return result; }
/// <summary> /// Compiles the specified data source wrapper sources into a managed assembly. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="output">The path to the assembly file which will be created.</param> /// <param name="infos">A collection of <see cref="DataSourceWrapperInfo"/> instances containing the source code to compile.</param> /// <param name="references">A collection of assembly locations which should be referenced by the compiled assembly.</param> /// <returns>A <see cref="CompilerResults"/> instance that represents the result of compilation.</returns> private static CompilerResults CompileDataSourceWrapperSources(ExpressionCompilerState state, String output, IEnumerable<DataSourceWrapperInfo> infos, IEnumerable<String> references) { var options = new CompilerParameters(); options.OutputAssembly = output; options.GenerateExecutable = false; options.GenerateInMemory = true; options.IncludeDebugInformation = false; options.TreatWarningsAsErrors = false; options.ReferencedAssemblies.AddRange(references.Distinct().ToArray()); var files = new List<String>(); foreach (var info in infos) { var path = GetWorkingFileForDataSourceWrapper(info); files.Add(path); File.WriteAllText(path, info.DataSourceWrapperSourceCode); } return state.Compiler.CompileAssemblyFromFile(options, files.ToArray()); }
/// <summary> /// Gets a collection of <see cref="DataSourceWrapperInfo"/> objects for all of the views defined within the specified root directory /// as well as all of the component templates which are currently registered with the Ultraviolet context. /// </summary> /// <param name="state">The expression compiler's current state.</param> /// <param name="root">The root directory to search for views.</param> /// <returns>A collection of <see cref="DataSourceWrapperInfo"/> instances which represent the views and templates which were found.</returns> private static IEnumerable<DataSourceWrapperInfo> GetDataSourceWrapperInfos(ExpressionCompilerState state, UltravioletContext uv, String root) { var viewDefinitions = RecursivelySearchForViews(root, root); var viewModelInfos = RetrieveDataSourceWrapperInfos(state, viewDefinitions); var templateDefinitions = RetrieveTemplateDefinitions(state); var templateModelInfos = RetrieveDataSourceWrapperInfos(state, templateDefinitions); return Enumerable.Union(viewModelInfos, templateModelInfos); }
/// <summary> /// Writes the source code for the specified data source wrapper. /// </summary> private static void WriteSourceCodeForDataSourceWrapper(ExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo) { using (var writer = new DataSourceWrapperWriter()) { // Using statements var imports = Enumerable.Union(new[] { "System" }, dataSourceWrapperInfo.Imports).Distinct().OrderBy(x => x); foreach (var import in imports) { writer.WriteLine("using {0};", import); } writer.WriteLine(); // Namespace declaration var @namespace = dataSourceWrapperInfo.DataSourceDefinition.DataSourceWrapperNamespace; writer.WriteLine("namespace " + @namespace); writer.WriteLine("{"); writer.WriteLine("#pragma warning disable 1591"); writer.WriteLine("#pragma warning disable 0184"); // Data source wrapper class - main WriteSourceCodeForDataSourceWrapperClass(state, dataSourceWrapperInfo, writer); // Data source wrapper class - dependent foreach (var dependentWrapperInfo in dataSourceWrapperInfo.DependentWrapperInfos) WriteSourceCodeForDataSourceWrapperClass(state, dependentWrapperInfo, writer); // Namespace complete writer.WriteLine("#pragma warning restore 0184"); writer.WriteLine("#pragma warning restore 1591"); writer.WriteLine("}"); dataSourceWrapperInfo.DataSourceWrapperSourceCode = writer.ToString(); } }