/**
 * Copyright (c) 2002-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.ecore.resource.impl;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.archive.ArchiveURLConnection;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.Resource;


/**
 * A highly functional and extensible URI converter implementation.
 * <p>
 * This implementation provides seamless transparent Eclipse integration
 * by supporting the <code>platform:/resource</code> mechanism both inside of Eclipse and outside of Eclipse.
 * Furthermore, although the implementation imports 
 * both {@link org.eclipse.core.runtime} and {@link org.eclipse.core.resources},
 * and hence requires the Eclipse libraries at development time,
 * the implementation does <b>not</b> require them at runtime. 
 * Clients of this implementation must be cautious if they wish to maintain this platform neutral behaviour.
 * </p>
 * @deprecated since 2.4; use {@link ExtensibleURIConverterImpl} instead.
 */
@Deprecated
public class URIConverterImpl extends ExtensibleURIConverterImpl
{
  /** 
   * An output stream that transfers its contents to an {@link IFile} upon closing.
   * @deprecated @since 2.4; use {@link PlatformResourceURIHandlerImpl.PlatformResourceOutputStream} directly.
   */
  @Deprecated
  public static class PlatformResourceOutputStream extends PlatformResourceURIHandlerImpl.PlatformResourceOutputStream
  {
    /**
     * @deprecated since 2.4; use {@link PlatformResourceURIHandlerImpl.PlatformResourceOutputStream#PlatformResourceOutputStream(IFile, boolean, boolean, IProgressMonitor)} instead.
     */
    @Deprecated
    public PlatformResourceOutputStream(IFile file,  boolean force, boolean keepHistory, IProgressMonitor progressMonitor)
    {
      super(file, force, keepHistory, progressMonitor);
    }
  }

  /**
   * Isolated Eclipse workbench utilities.
   * @deprecated since 2.4; use {@link PlatformResourceURIHandlerImpl.WorkbenchHelper} directly.
   */
  @Deprecated
  public static class WorkbenchHelper extends PlatformResourceURIHandlerImpl.WorkbenchHelper
  {
    /**
     * Creates an output stream for the given {@link IFile} path.
     * <p>
     * This implementation uses a {@link PlatformResourceURIHandlerImpl.PlatformResourceOutputStream}.
     * </p>
     * @return an open output stream.
     * @exception IOException if there is a problem obtaining an open output stream.
     * @see IWorkspaceRoot#getFile(org.eclipse.core.runtime.IPath)
     * @see PlatformResourceURIHandlerImpl.PlatformResourceOutputStream
     * @see IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)
     * @deprecated since 2.4; use {@link PlatformResourceURIHandlerImpl.WorkbenchHelper#createPlatformResourceOutputStream(String, Map)} directly.
     */
    @Deprecated
    public static OutputStream createPlatformResourceOutputStream(String platformResourcePath) throws IOException
    {
      return PlatformResourceURIHandlerImpl.WorkbenchHelper.createPlatformResourceOutputStream(platformResourcePath, null);
    }

    /**
     * Creates an input stream for the given {@link IFile} path.
     * <p>
     * This implementation uses {@link IFile#getContents() IFile.getContents}.
     * </p>
     * @return an open input stream.
     * @see IWorkspaceRoot#getFile(org.eclipse.core.runtime.IPath)
     * @see IFile#getContents()
     * @exception IOException if there is a problem obtaining an open input stream.
     * @deprecated since 2.4; use {@link PlatformResourceURIHandlerImpl.WorkbenchHelper#createPlatformResourceInputStream(String, Map)} directly.
     */
    @Deprecated
    public static InputStream createPlatformResourceInputStream(String platformResourcePath) throws IOException
    {
      return PlatformResourceURIHandlerImpl.WorkbenchHelper.createPlatformResourceInputStream(platformResourcePath, null);
    }
  }

  /**
   * The cached Eclipse workspace.
   * @deprecated
   */
  @Deprecated
  protected static IWorkspaceRoot workspaceRoot = EcorePlugin.getWorkspaceRoot();

