Fixing an issue with output parameters that are of type IntPtr
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / Internal / ActiveRecordModelBuilder.cs
blobd7132ab695f47bc1cb7e01ddc3282251d77dc3fb
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 static readonly IValidatorRegistry validatorRegistry = new CachedValidationRegistry();
35 private readonly ActiveRecordModelCollection coll = new ActiveRecordModelCollection();
37 private IModelBuilderExtension extension;
39 /// <summary>
40 /// Creates a <see cref="ActiveRecordModel"/> from the specified type.
41 /// </summary>
42 /// <param name="type">The type.</param>
43 /// <returns></returns>
44 public ActiveRecordModel Create(Type type)
46 if (type == null) throw new ArgumentNullException("type");
48 if (type.IsDefined(typeof(ActiveRecordSkipAttribute), false)) return null;
50 ActiveRecordModel model = new ActiveRecordModel(type);
52 coll.Add(model);
54 PopulateModel(model, type);
56 ActiveRecordBase.Register(type, model);
58 return model;
61 /// <summary>
62 /// Sets the extension.
63 /// </summary>
64 /// <param name="extension">The extension.</param>
65 public void SetExtension(IModelBuilderExtension extension)
67 this.extension = extension;
70 /// <summary>
71 /// Gets the models.
72 /// </summary>
73 /// <value>The models.</value>
74 public ActiveRecordModelCollection Models
76 get { return coll; }
79 /// <summary>
80 /// Gets the validator registry used to create the validators
81 /// </summary>
82 public static IValidatorRegistry ValidatorRegistry
84 get { return validatorRegistry; }
87 /// <summary>
88 /// Populates the model from tye type
89 /// </summary>
90 /// <param name="model">The model.</param>
91 /// <param name="type">The type.</param>
92 private void PopulateModel(ActiveRecordModel model, Type type)
94 if (extension != null)
96 extension.ProcessClass(type, model);
99 ProcessActiveRecordAttribute(type, model);
101 ProcessImports(type, model);
103 ProcessJoinedBaseAttribute(type, model);
105 ProcessProperties(type, model);
107 ProcessFields(type, model);
110 private static void ProcessImports(Type type, ActiveRecordModel model)
112 object[] attrs = type.GetCustomAttributes(typeof(ImportAttribute), false);
114 foreach (ImportAttribute att in attrs)
116 ImportModel im = new ImportModel(att);
117 model.Imports.Add(im);
121 private static void ProcessJoinedBaseAttribute(Type type, ActiveRecordModel model)
123 model.IsJoinedSubClassBase = type.IsDefined(typeof(JoinedBaseAttribute), false);
126 private static void PopulateActiveRecordAttribute(ActiveRecordAttribute attribute, ActiveRecordModel model)
128 model.ActiveRecordAtt = attribute;
130 if (attribute.DiscriminatorColumn != null)
132 model.IsDiscriminatorBase = true;
134 if (attribute.DiscriminatorValue == null)
136 throw new ActiveRecordException(
137 String.Format("You must specify a discriminator value for the type {0}", model.Type.FullName));
140 else if (attribute.DiscriminatorType != null)
142 throw new ActiveRecordException(
143 String.Format("The usage of DiscriminatorType for {0} is meaningless", model.Type.FullName));
146 if (model.ActiveRecordAtt.Table == null)
148 string safename = GetSafeName(model.Type.Name);
149 model.ActiveRecordAtt.Table = ActiveRecordModel.pluralizeTableNames
150 ? Inflector.Pluralize(safename)
151 : safename;
155 /// <summary>
156 /// Remove the generic part from the type name.
157 /// </summary>
158 /// <param name="name"></param>
159 /// <returns></returns>
160 private static string GetSafeName(string name)
162 int index = name.IndexOf("`");
164 if (index == -1) return name;
166 return name.Substring(0, index);
169 private void ProcessFields(Type type, ActiveRecordModel model)
171 //Check persistent fields of the base class as well
172 if (ShouldCheckBase(type))
174 ProcessFields(type.BaseType, model);
177 FieldInfo[] fields = type.GetFields(FieldDefaultBindingFlags);
179 foreach (FieldInfo field in fields)
181 if (field.IsDefined(typeof(FieldAttribute), false))
183 FieldAttribute fieldAtt = field.GetCustomAttributes(typeof(FieldAttribute), false)[0] as FieldAttribute;
185 model.Fields.Add(new FieldModel(field, fieldAtt));
188 if (extension != null)
190 extension.ProcessField(field, model);
195 private void ProcessProperties(Type type, ActiveRecordModel model)
197 // Check persistent properties of the base class as well
198 if (ShouldCheckBase(type))
200 ProcessProperties(type.BaseType, model);
203 PropertyInfo[] props = type.GetProperties(DefaultBindingFlags);
205 foreach(PropertyInfo prop in props)
207 bool isArProperty = false;
208 AnyModel anyModel;
209 HasManyToAnyModel hasManyToAnyModel;
211 if (extension != null)
213 extension.ProcessProperty(prop, model);
216 object[] valAtts = prop.GetCustomAttributes(typeof(AbstractValidationAttribute), true);
218 foreach(AbstractValidationAttribute valAtt in valAtts)
220 IValidator validator = valAtt.Build();
221 validator.Initialize(validatorRegistry, prop);
223 model.Validators.Add(validator);
226 foreach(object attribute in prop.GetCustomAttributes(false))
228 if (attribute is PrimaryKeyAttribute)
230 PrimaryKeyAttribute propAtt = attribute as PrimaryKeyAttribute;
231 isArProperty = true;
233 // Joined Subclasses must not have PrimaryKey
234 if (type.IsDefined(typeof(JoinedBaseAttribute), true) && // JoinedBase in a superclass
235 !type.IsDefined(typeof(JoinedBaseAttribute), false)) // but not here
237 throw new ActiveRecordException("You can't specify a PrimaryKeyAttribute in a joined subclass. " +
238 "Check type " + model.Type.FullName);
241 if (prop.PropertyType.IsDefined(typeof(CompositeKeyAttribute), true))
243 object[] att = prop.PropertyType.GetCustomAttributes(typeof(CompositeKeyAttribute), true);
245 CompositeKeyAttribute cAtt = att[0] as CompositeKeyAttribute;
247 model.CompositeKey = new CompositeKeyModel(prop, cAtt);
249 else
251 if (!propAtt.IsOverride && model.PrimaryKey != null)
253 throw new ActiveRecordException("You can't specify more than one PrimaryKeyAttribute in a " +
254 "class. Check type " + model.Type.FullName);
257 model.PrimaryKey = new PrimaryKeyModel(prop, propAtt);
260 else if (attribute is CompositeKeyAttribute)
262 CompositeKeyAttribute propAtt = attribute as CompositeKeyAttribute;
263 isArProperty = true;
265 model.CompositeKey = new CompositeKeyModel(prop, propAtt);
267 else if (attribute is AnyAttribute)
269 AnyAttribute anyAtt = attribute as AnyAttribute;
270 isArProperty = true;
271 anyModel = new AnyModel(prop, anyAtt);
272 model.Anys.Add(anyModel);
274 CollectMetaValues(anyModel.MetaValues, prop);
276 else if (attribute is PropertyAttribute)
278 PropertyAttribute propAtt = attribute as PropertyAttribute;
279 isArProperty = true;
281 // If this property overrides a base class property remove the old one
282 if (propAtt.IsOverride)
284 for (int index = 0; index < model.Properties.Count; ++index)
286 PropertyModel oldModel = (PropertyModel)model.Properties[index];
288 if (oldModel.Property.Name == prop.Name)
290 model.Properties.RemoveAt(index);
291 break;
296 PropertyModel propModel = new PropertyModel(prop, propAtt);
297 model.Properties.Add(propModel);
298 model.PropertyDictionary[prop.Name] = propModel;
300 else if (attribute is NestedAttribute)
302 NestedAttribute propAtt = attribute as NestedAttribute;
303 isArProperty = true;
305 ActiveRecordModel nestedModel = new ActiveRecordModel(prop.PropertyType);
307 nestedModel.IsNestedType = true;
309 Type nestedType = propAtt.MapType ?? prop.PropertyType;
310 nestedModel.IsNestedCompositeType = model.IsNestedCompositeType;
311 ProcessProperties(nestedType, nestedModel);
312 ProcessFields(nestedType, nestedModel);
314 NestedModel nested = new NestedModel(prop, propAtt, nestedModel);
315 nestedModel.ParentNested = nested;
317 model.Components.Add(nested);
319 else if (attribute is NestedParentReferenceAttribute)
321 NestedParentReferenceAttribute nestedParentAtt = attribute as NestedParentReferenceAttribute;
322 isArProperty = true;
324 model.ComponentParent.Add(new NestedParentReferenceModel(prop, nestedParentAtt));
326 else if (attribute is JoinedKeyAttribute)
328 JoinedKeyAttribute propAtt = attribute as JoinedKeyAttribute;
329 isArProperty = true;
331 if (model.Key != null)
333 throw new ActiveRecordException("You can't specify more than one JoinedKeyAttribute. " +
334 "Check type " + model.Type.FullName);
337 model.Key = new KeyModel(prop, propAtt);
339 else if (attribute is VersionAttribute)
341 VersionAttribute propAtt = attribute as VersionAttribute;
342 isArProperty = true;
344 if (model.Version != null)
346 throw new ActiveRecordException("You can't specify more than one VersionAttribute. " +
347 "Check type " + model.Type.FullName);
350 model.Version = new VersionModel(prop, propAtt);
352 else if (attribute is TimestampAttribute)
354 TimestampAttribute propAtt = attribute as TimestampAttribute;
355 isArProperty = true;
357 if (model.Timestamp != null)
359 throw new ActiveRecordException("You can't specify more than one TimestampAttribute. " +
360 "Check type " + model.Type.FullName);
363 model.Timestamp = new TimestampModel(prop, propAtt);
365 // Relations
366 else if (attribute is OneToOneAttribute)
368 OneToOneAttribute propAtt = attribute as OneToOneAttribute;
369 isArProperty = true;
371 model.OneToOnes.Add(new OneToOneModel(prop, propAtt));
373 else if (attribute is BelongsToAttribute)
375 BelongsToAttribute propAtt = attribute as BelongsToAttribute;
376 isArProperty = true;
378 BelongsToModel btModel = new BelongsToModel(prop, propAtt);
379 model.BelongsTo.Add(btModel);
380 model.BelongsToDictionary[prop.Name] = btModel;
382 if (extension != null)
384 extension.ProcessBelongsTo(prop, btModel, model);
387 // The ordering is important here, HasManyToAny must comes before HasMany!
388 else if (attribute is HasManyToAnyAttribute)
390 HasManyToAnyAttribute propAtt = attribute as HasManyToAnyAttribute;
391 isArProperty = true;
393 hasManyToAnyModel = new HasManyToAnyModel(prop, propAtt);
394 model.HasManyToAny.Add(hasManyToAnyModel);
395 model.HasManyToAnyDictionary[prop.Name] = hasManyToAnyModel;
397 CollectMetaValues(hasManyToAnyModel.MetaValues, prop);
399 if (extension != null)
401 extension.ProcessHasManyToAny(prop, hasManyToAnyModel, model);
404 else if (attribute is HasManyAttribute)
406 HasManyAttribute propAtt = attribute as HasManyAttribute;
407 isArProperty = true;
409 HasManyModel hasManyModel = new HasManyModel(prop, propAtt);
410 if (propAtt.DependentObjects)
412 ActiveRecordModel dependentObjectModel = new ActiveRecordModel(propAtt.MapType);
413 dependentObjectModel.IsNestedType = true;
414 dependentObjectModel.IsNestedCompositeType = true;
415 ProcessProperties(propAtt.MapType, dependentObjectModel);
417 hasManyModel.DependentObjectModel = new DependentObjectModel(prop, propAtt, dependentObjectModel);
419 model.HasMany.Add(hasManyModel);
420 model.HasManyDictionary[prop.Name] = hasManyModel;
422 if (extension != null)
424 extension.ProcessHasMany(prop, hasManyModel, model);
427 else if (attribute is HasAndBelongsToManyAttribute)
429 HasAndBelongsToManyAttribute propAtt = attribute as HasAndBelongsToManyAttribute;
430 isArProperty = true;
432 HasAndBelongsToManyModel habtManyModel = new HasAndBelongsToManyModel(prop, propAtt);
433 model.HasAndBelongsToMany.Add(habtManyModel);
434 model.HasAndBelongsToManyDictionary[prop.Name] = habtManyModel;
436 if (extension != null)
438 extension.ProcessHasAndBelongsToMany(prop, habtManyModel, model);
441 else if (attribute is Any.MetaValueAttribute)
443 if (prop.GetCustomAttributes(typeof(HasManyToAnyAttribute), false).Length == 0 &&
444 prop.GetCustomAttributes(typeof(AnyAttribute), false).Length == 0
446 throw new ActiveRecordException(
447 "You can't specify an Any.MetaValue without specifying the Any or HasManyToAny attribute. " +
448 "Check type " + prop.DeclaringType.FullName);
450 else if (attribute is CompositeUserTypeAttribute)
452 CompositeUserTypeAttribute propAtt = attribute as CompositeUserTypeAttribute;
453 isArProperty = true;
455 model.CompositeUserType.Add(new CompositeUserTypeModel(prop, propAtt));
458 if (attribute is CollectionIDAttribute)
460 CollectionIDAttribute propAtt = attribute as CollectionIDAttribute;
462 model.CollectionIDs.Add(new CollectionIDModel(prop, propAtt));
464 if (attribute is HiloAttribute)
466 HiloAttribute propAtt = attribute as HiloAttribute;
468 model.Hilos.Add(new HiloModel(prop, propAtt));
472 if (!isArProperty)
474 model.NotMappedProperties.Add(prop);
479 private static void CollectMetaValues(IList<Any.MetaValueAttribute> metaStore, PropertyInfo prop)
481 if (metaStore == null)
482 throw new ArgumentNullException("metaStore");
484 Any.MetaValueAttribute[] metaValues =
485 prop.GetCustomAttributes(typeof(Any.MetaValueAttribute), false) as Any.MetaValueAttribute[];
487 if (metaValues == null || metaValues.Length == 0)
488 return;
490 foreach (Any.MetaValueAttribute attribute in metaValues)
492 metaStore.Add(attribute);
496 private static bool ShouldCheckBase(Type type)
498 // Changed as suggested http://support.castleproject.org/jira/browse/AR-40
499 bool shouldCheck = IsRootType(type);
501 if (shouldCheck) // Perform more checks
503 Type basetype = type.BaseType;
505 while (basetype != typeof(object))
507 if (basetype.IsDefined(typeof(JoinedBaseAttribute), false)) return false;
509 object[] attrs = basetype.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
511 if (attrs.Length != 0)
513 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
514 if (arAttribute.DiscriminatorColumn != null)
515 return false;
517 else
519 return true;
522 basetype = basetype.BaseType;
526 return shouldCheck;
529 private static bool IsRootType(Type type)
531 bool isRootType = type.BaseType != typeof(object) &&
532 type.BaseType != typeof(ActiveRecordBase) &&
533 type.BaseType != typeof(ActiveRecordValidationBase);
534 // && !type.BaseType.IsDefined(typeof(ActiveRecordAttribute), false);
536 // generic check
537 if (type.BaseType.IsGenericType)
539 isRootType = type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordBase<>) &&
540 type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase<>);
543 return isRootType;
546 private static void ProcessActiveRecordAttribute(Type type, ActiveRecordModel model)
548 object[] attrs = type.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
550 if (attrs.Length == 0)
552 throw new ActiveRecordException(
553 String.Format("Type {0} is not using the ActiveRecordAttribute, which is obligatory.", type.FullName));
556 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
558 PopulateActiveRecordAttribute(arAttribute, model);