Free cookie consent management tool by TermsFeed Policy Generator

Ignore:
Timestamp:
03/15/10 00:00:12 (14 years ago)
Author:
epitzer
Message:

Move attribute discovery from attribute classes to StorableSerializer (in preparation for unified discovery and handling of AllFields, AllProperties, ...) (#548)

Location:
trunk/sources/HeuristicLab.Persistence/3.3
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/sources/HeuristicLab.Persistence/3.3/Default/CompositeSerializers/Storable/StorableAttribute.cs

    r3017 r3025  
    6060      return sb.ToString();
    6161    }
    62 
    63     private const BindingFlags instanceMembers =
    64       BindingFlags.Instance |
    65       BindingFlags.Public |
    66       BindingFlags.NonPublic |
    67       BindingFlags.DeclaredOnly;
    68 
    69     /// <summary>
    70     /// Encapsulate information about storable members of a class
    71     /// that have the storable attribute set.
    72     /// </summary>
    73     public sealed class StorableMemberInfo {
    74 
    75       /// <summary>
    76       /// Gets the [Storable] attribute itself.
    77       /// </summary>
    78       /// <value>The [Storable] attribute.</value>
    79       public StorableAttribute Attribute { get; private set; }
    80 
    81       /// <summary>
    82       /// Gets the .NET reflection MemberInfo.
    83       /// </summary>
    84       /// <value>The member info.</value>
    85       public MemberInfo MemberInfo { get; private set; }
    86 
    87 
    88       /// <summary>
    89       /// Gets disentangled name (i.e. unique access name regardless of
    90       /// type hierarchy.
    91       /// </summary>
    92       /// <value>The disentangled name.</value>
    93       public string DisentangledName { get; private set; }
    94 
    95 
    96       /// <summary>
    97       /// Gets the fully qualified member name.
    98       /// </summary>
    99       /// <value>The the fully qualified member name.</value>
    100       public string FullyQualifiedMemberName {
    101         get {
    102           return new StringBuilder()
    103             .Append(MemberInfo.ReflectedType.FullName)
    104             .Append('.')
    105             .Append(MemberInfo.Name)
    106             .ToString();
    107         }
    108       }
    109 
    110       internal StorableMemberInfo(StorableAttribute attribute, MemberInfo memberInfo) {
    111         this.Attribute = attribute;
    112         this.MemberInfo = memberInfo;
    113       }
    114 
    115       /// <summary>
    116       /// Returns a <see cref="System.String"/> that represents this instance.
    117       /// </summary>
    118       /// <returns>
    119       /// A <see cref="System.String"/> that represents this instance.
    120       /// </returns>
    121       public override string ToString() {
    122         return new StringBuilder()
    123           .Append('[').Append(Attribute).Append(", ")
    124           .Append(MemberInfo).Append('}').ToString();
    125       }
    126 
    127       internal void SetDisentangledName(string name) {
    128         DisentangledName = Attribute.Name ?? name;
    129       }
    130 
    131       /// <summary>
    132       /// Gets the delcaring type of the property.
    133       /// </summary>
    134       /// <returns></returns>
    135       public Type GetPropertyDeclaringBaseType() {
    136         return ((PropertyInfo)MemberInfo).GetGetMethod(true).GetBaseDefinition().DeclaringType;
    137       }
    138     }
    139 
    140     private sealed class TypeQuery {
    141       public Type Type { get; private set; }
    142       public bool Inherited { get; private set; }
    143       public TypeQuery(Type type, bool inherited) {
    144         this.Type = type;
    145         this.Inherited = inherited;
    146       }
    147     }
    148 
    149     private sealed class MemberCache : Dictionary<TypeQuery, IEnumerable<StorableMemberInfo>> { }
    150 
    151     private static MemberCache memberCache = new MemberCache();
    152 
    153 
    154     /// <summary>
    155     /// Get all fields and properties of a class that have the
    156     /// <c>[Storable]</c> attribute set.
    157     /// </summary>
    158     /// <param name="type">The type.</param>
    159     /// <returns>An enumerable of StorableMemberInfos.</returns>
    160     public static IEnumerable<StorableMemberInfo> GetStorableMembers(Type type) {
    161       return GetStorableMembers(type, true);
    162     }
    163 
    164     /// <summary>
    165     /// Get all fields and properties of a class that have the
    166     /// <c>[Storable]</c> attribute set.
    167     /// </summary>
    168     /// <param name="type">The type.</param>
    169     /// <param name="inherited">should storable members from base classes be included</param>
    170     /// <returns>An enumerable of StorableMemberInfos</returns>
    171     public static IEnumerable<StorableMemberInfo> GetStorableMembers(Type type, bool inherited) {
    172       lock (memberCache) {
    173         var query = new TypeQuery(type, inherited);
    174         if (memberCache.ContainsKey(query))
    175           return memberCache[query];
    176         var storablesMembers = GenerateStorableMembers(type, inherited);
    177         memberCache[query] = storablesMembers;
    178         return storablesMembers;
    179       }
    180     }
    181 
    182     private static IEnumerable<StorableMemberInfo> GenerateStorableMembers(Type type, bool inherited) {
    183       var storableMembers = new List<StorableMemberInfo>();
    184       if (inherited && type.BaseType != null)
    185         storableMembers.AddRange(GenerateStorableMembers(type.BaseType, true));
    186       foreach (MemberInfo memberInfo in type.GetMembers(instanceMembers)) {
    187         foreach (StorableAttribute attribute in memberInfo.GetCustomAttributes(typeof(StorableAttribute), false)) {         
    188           storableMembers.Add(new StorableMemberInfo(attribute, memberInfo));         
    189         }
    190       }
    191       return DisentangleNameMapping(storableMembers);
    192     }
    193 
    194 
    195     /// <summary>
    196     /// Get the associated accessors for all storable memebrs.
    197     /// </summary>
    198     /// <param name="obj">The object</param>
    199     /// <returns>An enumerable of storable accessors.</returns>
    200     public static IEnumerable<DataMemberAccessor> GetStorableAccessors(object obj) {     
    201       foreach (var memberInfo in GetStorableMembers(obj.GetType()))
    202         yield return new DataMemberAccessor(
    203           memberInfo.MemberInfo,
    204           memberInfo.DisentangledName,
    205           memberInfo.Attribute.DefaultValue,
    206           obj);     
    207     }
    208 
    209     private static IEnumerable<StorableMemberInfo> DisentangleNameMapping(
    210         IEnumerable<StorableMemberInfo> storableMemberInfos) {
    211       var nameGrouping = new Dictionary<string, List<StorableMemberInfo>>();
    212       foreach (StorableMemberInfo storable in storableMemberInfos) {
    213         if (!nameGrouping.ContainsKey(storable.MemberInfo.Name))
    214           nameGrouping[storable.MemberInfo.Name] = new List<StorableMemberInfo>();
    215         nameGrouping[storable.MemberInfo.Name].Add(storable);
    216       }
    217       var memberInfos = new List<StorableMemberInfo>();
    218       foreach (var storableMemberInfoGroup in nameGrouping.Values) {       
    219         if (storableMemberInfoGroup.Count == 1) {
    220           storableMemberInfoGroup[0].SetDisentangledName(storableMemberInfoGroup[0].MemberInfo.Name);
    221           memberInfos.Add(storableMemberInfoGroup[0]);
    222         } else if (storableMemberInfoGroup[0].MemberInfo.MemberType == MemberTypes.Field) {
    223           foreach (var storableMemberInfo in storableMemberInfoGroup) {           
    224             storableMemberInfo.SetDisentangledName(storableMemberInfo.FullyQualifiedMemberName);
    225             memberInfos.Add(storableMemberInfo);
    226           }
    227         } else {         
    228           memberInfos.AddRange(MergePropertyAccessors(storableMemberInfoGroup));
    229         }
    230       }
    231       return memberInfos;
    232     }
    233    
    234     private static IEnumerable<StorableMemberInfo> MergePropertyAccessors(List<StorableMemberInfo> members) {
    235       var uniqueAccessors = new Dictionary<Type, StorableMemberInfo>();
    236       foreach (var member in members)
    237         uniqueAccessors[member.GetPropertyDeclaringBaseType()] = member;                 
    238       if (uniqueAccessors.Count == 1) {
    239         var storableMemberInfo = uniqueAccessors.Values.First();
    240         storableMemberInfo.SetDisentangledName(storableMemberInfo.MemberInfo.Name);
    241         yield return storableMemberInfo;
    242       } else {
    243         foreach (var attribute in uniqueAccessors.Values) {
    244           attribute.SetDisentangledName(attribute.FullyQualifiedMemberName);
    245           yield return attribute;
    246         }
    247       }
    248     }   
    24962  }
    25063}
  • trunk/sources/HeuristicLab.Persistence/3.3/Default/CompositeSerializers/Storable/StorableClassAttribute.cs

    r3017 r3025  
    6969    }
    7070
    71     /// <summary>
    72     /// Check that the type is either empty i.e. has no fields or properties
    73     /// or conatins proper parameterization through the storable attribute.
    74     /// </summary>
    75     /// <param name="type">The type.</param>
    76     /// <param name="recusrive">if set to <c>true</c> recusrively checks class hierarchy.</param>
    77     /// <returns>
    78     /// <c>true</c> if the specified type is a storable type; otherwise, <c>false</c>.
    79     /// </returns>
    80     public static bool IsStorableType(Type type, bool recusrive) {
    81       if (IsEmptyType(type, recusrive))
    82         return true;
    83       StorableClassAttribute attribute = type
    84         .GetCustomAttributes(typeof(StorableClassAttribute), false)
    85         .Cast<StorableClassAttribute>().SingleOrDefault();
    86       if (attribute == null)
    87         return false;
    88       if (!recusrive || type.BaseType == null)
    89         return true;
    90       else
    91         return IsStorableType(type.BaseType, true);
    92     }
    93 
    94     private const BindingFlags allDeclaredMembers =
    95       BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
    96 
    97 
    98     /// <summary>
    99     /// Determines whether the specified type has no fields or properties except
    100     /// readonly properties or constant fields.
    101     /// </summary>
    102     /// <param name="type">The type.</param>
    103     /// <param name="recursive">if set to <c>true</c> recursively check class hierarchy.</param>
    104     /// <returns>
    105     /// <c>true</c> if the specified type is empty; otherwise, <c>false</c>.
    106     /// </returns>
    107     public static bool IsEmptyType(Type type, bool recursive) {
    108       foreach (MemberInfo memberInfo in type.GetMembers(allDeclaredMembers)) {
    109         if (
    110           memberInfo.MemberType == MemberTypes.Field && IsModifiableField((FieldInfo)memberInfo) ||
    111           memberInfo.MemberType == MemberTypes.Property && IsModifiableProperty((PropertyInfo)memberInfo)) {
    112           return false;
    113         }
    114       }
    115       if (!recursive || type.BaseType == null)
    116         return true;
    117       else
    118         return IsEmptyType(type.BaseType, true);
    119     }
    120 
    121     private static bool IsModifiableField(FieldInfo fi) {
    122       return !fi.IsLiteral && !fi.IsInitOnly;
    123     }
    124 
    125     private static bool IsModifiableProperty(PropertyInfo pi) {
    126       return pi.CanWrite;
    127     }
    12871  }
    12972}
    13073
    131 
    132  
  • trunk/sources/HeuristicLab.Persistence/3.3/Default/CompositeSerializers/Storable/StorableConstructorAttribute.cs

    r3016 r3025  
    1818  /// </summary>
    1919  [AttributeUsage(AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)]
    20   public sealed class StorableConstructorAttribute : Attribute {
    21 
    22     private static readonly BindingFlags allConstructors =
    23       BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;   
    24 
    25     private static Dictionary<Type, ConstructorInfo> constructorCache =
    26       new Dictionary<Type, ConstructorInfo>();
    27 
    28 
    29     /// <summary>
    30     /// Get a designated storable constructor for a type or <c>null</c>.
    31     /// </summary>
    32     /// <param name="type">The type.</param>
    33     /// <returns>The <see cref="ConstructorInfo"/> of the storable constructor or <c>null</c></returns>
    34     public static ConstructorInfo GetStorableConstructor(Type type) {
    35       lock (constructorCache) {
    36         if (constructorCache.ContainsKey(type))
    37           return constructorCache[type];
    38         foreach (ConstructorInfo ci in type.GetConstructors(allConstructors)) {
    39           if (ci.GetCustomAttributes(typeof(StorableConstructorAttribute), false).Length > 0) {
    40             if (ci.GetParameters().Length != 1 ||
    41                 ci.GetParameters()[0].ParameterType != typeof(bool))
    42               throw new PersistenceException("StorableConstructor must have exactly one argument of type bool");
    43             constructorCache[type] = ci;
    44             return ci;
    45           }
    46         }
    47         constructorCache[type] = null;
    48         return null;
    49       }
    50     }
    51   }
     20  public sealed class StorableConstructorAttribute : Attribute {  }
    5221}
  • trunk/sources/HeuristicLab.Persistence/3.3/Default/CompositeSerializers/Storable/StorableSerializer.cs

    r3017 r3025  
    66using System.Reflection;
    77using HeuristicLab.Persistence.Auxiliary;
     8using System.Text;
    89
    910namespace HeuristicLab.Persistence.Default.CompositeSerializers.Storable {
     
    1819  public class StorableSerializer : ICompositeSerializer {
    1920
     21
     22    #region ICompositeSerializer implementation
     23
    2024    public int Priority {
    2125      get { return 200; }
     
    2428    public bool CanSerialize(Type type) {
    2529      if (!ReflectionTools.HasDefaultConstructor(type) &&
    26         StorableConstructorAttribute.GetStorableConstructor(type) == null)
     30        GetStorableConstructor(type) == null)
    2731        return false;
    28       return StorableClassAttribute.IsStorableType(type, true);
     32      return IsEmptyOrStorableType(type, true);
    2933    }
    3034
    3135    public string JustifyRejection(Type type) {
    3236      if (!ReflectionTools.HasDefaultConstructor(type) &&
    33         StorableConstructorAttribute.GetStorableConstructor(type) == null)
     37        GetStorableConstructor(type) == null)
    3438        return "no default constructor and no storable constructor";
    35       return "class or one of its base classes is not empty and has no [StorableClass] attribute";
     39      if (!IsEmptyOrStorableType(type, true))
     40        return "class is not marked with the storable class attribute";
     41      return "no reason";
    3642    }
    3743
     
    4248
    4349    public IEnumerable<Tag> Decompose(object obj) {
    44       foreach (var accessor in StorableAttribute.GetStorableAccessors(obj)) {
     50      foreach (var accessor in GetStorableAccessors(obj)) {
    4551        yield return new Tag(accessor.Name, accessor.Get());
    4652      }
     
    5157    public object CreateInstance(Type type, IEnumerable<Tag> metaInfo) {
    5258      try {
    53         ConstructorInfo constructor = StorableConstructorAttribute.GetStorableConstructor(type);
     59        ConstructorInfo constructor = GetStorableConstructor(type);
    5460        return constructor != null ? constructor.Invoke(defaultArgs) : Activator.CreateInstance(type, true);
    5561      } catch (TargetInvocationException x) {
     
    6672        memberDict.Add(iter.Current.Name, iter.Current);
    6773      }     
    68       foreach (var accessor in StorableAttribute.GetStorableAccessors(instance)) {
     74      foreach (var accessor in GetStorableAccessors(instance)) {
    6975        if (memberDict.ContainsKey(accessor.Name)) {
    7076          accessor.Set(memberDict[accessor.Name].Value);
     
    7581      StorableHookAttribute.InvokeHook(HookType.AfterDeserialization, instance);
    7682    }
     83
     84    #endregion
     85
     86    #region constances & private data types
     87
     88    private const BindingFlags ALL_CONSTRUCTORS =
     89      BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
     90
     91    private const BindingFlags DECLARED_INSTANCE_MEMBERS =
     92      BindingFlags.Instance |
     93      BindingFlags.Public |
     94      BindingFlags.NonPublic |
     95      BindingFlags.DeclaredOnly;
     96
     97    private sealed class StorableMemberInfo {
     98      public StorableAttribute Attribute { get; private set; }
     99      public MemberInfo MemberInfo { get; private set; }
     100      public string DisentangledName { get; private set; }
     101      public string FullyQualifiedMemberName {
     102        get {
     103          return new StringBuilder()
     104            .Append(MemberInfo.ReflectedType.FullName)
     105            .Append('.')
     106            .Append(MemberInfo.Name)
     107            .ToString();
     108        }
     109      }
     110      public StorableMemberInfo(StorableAttribute attribute, MemberInfo memberInfo) {
     111        this.Attribute = attribute;
     112        this.MemberInfo = memberInfo;
     113      }
     114      public override string ToString() {
     115        return new StringBuilder()
     116          .Append('[').Append(Attribute).Append(", ")
     117          .Append(MemberInfo).Append('}').ToString();
     118      }
     119      public void SetDisentangledName(string name) {
     120        DisentangledName = Attribute.Name ?? name;
     121      }
     122      public Type GetPropertyDeclaringBaseType() {
     123        return ((PropertyInfo)MemberInfo).GetGetMethod(true).GetBaseDefinition().DeclaringType;
     124      }
     125    }
     126
     127    private sealed class TypeQuery {
     128      public Type Type { get; private set; }
     129      public bool Inherited { get; private set; }
     130      public TypeQuery(Type type, bool inherited) {
     131        this.Type = type;
     132        this.Inherited = inherited;
     133      }
     134    }
     135
     136    private sealed class MemberCache : Dictionary<TypeQuery, IEnumerable<StorableMemberInfo>> { }
     137
     138    #endregion
     139
     140    #region caches
     141
     142    private MemberCache storableMemberCache = new MemberCache();
     143    private Dictionary<Type, ConstructorInfo> constructorCache =
     144      new Dictionary<Type, ConstructorInfo>();
     145
     146    #endregion
     147
     148    #region auxiliary attribute reflection tools
     149
     150    private IEnumerable<StorableMemberInfo> GetStorableMembers(Type type) {
     151      return GetStorableMembers(type, true);
     152    }
     153
     154    private IEnumerable<StorableMemberInfo> GetStorableMembers(Type type, bool inherited) {
     155      lock (storableMemberCache) {
     156        var query = new TypeQuery(type, inherited);
     157        if (storableMemberCache.ContainsKey(query))
     158          return storableMemberCache[query];
     159        var storablesMembers = GenerateStorableMembers(type, inherited);
     160        storableMemberCache[query] = storablesMembers;
     161        return storablesMembers;
     162      }
     163    }
     164
     165    private static IEnumerable<StorableMemberInfo> GenerateStorableMembers(Type type, bool inherited) {
     166      var storableMembers = new List<StorableMemberInfo>();
     167      if (inherited && type.BaseType != null)
     168        storableMembers.AddRange(GenerateStorableMembers(type.BaseType, true));
     169      foreach (MemberInfo memberInfo in type.GetMembers(DECLARED_INSTANCE_MEMBERS)) {
     170        foreach (StorableAttribute attribute in memberInfo.GetCustomAttributes(typeof(StorableAttribute), false)) {
     171          storableMembers.Add(new StorableMemberInfo(attribute, memberInfo));
     172        }
     173      }
     174      return DisentangleNameMapping(storableMembers);
     175    }
     176
     177    private IEnumerable<DataMemberAccessor> GetStorableAccessors(object obj) {
     178      foreach (var memberInfo in GetStorableMembers(obj.GetType()))
     179        yield return new DataMemberAccessor(
     180          memberInfo.MemberInfo,
     181          memberInfo.DisentangledName,
     182          memberInfo.Attribute.DefaultValue,
     183          obj);
     184    }
     185
     186    private static IEnumerable<StorableMemberInfo> DisentangleNameMapping(
     187        IEnumerable<StorableMemberInfo> storableMemberInfos) {
     188      var nameGrouping = new Dictionary<string, List<StorableMemberInfo>>();
     189      foreach (StorableMemberInfo storable in storableMemberInfos) {
     190        if (!nameGrouping.ContainsKey(storable.MemberInfo.Name))
     191          nameGrouping[storable.MemberInfo.Name] = new List<StorableMemberInfo>();
     192        nameGrouping[storable.MemberInfo.Name].Add(storable);
     193      }
     194      var memberInfos = new List<StorableMemberInfo>();
     195      foreach (var storableMemberInfoGroup in nameGrouping.Values) {
     196        if (storableMemberInfoGroup.Count == 1) {
     197          storableMemberInfoGroup[0].SetDisentangledName(storableMemberInfoGroup[0].MemberInfo.Name);
     198          memberInfos.Add(storableMemberInfoGroup[0]);
     199        } else if (storableMemberInfoGroup[0].MemberInfo.MemberType == MemberTypes.Field) {
     200          foreach (var storableMemberInfo in storableMemberInfoGroup) {
     201            storableMemberInfo.SetDisentangledName(storableMemberInfo.FullyQualifiedMemberName);
     202            memberInfos.Add(storableMemberInfo);
     203          }
     204        } else {
     205          memberInfos.AddRange(MergePropertyAccessors(storableMemberInfoGroup));
     206        }
     207      }
     208      return memberInfos;
     209    }
     210
     211    private static IEnumerable<StorableMemberInfo> MergePropertyAccessors(List<StorableMemberInfo> members) {
     212      var uniqueAccessors = new Dictionary<Type, StorableMemberInfo>();
     213      foreach (var member in members)
     214        uniqueAccessors[member.GetPropertyDeclaringBaseType()] = member;
     215      if (uniqueAccessors.Count == 1) {
     216        var storableMemberInfo = uniqueAccessors.Values.First();
     217        storableMemberInfo.SetDisentangledName(storableMemberInfo.MemberInfo.Name);
     218        yield return storableMemberInfo;
     219      } else {
     220        foreach (var attribute in uniqueAccessors.Values) {
     221          attribute.SetDisentangledName(attribute.FullyQualifiedMemberName);
     222          yield return attribute;
     223        }
     224      }
     225    }
     226
     227    private static bool IsEmptyOrStorableType(Type type, bool recusrive) {
     228      if (IsEmptyType(type, recusrive)) return true;
     229      if (!HastStorableClassAttribute(type)) return false;
     230      return !recusrive || type.BaseType == null || IsEmptyOrStorableType(type.BaseType, true);
     231    }
     232
     233    private static bool HastStorableClassAttribute(Type type) {
     234      return type.GetCustomAttributes(typeof(StorableClassAttribute), false).Length > 0;
     235    }
     236
     237    private static bool IsEmptyType(Type type, bool recursive) {
     238      foreach (MemberInfo memberInfo in type.GetMembers(DECLARED_INSTANCE_MEMBERS)) {
     239        if (IsModifiableMember(memberInfo)) return false;
     240      }
     241      return !recursive || type.BaseType == null || IsEmptyType(type.BaseType, true);
     242    }
     243
     244    private static bool IsModifiableMember(MemberInfo memberInfo) {
     245      return memberInfo.MemberType == MemberTypes.Field && IsModifiableField((FieldInfo)memberInfo) ||
     246                memberInfo.MemberType == MemberTypes.Property && IsModifiableProperty((PropertyInfo)memberInfo);
     247    }
     248
     249    private static bool IsModifiableField(FieldInfo fi) {
     250      return !fi.IsLiteral && !fi.IsInitOnly;
     251    }
     252
     253    private static bool IsModifiableProperty(PropertyInfo pi) {
     254      return pi.CanWrite;
     255    }
     256
     257    private ConstructorInfo GetStorableConstructor(Type type) {
     258      lock (constructorCache) {
     259        if (constructorCache.ContainsKey(type))
     260          return constructorCache[type];
     261        foreach (ConstructorInfo ci in type.GetConstructors(ALL_CONSTRUCTORS)) {
     262          if (ci.GetCustomAttributes(typeof(StorableConstructorAttribute), false).Length > 0) {
     263            if (ci.GetParameters().Length != 1 ||
     264                ci.GetParameters()[0].ParameterType != typeof(bool))
     265              throw new PersistenceException("StorableConstructor must have exactly one argument of type bool");
     266            constructorCache[type] = ci;
     267            return ci;
     268          }
     269        }
     270        constructorCache[type] = null;
     271        return null;
     272      }
     273    }
     274
     275    #endregion
    77276  }
    78277}
  • trunk/sources/HeuristicLab.Persistence/3.3/Tests

    • Property svn:ignore
      •  

        old new  
        11bin
        22obj
         3*.user
  • trunk/sources/HeuristicLab.Persistence/3.3/Tests/StorableAttributeTests.cs

    r3017 r3025  
    5454  }
    5555
    56   [TestClass]
     56/*  [TestClass]
    5757  public class AttributeTest {
    5858
     
    9292    }
    9393
    94   }
     94  }*/
    9595
    9696}
Note: See TracChangeset for help on using the changeset viewer.