package bilab;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.swt.widgets.Composite;
import org.osgi.framework.Bundle;

import scigol.Debug;
import scigol.TypeSpec; // interpreter dependence


// Manages resources 
//  (for example, resources associated with bilab types, such files
//    that types can import and/or export from/to)
public class ResourceManager
{
  public ResourceManager(Plugin plugin, String resourceBundleName)
  {
    this.plugin = plugin; 
    this.resourceBundleName = resourceBundleName;
    
    viewerRegistry = new LinkedList<RegistryPair>();
    resTypeRegistry = new HashMap<String, ResourceTypeInfo>();
    resExtRegistry = new HashMap<String, java.util.List<String> >();
    resImporterRegistry = new java.util.HashMap<String, java.util.List<TypeSpec> >();
    resExporterRegistry = new java.util.HashMap<String, java.util.List<TypeSpec> >();
  }
  
  
  
  public ResourceBundle getPluginResourceBundle()
  {
    if (resourceBundle==null)
      resourceBundle = ResourceBundle.getBundle(resourceBundleName);
    return resourceBundle;
  }
  
  
 
  // Returns the string from the plugin's resource bundle, or 'key' if not
  // found.
  public String getStringResource(String key)
  {
    try {
      return (getPluginResourceBundle() != null) ? getPluginResourceBundle().getString(key) : key;
    } catch (MissingResourceException e) {
      return key;
    }
  }

  
  // returns the filesystem path of the Plugin root directory
  //  (useful for accessing external distribution files e.g. .exe's etc.)
  public String getPluginFilesystemRoot() throws IOException
  {
    // this is kinda kludgey - get a handle on the libs dir and then delete libs/ of the end
    //   off the path
    
    Bundle bundle = plugin.getBundle();
    URL libsURL = Platform.find(bundle, new Path("libs"));
    String libsPath = Platform.resolve(libsURL).getFile();
    
    String rootPath = libsPath+"/../..";
    File rootAbsPath = new File(rootPath);
    
    return rootAbsPath.getCanonicalPath();
  }

  

  
  // a resource name is either a relative path from and of the resource directories, an absolute file-system path
  //  or a URL (including file: URLs)
  public URL findResource(String resourceName) throws IOException
  {
    // if resource name looks like an absolute path or a URL, leave it as it
    if (resourceName.startsWith("/")) return Platform.resolve(new URL("file:"+resourceName));
    if (resourceName.startsWith("file:") || resourceName.startsWith("http:")) return Platform.resolve(new URL(resourceName));
    
    Bundle bundle = plugin.getBundle();
    Path path = new Path(resourceName);
    URL resURL = Platform.find(bundle, path);
    if (resURL == null) {
      path = new Path("resources/"+resourceName);
      resURL = Platform.find(bundle, path);
      if (resURL == null) {
        path = new Path("libs/"+resourceName);
        resURL = Platform.find(bundle, path);
        if (resURL == null) {
          path = new Path("../"+resourceName);
          resURL = Platform.find(bundle, path);
          if (resURL == null)
            throw new IOException("not found");
        }
      }
    }
    return Platform.resolve(resURL);
  }

  
  public String resourceNameToNativeFileName(String resourceName)
  {
    // if resource name looks like an absolute path or a URL, resolve it
    //  otherwise treat is as relative to the default resources directory
    try {
      if (resourceName.startsWith("/")) 
        resourceName = Platform.resolve(new URL("file:"+resourceName)).getFile();
      else if (resourceName.startsWith("file:") || resourceName.startsWith("http:")) 
        resourceName = Platform.resolve(new URL(resourceName)).getFile();
      else
        resourceName = getPluginFilesystemRoot()+"/resources/"+resourceName;
    } catch (IOException e) {}
    
    return Util.toNativePathSeparator(resourceName);
    
  }
  
  
  
  public java.util.List<String> getResourceTypesWithExtension(String extension)
  {
    if (resExtRegistry.containsKey(extension.toLowerCase()))
      return resExtRegistry.get(extension.toLowerCase());
    return new LinkedList<String>(); // empty
  }
  
