/* Copyright (c) Microsoft Corporation All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. */ using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Diagnostics; namespace Microsoft.Research.DryadLinq { /// /// The Nullable attribute specifies if a field is nullable. The information is used by DryadLINQ /// serialization. DryadLINQ serialization by default treats all fields not nullable. /// [AttributeUsage(AttributeTargets.Field|AttributeTargets.Property|AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false)] public sealed class NullableAttribute : Attribute { private bool m_canBeNull; /// /// Initializes an instance of NullableAttribute. /// /// true iff the target of the attribute is nullable public NullableAttribute(bool canBeNull) { this.m_canBeNull = canBeNull; } /// /// Determines if the target of this attribute is nullable. /// public bool CanBeNull { get { return this.m_canBeNull; } } } [AttributeUsage(AttributeTargets.Method|AttributeTargets.Constructor, AllowMultiple = true)] internal sealed class FieldMappingAttribute : Attribute { private string m_source; private string m_destination; public FieldMappingAttribute(string src, string dest) { this.m_source = src; this.m_destination = dest; } public string Source { get { return this.m_source; } } public string Destination { get { return this.m_destination; } } } [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface, AllowMultiple = false)] internal sealed class AutoTypeInferenceAttribute : Attribute { public AutoTypeInferenceAttribute() { } } [AttributeUsage(AttributeTargets.Method|AttributeTargets.Constructor, AllowMultiple = false)] internal sealed class DistinctAttribute : Attribute { private bool m_mustBeDistinct; private string m_comparer; public DistinctAttribute() { this.m_mustBeDistinct = false; this.m_comparer = null; } public DistinctAttribute(string comparer) { this.m_mustBeDistinct = false; this.m_comparer = comparer; } public bool MustBeDistinct { get { return this.m_mustBeDistinct; } set { this.m_mustBeDistinct = value; } } public object Comparer { get { if (this.m_comparer == null) return null; object val = TypeSystem.GetFieldValue(this.m_comparer); if (val == null) { throw new DryadLinqException(DryadLinqErrorCode.DistinctAttributeComparerNotDefined, String.Format(SR.DistinctAttributeComparerNotDefined, this.m_comparer)); } return val; } } } /// /// The Resource attribute is used to specify the computation cost of a user defined /// function (UDF). IsStateful asserts that the function is stateful; IsExpensive /// asserts that the function is expensive to compute. The information is useful in /// generating better execution plan. For example, expensive associative aggregation /// functions enables the use of multiple aggregation layers. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class ResourceAttribute : Attribute { private bool m_isStateful; private bool m_isExpensive; /// /// Initializes an instance of the Resource attribute. The default value of /// IsStateful is true; the default value of IsExpensive is false. /// public ResourceAttribute() { this.m_isStateful = true; this.m_isExpensive = false; } /// /// Gets and sets the IsStateful flag. /// public bool IsStateful { get { return this.m_isStateful; } set { this.m_isStateful = value; } } /// /// Gets and sets the IsExpensive flag. /// public bool IsExpensive { get { return this.m_isExpensive; } set { this.m_isExpensive = value; } } } /// /// Indicates that a method can be decomposed to multiple methods. The argument to the /// constructor must be of type IDecomposable. The computation of the method annotated /// by this attribute can be decomposed to a sequence of calls to the Seed, Accumulate, /// RecursiveAccumulate methods and a FinalReduce. /// /// /// If a method is decomposable, a user can annotate it with this attribute. This enables /// DryadLINQ to perform a generalized "combiner" optimization. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class DecomposableAttribute : Attribute { private Type m_decompositionType; /// /// Initializes an instance of DecomposableAttribute. The argument is a type that implements /// . /// /// A type that implements IDecomposable{TSource, TAccumulate, TResult} public DecomposableAttribute(Type decompositionType) { m_decompositionType = decompositionType; } /// /// A type that implements IDecomposable{TSource, TAccumulate, TResult} where /// TSource is the element type of the input, TAccumulate is the element type /// of an intermediate dataset, and TResult is the output type of the method /// annotated by this attribute. /// public Type DecompositionType { get { return m_decompositionType; } } } /// /// Indicates that a method is an associative aggregation method. The argument to the /// constructor must be of type IAssociative. The computation of the method annotated /// by this attribute can be decomposed to a sequence of calls to the Seed and /// RecursiveAccumulate methods. /// /// /// If a method is associative, a user can annotate it with this attribute. This enables /// DryadLINQ to perform the "combiner" optimization. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class AssociativeAttribute : Attribute { private Type m_associativeType; /// /// Initializes an instance of AssociativeAttribute. The argument is a type that implements /// . /// /// /// During aggregation, the recursiveAccumulator will be used to aggregate items arising /// from the main aggregation. /// /// A type that implements IAssociative{T}, where T /// is the output type of the method annotated by this attribute. public AssociativeAttribute(Type associativeType) { this.m_associativeType = associativeType; } /// /// A type that implements IAssociative{T} where T is the output type of methods /// that are decorated with this attribute. /// public Type AssociativeType { get { return this.m_associativeType; } } } /// /// Provides a user-defined serialization method for a .NET type. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited=false)] public sealed class CustomDryadLinqSerializerAttribute : Attribute { /// /// Initializes an instance of CustomDryadLinqSerializer attribute. /// /// A type that implements IDryadLinqSerializer{T}, where T /// is the .NET type to be serialized. public CustomDryadLinqSerializerAttribute(Type serializerType) { this.SerializerType = serializerType; // We need to make sure serializerType implements IDryadLinqSerializer // However we will defer that check until DryadCodeGen.FindCustomSerializerType(), because // 1) we don't have access to here but it's available at code gen time, and // 2) because an exception coming from the attribute ctor leads to an obscure failure. } /// /// Gets and sets the type object for serialization. /// public Type SerializerType { get; private set; } } internal static class AttributeSystem { private static Dictionary attribMap = new Dictionary(); internal static void Add(LambdaExpression func, Attribute attrib) { Attribute[] attribs; if (attribMap.TryGetValue(func, out attribs)) { Attribute[] oldAttribs = attribs; attribs = new Attribute[oldAttribs.Length+1]; Array.Copy(oldAttribs, attribs, oldAttribs.Length); attribs[oldAttribs.Length] = attrib; attribMap.Remove(func); } else { attribs = new Attribute[] { attrib }; } attribMap[func] = attribs; } private static Attribute[] Get(LambdaExpression func, Type attribType) { Attribute[] attribs; attribMap.TryGetValue(func, out attribs); if (attribs != null) { ArrayList alist = new ArrayList(); foreach (var x in attribs) { if (x.GetType() == attribType) { alist.Add(x); } } attribs = (Attribute[])alist.ToArray(attribType); } return attribs; } internal static Attribute[] GetAttribs(LambdaExpression func, Type attribType) { Attribute[] attribs1 = AttributeSystem.Get(func, attribType); Attribute[] attribs2 = null; if (func.Body is MethodCallExpression) { MethodCallExpression expr = (MethodCallExpression)func.Body; attribs2 = Attribute.GetCustomAttributes(expr.Method, attribType); } else if (func.Body is NewExpression && ((NewExpression)func.Body).Constructor != null) { NewExpression expr = (NewExpression)func.Body; attribs2 = Attribute.GetCustomAttributes(expr.Constructor, attribType); } else if (func.Body is BinaryExpression) { BinaryExpression expr = (BinaryExpression)func.Body; if (expr.Method != null) { attribs2 = Attribute.GetCustomAttributes(expr.Method, attribType); } } else if (func.Body is InvocationExpression) { InvocationExpression expr = (InvocationExpression)func.Body; if (expr.Expression is LambdaExpression) { attribs2 = GetAttribs((LambdaExpression)expr.Expression, attribType); } } if (attribs1 == null) { return attribs2; } if (attribs2 == null) { return attribs1; } ArrayList alist = new ArrayList(); foreach (var x in attribs1) { alist.Add(x); } foreach (var x in attribs2) { alist.Add(x); } Attribute[] attribs = (Attribute[])alist.ToArray(attribType); return attribs; } internal static Attribute GetAttrib(Expression expr, Type attribType) { Attribute[] attribs = null; if (expr is MethodCallExpression) { attribs = Attribute.GetCustomAttributes(((MethodCallExpression)expr).Method, attribType); } else if (expr is NewExpression && ((NewExpression) expr).Constructor != null) { attribs = Attribute.GetCustomAttributes(((NewExpression)expr).Constructor, attribType); } else if (expr is LambdaExpression) { attribs = GetAttribs((LambdaExpression)expr, attribType); } if (attribs == null || attribs.Length == 0) return null; return attribs[0]; } internal static DecomposableAttribute GetDecomposableAttrib(Expression expr) { return (DecomposableAttribute)GetAttrib(expr, typeof(DecomposableAttribute)); } internal static AssociativeAttribute GetAssociativeAttrib(Expression expr) { return (AssociativeAttribute)GetAttrib(expr, typeof(AssociativeAttribute)); } internal static ResourceAttribute GetResourceAttrib(LambdaExpression func) { return (ResourceAttribute)GetAttrib(func, typeof(ResourceAttribute)); } internal static FieldMappingAttribute[] GetFieldMappingAttribs(LambdaExpression func) { Attribute[] a = GetAttribs(func, typeof(FieldMappingAttribute)); if (a == null || a.Length == 0) return null; return (FieldMappingAttribute[])a; } internal static bool DoAutoTypeInference(DryadLinqContext context, Type type) { if (!StaticConfig.AllowAutoTypeInference) return false; object[] a = type.GetCustomAttributes(typeof(AutoTypeInferenceAttribute), true); return (a.Length != 0); } internal static DistinctAttribute GetDistinctAttrib(LambdaExpression func) { return (DistinctAttribute)GetAttrib(func, typeof(DistinctAttribute)); } internal static bool FieldCanBeNull(FieldInfo finfo) { if (finfo == null || finfo.FieldType.IsValueType) return false; object[] attribs = finfo.GetCustomAttributes(typeof(NullableAttribute), true); if (attribs.Length == 0) { return StaticConfig.AllowNullFields; } return ((NullableAttribute)attribs[0]).CanBeNull; } internal static bool RecordCanBeNull(DryadLinqContext context, Type type) { if (type == null || type.IsValueType) return false; object[] attribs = type.GetCustomAttributes(typeof(NullableAttribute), true); if (attribs.Length == 0) { return StaticConfig.AllowNullRecords; } return ((NullableAttribute)attribs[0]).CanBeNull; } } }