  private static Map<String, Boolean> efsScheme;
  private static final Method EFS_GET_FILE_SYSTEM_METHOD;
  private static final Method EFS_GET_STORE_METHOD;
  private static final Method FILE_STORE_OPEN_INPUT_STREAM_METHOD;
  private static final Method FILE_STORE_OPEN_OUTPUT_STREAM_METHOD;
  static
  {
    Method efsGetStoreMethod = null;
    Method efsGetFileSystemMethod = null;
    Method fileStoreOpenInputStreamMethod = null;
    Method fileStoreOpenOutputStreamMethod = null;
    try
    {
      Class <?> efsClass  = CommonPlugin.loadClass("org.eclipse.core.filesystem", "org.eclipse.core.filesystem.EFS");
      efsGetStoreMethod = efsClass.getMethod("getStore", java.net.URI.class);
      efsGetFileSystemMethod = efsClass.getMethod("getFileSystem", String.class);
      Class <?> fileStoreClass = efsGetStoreMethod.getReturnType();
      fileStoreOpenInputStreamMethod = fileStoreClass.getMethod("openInputStream", Integer.TYPE, IProgressMonitor.class);
      fileStoreOpenOutputStreamMethod = fileStoreClass.getMethod("openOutputStream", Integer.TYPE, IProgressMonitor.class);
    }
    catch (Throwable exeption)
    {
      // Ignore any exceptions and assume the class isn't available.
    }
    EFS_GET_STORE_METHOD = efsGetStoreMethod;
    EFS_GET_FILE_SYSTEM_METHOD = efsGetFileSystemMethod;
    FILE_STORE_OPEN_INPUT_STREAM_METHOD = fileStoreOpenInputStreamMethod;
    FILE_STORE_OPEN_OUTPUT_STREAM_METHOD = fileStoreOpenOutputStreamMethod;
  }

  /**
   * A map that remaps URIs.
   * @deprecated since 2.4 use {@link ExtensibleURIConverterImpl.URIMap} instead.
   */
  @Deprecated
  public interface URIMap extends ExtensibleURIConverterImpl.URIMap
  {
    // No methods added.
  }

  /**
   * Creates an instance.
   * @deprecated since 2.4; 
   * use new {@link ExtensibleURIConverterImpl#ExtensibleURIConverterImpl() ExtensibleURIConverterImpl()} instead.
   */
  @Deprecated
  public URIConverterImpl()
  {
    super();
  }

  /**
   * Returns whether the scheme is one that this implementation should treat as an archive.
   * This implementation returns <code>true</code> when the scheme is <code>"archive"</code>.
   * @param scheme the scheme to consider.
   * @return whether the scheme is one that this implementation treats as an archive.
   */
  protected boolean isArchiveScheme(String scheme)
  {
    return "archive".equals(scheme);
  }

  /**
   * Returns whether the scheme is one that this implementation should treat as a supported Eclipse File System scheme.
   * This implementation uses Java reflection to check whether there is an Eclipse File System available and if so whether it supports this scheme.
   * @param scheme the scheme to consider.
   * @return whether the scheme is one that this implementation treats as an Eclipse File System scheme.
   */
  protected boolean isEFSScheme(String scheme)
  {
    if (EFS_GET_FILE_SYSTEM_METHOD == null)
    {
      return false;
    }
    else
    {
      Boolean result = efsScheme == null ? null : efsScheme.get(scheme);
      if (result == null)
      {
        try
        {
          result = EFS_GET_FILE_SYSTEM_METHOD.invoke(null, scheme) != null;
        }
        catch (Throwable exception)
        {
          result = Boolean.FALSE;
        }
        Map<String, Boolean> map = new HashMap<String, Boolean>();
        if (efsScheme != null)
        {
          map.putAll(efsScheme);
        }
        map.put(scheme, result);
        efsScheme = map;
      }
      return result == Boolean.TRUE;
    }
  }