  public java.util.List<String> getResourceTypesWithExtensions(java.util.List<String> extensions)
  {
    java.util.List<String> types = new LinkedList<String>();
    for(String ext : extensions) {
      java.util.List<String> typesForExt = getResourceTypesWithExtension(ext.toLowerCase());
      for(String type : typesForExt) types.add(type);
    }
    return types;
  }
  
  
  public static String resourceURLToFilename(URL url)
  {
    if (!url.getProtocol().equals("file"))
      throw new BilabException("URL doesn't refer to a local file: "+url);

    return url.getFile();
  }
  
  public static String filenameToResourceName(String nativeFilename)
  {
    String resname = nativeFilename;
    if (platformOS().equals("win") && (nativeFilename.length()>1) && (nativeFilename.charAt(1)==':'))
      resname = "/"+resname;
    resname = Util.toForwardPathSeparator(resname);
    return resname;
  }
  
  
  
  
  public InputStream findResourceStream(String resourceName) throws IOException
  {
    // try simplest way first
    InputStream is = BilabPlugin.class.getResourceAsStream(resourceName);
    
    if (is!=null) return is;
    
    // failing that, do a more exhaustive search
    URL url = findResource(resourceName);
    return url.openStream();
  }
  
  
   
  public OutputStream createResourceStream(String resourceName) throws IOException
  {
    // if resourceName looks like an absolute path, leave it alone.  Otheriwse, prepend
    //  the workspace area
    
    String pathName = resourceName;
    
    if (resourceName.startsWith("file:") || resourceName.startsWith("http:")) {
      URL resURL = new URL(resourceName);
      if (!resURL.getProtocol().equals("file"))
        throw new BilabException("Resources can only be created using local file: URLs or filesystem paths");
      pathName = resURL.getFile();
    }
    
    if (!pathName.startsWith("/")) { // not absolute, so prepend workspace root (or something!?)
      
      Notify.unimplemented(this,"relative paths for resource creation; path:"+pathName);
      
      // should probably create stuff using the IFile eclipse APIs for resources within the workspace/project
      return null;
    }
    else {
      // possibly (probably?) outside the project/workspace, so use the Java IO API for create the file
      String nativePathName = Util.toNativePathSeparator(pathName);
      
      File file = new File(nativePathName);
      FileOutputStream fileOutputStream = new FileOutputStream(file);
      
      return fileOutputStream;
    }
    
  }
  
  
  public void deleteResource(String resourceName) throws IOException
  {
    // if resourceName looks like an absolute path, leave it alone.  Otheriwse, prepend
    //  the workspace area
    
    String pathName = resourceName;
    
    if (resourceName.startsWith("file:") || resourceName.startsWith("http:")) {
      URL resURL = new URL(resourceName);
      if (!resURL.getProtocol().equals("file"))
        throw new BilabException("Resources can only be deleted using local file: URLs or filesystem paths");
      pathName = resURL.getFile();
    }
    
    if (!pathName.startsWith("/")) { // not absolute, so prepend workspace root (or something!?)
      
      Debug.Unimplemented("relative paths for resource deletion");
      
      // should probably be using the IFile eclipse APIs for resources within the workspace/project
    }
    else {
      // possibly (probably?) outside the project/workspace, so use the Java IO API for create the file
      String nativePathName = Util.toNativePathSeparator(pathName);
      
      File file = new File(nativePathName);
      file.delete();
    }    
  }
  
  
  
  
  public void registerViewer(TypeSpec valueType, TypeSpec viewerType)
  {
    Debug.Assert(viewerType.isClass());
    viewerRegistry.add(new RegistryPair(valueType, viewerType));
  }
  
  
  
