using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using CppSharp.AST; using CppSharp.AST.Extensions; using Type = CppSharp.AST.Type; namespace CppSharp.Passes { public class GetterSetterToPropertyAdvancedPass : TranslationUnitPass { private class PropertyGenerator { private readonly List getters = new List(); private readonly List setters = new List(); private readonly List setMethods = new List(); private readonly List nonSetters = new List(); public PropertyGenerator(Class @class) { foreach (var method in @class.Methods.Where( m => !m.IsConstructor && !m.IsDestructor && !m.IsOperator && !m.IsSynthetized)) DistributeMethod(method); } public void GenerateProperties() { GenerateProperties(setters, false); GenerateProperties(setMethods, true); foreach (Method getter in from getter in getters where getter.IsGenerated && ((Class) getter.Namespace).Methods.All(m => m == getter || m.Name != getter.Name) select getter) { // Make it a read-only property GenerateProperty(getter.Namespace, getter); } } private void GenerateProperties(IEnumerable settersToUse, bool readOnly) { foreach (var setter in settersToUse) { Class type = (Class) setter.Namespace; StringBuilder nameBuilder = new StringBuilder(setter.Name.Substring(3)); if (char.IsLower(setter.Name[0])) nameBuilder[0] = char.ToLowerInvariant(nameBuilder[0]); string afterSet = nameBuilder.ToString(); foreach (var getter in nonSetters.Where(m => m.Namespace == type)) { var name = GetReadWritePropertyName(getter, afterSet); if (name == afterSet && GetUnderlyingType(getter.OriginalReturnType).Equals( GetUnderlyingType(setter.Parameters[0].QualifiedType)) && !type.Methods.Any(m => m != getter && name == m.Name)) { getter.Name = name; GenerateProperty(getter.Namespace, getter, readOnly ? null : setter); goto next; } } Property baseVirtualProperty = type.GetRootBaseProperty(new Property { Name = afterSet }); if (!type.IsInterface && baseVirtualProperty != null) { bool isReadOnly = baseVirtualProperty.SetMethod == null; GenerateProperty(setter.Namespace, baseVirtualProperty.GetMethod, readOnly || isReadOnly ? null : setter); } next: ; } foreach (Method nonSetter in nonSetters) { Class type = (Class) nonSetter.Namespace; string name = GetPropertyName(nonSetter.Name); Property baseVirtualProperty = type.GetRootBaseProperty(new Property { Name = name }); if (!type.IsInterface && baseVirtualProperty != null) { bool isReadOnly = baseVirtualProperty.SetMethod == null; if (readOnly == isReadOnly) { GenerateProperty(nonSetter.Namespace, nonSetter, readOnly ? null : baseVirtualProperty.SetMethod); } } } } private static string GetReadWritePropertyName(INamedDecl getter, string afterSet) { string name = GetPropertyName(getter.Name); if (name != afterSet && name.StartsWith("is")) { name = char.ToLowerInvariant(name[2]) + name.Substring(3); } return name; } private static Type GetUnderlyingType(QualifiedType type) { TagType tagType = type.Type as TagType; if (tagType != null) return type.Type; // TODO: we should normally check pointer types for const; // however, there's some bug, probably in the parser, that returns IsConst = false for "const Type& arg" // so skip the check for the time being PointerType pointerType = type.Type as PointerType; return pointerType != null ? pointerType.Pointee : type.Type; } private static void GenerateProperty(DeclarationContext context, Method getter, Method setter = null) { Class type = (Class) context; if (type.Properties.All(p => getter.Name != p.Name || p.ExplicitInterfaceImpl != getter.ExplicitInterfaceImpl)) { Property property = new Property(); property.Name = GetPropertyName(getter.Name); property.Namespace = type; property.QualifiedType = getter.OriginalReturnType; if (getter.IsOverride || (setter != null && setter.IsOverride)) { Property baseVirtualProperty = type.GetRootBaseProperty(property); if (baseVirtualProperty.SetMethod == null) setter = null; } property.GetMethod = getter; property.SetMethod = setter; property.ExplicitInterfaceImpl = getter.ExplicitInterfaceImpl; if (property.ExplicitInterfaceImpl == null && setter != null) { property.ExplicitInterfaceImpl = setter.ExplicitInterfaceImpl; } if (getter.Comment != null) { var comment = new RawComment(); comment.Kind = getter.Comment.Kind; comment.BriefText = getter.Comment.BriefText; comment.Text = getter.Comment.Text; comment.FullComment = new FullComment(); comment.FullComment.Blocks.AddRange(getter.Comment.FullComment.Blocks); if (setter != null && setter.Comment != null) { comment.BriefText += Environment.NewLine + setter.Comment.BriefText; comment.Text += Environment.NewLine + setter.Comment.Text; comment.FullComment.Blocks.AddRange(setter.Comment.FullComment.Blocks); } property.Comment = comment; } type.Properties.Add(property); getter.GenerationKind = GenerationKind.Internal; if (setter != null) setter.GenerationKind = GenerationKind.Internal; } } private static string GetPropertyName(string name) { if (GetFirstWord(name) == "get" && name != "get") { if (char.IsLower(name[0])) { if (name.Length == 4) { return char.ToLowerInvariant( name[3]).ToString(CultureInfo.InvariantCulture); } return char.ToLowerInvariant( name[3]).ToString(CultureInfo.InvariantCulture) + name.Substring(4); } return name.Substring(3); } return name; } private void DistributeMethod(Method method) { if (GetFirstWord(method.Name) == "set" && method.Name.Length > 3 && method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void)) { if (method.Parameters.Count == 1) setters.Add(method); else setMethods.Add(method); } else { if (IsGetter(method)) getters.Add(method); if (method.Parameters.All(p => p.Kind == ParameterKind.IndirectReturnType)) nonSetters.Add(method); } } private static bool IsGetter(Method method) { if (method.IsDestructor || (method.OriginalReturnType.Type.IsPrimitiveType(PrimitiveType.Void)) || method.Parameters.Any(p => p.Kind != ParameterKind.IndirectReturnType)) return false; var result = GetFirstWord(method.Name); return (result.Length < method.Name.Length && (result == "get" || result == "is" || result == "has")) || (result != "to" && result != "new" && !verbs.Contains(result)); } private static string GetFirstWord(string name) { List firstVerb = new List { char.ToLowerInvariant(name[0]) }; firstVerb.AddRange(name.Skip(1).TakeWhile( c => char.IsLower(c) || !char.IsLetterOrDigit(c))); return new string(firstVerb.ToArray()); } } private static readonly HashSet verbs = new HashSet(); static GetterSetterToPropertyAdvancedPass() { LoadVerbs(); } private static void LoadVerbs() { var assembly = Assembly.GetExecutingAssembly(); using (var resourceStream = GetResourceStream(assembly)) { using (var streamReader = new StreamReader(resourceStream)) while (!streamReader.EndOfStream) verbs.Add(streamReader.ReadLine()); } } private static Stream GetResourceStream(Assembly assembly) { var stream = assembly.GetManifestResourceStream("CppSharp.Generator.Passes.verbs.txt"); // HACK: a bug in premake for OS X causes resources to be embedded with an incorrect location return stream ?? assembly.GetManifestResourceStream("verbs.txt"); } public GetterSetterToPropertyAdvancedPass() { Options.VisitClassProperties = false; } public override bool VisitClassDecl(Class @class) { bool result = base.VisitClassDecl(@class); new PropertyGenerator(@class).GenerateProperties(); return result; } } }