/*
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;
}
}
}