Removed untyped contructor from ComponentRegistration and add a protected setter.
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / Internal / ActiveRecordModelBuilder.cs
blob60630ef58d5e95976e968874926120d0cbf26d3a
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.ActiveRecord.Framework.Internal
17 using System;
18 using System.Collections.Generic;
19 using System.Reflection;
20 using Castle.Components.Validator;
22 /// <summary>
23 /// Bulids an <see cref="ActiveRecordModel"/> from a type and does some inital validation.
24 /// </summary>
25 public class ActiveRecordModelBuilder
27 private static readonly BindingFlags DefaultBindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public |
28 BindingFlags.Instance | BindingFlags.NonPublic;
30 private static readonly BindingFlags FieldDefaultBindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public |
31 BindingFlags.NonPublic | BindingFlags.Instance;
33 private readonly ActiveRecordModelCollection coll = new ActiveRecordModelCollection();
35 private static readonly IValidatorRegistry validatorRegistry = new CachedValidationRegistry();
37 /// <summary>
38 /// Creates a <see cref="ActiveRecordModel"/> from the specified type.
39 /// </summary>
40 /// <param name="type">The type.</param>
41 /// <returns></returns>
42 public ActiveRecordModel Create(Type type)
44 if (type == null) throw new ArgumentNullException("type");
46 if (type.IsDefined(typeof(ActiveRecordSkipAttribute), false)) return null;
48 ActiveRecordModel model = new ActiveRecordModel(type);
50 coll.Add(model);
52 PopulateModel(model, type);
54 ActiveRecordBase.Register(type, model);
56 return model;
59 /// <summary>
60 /// Gets the models.
61 /// </summary>
62 /// <value>The models.</value>
63 public ActiveRecordModelCollection Models
65 get { return coll; }
68 /// <summary>
69 /// Gets the validator registry used to create the validators
70 /// </summary>
71 public static IValidatorRegistry ValidatorRegistry
73 get { return validatorRegistry; }
76 /// <summary>
77 /// Populates the model from tye type
78 /// </summary>
79 /// <param name="model">The model.</param>
80 /// <param name="type">The type.</param>
81 private static void PopulateModel(ActiveRecordModel model, Type type)
83 ProcessActiveRecordAttribute(type, model);
85 ProcessImports(type, model);
87 ProcessJoinedBaseAttribute(type, model);
89 ProcessProperties(type, model);
91 ProcessFields(type, model);
94 private static void ProcessImports(Type type, ActiveRecordModel model)
96 object[] attrs = type.GetCustomAttributes(typeof(ImportAttribute), false);
98 foreach (ImportAttribute att in attrs)
100 ImportModel im = new ImportModel(att);
101 model.Imports.Add(im);
105 private static void ProcessJoinedBaseAttribute(Type type, ActiveRecordModel model)
107 model.IsJoinedSubClassBase = type.IsDefined(typeof(JoinedBaseAttribute), false);
110 private static void PopulateActiveRecordAttribute(ActiveRecordAttribute attribute, ActiveRecordModel model)
112 model.ActiveRecordAtt = attribute;
114 if (attribute.DiscriminatorColumn != null)
116 model.IsDiscriminatorBase = true;
118 if (attribute.DiscriminatorValue == null)
120 throw new ActiveRecordException(
121 String.Format("You must specify a discriminator value for the type {0}", model.Type.FullName));
124 else if (attribute.DiscriminatorType != null)
126 throw new ActiveRecordException(
127 String.Format("The usage of DiscriminatorType for {0} is meaningless", model.Type.FullName));
130 if (model.ActiveRecordAtt.Table == null)
132 string safename = GetSafeName(model.Type.Name);
133 model.ActiveRecordAtt.Table = ActiveRecordModel.pluralizeTableNames
134 ? Inflector.Pluralize(safename)
135 : safename;
139 /// <summary>
140 /// Remove the generic part from the type name.
141 /// </summary>
142 /// <param name="name"></param>
143 /// <returns></returns>
144 private static string GetSafeName(string name)
146 int index = name.IndexOf("`");
148 if (index == -1) return name;
150 return name.Substring(0, index);
153 private static void ProcessFields(Type type, ActiveRecordModel model)
155 //Check persistent fields of the base class as well
156 if (ShouldCheckBase(type))
158 ProcessFields(type.BaseType, model);
161 FieldInfo[] fields = type.GetFields(FieldDefaultBindingFlags);
163 foreach (FieldInfo field in fields)
165 if (field.IsDefined(typeof(FieldAttribute), false))
167 FieldAttribute fieldAtt = field.GetCustomAttributes(typeof(FieldAttribute), false)[0] as FieldAttribute;
169 model.Fields.Add(new FieldModel(field, fieldAtt));
174 private static void ProcessProperties(Type type, ActiveRecordModel model)
176 // Check persistent properties of the base class as well
177 if (ShouldCheckBase(type))
179 ProcessProperties(type.BaseType, model);
182 PropertyInfo[] props = type.GetProperties(DefaultBindingFlags);
184 foreach (PropertyInfo prop in props)
186 bool isArProperty = false;
187 AnyModel anyModel;
188 HasManyToAnyModel hasManyToAnyModel;
190 object[] valAtts = prop.GetCustomAttributes(typeof(AbstractValidationAttribute), true);
192 foreach (AbstractValidationAttribute valAtt in valAtts)
194 IValidator validator = valAtt.Build();
195 validator.Initialize(validatorRegistry, prop);
197 model.Validators.Add(validator);
200 foreach (object attribute in prop.GetCustomAttributes(false))
202 if (attribute is PrimaryKeyAttribute)
204 PrimaryKeyAttribute propAtt = attribute as PrimaryKeyAttribute;
205 isArProperty = true;
207 // Joined Subclasses must not have PrimaryKey
208 if (type.IsDefined(typeof(JoinedBaseAttribute), true) && // JoinedBase in a superclass
209 !type.IsDefined(typeof(JoinedBaseAttribute), false)) // but not here
211 throw new ActiveRecordException("You can't specify a PrimaryKeyAttribute in a joined subclass. " +
212 "Check type " + model.Type.FullName);
215 if (prop.PropertyType.IsDefined(typeof(CompositeKeyAttribute), true))
217 object[] att = prop.PropertyType.GetCustomAttributes(typeof(CompositeKeyAttribute), true);
219 CompositeKeyAttribute cAtt = att[0] as CompositeKeyAttribute;
221 model.CompositeKey = new CompositeKeyModel(prop, cAtt);
223 else
225 if (!propAtt.IsOverride && model.PrimaryKey != null)
227 throw new ActiveRecordException("You can't specify more than one PrimaryKeyAttribute in a " +
228 "class. Check type " + model.Type.FullName);
231 model.PrimaryKey = new PrimaryKeyModel(prop, propAtt);
234 else if (attribute is CompositeKeyAttribute)
236 CompositeKeyAttribute propAtt = attribute as CompositeKeyAttribute;
237 isArProperty = true;
239 model.CompositeKey = new CompositeKeyModel(prop, propAtt);
241 else if (attribute is AnyAttribute)
243 AnyAttribute anyAtt = attribute as AnyAttribute;
244 isArProperty = true;
245 anyModel = new AnyModel(prop, anyAtt);
246 model.Anys.Add(anyModel);
248 CollectMetaValues(anyModel.MetaValues, prop);
250 else if (attribute is PropertyAttribute)
252 PropertyAttribute propAtt = attribute as PropertyAttribute;
253 isArProperty = true;
255 // If this property overrides a base class property remove the old one
256 if (propAtt.IsOverride)
258 for (int index = 0; index < model.Properties.Count; ++index)
260 PropertyModel oldModel = (PropertyModel)model.Properties[index];
262 if (oldModel.Property.Name == prop.Name)
264 model.Properties.RemoveAt(index);
265 break;
270 model.Properties.Add(new PropertyModel(prop, propAtt));
272 else if (attribute is NestedAttribute)
274 NestedAttribute propAtt = attribute as NestedAttribute;
275 isArProperty = true;
277 ActiveRecordModel nestedModel = new ActiveRecordModel(prop.PropertyType);
279 nestedModel.IsNestedType = true;
281 Type nestedType = propAtt.MapType ?? prop.PropertyType;
282 nestedModel.IsNestedCompositeType = model.IsNestedCompositeType;
283 ProcessProperties(nestedType, nestedModel);
284 ProcessFields(nestedType, nestedModel);
286 NestedModel nested = new NestedModel(prop, propAtt, nestedModel);
287 nestedModel.ParentNested = nested;
289 model.Components.Add(nested);
291 else if (attribute is NestedParentReferenceAttribute)
293 NestedParentReferenceAttribute nestedParentAtt = attribute as NestedParentReferenceAttribute;
294 isArProperty = true;
296 model.ComponentParent.Add(new NestedParentReferenceModel(prop, nestedParentAtt));
298 else if (attribute is JoinedKeyAttribute)
300 JoinedKeyAttribute propAtt = attribute as JoinedKeyAttribute;
301 isArProperty = true;
303 if (model.Key != null)
305 throw new ActiveRecordException("You can't specify more than one JoinedKeyAttribute. " +
306 "Check type " + model.Type.FullName);
309 model.Key = new KeyModel(prop, propAtt);
311 else if (attribute is VersionAttribute)
313 VersionAttribute propAtt = attribute as VersionAttribute;
314 isArProperty = true;
316 if (model.Version != null)
318 throw new ActiveRecordException("You can't specify more than one VersionAttribute. " +
319 "Check type " + model.Type.FullName);
322 model.Version = new VersionModel(prop, propAtt);
324 else if (attribute is TimestampAttribute)
326 TimestampAttribute propAtt = attribute as TimestampAttribute;
327 isArProperty = true;
329 if (model.Timestamp != null)
331 throw new ActiveRecordException("You can't specify more than one TimestampAttribute. " +
332 "Check type " + model.Type.FullName);
335 model.Timestamp = new TimestampModel(prop, propAtt);
337 // Relations
338 else if (attribute is OneToOneAttribute)
340 OneToOneAttribute propAtt = attribute as OneToOneAttribute;
341 isArProperty = true;
343 model.OneToOnes.Add(new OneToOneModel(prop, propAtt));
345 else if (attribute is BelongsToAttribute)
347 BelongsToAttribute propAtt = attribute as BelongsToAttribute;
348 isArProperty = true;
350 model.BelongsTo.Add(new BelongsToModel(prop, propAtt));
352 // The ordering is important here, HasManyToAny must comes before HasMany!
353 else if (attribute is HasManyToAnyAttribute)
355 HasManyToAnyAttribute propAtt = attribute as HasManyToAnyAttribute;
356 isArProperty = true;
358 hasManyToAnyModel = new HasManyToAnyModel(prop, propAtt);
359 model.HasManyToAny.Add(hasManyToAnyModel);
361 CollectMetaValues(hasManyToAnyModel.MetaValues, prop);
363 else if (attribute is HasManyAttribute)
365 HasManyAttribute propAtt = attribute as HasManyAttribute;
366 isArProperty = true;
368 HasManyModel hasManyModel = new HasManyModel(prop, propAtt);
369 if (propAtt.DependentObjects)
371 ActiveRecordModel dependentObjectModel = new ActiveRecordModel(propAtt.MapType);
372 dependentObjectModel.IsNestedType = true;
373 dependentObjectModel.IsNestedCompositeType = true;
374 ProcessProperties(propAtt.MapType, dependentObjectModel);
376 hasManyModel.DependentObjectModel = new DependentObjectModel(prop, propAtt, dependentObjectModel);
378 model.HasMany.Add(hasManyModel);
380 else if (attribute is HasAndBelongsToManyAttribute)
382 HasAndBelongsToManyAttribute propAtt = attribute as HasAndBelongsToManyAttribute;
383 isArProperty = true;
385 model.HasAndBelongsToMany.Add(new HasAndBelongsToManyModel(prop, propAtt));
387 else if (attribute is Any.MetaValueAttribute)
389 if (prop.GetCustomAttributes(typeof(HasManyToAnyAttribute), false).Length == 0 &&
390 prop.GetCustomAttributes(typeof(AnyAttribute), false).Length == 0
392 throw new ActiveRecordException(
393 "You can't specify an Any.MetaValue without specifying the Any or HasManyToAny attribute. " +
394 "Check type " + prop.DeclaringType.FullName);
396 else if (attribute is CompositeUserTypeAttribute)
398 CompositeUserTypeAttribute propAtt = attribute as CompositeUserTypeAttribute;
399 isArProperty = true;
401 model.CompositeUserType.Add(new CompositeUserTypeModel(prop, propAtt));
404 if (attribute is CollectionIDAttribute)
406 CollectionIDAttribute propAtt = attribute as CollectionIDAttribute;
408 model.CollectionIDs.Add(new CollectionIDModel(prop, propAtt));
410 if (attribute is HiloAttribute)
412 HiloAttribute propAtt = attribute as HiloAttribute;
414 model.Hilos.Add(new HiloModel(prop, propAtt));
418 if (!isArProperty)
420 model.NotMappedProperties.Add(prop);
425 private static void CollectMetaValues(IList<Any.MetaValueAttribute> metaStore, PropertyInfo prop)
427 if (metaStore == null)
428 throw new ArgumentNullException("metaStore");
430 Any.MetaValueAttribute[] metaValues =
431 prop.GetCustomAttributes(typeof(Any.MetaValueAttribute), false) as Any.MetaValueAttribute[];
433 if (metaValues == null || metaValues.Length == 0)
434 return;
436 foreach (Any.MetaValueAttribute attribute in metaValues)
438 metaStore.Add(attribute);
442 private static bool ShouldCheckBase(Type type)
444 // Changed as suggested http://support.castleproject.org/jira/browse/AR-40
445 bool shouldCheck = IsRootType(type);
447 if (shouldCheck) // Perform more checks
449 Type basetype = type.BaseType;
451 while (basetype != typeof(object))
453 if (basetype.IsDefined(typeof(JoinedBaseAttribute), false)) return false;
455 object[] attrs = basetype.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
457 if (attrs.Length != 0)
459 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
460 if (arAttribute.DiscriminatorColumn != null)
461 return false;
463 else
465 return true;
468 basetype = basetype.BaseType;
472 return shouldCheck;
475 private static bool IsRootType(Type type)
477 bool isRootType = type.BaseType != typeof(object) &&
478 type.BaseType != typeof(ActiveRecordBase) &&
479 type.BaseType != typeof(ActiveRecordValidationBase);
480 // && !type.BaseType.IsDefined(typeof(ActiveRecordAttribute), false);
482 // generic check
483 if (type.BaseType.IsGenericType)
485 isRootType = type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordBase<>) &&
486 type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase<>);
489 return isRootType;
492 private static void ProcessActiveRecordAttribute(Type type, ActiveRecordModel model)
494 object[] attrs = type.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
496 if (attrs.Length == 0)
498 throw new ActiveRecordException(
499 String.Format("Type {0} is not using the ActiveRecordAttribute, which is obligatory.", type.FullName));
502 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
504 PopulateActiveRecordAttribute(arAttribute, model);