blob: 9599c96fd8b7c8d8ef14b6896cc15bc3d02a2147 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Org.Apache.REEF.Tang.Annotations;
using Org.Apache.REEF.Tang.Exceptions;
using Org.Apache.REEF.Tang.Interface;
using Org.Apache.REEF.Tang.Types;
using Org.Apache.REEF.Tang.Util;
using Org.Apache.REEF.Utilities.Logging;
namespace Org.Apache.REEF.Tang.Implementations.ClassHierarchy
{
internal sealed class ClassHierarchyImpl : ICsClassHierarchy
{
private static readonly Logger LOGGER = Logger.GetLogger(typeof(ClassHierarchyImpl));
private readonly INode rootNode;
private readonly MonotonicTreeMap<string, INamedParameterNode> shortNames = new MonotonicTreeMap<string, INamedParameterNode>();
private readonly IList<string> assemblies;
private readonly AssemblyLoader loader = null;
private object _nodeLock = new object();
private object _mergeLock = new object();
private object _implLock = new object();
// alias is indexed by language, for each language, a mapping between alias and corresponding name kept in a Dictionary
private readonly IDictionary<string, IDictionary<string, string>> _aliasLookupTable = new Dictionary<string, IDictionary<string, string>>();
public ParameterParser Parameterparser = new ParameterParser();
public ClassHierarchyImpl(string file) : this(new string[] { file }, new Type[0])
{
}
public ClassHierarchyImpl(string[] assemblies) : this(assemblies, new Type[0])
{
}
// parameterParsers are classes that extends from IExternalConstructor
public ClassHierarchyImpl(string[] assemblies, Type[] parameterParsers)
{
this.assemblies = assemblies;
rootNode = NodeFactory.CreateRootPackageNode();
loader = new AssemblyLoader(assemblies);
foreach (Type p in parameterParsers)
{
// p must be extend from IExternalConstructor
try
{
Parameterparser.AddParser(p);
}
catch (BindException e)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Caught(e, Level.Error, LOGGER);
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new ArgumentException("Could not register parameter parsers", e), LOGGER);
}
}
foreach (var a in loader.Assemblies)
{
Type[] types;
try
{
types = a.GetTypes();
}
catch (ReflectionTypeLoadException exception)
{
LOGGER.Log(Level.Warning,
"GetTypes failed for assembly: {0} LoaderExceptions: {1}",
a,
string.Join<Exception>("; ", exception.LoaderExceptions));
continue;
}
foreach (var t in types)
{
try
{
RegisterType(t);
}
catch (FileNotFoundException exception)
{
LOGGER.Log(Level.Warning, "Could not register type: {0} Exception: {1}", t, exception);
}
}
}
}
public INode RegisterType(string assemblyQualifiedName)
{
Type type = this.loader.GetType(assemblyQualifiedName);
if (type != null)
{
return RegisterType(type);
}
return null;
}
public INode RegisterType(Type type)
{
if (ReflectionUtilities.IsAnonymousType(type))
{
// DevNote: Kinda hacky way to indicate the no-op case.
return rootNode;
}
INode n = GetAlreadyBoundNode(type);
if (n != null)
{
return n;
}
if (type.BaseType != null)
{
RegisterType(type.BaseType);
}
foreach (Type interf in type.GetInterfaces())
{
RegisterType(ReflectionUtilities.EnsureInterfaceType(interf));
}
Type enclosingClass = type.DeclaringType; // this.GetEnclosingClass(type);
if (enclosingClass != null)
{
RegisterType(enclosingClass);
}
INode node = RegisterClass(type);
foreach (Type inner in type.GetNestedTypes())
{
RegisterType(inner);
}
IClassNode classNode = node as ClassNodeImpl;
if (classNode != null)
{
foreach (IConstructorDef constructorDef in classNode.GetInjectableConstructors())
{
foreach (IConstructorArg constructorArg in constructorDef.GetArgs())
{
if (constructorArg.Gettype() == null)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new ArgumentException("not type in arg"), LOGGER);
}
RegisterType(constructorArg.Gettype()); // Gettype returns param's Type.fullname
if (constructorArg.GetNamedParameterName() != null)
{
var pn = RegisterType(constructorArg.GetNamedParameterName());
if (!(pn is INamedParameterNode))
{
string message = string.Format(CultureInfo.CurrentCulture,
"The class {0}, used in the constructor of {1}, should not be defined as a NamedParameter.",
constructorArg.GetNamedParameterName(),
constructorDef.GetClassName());
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new ArgumentException(message), LOGGER);
}
INamedParameterNode np = (INamedParameterNode)RegisterType(constructorArg.GetNamedParameterName());
try
{
if (np.IsSet() || np.IsList())
{
// throw new NotImplementedException();
}
else
{
if (!ReflectionUtilities.IsCoercable(ClassForName(constructorArg.Gettype()), ClassForName(np.GetFullArgName())))
{
var e = new ClassHierarchyException(
"Named parameter type mismatch in " + classNode.GetFullName() + ". Constructor expects a "
+ constructorArg.Gettype() + " but " + np.GetName() + " is a "
+ np.GetFullArgName());
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(e, LOGGER);
}
}
}
catch (TypeLoadException e)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Caught(e, Level.Error, LOGGER);
var ex = new ClassHierarchyException("Constructor refers to unknown class "
+ constructorArg.GetType(), e);
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
}
}
}
}
else
{
INamedParameterNode npNode = node as INamedParameterNode;
if (npNode != null)
{
RegisterType(npNode.GetFullArgName());
}
}
return node;
}
private INode RegisterClass(Type type)
{
INode node = GetAlreadyBoundNode(type);
if (node != null)
{
return node;
}
node = BuildPathToNode(type);
IClassNode classNode = node as IClassNode;
if (classNode != null)
{
Type baseType = type.BaseType;
if (baseType != null)
{
IClassNode n = (IClassNode)GetAlreadyBoundNode(baseType);
if (n != null)
{
n.PutImpl(classNode);
}
else
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new IllegalStateException("Error in finding Node for BaseType"), LOGGER);
}
}
foreach (Type interf in ReflectionUtilities.GetInterfaces(type, false))
{
IClassNode n = (IClassNode)GetAlreadyBoundNode(ReflectionUtilities.EnsureInterfaceType(interf));
if (n != null)
{
n.PutImpl(classNode);
}
else
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new IllegalStateException("Error in finding Node for Interface"), LOGGER);
}
}
}
return node;
}
public INode BuildPathToNode(Type type)
{
INode parent = GetParentNode(type);
Type argType = ReflectionUtilities.GetNamedParameterTargetOrNull(type);
if (argType == null)
{
return NodeFactory.CreateClassNode(parent, type);
}
INamedParameterNode np = NodeFactory.CreateNamedParameterNode(parent, type, argType);
if (Parameterparser.CanParse(ReflectionUtilities.GetAssemblyQualifiedName(argType)))
{
if (type.GetCustomAttribute<NamedParameterAttribute>().DefaultClass != null)
{
var e = new ClassHierarchyException("Named parameter " + ReflectionUtilities.GetAssemblyQualifiedName(type) + " defines default implementation for parsable type " + ReflectionUtilities.GetAssemblyQualifiedName(argType));
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(e, LOGGER);
}
}
if (!string.IsNullOrEmpty(np.GetAlias()))
{
IDictionary<string, string> mapping = null;
_aliasLookupTable.TryGetValue(np.GetAliasLanguage().ToString(), out mapping);
if (null == mapping)
{
mapping = new Dictionary<string, string>();
_aliasLookupTable.Add(np.GetAliasLanguage().ToString(), mapping);
}
try
{
mapping.Add(np.GetAlias(), np.GetFullName());
}
catch (Exception)
{
var e = new ApplicationException(string.Format(CultureInfo.CurrentCulture, "Duplicated alias {0} on named parameter {1}.", np.GetAlias(), np.GetFullName()));
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(e, LOGGER);
}
}
string shortName = np.GetShortName();
if (shortName != null && !shortName.Equals(string.Empty))
{
INamedParameterNode oldNode = null;
shortNames.TryGetValue(shortName, out oldNode);
if (oldNode != null)
{
if (oldNode.GetFullName().Equals(np.GetFullName()))
{
var ex = new IllegalStateException("Tried to double bind "
+ oldNode.GetFullName() + " to short name " + shortName);
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
var e = new ClassHierarchyException("Named parameters " + oldNode.GetFullName()
+ " and " + np.GetFullName() + " have the same short name: "
+ shortName);
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(e, LOGGER);
}
shortNames.Add(shortName, np);
}
return np;
}
// return Type T if type implements Name<T>, null otherwise
// e.g. [NamedParameter(typeof(System.String), "Number of seconds to sleep", "10", "sec")]
// class Seconds : Name<Int32> { }
// return Int32
// TODO add error handlings
public Type GetNamedParameterTargetOrNull(Type type)
{
var npAnnotation = type.GetCustomAttribute<NamedParameterAttribute>();
if (npAnnotation != null)
{
Type[] intfs = type.GetInterfaces();
if (intfs.Length == 1)
{
if (intfs[0].Name.Equals(GetNameOfNameInterface()))
{
Type[] args = intfs[0].GetGenericArguments();
if (args.Length == 1)
{
return args[0];
}
}
}
}
return null;
}
private INode GetAlreadyBoundNode(Type t)
{
// get outclass names including itsself
string[] outerClassNames = ReflectionUtilities.GetEnclosingClassNames(t);
INode current = rootNode;
for (int i = 0; i < outerClassNames.Length; i++)
{
current = current.Get(outerClassNames[i]);
if (current == null)
{
StringBuilder sb = new StringBuilder();
for (int j = 0; j <= i; j++)
{
sb.Append(outerClassNames[j]);
if (j != i)
{
sb.Append(".");
}
}
return null;
//// throw new NameResolutionException(t.FullName, sb.ToString());
}
}
return current;
}
// starting from the root, get child for each eclosing class excluding the type itsself
// all enclosing classes should be already in the hierarchy
// Type B2 = asm.GetType(@"Org.Apache.REEF.Tang.Examples.B+B1+B2");
// string[] pathB2 = ClassNameParser.GetEnclosingClassShortNames(B2);
// Assert.AreEqual(pathB2[0], "B");
// Assert.AreEqual(pathB2[1], "B1");
// Assert.AreEqual(pathB2[2], "B2");
// return INode for B1
private INode GetParentNode(Type type)
{
INode current = rootNode;
string[] enclosingPath = ReflectionUtilities.GetEnclosingClassNames(type);
for (int i = 0; i < enclosingPath.Length - 1; i++)
{
current = current.Get(enclosingPath[i]);
}
return current;
}
private string GetNameOfNameInterface()
{
var tn = typeof(Name<int>);
return tn.Name;
}
public INode GetNode(string fullName)
{
Type t = loader.GetType(fullName);
if (t == null)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(new NameResolutionException(fullName, fullName), LOGGER);
}
return this.GetNode(t);
}
/// <summary>
/// This method get INode from the class hierarchy by fullName.
/// If the Type for the name is not found in assemblies, it will found alias for aliasLanguage.
/// If alias is found, it will get the Type for the alias then call GetNode(Type).
/// </summary>
/// <param name="fullName"></param>
/// <param name="aliasLanguage"></param>
/// <returns></returns>
public INode GetNode(string fullName, string aliasLanguage)
{
Type t = null;
try
{
t = loader.GetType(fullName);
}
catch (ApplicationException)
{
IDictionary<string, string> mapping = null;
_aliasLookupTable.TryGetValue(aliasLanguage, out mapping);
if (mapping != null)
{
string assemblyName;
mapping.TryGetValue(fullName, out assemblyName);
if (assemblyName != null)
{
t = loader.GetType(assemblyName);
}
else
{
t = null;
}
}
else
{
t = null;
}
}
if (t == null)
{
Utilities.Diagnostics.Exceptions.Throw(new NameResolutionException(fullName, fullName), LOGGER);
}
return this.GetNode(t);
}
public INode GetNode(Type type)
{
lock (_nodeLock)
{
RegisterType(type);
return GetAlreadyBoundNode(type);
}
}
public INode GetNamespace()
{
return rootNode;
}
public bool IsImplementation(IClassNode inter, IClassNode impl)
{
lock (_implLock)
{
return impl.IsImplementationOf(inter);
}
}
public IClassHierarchy Merge(IClassHierarchy ch)
{
if (this == ch)
{
return this;
}
if (!(ch is ClassHierarchyImpl))
{
Utilities.Diagnostics.Exceptions.Throw(new NotSupportedException("Can't merge java and non-java class hierarchies yet!"), LOGGER);
}
if (this.assemblies.Count == 0)
{
return ch;
}
lock (_mergeLock)
{
ClassHierarchyImpl chi = (ClassHierarchyImpl)ch;
MonotonicHashSet<string> otherJars = new MonotonicHashSet<string>();
otherJars.AddAll(chi.assemblies);
MonotonicHashSet<string> myJars = new MonotonicHashSet<string>();
myJars.AddAll(this.assemblies);
if (myJars.ContainsAll(otherJars))
{
return this;
}
if (otherJars.ContainsAll(myJars))
{
return ch;
}
myJars.Union(otherJars);
return new ClassHierarchyImpl(myJars.ToArray());
}
}
public object Parse(INamedParameterNode np, string value)
{
IClassNode iface = null;
try
{
iface = (IClassNode)GetNode(np.GetFullArgName());
}
catch (NameResolutionException e)
{
Utilities.Diagnostics.Exceptions.Caught(e, Level.Error, LOGGER);
var ex = new IllegalStateException("Could not parse validated named parameter argument type. NamedParameter is " + np.GetFullName() + " argument type is " + np.GetFullArgName(), e);
Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
Type clazz;
string fullName;
try
{
clazz = (Type)ClassForName(iface.GetFullName());
fullName = null;
}
catch (TypeLoadException e)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Caught(e, Level.Warning, LOGGER);
clazz = null;
fullName = iface.GetFullName();
}
object result = null;
if (clazz != null)
{
result = Parameterparser.Parse(clazz, value);
}
else
{
result = Parameterparser.Parse(fullName, value);
}
if (result == null)
{
try
{
INode impl = GetNode(value);
if (impl is IClassNode)
{
if (IsImplementation(iface, (IClassNode)impl))
{
return impl;
}
}
var ex =
new ParseException(
"Name<" + iface.GetFullName() + "> " + np.GetFullName() + " cannot take non-subclass " +
impl.GetFullName());
Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
catch (NameResolutionException ec)
{
Utilities.Diagnostics.Exceptions.Caught(ec, Level.Error, LOGGER);
var ex =
new ParseException(
"Name<" + iface.GetFullName() + "> " + np.GetFullName() + " cannot take non-class " + value,
ec);
Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
}
return result;
}
public object ParseDefaultValue(INamedParameterNode name)
{
string[] vals = name.GetDefaultInstanceAsStrings();
object[] ret = new object[vals.Length];
for (int i = 0; i < vals.Length; i++)
{
string val = vals[i];
try
{
ret[i] = Parse(name, val);
}
catch (ParseException e)
{
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Caught(e, Level.Error, LOGGER);
var ex = new ClassHierarchyException("Could not parse default value " + val, e);
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(ex, LOGGER);
}
}
if (name.IsSet())
{
return new HashSet<object>(ret.ToList<object>());
}
if (name.IsList())
{
return new List<object>(ret.ToList<object>());
}
if (ret.Length == 0)
{
return null;
}
if (ret.Length == 1)
{
return ret[0];
}
var ec = new IllegalStateException("Multiple defaults for non-set named parameter! " + name.GetFullName());
Org.Apache.REEF.Utilities.Diagnostics.Exceptions.Throw(ec, LOGGER);
return null; // this line would be never reached as Throw will throw an exception
}
public Type ClassForName(string name)
{
return this.GetType(name);
}
public Type GetType(string name)
{
return this.loader.GetType(name);
}
}
}