  /**
   * Creates an output stream for the URI and returns it.
   * <p>
   * This implementation {@link #normalize(URI) normalizes} the URI and uses that as the basis for further processing.
   * A {@link URI#isFile() file-based} URI is {@link URI#toFileString converted} to a file path, e.g.,
   *<pre>
   *  file:///C:/directory/file
   *    ->
   *   C:/directory/file
   *</pre>
   * and is delegated to {@link #createFileOutputStream createFileOutputStream}.
   * An {@link #isArchiveScheme(String) archive-based} URI is delegated to  {@link #createArchiveOutputStream createArchiveOutputStream}.
   * A {@link URI#isPlatformResource() platform-based} URI is {@link URI#toPlatformString(boolean) converted} to a platform path 
   * by trimming the leading <code>platform:/resource</code>, e.g.,
   *<pre>
   *  platform:/resource/project/directory/file 
   *    ->
   *  /project/directory/file 
   *</pre>
   * and is delegated to {@link #createPlatformResourceOutputStream createPlatformResourceOutputStream}.
   * An {@link #isEFSScheme(String) EFS-based} URI is delegated to {@link #createEFSInputStream(URI) createEFSOutputStream}.
   * And all other cases are handled as standard URLs by {@link #createURLOutputStream createURLOutputStream}.
   * </p>
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream.
   */
  @Override
  public OutputStream createOutputStream(URI uri) throws IOException
  {
    URI converted = normalize(uri);
    if (converted.isFile())
    {
      String filePath = converted.toFileString();
      return createFileOutputStream(filePath);
    }
    else 
    {
      String scheme = converted.scheme();
      if (isArchiveScheme(scheme))
      {
        return createArchiveOutputStream(converted);  
      }
      else if (converted.isPlatformResource())
      {
        return createPlatformResourceOutputStream(converted.toPlatformString(true));
      }
      else if (isEFSScheme(scheme))
      {
        return createEFSOutputStream(converted);
      }
      else
      {
        return createURLOutputStream(converted);
      }
    }
  }

  @Override
  public OutputStream createOutputStream(URI uri, Map<?, ?> options) throws IOException
  {
    return createOutputStream(uri);
  }

  /**
   * Creates an output stream for the file path and returns it.
   * <p>
   * This implementation allocates a {@link FileOutputStream} and creates subdirectories as necessary.
   * </p>
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream.
   */
  protected OutputStream createFileOutputStream(String filePath) throws IOException
  {
    File file = new File(filePath);
    String parent = file.getParent();
    if (parent != null)
    {
      new File(parent).mkdirs();
    }
    OutputStream outputStream = new FileOutputStream(file);
    return outputStream;
  }
  
  /**
   * Creates an output stream for the archive access.
   * </p>
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream.
   */
  protected OutputStream createArchiveOutputStream(URI archiveURI) throws IOException
  {
    return createArchive(archiveURI).getOutputStream();
  }

  /**
   * Creates an output stream for the platform resource path and returns it.
   * <p>
   * This implementation does one of two things, depending on the runtime environment.
   * If there is an Eclipse workspace, it delegates to 
   * {@link WorkbenchHelper#createPlatformResourceOutputStream WorkbenchHelper.createPlatformResourceOutputStream},
   * which gives the expected Eclipse behaviour.
   * Otherwise, the {@link EcorePlugin#resolvePlatformResourcePath resolved} URI 
   * is delegated to {@link #createOutputStream(URI, Map) createOutputStream}
   * for recursive processing.
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream or a valid interpretation of the path.
   * @see EcorePlugin#resolvePlatformResourcePath(String)
   */
  protected OutputStream createPlatformResourceOutputStream(String platformResourcePath) throws IOException
  {
    // ECLIPSE-DEPEND-BEGIN
    if (workspaceRoot != null)
    {
      return WorkbenchHelper.createPlatformResourceOutputStream(platformResourcePath);
    }
    else
    // ECLIPSE-DEPEND-END
    {
      URI resolvedLocation = EcorePlugin.resolvePlatformResourcePath(platformResourcePath);
      if (resolvedLocation != null)
      {
        return createOutputStream(resolvedLocation);
      }

      throw new IOException("The path '" + platformResourcePath + "' is unmapped");
    }
  }

