/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.boot.archive.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;

import org.hibernate.boot.archive.spi.ArchiveException;

import org.jboss.logging.Logger;

/**
 * Helper for dealing with archives
 *
 * @author Emmanuel Bernard
 * @author Steve Ebersole
 */
public class ArchiveHelper {
	private static final Logger log = Logger.getLogger( ArchiveHelper.class );

	/**
	 * Get the JAR URL of the JAR containing the given entry
	 * Method used in a non managed environment
	 *
	 * @param url URL pointing to the known file in the JAR
	 * @param entry file known to be in the JAR
	 * @return the JAR URL
	 * @throws IllegalArgumentException if none URL is found
	 */
	public static URL getJarURLFromURLEntry(URL url, String entry) throws IllegalArgumentException {
		URL jarUrl;
		String file = url.getFile();
		if ( ! entry.startsWith( "/" ) ) {
			entry = "/" + entry;
		}
		file = file.substring( 0, file.length() - entry.length() );
		if ( file.endsWith( "!" ) ) {
			file = file.substring( 0, file.length() - 1 );
		}
		try {
			final String protocol = url.getProtocol();

			if ( "jar".equals( protocol ) || "wsjar".equals( protocol ) ) {
				//Original URL is like jar:protocol
				//WebSphere has it's own way
				jarUrl = new URL( file );
				if ( "file".equals( jarUrl.getProtocol() ) ) {
					if ( file.indexOf( ' ' ) != -1 ) {
						//not escaped, need to voodoo; goes by toURI to escape the path
						jarUrl = new File( jarUrl.getFile() ).toURI().toURL();
					}
				}
			}
			else if ( "zip".equals( protocol )
					//OC4J prevent ejb.jar access (ie everything without path)
					|| "code-source".equals( url.getProtocol() )
					//if no wrapping is done
					|| "file".equals( protocol ) ) {
				//we have extracted the zip file, so it should be read as a file
				if ( file.indexOf( ' ' ) != -1 ) {
					//not escaped, need to voodoo; goes by toURI to escape the path
					jarUrl = new File(file).toURI().toURL();
				}
				else {
					jarUrl = new File(file).toURL();
				}
			}
			else {
				try {
					//We reconstruct the URL probably to make it work in some specific environments
					//Forgot the exact details, sorry (and the Git history does not help)
					jarUrl = new URL( protocol, url.getHost(), url.getPort(), file );
				}
				//HHH-6442: Arquilian
				catch ( final MalformedURLException e ) {
					//Just use the provided URL as-is, likely it has a URLStreamHandler
					//associated w/ the instance
					jarUrl = url;
				}
			}
		}
		catch (MalformedURLException e) {
			throw new IllegalArgumentException(
					"Unable to determine JAR Url from " + url + ". Cause: " + e.getMessage()
			);
		}
		log.trace( "JAR URL from URL Entry: " + url + " >> " + jarUrl );
		return jarUrl;
	}

	/**
	 * get the URL from a given path string
	 *
	 * @param jarPath The path that represents a URL
	 *
	 * @return The resolved URL reference
	 *
	 * @throws IllegalArgumentException is something goes wrong
	 */
	public static URL getURLFromPath(String jarPath) {
		URL jarUrl;
		try {
			//is it an url
			jarUrl = new URL( jarPath );
		}
		catch ( MalformedURLException e) {
			try {
				//consider it as a file path
				jarUrl = new URL( "file:" + jarPath );
			}
			catch (MalformedURLException ee) {
				throw new IllegalArgumentException( "Unable to find jar:" + jarPath, ee );
			}
		}
		return jarUrl;
	}

	/**
	 * Extracts the bytes out of an InputStream.  This form is the same as {@link #getBytesFromInputStream}
	 * except that any {@link IOException} are wrapped as (runtime) {@link ArchiveException}
	 *
	 * @param inputStream The stream from which to extract bytes.
	 *
	 * @return The bytes
	 *
	 * @throws ArchiveException Indicates a problem accessing the stream
	 */
	public static byte[] getBytesFromInputStreamSafely(InputStream inputStream) throws ArchiveException {
		try {
			return getBytesFromInputStream( inputStream );
		}
		catch (IOException e) {
			throw new ArchiveException( "Unable to extract bytes from InputStream", e );
		}
	}

	/**
	 * Extracts the bytes out of an InputStream.
	 *
	 * @param inputStream The stream from which to extract bytes.
	 *
	 * @return The bytes
	 *
	 * @throws IOException Indicates a problem accessing the stream
	 *
	 * @see #getBytesFromInputStreamSafely(java.io.InputStream)
	 */
	public static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
		// Optimized by HHH-7835
		int size;
		final List<byte[]> data = new LinkedList<byte[]>();
		final int bufferSize = 4096;
		byte[] tmpByte = new byte[bufferSize];
		int offset = 0;
		int total = 0;
		for ( ;; ) {
			size = inputStream.read( tmpByte, offset, bufferSize - offset );
			if ( size == -1 ) {
				break;
			}

			offset += size;

			if ( offset == tmpByte.length ) {
				data.add( tmpByte );
				tmpByte = new byte[bufferSize];
				offset = 0;
				total += tmpByte.length;
			}
		}

		final byte[] result = new byte[total + offset];
		int count = 0;
		for ( byte[] arr : data ) {
			System.arraycopy( arr, 0, result, count * arr.length, arr.length );
			count++;
		}
		System.arraycopy( tmpByte, 0, result, count * tmpByte.length, offset );

		return result;
	}

	private ArchiveHelper() {
	}
}