  public boolean existsRegisteredViewer(TypeSpec valueType)
  {
    // look for matching valueType
    for(RegistryPair viewerPair : viewerRegistry) 
      if (valueType.isA(viewerPair.valueType)) return true;
    return false;  
  }
  
  
  public ViewerBase instantiateViewer(TypeSpec valueType, Composite parent)
  {
    // look for matching valueType and instantiate the corresponding viewer type by fetching the single arg
    //  constructor via reflection
    for(RegistryPair viewerPair : viewerRegistry) {
      if (valueType.isA(viewerPair.valueType)) {
        TypeSpec viewerType = viewerPair.viewerType;

        if (!viewerType.getClassInfo().isExternal()) {
          Debug.Unimplemented("viewers implemented in scigol");
        }
        else {
          java.lang.Class viewerClass = (java.lang.Class)viewerType.getClassInfo().getSysType();
        
          // find the required constructor that takes a single Composite
          java.lang.Class[] argTypes = new java.lang.Class[1];
          argTypes[0] = Composite.class;
          java.lang.reflect.Constructor ctor = null;
          try {
            ctor = viewerClass.getConstructor(argTypes);
          } catch (NoSuchMethodException e) {
            Debug.Assert(false, "viewer type doesn't implement required constructor with Composite argument");
          }
          
          Object[] args = new Object[1];
          args[0] = parent;
          try {
            ViewerBase instance = (ViewerBase)ctor.newInstance(args);
            return instance;
          } catch (Exception e) { 
            Notify.devWarning(BilabPlugin.class,"failed to instantiate new viewer of type:"+viewerType+" - "+e);
          } 
          return null; // instantiation failed, just pretend no viewer for the type was available
        }
      }
    }

    return null;
  }
  
  
  public boolean existsViewer(TypeSpec valueType)
  {
    // look for matching valueType and instantiate the corresponding viewer type by fetching the single arg
    //  constructor via reflection
    for(RegistryPair viewerPair : viewerRegistry) {
      if (valueType.isA(viewerPair.valueType)) 
        return true;
    }
    return false;
  }
  
  
  
  
  public void registerResourceType(String resourceTypeName, String description, String extension)
  {
    // first enter in into the map keyed on type name
    ResourceTypeInfo rtinfo = null;
    String[] exts = new String[1];
    exts[0] = extension.toLowerCase();
    if (extension == null) exts = new String[0];
    if (!existsResourceType(resourceTypeName)) { // new type
      
      Notify.logInfo(BilabPlugin.class,"  registering "+resourceTypeName+": "+description);

      rtinfo = new ResourceTypeInfo(resourceTypeName, description, exts);
      resTypeRegistry.put(resourceTypeName, rtinfo);
    }
    else {
      // just add to the list of extensions (description is ignored)
      rtinfo = resTypeRegistry.get(resourceTypeName);
      
      // copy the existing extension into a new array and add ant new ones also
      java.util.List<String> newexts = new LinkedList<String>();
      for(String ext : rtinfo.extensions) newexts.add(ext);
      for(String ext : exts) 
        if (!newexts.contains(ext)) newexts.add(ext);
      rtinfo.extensions = newexts.toArray(new String[0]); // replace old array with new one
    }
    
    // now enter into the map keyed by extension
    if (!resExtRegistry.containsKey(extension.toLowerCase())) {
      java.util.List<String> l = new LinkedList<String>();
      l.add(resourceTypeName);
      resExtRegistry.put(extension.toLowerCase(), l);
    }
    else {
      // just add the type to the existing list for this extension
      java.util.List<String> l = resExtRegistry.get(extension.toLowerCase());
      if (!l.contains(resourceTypeName))
        l.add(resourceTypeName);
    }
  }
  
  
 
    
  // convenience
  public void registerResourceType(String resourceTypeName, String description, String ext1, String ext2)
  {
    registerResourceType(resourceTypeName, description, ext1);
    registerResourceType(resourceTypeName, description, ext2);
  }

  public void registerResourceType(String resourceTypeName, String description, String ext1, String ext2, String ext3)
  {
    registerResourceType(resourceTypeName, description, ext1);
    registerResourceType(resourceTypeName, description, ext2);
    registerResourceType(resourceTypeName, description, ext3);
  }