  /**
   * Creates an output stream for the URI, assuming it's a URI recognized by the Eclipse File System, and returns it.
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream.
   */
  protected OutputStream createEFSOutputStream(URI uri) throws IOException
  {
    if (EFS_GET_STORE_METHOD != null)
    {
      try
      {
        Object store = EFS_GET_STORE_METHOD.invoke(null, new java.net.URI(uri.toString()));
        if (store != null)
        {
          return (OutputStream)FILE_STORE_OPEN_OUTPUT_STREAM_METHOD.invoke(store, 0, null);
        }
      }
      catch (Exception exception)
      {
        throw new Resource.IOWrappedException(exception);
      }
    }
    throw new IOException("EFS unavailable");
  }

  /**
   * Creates an output stream for the URI, assuming it's a URL, and returns it.
   * @return an open output stream.
   * @exception IOException if there is a problem obtaining an open output stream.
   */
  protected OutputStream createURLOutputStream(URI uri) throws IOException
  {
    try
    {
      URL url = new URL(uri.toString());
      URLConnection urlConnection = url.openConnection();
      urlConnection.setDoOutput(true);
      return urlConnection.getOutputStream();
    }
    catch (RuntimeException exception)
    {
      throw new Resource.IOWrappedException(exception);
    }
  }

  /**
   * Creates an input stream for the URI and returns it.
   * <p>
   * This implementation {@link #normalize(URI) normalizes} the URI and uses that as the basis for further processing.
   * A {@link URI#isFile() file-based} URI is {@link URI#toFileString converted} to a file path, e.g.,
   *<pre>
   *  file:///C:/directory/file
   *    ->
   *   C:/directory/file
   *</pre>
   * and is delegated to {@link #createFileInputStream createFileInputStream}.
   * An {@link #isArchiveScheme(String) archive-based} URI is delegated to  {@link #createArchiveInputStream createArchiveInputStream}.
   * A {@link URI#isPlatformResource() platform-based} URI is {@link URI#toPlatformString(boolean) converted} to a platform path 
   * by trimming the leading <code>platform:/resource</code>, e.g.,
   *<pre>
   *  platform:/resource/project/directory/file 
   *    ->
   *  /project/directory/file 
   *</pre>
   * and is delegated to {@link #createPlatformResourceInputStream createPlatformResourceInputStream}.
   * An {@link #isEFSScheme(String) EFS-based} URI is delegated to {@link #createEFSInputStream(URI) createEFSInputStream}.
   * And all other cases are handled as standard URLs by {@link #createURLInputStream createURLInputStream}.
   * </p>
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream.
   */
  @Override
  public InputStream createInputStream(URI uri) throws IOException
  {
    URI converted = normalize(uri);
    if (converted.isFile())
    {
      String filePath = converted.toFileString();
      return createFileInputStream(filePath);
    }
    else
    {
      String scheme = converted.scheme();
      if (isArchiveScheme(scheme))
      {
        return createArchiveInputStream(converted);
      }
      else if (converted.isPlatformResource())
      {
        return createPlatformResourceInputStream(converted.toPlatformString(true));
      }      
      else if (isEFSScheme(scheme))
      {
        return createEFSInputStream(converted);
      }
      else
      {
        return createURLInputStream(converted);
      }
    }
  }
  
  @Override
  public InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException
  {
    return createInputStream(uri);
  }

  /**
   * Creates an input stream for the file path and returns it.
   * <p>
   * This implementation allocates a {@link FileInputStream}.
   * </p>
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream.
   */
  protected InputStream createFileInputStream(String filePath) throws IOException
  {
    File file = new File(filePath);
    InputStream inputStream = new FileInputStream(file);
    return inputStream;
  }
  
