/// <summary> /// Creates an options entry wrapper for the specified property, iterating its internal /// fields to create sub-options if needed (recursively). /// </summary> /// <param name="info">The property to wrap.</param> /// <param name="spec">The option title and tool tip.</param> /// <param name="depth">The current depth of iteration to avoid infinite loops.</param> /// <returns>An options wrapper, or null if no inner properties are themselves options.</returns> internal static CompositeOptionsEntry Create(IOptionSpec spec, PropertyInfo info, int depth) { var type = info.PropertyType; var composite = new CompositeOptionsEntry(info.Name, spec, type); // Skip static properties if they exist foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags. Instance)) { var entry = TryCreateEntry(prop, depth + 1); if (entry != null) { composite.AddField(prop, entry); } } return(composite.ChildCount > 0 ? composite : null); }
/// <summary> /// Creates an options entry if an attribute is a valid IOptionSpec or /// DynamicOptionAttribute. /// </summary> /// <param name="attribute">The attribute to parse.</param> /// <param name="prop">The property to inspect.</param> /// <param name="depth">The current depth of iteration to avoid infinite loops.</param> /// <returns>The OptionsEntry created from the attribute, or null if none was.</returns> private static IOptionsEntry TryCreateEntry(Attribute attribute, PropertyInfo prop, int depth) { IOptionsEntry result = null; if (prop == null) { throw new ArgumentNullException(nameof(prop)); } if (attribute is IOptionSpec spec) { if (string.IsNullOrEmpty(spec.Title)) { spec = HandleDefaults(spec, prop); } // Attempt to find a class that will represent it var type = prop.PropertyType; result = FindOptionClass(spec, prop); // See if it has entries that can themselves be added, ignore // value types and avoid infinite recursion if (result == null && !type.IsValueType && depth < 16 && type != prop.DeclaringType) { result = CompositeOptionsEntry.Create(spec, prop, depth); } } else if (attribute is DynamicOptionAttribute doa && typeof(IOptionsEntry).IsAssignableFrom(doa.Handler)) { try { result = Activator.CreateInstance(doa.Handler) as IOptionsEntry; } catch (TargetInvocationException e) { PUtil.LogError("Unable to create option handler for property " + prop.Name + ":"); PUtil.LogException(e.GetBaseException() ?? e); } catch (MissingMethodException) { PUtil.LogWarning("Unable to create option handler for property " + prop.Name + ", it must have a public default constructor"); } } return(result); }