Home
Manage Your Code
Snippet: SAP Parser (C#)
Title: SAP Parser Language: C#
Description: A sequential access parser (api) for parsing a types graph Views: 340
Author: Stephen Smith Date Added: 6/27/2012
Copy Code  
1/// <summary>

2/// A sequential access parser (api) for parsing a types graph

3/// </summary>

4/// <remarks>

5/// Throws an <see cref="InvalidOperationException"/> if a cyclic

6/// dependency is found.

7/// </remarks>

8public class SapTypeParser
9    {
10        protected static Type[] DefaultNotCustomClasses =
11            {
12                typeof (Type),
13                typeof (Uri)
14            };
15
16        private readonly List<Type> _isMapped = new List<Type>(3);
17        private readonly Type[] _notCustomClasses;
18        private char _pathSeperator;
19
20        public enum Continuation
21        {
22            Continue,
23            Stop,
24            Skip
25        };
26
27        public delegate Continuation ParseTypeEventHandler(Type type, string path);
28        public delegate Continuation ParsePropertyEventHandler(PropertyInfo property, string path);
29
30        public event ParseTypeEventHandler OnType;
31        public event ParseTypeEventHandler OnTypeEnd;
32        public event ParsePropertyEventHandler ScalarProperty;
33        public event ParsePropertyEventHandler ClassProperty;
34        public event ParsePropertyEventHandler ClassPropertyEnd;
35        public event ParsePropertyEventHandler EnumerableProperty;
36        public event ParsePropertyEventHandler EnumerablePropertyEnd;
37        public event ParsePropertyEventHandler OnProperty;
38
39        public SapTypeParser(Type type, params Type[] notCustomClasses)
40        {
41            Type = type;
42            _notCustomClasses = notCustomClasses.Length == 0 ? DefaultNotCustomClasses : notCustomClasses;
43        }
44
45        protected Type Type { get; private set; }
46
47        public void Parse(Func<PropertyInfo, bool> predicate = null, char pathSeperator = '.')
48        {
49            _pathSeperator = pathSeperator;
50            predicate = predicate ?? (p => true);
51            Parse(Type, string.Empty, predicate);
52        }
53
54        protected virtual Continuation Parse(Type type, string path, Func<PropertyInfo, bool> predicate)
55        {
56            if (_isMapped.Contains(type))
57                throw new InvalidOperationException(
58                    string.Format(
59                        "A cyclic dependency has been identified for type {0} ({1})! We cannot parse cyclic dependant graphs.",
60                        type.Name, path));
61
62            var result = DoOnType(type, ref path, predicate);
63
64            if (result != Continuation.Stop)
65                result = DoOnTypeEnd(type, ref path);
66
67            return result;
68        }
69
70        protected virtual Continuation DoOnType(Type type, ref string path, Func<PropertyInfo, bool> predicate)
71        {
72            if (OnType != null)
73            {
74                var continuation = OnType(type, path);
75                if (!Continue(ref continuation)) return continuation;
76            }
77            var properties = type.GetProperties().Where(predicate);
78            return ParseProperties(properties, ref path, predicate);
79        }
80
81        protected virtual Continuation DoOnTypeEnd(Type type, ref string path)
82        {
83            var result = Continuation.Continue;
84
85            if (OnTypeEnd != null)
86            {
87                result = OnTypeEnd(type, path);
88            }
89            return result;
90        }
91
92        private Continuation ParseProperties(IEnumerable<PropertyInfo> properties, ref string path,
93                                             Func<PropertyInfo, bool> predicate)
94        {
95            var result = Continuation.Continue;
96
97            foreach (var property in properties)
98            {
99                DoOnProperty(property, ref path);
100
101                if (result == Continuation.Continue)
102                {
103                    if (IsEnumerable(property))
104                    {
105                        DoEnumerableProperty(property, ref path, predicate);
106                        if (result == Continuation.Continue)
107                            DoEnumerablePropertyEnd(property, ref path);
108                    }
109                    else if (IsCustomClass(property))
110                    {
111                        result = DoClassProperty(property, ref path, predicate);
112                        if (result == Continuation.Continue)
113                            result = DoClassPropertyEnd(property, ref path);
114                    }
115                    else
116                    {
117                        DoScalarProperty(property, ref path);
118                    }
119                }
120                if (result == Continuation.Stop) return result;
121            }
122            return Continuation.Continue;
123        }
124
125        protected virtual Continuation DoOnProperty(PropertyInfo property, ref string path)
126        {
127            var result = Continuation.Continue;
128            if (OnProperty != null)
129                result = OnProperty(property, path);
130            return result;
131        }
132
133        protected virtual Continuation DoEnumerableProperty(PropertyInfo property, ref string path,
134                                             Func<PropertyInfo, bool> predicate)
135        {
136            var result = Continuation.Continue;
137            
138            if (EnumerableProperty != null)
139            {
140                result = EnumerableProperty(property, path);
141            }
142
143            if (result == Continuation.Continue)
144            {
145                var elementType = GetElementType(property);
146                if (IsCustomClass(elementType))
147                {
148                    result = Parse(elementType, Append(path, property.Name), predicate);
149                }
150            }
151
152            return result;
153        }
154
155        protected virtual Continuation DoEnumerablePropertyEnd(PropertyInfo property, ref string path)
156        {
157            var result = Continuation.Continue;
158            if (EnumerablePropertyEnd != null)
159                result = EnumerablePropertyEnd(property, path);
160            return result;
161        }
162
163        protected virtual Continuation DoClassProperty(PropertyInfo property, ref string path,
164                                             Func<PropertyInfo, bool> predicate)
165        {
166            var result = Continuation.Continue;
167
168            if (ClassProperty != null)
169            {
170                result = ClassProperty(property, path);
171            }
172
173            if (result == Continuation.Continue)
174            {
175                result = Parse(property.PropertyType, Append(path, property.Name), predicate);
176            }
177
178            return result;
179        }
180
181        protected virtual Continuation DoClassPropertyEnd(PropertyInfo property, ref string path)
182        {
183            return ClassPropertyEnd != null ? ClassPropertyEnd(property, path) : Continuation.Continue;
184        }
185
186        protected virtual Continuation DoScalarProperty(PropertyInfo property, ref string path)
187        {
188            return ScalarProperty != null ? ScalarProperty(property, path) : Continuation.Continue;
189        }
190
191        private static bool Continue(ref Continuation continuation)
192        {
193            switch (continuation)
194            {
195                case Continuation.Skip:
196                    continuation = Continuation.Continue;
197                    return false;
198                case Continuation.Stop:
199                    return false;
200                case Continuation.Continue:
201                    return true;
202                default:
203                    throw new NotImplementedException("Continuation value is not supported! Internal error.");
204            }
205        }
206
207        private string Append(string path, string name)
208        {
209            if (string.IsNullOrEmpty(path)) return name;
210            path = path.TrimEnd(_pathSeperator);
211            return string.Format("{0}{1}{2}", path, _pathSeperator, name);
212        }
213
214        private bool IsCustomClass(PropertyInfo property)
215        {
216            var type = property.PropertyType;
217            return (Type.GetTypeCode(type) == TypeCode.Object && !_notCustomClasses.Contains(type));
218        }
219
220        private static bool IsCustomClass(Type type)
221        {
222            return (Type.GetTypeCode(type) == TypeCode.Object);  
223        }
224
225        private static bool IsEnumerable(PropertyInfo property)
226        {
227            var type = property.PropertyType;
228            return typeof (IEnumerable).IsAssignableFrom(type) && Type.GetTypeCode(type) != TypeCode.String;
229        }
230
231        private static Type GetElementType(PropertyInfo property)
232        {
233            var type = property.PropertyType;
234
235            if (type.IsArray)
236            {
237                return type.GetElementType();
238            }
239
240            return type.GetInterfaces()
241                .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
242                .Select(i => i.GetGenericArguments()[0])
243                .FirstOrDefault()
244                ??
245                typeof(object); // default to object, for non-generic collections!

246        }
247    }
248
Usage
Use the various events to handle the parsed token.
Code is easy to read so usage can easily be understood