public static (List <AttributeListSyntax>, List <MemberDeclarationSyntax>) GenerateSyntax( Assembly targetAssembly, List <GrainInterfaceDescription> grainInterfaces, List <GrainClassDescription> grainClasses, SerializationTypeDescriptions typeDescriptions) { var attributes = new List <AttributeListSyntax>(); var members = new List <MemberDeclarationSyntax>(); var className = CodeGeneratorCommon.ClassPrefix + Guid.NewGuid().ToString("N").Substring(0, 10) + ClassSuffix; // Generate a class for populating the metadata. var classSyntax = SF.ClassDeclaration(className) .AddBaseListTypes( SF.SimpleBaseType(typeof(IFeaturePopulator <GrainInterfaceFeature>).GetTypeSyntax()), SF.SimpleBaseType(typeof(IFeaturePopulator <GrainClassFeature>).GetTypeSyntax()), SF.SimpleBaseType(typeof(IFeaturePopulator <SerializerFeature>).GetTypeSyntax())) .AddModifiers(SF.Token(SyntaxKind.InternalKeyword), SF.Token(SyntaxKind.SealedKeyword)) .AddMembers(GeneratePopulateMethod(grainInterfaces), GeneratePopulateMethod(grainClasses), GeneratePopulateMethod(typeDescriptions)) .AddAttributeLists(SF.AttributeList(SF.SingletonSeparatedList(CodeGeneratorCommon.GetGeneratedCodeAttributeSyntax()))); var namespaceSyntax = SF.NamespaceDeclaration(NamespaceName.ToIdentifierName()).AddMembers(classSyntax); members.Add(namespaceSyntax); // Generate an assembly-level attribute with an instance of that class. var attribute = SF.AttributeList( SF.AttributeTargetSpecifier(SF.Token(SyntaxKind.AssemblyKeyword)), SF.SingletonSeparatedList( SF.Attribute(typeof(FeaturePopulatorAttribute).GetNameSyntax()) .AddArgumentListArguments(SF.AttributeArgument(SF.TypeOfExpression(SF.ParseTypeName(NamespaceName + "." + className)))))); attributes.Add(attribute); return(attributes, members); }
private static MemberDeclarationSyntax GeneratePopulateMethod(SerializationTypeDescriptions typeDescriptions) { var interfaceMethod = TypeUtils.Method((IFeaturePopulator <SerializerFeature> _) => _.Populate(default(SerializerFeature))); var featureParameter = interfaceMethod.GetParameters()[0].Name.ToIdentifierName(); var bodyStatements = new List <StatementSyntax>(); var addSerializerTypeMethod = TypeUtils.Method((SerializerFeature _) => _.AddSerializerType(default(Type), default(Type))); foreach (var serializerType in typeDescriptions.SerializerTypes) { bodyStatements.Add( SF.ExpressionStatement( SF.InvocationExpression(featureParameter.Member(addSerializerTypeMethod.Name)) .AddArgumentListArguments( SF.Argument(SF.TypeOfExpression(serializerType.Target)), SF.Argument(SF.TypeOfExpression(serializerType.Serializer))))); } var addKnownType = TypeUtils.Method((SerializerFeature _) => _.AddKnownType(default(string), default(string))); foreach (var knownType in typeDescriptions.KnownTypes) { bodyStatements.Add( SF.ExpressionStatement( SF.InvocationExpression(featureParameter.Member(addKnownType.Name)) .AddArgumentListArguments( SF.Argument(knownType.Type.GetLiteralExpression()), SF.Argument(knownType.TypeKey.GetLiteralExpression())))); } return(interfaceMethod.GetDeclarationSyntax().AddBodyStatements(bodyStatements.ToArray())); }
/// <summary> /// Adds serialization type descriptions from <paramref name="targetAssembly"/> to <paramref name="serializationTypes"/>. /// </summary> /// <param name="serializationTypes">The serialization type descriptions.</param> /// <param name="targetAssembly">The target assembly for generated code.</param> /// <param name="assemblies"></param> private void AddSerializationTypes(SerializationTypeDescriptions serializationTypes, Assembly targetAssembly, List <Assembly> assemblies) { // Only types which exist in assemblies referenced by the target assembly can be referenced. var references = new HashSet <string>( assemblies.SelectMany(asm => asm.GetReferencedAssemblies() .Select(referenced => referenced.Name) .Concat(new[] { asm.GetName().Name }))); bool IsAssemblyReferenced(Type type) { // If the target doesn't reference this type's assembly, it cannot reference a type within that assembly. return(references.Contains(type.Assembly.GetName().Name)); } // Visit all types in other assemblies for serialization metadata. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (!references.Contains(assembly.GetName().Name)) { continue; } foreach (var type in TypeUtils.GetDefinedTypes(assembly, this.logger)) { this.typeCollector.RecordEncounteredType(type); } } // Returns true if a type can be accessed from source and false otherwise. bool IsAccessibleType(Type type) => TypeUtilities.IsAccessibleFromAssembly(type, targetAssembly); foreach (var type in this.typeCollector.EncounteredTypes) { // Skip types which can not or should not be referenced. if (type.IsGenericParameter) { continue; } if (!IsAssemblyReferenced(type)) { continue; } if (type.IsNestedPrivate) { continue; } if (type.GetCustomAttribute <CompilerGeneratedAttribute>() != null) { continue; } if (IsOrleansGeneratedCode(type)) { continue; } var qualifiedTypeName = RuntimeTypeNameFormatter.Format(type); if (this.knownTypes.Contains(qualifiedTypeName)) { continue; } var typeKeyString = type.OrleansTypeKeyString(); serializationTypes.KnownTypes.Add(new KnownTypeDescription { Type = qualifiedTypeName, TypeKey = typeKeyString }); if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug( "Found type {0} with type key \"{1}\"", type.GetParseableName(), typeKeyString); } if (!IsAccessibleType(type)) { continue; } var typeSyntax = type.GetTypeSyntax(includeGenericParameters: false); var serializerAttributes = type.GetCustomAttributes <SerializerAttribute>().ToList(); if (serializerAttributes.Count > 0) { // Account for serializer types. foreach (var serializerAttribute in serializerAttributes) { if (!IsAccessibleType(serializerAttribute.TargetType)) { continue; } if (this.logger.IsEnabled(LogLevel.Information)) { this.logger.Info( "Found type {0} is a serializer for type {1}", type.GetParseableName(), serializerAttribute.TargetType.GetParseableName()); } serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = typeSyntax, Target = serializerAttribute.TargetType.GetTypeSyntax(includeGenericParameters: false) }); } } else { // Account for self-serializing types. SerializationManager.GetSerializationMethods(type, out var copier, out var serializer, out var deserializer); if (copier != null || serializer != null || deserializer != null) { if (this.logger.IsEnabled(LogLevel.Information)) { this.logger.Info( "Found type {0} is self-serializing.", type.GetParseableName()); } serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = typeSyntax, Target = typeSyntax }); } } } }
/// <summary> /// Adds serialization type descriptions from <paramref name="types"/> to <paramref name="serializationTypes"/>. /// </summary> /// <param name="serializationTypes">The serialization type descriptions.</param> /// <param name="targetAssembly">The target assembly for generated code.</param> /// <param name="types">The types.</param> private void AddSerializationTypes(SerializationTypeDescriptions serializationTypes, Assembly targetAssembly) { // Only types which exist in assemblies referenced by the target assembly can be referenced. var references = new HashSet <string>(targetAssembly.GetReferencedAssemblies().Select(asm => asm.Name)); bool IsAssemblyReferenced(Type type) { // If the target doesn't reference this type's assembly, it cannot reference a type within that assembly. var asmName = type.Assembly.GetName().Name; if (type.Assembly != targetAssembly) { if (!references.Contains(asmName)) { return(false); } if (!type.IsSerializable) { return(false); } } return(true); } // Visit all types in other assemblies for serialization metadata. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (!references.Contains(assembly.GetName().Name)) { continue; } foreach (var type in TypeUtils.GetDefinedTypes(assembly, this.Logger)) { this.typeCollector.RecordEncounteredType(type); } } // Returns true if a type can be accessed from source and false otherwise. bool IsAccessibleType(Type type) { var accessible = !type.IsGenericParameter; if (type.IsSpecialName) { accessible = false; } if (type.GetCustomAttribute <CompilerGeneratedAttribute>() != null) { accessible = false; } // Obsolete types can be accessed, however obsolete types which have IsError set cannot. var obsoleteAttr = type.GetCustomAttribute <ObsoleteAttribute>(); if (obsoleteAttr != null && obsoleteAttr.IsError) { accessible = false; } if (!TypeUtilities.IsAccessibleFromAssembly(type, targetAssembly)) { accessible = false; } return(accessible); } foreach (var type in this.typeCollector.EncounteredTypes) { // Skip types which can not or should not be referenced. if (!IsAssemblyReferenced(type)) { continue; } if (type.IsNestedPrivate) { continue; } if (type.GetCustomAttribute <CompilerGeneratedAttribute>() != null) { continue; } if (type.GetCustomAttribute <GeneratedCodeAttribute>() != null) { continue; } var qualifiedTypeName = RuntimeTypeNameFormatter.Format(type); if (this.knownTypes.Contains(qualifiedTypeName)) { continue; } serializationTypes.KnownTypes.Add(new KnownTypeDescription { Type = qualifiedTypeName, TypeKey = type.OrleansTypeKeyString() }); if (!IsAccessibleType(type)) { continue; } var typeSyntax = type.GetTypeSyntax(includeGenericParameters: false); var serializerAttributes = type.GetCustomAttributes <SerializerAttribute>().ToList(); if (serializerAttributes.Count > 0) { // Account for serializer types. foreach (var serializerAttribute in serializerAttributes) { if (!IsAccessibleType(serializerAttribute.TargetType)) { continue; } serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = typeSyntax, Target = serializerAttribute.TargetType.GetTypeSyntax(includeGenericParameters: false) }); } } else { // Account for self-serializing types. SerializationManager.GetSerializationMethods(type, out var copier, out var serializer, out var deserializer); if (copier != null || serializer != null || deserializer != null) { serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = typeSyntax, Target = typeSyntax }); } } } }
/// <summary> /// Generates a syntax tree for the provided assemblies. /// </summary> /// <param name="assemblies">The assemblies to generate code for.</param> /// <param name="runtime">Whether or not runtime code generation is being performed.</param> /// <returns>The generated syntax tree.</returns> private GeneratedSyntax GenerateForAssemblies(Assembly targetAssembly, bool runtime) { var grainInterfaces = new List <GrainInterfaceDescription>(); var grainClasses = new List <GrainClassDescription>(); var serializationTypes = new SerializationTypeDescriptions(); var members = new List <MemberDeclarationSyntax>(); // Expand the list of included assemblies and types. var(includedTypes, assemblies) = this.GetIncludedTypes(targetAssembly, runtime); if (Logger.IsVerbose) { Logger.Verbose( "Generating code for assemblies: {0}", string.Join(", ", assemblies.Select(_ => _.FullName))); } var serializerNamespaceMembers = new List <MemberDeclarationSyntax>(); var serializerNamespaceName = $"{SerializerNamespacePrefix}{targetAssembly?.GetName().Name.GetHashCode():X}"; // Group the types by namespace and generate the required code in each namespace. foreach (var group in includedTypes.GroupBy(_ => CodeGeneratorCommon.GetGeneratedNamespace(_))) { var namespaceMembers = new List <MemberDeclarationSyntax>(); var namespaceName = group.Key; foreach (var type in group) { // Skip generated classes. if (type.GetCustomAttribute <GeneratedCodeAttribute>() != null) { continue; } // Every type which is encountered must be considered for serialization. void OnEncounteredType(Type encounteredType) { // If a type was encountered which can be accessed, process it for serialization. this.typeCollector.RecordEncounteredType(type); this.serializerGenerationManager.RecordTypeToGenerate(encounteredType, targetAssembly); } if (Logger.IsVerbose2) { Logger.Verbose2("Generating code for: {0}", type.GetParseableName()); } if (GrainInterfaceUtils.IsGrainInterface(type)) { if (Logger.IsVerbose2) { Logger.Verbose2( "Generating GrainReference and MethodInvoker for {0}", type.GetParseableName()); } GrainInterfaceUtils.ValidateInterfaceRules(type); var referenceTypeName = GrainReferenceGenerator.GetGeneratedClassName(type); var invokerTypeName = GrainMethodInvokerGenerator.GetGeneratedClassName(type); namespaceMembers.Add(GrainReferenceGenerator.GenerateClass(type, referenceTypeName, OnEncounteredType)); namespaceMembers.Add(GrainMethodInvokerGenerator.GenerateClass(type, invokerTypeName)); var genericTypeSuffix = GetGenericTypeSuffix(type.GetGenericArguments().Length); grainInterfaces.Add( new GrainInterfaceDescription { Interface = type.GetTypeSyntax(includeGenericParameters: false), Reference = SF.ParseTypeName(namespaceName + '.' + referenceTypeName + genericTypeSuffix), Invoker = SF.ParseTypeName(namespaceName + '.' + invokerTypeName + genericTypeSuffix), InterfaceId = GrainInterfaceUtils.GetGrainInterfaceId(type) }); } if (TypeUtils.IsConcreteGrainClass(type)) { grainClasses.Add( new GrainClassDescription { ClassType = type.GetTypeSyntax(includeGenericParameters: false) }); } // Generate serializers. var first = true; while (this.serializerGenerationManager.GetNextTypeToProcess(out var toGen)) { if (first) { Logger.Info("ClientGenerator - Generating serializer classes for types:"); first = false; } Logger.Info( "\ttype " + toGen.FullName + " in namespace " + toGen.Namespace + " defined in Assembly " + toGen.GetTypeInfo().Assembly.GetName()); if (Logger.IsVerbose2) { Logger.Verbose2( "Generating serializer for type {0}", toGen.GetParseableName()); } var generatedSerializerName = SerializerGenerator.GetGeneratedClassName(toGen); serializerNamespaceMembers.Add(SerializerGenerator.GenerateClass(generatedSerializerName, toGen, OnEncounteredType)); var qualifiedSerializerName = serializerNamespaceName + '.' + generatedSerializerName + GetGenericTypeSuffix(toGen.GetGenericArguments().Length); serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = SF.ParseTypeName(qualifiedSerializerName), Target = toGen.GetTypeSyntax(includeGenericParameters: false) }); } } if (namespaceMembers.Count == 0) { if (Logger.IsVerbose) { Logger.Verbose2("Skipping namespace: {0}", namespaceName); } continue; } members.Add(CreateNamespace(namespaceName, namespaceMembers)); } // Add all generated serializers to their own namespace. members.Add(CreateNamespace(serializerNamespaceName, serializerNamespaceMembers)); // Add serialization metadata for the types which were encountered. this.AddSerializationTypes(serializationTypes, targetAssembly); // Generate metadata directives for all of the relevant types. var(attributeDeclarations, memberDeclarations) = FeaturePopulatorGenerator.GenerateSyntax(targetAssembly, grainInterfaces, grainClasses, serializationTypes); members.AddRange(memberDeclarations); var compilationUnit = SF.CompilationUnit().AddAttributeLists(attributeDeclarations.ToArray()).AddMembers(members.ToArray()); return(new GeneratedSyntax { SourceAssemblies = assemblies, Syntax = compilationUnit }); string GetGenericTypeSuffix(int numParams) { if (numParams == 0) { return(string.Empty); } return('<' + new string(',', numParams - 1) + '>'); } NamespaceDeclarationSyntax CreateNamespace(string namespaceName, IEnumerable <MemberDeclarationSyntax> namespaceMembers) { return (SF.NamespaceDeclaration(SF.ParseName(namespaceName)) .AddUsings( TypeUtils.GetNamespaces(typeof(GrainExtensions), typeof(IntrospectionExtensions)) .Select(_ => SF.UsingDirective(SF.ParseName(_))) .ToArray()) .AddMembers(namespaceMembers.ToArray())); } }