  /**
   * A specialized class for reading from an archive.
   */
  protected class Archive extends ArchiveURLConnection
  {
    public Archive(URI uri)
    {
      super(uri.toString());
    }
    
    @Override
    protected boolean emulateArchiveScheme()
    {
      return false;
    }
    
    @Override
    protected boolean useZipFile()
    {
      return true;
    }
    
    @Override
    protected InputStream createInputStream(String nestedURL) throws IOException
    {
      return URIConverterImpl.this.createInputStream(URI.createURI(nestedURL));
    }
    
    @Override
    protected OutputStream createOutputStream(String nestedURL) throws IOException
    {
      return URIConverterImpl.this.createOutputStream(URI.createURI(nestedURL));
    }
  }
  
  protected Archive createArchive(URI uri)
  {
    return new Archive(uri);
  }
  
  /**
   * Creates an input stream for the archive paths and returns it.
   * It uses {@link Archive} to implement read access.
   * </p>
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream.
   */
  protected InputStream createArchiveInputStream(URI archiveURI) throws IOException
  {
    return createArchive(archiveURI).getInputStream();
  }

  /**
   * Creates an input stream for the platform resource path and returns it.
   * <p>
   * This implementation does one of two things, depending on the runtime environment.
   * If there is an Eclipse workspace, it delegates to 
   * {@link WorkbenchHelper#createPlatformResourceInputStream WorkbenchHelper.createPlatformResourceInputStream},
   * which gives the expected Eclipse behaviour.
   * Otherwise, the {@link EcorePlugin#resolvePlatformResourcePath resolved} URI 
   * is delegated to {@link #createInputStream(URI, Map) createInputStream}
   * for recursive processing.
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream or a valid interpretation of the path.
   * @see EcorePlugin#resolvePlatformResourcePath(String)
   */
  protected InputStream createPlatformResourceInputStream(String platformResourcePath) throws IOException
  {
    // ECLIPSE-DEPEND-BEGIN
    if (workspaceRoot != null)
    {
      return WorkbenchHelper.createPlatformResourceInputStream(platformResourcePath);
    }
    else
    // ECLIPSE-DEPEND-END
    {
      URI resolvedLocation = EcorePlugin.resolvePlatformResourcePath(platformResourcePath);
      if (resolvedLocation != null)
      {
        return createInputStream(resolvedLocation);
      }

      throw new IOException("The path '" + platformResourcePath + "' is unmapped");
    }
  }

  /**
   * Creates an input stream for the URI, assuming it's a URI recognized by the Eclipse File System, and returns it.
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream.
   */
  protected InputStream createEFSInputStream(URI uri) throws IOException
  {
    if (EFS_GET_STORE_METHOD != null)
    {
      try
      {
        Object store = EFS_GET_STORE_METHOD.invoke(null, new java.net.URI(uri.toString()));
        if (store != null)
        {
          return (InputStream)FILE_STORE_OPEN_INPUT_STREAM_METHOD.invoke(store, 0, null);
        }
      }
      catch (Exception exception)
      {
        throw new Resource.IOWrappedException(exception);
      }
    }
    throw new IOException("EFS unavailable");
  }

  /**
   * Creates an input stream for the URI, assuming it's a URL, and returns it.
   * @return an open input stream.
   * @exception IOException if there is a problem obtaining an open input stream.
   */
  protected InputStream createURLInputStream(URI uri) throws IOException
  {
    try
    {
      URL url = new URL(uri.toString());
      URLConnection urlConnection = url.openConnection();
      return urlConnection.getInputStream();
    }
    catch (RuntimeException exception)
    {
      throw new Resource.IOWrappedException(exception);
    }
  }

  /**
   * Returns the internal version of the URI map.
   * @return the internal version of the URI map.
   * @deprecated since 2.4; there should be no need to call this method directly so use {@link #getURIMap()} instead.
   */
  @Override
  @Deprecated
  protected URIMap getInternalURIMap()
  {
    return (URIMap)super.getInternalURIMap();
  }
}
