context: /* ****************************************************************************
   *
   * Copyright (c) Microsoft Corporation. 
   *
   * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
   * copy of the license can be found in the License.html file at the root of this distribution. If 
   * you cannot locate the  Apache License, Version 2.0, please send an email to 
   * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
   * by the terms of the Apache License, Version 2.0.
   *
   * You must not remove this notice, or any other, from this software.
   *
   *
   * ***************************************************************************/
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Reflection;
  using System.Text;
  
  using Microsoft.Scripting;
  using Microsoft.Scripting.Actions;
  using Microsoft.Scripting.Runtime;
  using Microsoft.Scripting.Utils;
  
  using IronPython.Modules;
  using IronPython.Runtime.Exceptions;
  using IronPython.Runtime.Operations;
  using IronPython.Runtime.Types;
  
  namespace IronPython.Runtime {
  
      /// <summary>
      /// Importer class - used for importing modules.  Used by Ops and __builtin__
      /// Singleton living on Python engine.
      /// </summary>
      public static class Importer {
          internal const string ModuleReloadMethod = "PerformModuleReload";
  
          #region Internal API Surface
  
          /// <summary>
          /// Gateway into importing ... called from Ops.  Performs the initial import of
          /// a module and returns the module.
          /// </summary>
          public static object Import(CodeContext/*!*/ context, string fullName, PythonTuple from, int level) {
              return LightExceptions.CheckAndThrow(ImportLightThrow(context, fullName, from, level));
          }
  
          /// <summary>
          /// Gateway into importing ... called from Ops.  Performs the initial import of
          /// a module and returns the module.  This version returns light exceptions instead of throwing.
          /// </summary>
          [LightThrowing]
          internal static object ImportLightThrow(CodeContext/*!*/ context, string fullName, PythonTuple from, int level) {
              PythonContext pc = PythonContext.GetContext(context);
  
              if (level == -1) {
                  // no specific level provided, call the 4 param version so legacy code continues to work
                  var site = pc.OldImportSite;
                  return site.Target(
                      site,
                      context,
                      FindImportFunction(context),
                      fullName,
                      Builtin.globals(context),
                      context.Dict,
                      from
                  );
              } else {
  
                  // relative import or absolute import, in other words:
                  //
                  // from . import xyz
                  // or 
                  // from __future__ import absolute_import
                  var site = pc.ImportSite;
                  return site.Target(
                      site,
                      context,
                      FindImportFunction(context),
                      fullName,
                      Builtin.globals(context),
                      context.Dict,
                      from,
                      level
                  );
              }
          }
  
          /// <summary>
          /// Gateway into importing ... called from Ops.  This is called after
          /// importing the module and is used to return individual items from
          /// the module.  The outer modules dictionary is then updated with the
          /// result.
          /// </summary>
          public static object ImportFrom(CodeContext/*!*/ context, object from, string name) {
                  PythonModule scope = from as PythonModule;
                  PythonType pt;
                  NamespaceTracker nt;
                  if (scope != null) {
                      object ret;
                      if (scope.GetType() == typeof(PythonModule)) {
                          if (scope.__dict__.TryGetValue(name, out ret)) {
                              return ret;
                          }
                      } else {
                          // subclass of module, it could have overridden __getattr__ or __getattribute__
                          if (PythonOps.TryGetBoundAttr(context, scope, name, out ret)) {
                              return ret;
                          }
                      }
  
                      object path;
                      List listPath;
                      if (scope.__dict__._storage.TryGetPath(out path) && (listPath = path as List) != null) {
                          return ImportNestedModule(context, scope, new [] {name}, 0, listPath);
                      }
                  } else if ((pt = from as PythonType) != null) {
                      PythonTypeSlot pts;
                      object res;
                      if (pt.TryResolveSlot(context, name, out pts) &&
                          pts.TryGetValue(context, null, pt, out res)) {
                          return res;
                      }
                  } else if ((nt = from as NamespaceTracker) != null) {
                      object res = NamespaceTrackerOps.GetCustomMember(context, nt, name);
                      if (res != OperationFailed.Value) {
                          return res;
                      }
                  } else {
                      // This is too lax, for example it allows from module.class import member
                      object ret;
                      if (PythonOps.TryGetBoundAttr(context, from, name, out ret)) {
                          return ret;
                      }
                  }
              
              throw PythonOps.ImportError("Cannot import name {0}", name);
          }
  
          private static object ImportModuleFrom(CodeContext/*!*/ context, object from, string[] parts, int current) {
              PythonModule scope = from as PythonModule;
              if (scope != null) {
                  object path;
                  List listPath;
                  if (scope.__dict__._storage.TryGetPath(out path) && (listPath = path as List) != null) {
                      return ImportNestedModule(context, scope, parts, current, listPath);
                  }
              }
  
              NamespaceTracker ns = from as NamespaceTracker;
              if (ns != null) {
                  object val;
                  if (ns.TryGetValue(parts[current], out val)) {
                      return MemberTrackerToPython(context, val);
                  }
              }
  
              throw PythonOps.ImportError("No module named {0}", parts[current]);
          }
  
          /// <summary>
          /// Called by the __builtin__.__import__ functions (general importing) and ScriptEngine (for site.py)
          /// 
          /// level indiciates whether to perform absolute or relative imports.
          ///     -1 indicates both should be performed
          ///     0 indicates only absolute imports should be performed
          ///     Positive numbers indicate the # of parent directories to search relative to the calling module
          /// </summary>        
          public static object ImportModule(CodeContext/*!*/ context, object globals, string/*!*/ modName, bool bottom, int level) {
              if (modName.IndexOf(Path.DirectorySeparatorChar) != -1) {
                  throw PythonOps.ImportError("Import by filename is not supported.", modName);
              }
  
              string package = null;
              object attribute;
              PythonDictionary pyGlobals = globals as PythonDictionary;
              if (pyGlobals != null) {
                  if (pyGlobals._storage.TryGetPackage(out attribute)) {
                      package = attribute as string;
                      if (package == null && attribute != null) {
                          throw PythonOps.ValueError("__package__ set to non-string");
                      }
                  } else {
                      package = null;
                      if (level > 0) {
                          // explicit relative import, calculate and store __package__
                          object pathAttr, nameAttr;
                          if (pyGlobals._storage.TryGetName(out nameAttr) && nameAttr is string) {
                              if (pyGlobals._storage.TryGetPath(out pathAttr)) {
                                  pyGlobals["__package__"] = nameAttr;
                              } else {
                                  pyGlobals["__package__"] = ((string)nameAttr).rpartition(".")[0];
                              }
                          }
                      }
                  }
              }
  
              object newmod = null;
              string firstName;
              int firstDot = modName.IndexOf('.');
              if (firstDot == -1) {
                  firstName = modName;
              } else {
                  firstName = modName.Substring(0, firstDot);
              }
              string finalName = null;
  
              if (level != 0) {
                  // try a relative import
  
                  // if importing a.b.c, import "a" first and then import b.c from a
                  string name;    // name of the module we are to import in relation to the current module
                  PythonModule parentModule;
                  List path;      // path to search
                  if (TryGetNameAndPath(context, globals, firstName, level, package, out name, out path, out parentModule)) {
                      finalName = name;
                      // import relative
                      if (!TryGetExistingOrMetaPathModule(context, name, path, out newmod)) {
                          newmod = ImportFromPath(context, firstName, name, path);
                          if (newmod == null) {
                              // add an indirection entry saying this module does not exist
                              // see http://www.python.org/doc/essays/packages.html "Dummy Entries"
                              context.LanguageContext.SystemStateModules[name] = null;
                          } else if (parentModule != null) {
                              parentModule.__dict__[firstName] = newmod;
                          }
                          
                      } else if (firstDot == -1) {
                          // if we imported before having the assembly
                          // loaded and then loaded the assembly we want
                          // to make the assembly available now.
  
                          if (newmod is NamespaceTracker) {
                              context.ShowCls = true;
                          }
                      }
                  }
              }
  
              if (level <= 0) {
                  // try an absolute import
                  if (newmod == null) {
                      object parentPkg;
                      if (!String.IsNullOrEmpty(package) && !PythonContext.GetContext(context).SystemStateModules.TryGetValue(package, out parentPkg)) {
                          PythonModule warnModule = new PythonModule();
                          warnModule.__dict__["__file__"] = package;
                          warnModule.__dict__["__name__"] = package;
                          ModuleContext modContext = new ModuleContext(warnModule.__dict__, context.LanguageContext);
                          PythonOps.Warn(
                              modContext.GlobalContext,
                              PythonExceptions.RuntimeWarning,
                              "Parent module '{0}' not found while handling absolute import",
                              package);
                      }
                      newmod = ImportTopAbsolute(context, firstName);
                      finalName = firstName;
                      if (newmod == null) {
                          return null;
                      }
                  }
              }
  
              // now import the a.b.c etc.  a needs to be included here
              // because the process of importing could have modified
              // sys.modules.
              string[] parts = modName.Split('.');
              object next = newmod;
              string curName = null;
              for (int i = 0; i < parts.Length; i++) {
                  curName = i == 0 ? finalName : curName + "." + parts[i];
                  object tmpNext;
                  if (TryGetExistingModule(context, curName, out tmpNext)) {
                      next = tmpNext;
                      if (i == 0) {
                          // need to update newmod if we pulled it out of sys.modules
                          // just in case we're in bottom mode.
                          newmod = next;
                      }
                  } else if (i != 0) {
                      // child module isn't loaded yet, import it.
                      next = ImportModuleFrom(context, next, parts, i);
                  } else {
                      // top-level module doesn't exist in sys.modules, probably
                      // came from some weird meta path hook.
                      newmod = next;
                  }
              }
  
              return bottom ? next : newmod;
          }
  
          /// <summary>
          /// Interrogates the importing module for __name__ and __path__, which determine
          /// whether the imported module (whose name is 'name') is being imported as nested
          /// module (__path__ is present) or as sibling.
          /// 
          /// For sibling import, the full name of the imported module is parent.sibling
          /// For nested import, the full name of the imported module is parent.module.nested
          /// where parent.module is the mod.__name__
          /// </summary>
          /// <param name="context"></param>
          /// <param name="globals">the globals dictionary</param>
          /// <param name="name">Name of the module to be imported</param>
          /// <param name="full">Output - full name of the module being imported</param>
          /// <param name="path">Path to use to search for "full"</param>
          /// <param name="level">the import level for relaive imports</param>
          /// <param name="parentMod">the parent module</param>
          /// <param name="package">the global __package__ value</param>
          /// <returns></returns>
          private static bool TryGetNameAndPath(CodeContext/*!*/ context, object globals, string name, int level, string package, out string full, out List path, out PythonModule parentMod) {
              Debug.Assert(level != 0);   // shouldn't be here for absolute imports
  
              // Unless we can find enough information to perform relative import,
              // we are going to import the module whose name we got
              full = name;
              path = null;
              parentMod = null;
  
              // We need to get __name__ to find the name of the imported module.
              // If absent, fall back to absolute import
              object attribute;
  
              PythonDictionary pyGlobals = globals as PythonDictionary;
              if (pyGlobals == null || !pyGlobals._storage.TryGetName(out attribute)) {
                  return false;
              }
  
              // And the __name__ needs to be string
              string modName = attribute as string;
              if (modName == null) {
                  return false;
              }
  
              string pn;
              if (package == null) {
                  // If the module has __path__ (and __path__ is list), nested module is being imported
                  // otherwise, importing sibling to the importing module
                  if (pyGlobals._storage.TryGetPath(out attribute) && (path = attribute as List) != null) {
                      // found __path__, importing nested module. The actual name of the nested module
                      // is the name of the mod plus the name of the imported module
                      if (level == -1) {
                          // absolute import of some module
                          full = modName + "." + name;
                          object parentModule;
                          if (PythonContext.GetContext(context).SystemStateModules.TryGetValue(modName, out parentModule)) {
                              parentMod = parentModule as PythonModule;
                          }
                      } else if (String.IsNullOrEmpty(name)) {
                          // relative import of ancestor
                          full = (StringOps.rsplit(modName, ".", level - 1)[0] as string);
                      } else {
                          // relative import of some ancestors child
                          string parentName = (StringOps.rsplit(modName, ".", level - 1)[0] as string);
                          full = parentName + "." + name;
                          object parentModule;
                          if (PythonContext.GetContext(context).SystemStateModules.TryGetValue(parentName, out parentModule)) {
                              parentMod = parentModule as PythonModule;
                          }
                      }
                      return true;
                  }
  
                  // importing sibling. The name of the imported module replaces
                  // the last element in the importing module name
                  int lastDot = modName.LastIndexOf('.');
                  if (lastDot == -1) {
                      // name doesn't include dot, only absolute import possible
                      if (level > 0) {
                          throw PythonOps.ValueError("Attempted relative import in non-package");
                      }
  
                      return false;
                  }
  
                  // need to remove more than one name
                  int tmpLevel = level;
                  while (tmpLevel > 1 && lastDot != -1) {
                      lastDot = modName.LastIndexOf('.', lastDot - 1);
                      tmpLevel--;
                  }
  
                  if (lastDot == -1) {
                      pn = modName;
                  } else {
                      pn = modName.Substring(0, lastDot);
                  }
              } else {
                  // __package__ doesn't include module name, so level is - 1.
                  pn = GetParentPackageName(level - 1, package.Split('.'));
              }
  
              path = GetParentPathAndModule(context, pn, out parentMod);
              if (path != null) {
                  if (String.IsNullOrEmpty(name)) {
                      full = pn;
                  } else {
                      full = pn + "." + name;
                  }
                  return true;
              }
  
              if (level > 0) {
                  throw PythonOps.SystemError("Parent module '{0}' not loaded, cannot perform relative import", pn);
              }
              // not enough information - absolute import
              return false;
          }
  
          private static string GetParentPackageName(int level, string[] names) {
              StringBuilder parentName = new StringBuilder(names[0]);
  
              if (level < 0) level = 1;
              for (int i = 1; i < names.Length - level; i++) {
                  parentName.Append('.');
                  parentName.Append(names[i]);
              }
              return parentName.ToString();
          }
  
          public static object ReloadModule(CodeContext/*!*/ context, PythonModule/*!*/ module) {
              return ReloadModule(context, module, null);
          }
  
          internal static object ReloadModule(CodeContext/*!*/ context, PythonModule/*!*/ module, PythonFile file) {
  
gt:             PythonContext pc = PythonContext.GetContext(context);