  public void registerResourceType(String resourceTypeName, String description, String ext1, String ext2, String ext3, String ext4)
  {
    registerResourceType(resourceTypeName, description, ext1);
    registerResourceType(resourceTypeName, description, ext2);
    registerResourceType(resourceTypeName, description, ext3);
    registerResourceType(resourceTypeName, description, ext4);
  }

  
  
  
  public void registerResourceIOProvider(TypeSpec provider)
  {
    Debug.Assert((provider!=null) && provider.isClass());
    if (!provider.getClassInfo().isExternal()) {
      Notify.devError(BilabPlugin.class,"scigol IResourceIOProviders unsupported");
      return;
    }
    
    Debug.Assert( provider.isA(new TypeSpec(IResourceIOProvider.class)) );
    
    // get the list of supported resource types by reflectively finding the 
    //  getSupported[Import|Export]ResourceTypes() methods and calling them
    java.lang.Class sysClass = null;
    try {
      sysClass = (java.lang.Class)provider.getClassInfo().getSysType();
      Method supportedImportMethod = sysClass.getMethod("getSupportedImportResourceTypes", new java.lang.Class[0]);
      Method supportedExportMethod = sysClass.getMethod("getSupportedExportResourceTypes", new java.lang.Class[0]);
    
      // First import resource types
      java.util.List<String> importTypesSupported = (java.util.List<String>)supportedImportMethod.invoke(null, new Object[0]);
      for(String resourceTypeName : importTypesSupported) {
        
        if (!existsResourceType(resourceTypeName))
          Notify.devError(BilabPlugin.class,"IResourceIOProvider class "+sysClass+" imports resource type '"+resourceTypeName+"' that hasn't been registered");
        
        if (!resImporterRegistry.containsKey(resourceTypeName)) {
          java.util.List<TypeSpec> importers = new LinkedList<TypeSpec>();
          importers.add(provider);
          resImporterRegistry.put(resourceTypeName, importers);  
        }
        else { // add to existing list of importers for this type
          java.util.List<TypeSpec> importers = resImporterRegistry.get(resourceTypeName);
          if (!importers.contains(provider))
            importers.add(provider);
        }
      }

      // Next, export resource types
      java.util.List<String> exportTypesSupported = (java.util.List<String>)supportedExportMethod.invoke(null, new Object[0]);
      for(String resourceTypeName : exportTypesSupported) {
        
        if (!existsResourceType(resourceTypeName))
          Notify.devError(BilabPlugin.class,"IResourceIOProvider class "+sysClass+" exports resource type '"+resourceTypeName+"' that hasn't been registered");
        
        if (!resExporterRegistry.containsKey(resourceTypeName)) {
          java.util.List<TypeSpec> exporters = new LinkedList<TypeSpec>();
          exporters.add(provider);
          resExporterRegistry.put(resourceTypeName, exporters);  
        }
        else { // add to existing list of exporters for this type
          java.util.List<TypeSpec> exporters = resExporterRegistry.get(resourceTypeName);
          if (!exporters.contains(provider))
            exporters.add(provider);
        }
      }

    } catch (NoSuchMethodException e) {
      Notify.devError(BilabPlugin.class,"class "+sysClass+" marked with interace IResourceIOProvider doesn't have required getSupported[Import|Export]ResourceTypes() method.");
    } catch (Exception e) {
      Notify.devWarning(BilabPlugin.class,"error registering IResourceIOProvider:"+e);
    }
  }
  
  
  
  public boolean existsResourceType(String resourceTypeName)
  {
    return resTypeRegistry.containsKey(resourceTypeName);
  }

  
  
  
  public java.util.List<TypeSpec> getResourceImportersForType(String resourceTypeName)
  {
    if (resImporterRegistry.containsKey(resourceTypeName)) {
      return resImporterRegistry.get(resourceTypeName);
    }
    return new LinkedList<TypeSpec>(); // empty
  }

  public java.util.List<TypeSpec> getResourceExportersForType(String resourceTypeName)
  {
    if (resExporterRegistry.containsKey(resourceTypeName)) {
      return resExporterRegistry.get(resourceTypeName);
    }
    return new LinkedList<TypeSpec>(); // empty
  }

  
  public String getResourceTypeDefaultExtension(String resourceType)
  {
    ResourceTypeInfo rinfo = resTypeRegistry.get(resourceType);
    if (rinfo==null) return "dat";
    if (rinfo.extensions.length > 0)
      return rinfo.extensions[0];
    return "dat";
  }
  
  
  public Object instantiateObjectFromResource(TypeSpec importerType, String resourceName, String resourceType)
  {
    // if the resourceType is "unknown" (or "") see if we can *uniquely* deduce it from the resourceName
    //  (currently only by looking at the extension)
    if (resourceType.equals("unknown") || resourceType.length()==0) {
      String ext = Util.extension(resourceName);
      if (ext.length() != 0) {
        java.util.List<String> types = getResourceTypesWithExtension( ext );
        if (types.size() == 1) { // yes, extension maps uniquely to a type
          resourceType = types.get(0);
        }
      }
    }
    
    
    Object imported = null;
    java.lang.Class sysClass = null;
    try {
      // use reflection to find & invoke the factory method
      sysClass = (java.lang.Class)importerType.getClassInfo().getSysType();
      java.lang.Class[] argTypes = new java.lang.Class[3];
      argTypes[0] = ResourceManager.class;
      argTypes[1] = argTypes[2] = String.class;
      Method method = sysClass.getMethod("importResource", argTypes);
            
      // invoke it
      Object[] args = new Object[3];
      args[0] = this;
      args[1] = resourceName;
      args[2] = resourceType;

      imported = method.invoke(null, args);

      return imported;
      
    } catch (NoSuchMethodException e) {
      Notify.devError(BilabPlugin.class,"class "+sysClass+" marked with interace IResourceIOProvider doesn't have the required importResource(String,String) method.");
    } catch (InvocationTargetException e) {
      throw new BilabException("error importing resource:"+e.getCause().getMessage(),e.getCause()); 
    } catch (Exception e) {
      Notify.devInfo(BilabPlugin.class,"unable to instantiate object from resource via type "+sysClass+" - "+e);
    }
    return null;
  }
  
  
  
  // instantiate a new object from the given resource, or null if unable to locate a suitable importer
  public Object instantiateObjectFromResource(String resourceName, String resourceType)
  {
    // first, get all importers for the given type (if type is "unknown" or "", try to deduce it from the extension)
    java.util.List<String> resTypes = new LinkedList<String>();
    
    if (resourceType.equals("unknown") || resourceType.length()==0) {
      String ext = Util.extension(resourceName);
      if (ext.length() != 0) {
        java.util.List<String> extTypes = getResourceTypesWithExtension( ext ); // possibly more that one type with this extension
        for(String type : extTypes) resTypes.add(type);
      }
    }
    else
      resTypes.add(resourceType);
    
    
    // now, for each resource type (if any) we get the list of possible importer types and try
    //  each in turn until we succefully import the resource (and return null if no importers succeed)
    Object imported = null;
    
    for(String resType : resTypes) {
      
      java.util.List<TypeSpec> importerTypes = getResourceImportersForType(resType);
      
      for(TypeSpec importerType : importerTypes) {

        try {
          //scigol.Debug.WL("ResourceManager: trying IOProvider "+importerType+" to import type "+resType+" for resource "+resourceName);

          imported = instantiateObjectFromResource(importerType, resourceName, resType); // instantiate 
          if (imported != null) return imported;
          
        } catch (BilabException e) {
          throw e;
        } catch (Exception e) {
          Notify.devWarning(BilabPlugin.class,"failed to instantiate object for resource of type '"+resType+"' via type '"+importerType+"' - "+e);
        }
        
      }
      
      // also try some native types that aren't registerd as importers - like String for type TEXT
      if (resType.equals("TEXT")) {
        InputStream istream = null;
        try {
          istream = findResourceStream(resourceName);
          BufferedReader reader = new BufferedReader(new InputStreamReader(istream));
          StringBuilder sb = new StringBuilder();
          String line = null;
          do {
            line = reader.readLine();
            if (line!=null)
              sb.append(line+"\n");
          } while (line!=null);
          
          istream.close();
          
          return sb.toString(); // return string
          
        } catch (IOException e) {
        } finally {
          try {
            if (istream != null) istream.close();
          } catch (IOException e) {}
        }
        
      }
      
    }

    return null; // exhausted options, failed to import resource
  }
  
  
  
  
  
  public java.util.List<TypeSpec> getResourceImportersForTypes(java.util.List<String> resourceTypeNames)
  {
    // get the list of importers for each type name and put them the importers list for return
    LinkedList<TypeSpec> importers = new LinkedList<TypeSpec>();
    for(String resourceTypeName : resourceTypeNames) {
      java.util.List<TypeSpec> imps = getResourceImportersForType(resourceTypeName);
      for(TypeSpec importer : imps) importers.add(importer);
    }
Debug.WL("importer types for resource types "+resourceTypeNames+" - "+importers);    
    return importers;
  }
  
  
  
  public void exportObjectToResource(Object value, String resourceType, String resourceName)
  {
    java.util.List<TypeSpec> exporterTypes = getResourceExportersForType(resourceType);
    if (exporterTypes.size()==0)
      throw new BilabException("no registered exporters for resource type '"+resourceType+"'");
    
    if (!(exporterTypes.get(0).getSysType() instanceof java.lang.Class))
      Notify.devError(this,"Scigol IResourceIOProviers unsupported");
    
    java.lang.Class sysClass = (java.lang.Class)exporterTypes.get(0).getSysType();

    // Invoke exportResource() method
    // First try to find it by matching exportResource(ResourceManager, <value's type>, String, String)
    //  where <value's type> is value.getClass() and all super classes
    Method method = null;
    boolean done=false;
    java.lang.Class vtype = value.getClass();
    while ((method == null) && !done) {
    
      java.lang.Class[] argTypes = new java.lang.Class[4];
      argTypes[0] = ResourceManager.class;
      argTypes[1] = vtype;
      argTypes[2] = argTypes[3] = String.class;
      try {
        method = sysClass.getMethod("exportResource", argTypes);
      } catch (NoSuchMethodException e) {}
      
      if (method == null) {
        if (vtype.getSuperclass() == null)
          done = true;
        else
          vtype = vtype.getSuperclass();
      }
    }

    if (method==null)
      Notify.devError(this,"class '"+sysClass+"' marked as implementing interface 'IResourceIOProvider' has no exportResource(...) method as required");

    // invoke it
    try {
      Object[] args = new Object[4];
      args[0] = this;
      args[1] = value;
      args[2] = resourceName;
      args[3] = resourceType;
      
      method.invoke(null, args);
      
    } catch (BilabException e) {
      
    } catch (IllegalAccessException e) {
      Notify.devError(this,"illegal access exception invoking "+sysClass+".exportResource(...):"+e.getCause().getMessage());
    } catch (InvocationTargetException e) {
      throw new BilabException("error exporting resource:"+e.getCause().getMessage(),e.getCause());
    }
    
  }
  
  
  
  
  
  //!!! tmp
  public scigol.Map resourceTypesMap()
  {
    scigol.Map map = new scigol.Map();
    for(String key : resTypeRegistry.keySet()) {
      ResourceTypeInfo rtinfo = resTypeRegistry.get(key);
      String exts = "; exts:";
      for(String ext : rtinfo.extensions) exts += " "+ext;
      map.put(key,rtinfo.description+exts);
    }
    return map;
  }

  
  
  // generates a unique resource name in the temporary area
  public String uniqueTemporaryResourceName(String resourceType)
  {
    String extension = getResourceTypeDefaultExtension(resourceType);
    String name = "temp";
    
    uniqueIDCount++;
    
    return "/C:/TEMP/"+name+uniqueIDCount+"."+extension; //!!! FIXME
  }
  
  private int uniqueIDCount = 0;

  
  // return platform OS (win or unix)
  public static String platformOS()
  {
    // just guess based on path seperator for now
    if (File.separatorChar == '/')
      return "unix";
    return "win";
  }

  
  
  public static String platformUSerHomeDir()
  {
    String absHomePath = Util.toForwardPathSeparator( System.getProperty("user.home") );
    if (absHomePath.charAt(0)!='/') absHomePath = "/"+absHomePath; // add '/' to 'C:/.' type paths
    absHomePath = absHomePath.replace(":","");
    return absHomePath;
  }

  
  
  
  // plugin for which we're managing resources
  private Plugin plugin;
  private String resourceBundleName;
  private ResourceBundle resourceBundle = null;

  
  // registry of resource types
  final static class ResourceTypeInfo {

    public ResourceTypeInfo(String resourceType, String desc, String[] exts)
    { 
      type = resourceType; description = desc; extensions = exts; 
    }

    public String type;        // e.g. PDB
    public String description; // e.g. Protein Data Bank file
    public String[] extensions;// e.g. { "pdb" }
  }

  private java.util.Map<String, ResourceTypeInfo> resTypeRegistry;       // mapping from resource type name to info
  private java.util.Map<String, java.util.List<String> > resExtRegistry; // mapping from extension to types
  
  
  // registry of resource IO providers
  private java.util.Map<String, java.util.List<TypeSpec> > resImporterRegistry; // map from resource type name to list of types that implement IResourceIOProvider
  private java.util.Map<String, java.util.List<TypeSpec> > resExporterRegistry; // map from resource type name to list of types that implement IResourceIOProvider

  // registry of Viewer for specific value types
  final static class RegistryPair {
    public RegistryPair(TypeSpec valType, TypeSpec viewType) { valueType=valType; viewerType=viewType; }
    public TypeSpec valueType;
    public TypeSpec viewerType;
  }
  
  private LinkedList<RegistryPair> viewerRegistry;

  

  
  protected static final String exeSuffix = platformOS().equals("win")?".exe":"";

  
}
