/*******************************************************************************
 * Copyright (c) 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.osgi.framework.adaptor.core;

import java.io.IOException;
import java.net.*;
import org.eclipse.osgi.framework.internal.core.AbstractBundle;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.BundleContext;

/**
 * URLStreamHandler the bundleentry and bundleresource protocols.
 */

public abstract class BundleResourceHandler extends URLStreamHandler {
	public static final String SECURITY_AUTHORIZED = "SECURITY_AUTHORIZED"; //$NON-NLS-1$
	protected static BundleContext context;
	protected BundleEntry bundleEntry;

	/** Single object for permission checks */
	protected AdminPermission adminPermission;

	/**
	 * Constructor for a bundle protocol resource URLStreamHandler.
	 */
	public BundleResourceHandler() {
	}

	public BundleResourceHandler(BundleEntry bundleEntry) {
		this.bundleEntry = bundleEntry;
	}

	/** 
	 * Parse reference URL. 
	 */
	protected void parseURL(URL url, String str, int start, int end) {
		// Check the permission of the caller to see if they
		// are allowed access to the resource.
		checkAdminPermission();
		if (end < start) 
			return;
		if (url.getPath() != null)
			// A call to a URL constructor has been made that uses an authorized  URL as its context.
			// Null out bundleEntry because it will not be valid for the new path
			bundleEntry = null;
		String spec = ""; //$NON-NLS-1$
		if (start < end)
			spec = str.substring(start, end);
		end -= start;
		//Default is to use path and bundleId from context
		String path = url.getPath();
		String bundleId = url.getHost();
		int pathIdx = 0;
		if (spec.startsWith("//")) { //$NON-NLS-1$
			int bundleIdIdx = 2;
			pathIdx = spec.indexOf('/', bundleIdIdx);
			if (pathIdx == -1) {
				pathIdx = end;
				// Use default
				path = ""; //$NON-NLS-1$
			}
			bundleId = spec.substring(bundleIdIdx, pathIdx);
		}
		if (pathIdx < end && spec.charAt(pathIdx) == '/')
			path = spec.substring(pathIdx, end);
		else if (end > pathIdx) {
			if (path == null || path.equals("")) //$NON-NLS-1$
				path = "/"; //$NON-NLS-1$
			int last = path.lastIndexOf('/') + 1;
			if (last == 0)
				path = spec.substring(pathIdx, end);
			else
				path = path.substring(0, last) + spec.substring(pathIdx, end);
		}
		if (path == null)
			path = ""; //$NON-NLS-1$
		//modify path if there's any relative references
		int dotIndex;
		while ((dotIndex = path.indexOf("/./")) >= 0) //$NON-NLS-1$
			path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 3);
		if (path.endsWith("/.")) //$NON-NLS-1$
			path = path.substring(0, path.length() - 1);
		while ((dotIndex = path.indexOf("/../")) >= 0) { //$NON-NLS-1$
			if (dotIndex != 0)
				path = path.substring(0, path.lastIndexOf('/', dotIndex - 1)) + path.substring(dotIndex + 3);
			else
				path = path.substring(dotIndex + 3);
		}
		if (path.endsWith("/..") && path.length() > 3) //$NON-NLS-1$
			path = path.substring(0, path.length() - 2);

		// Setting the authority portion of the URL to SECURITY_ATHORIZED
		// ensures that this URL was created by using this parseURL
		// method.  The openConnection method will only open URLs
		// that have the authority set to this.
		setURL(url, url.getProtocol(), bundleId, 0, SECURITY_AUTHORIZED, null, path, null, null);
	}

	/**
	 * Establishes a connection to the resource specified by <code>URL</code>.
	 * Since different protocols may have unique ways of connecting, it must be
	 * overridden by the subclass.
	 *
	 * @return java.net.URLConnection
	 * @param url java.net.URL
	 *
	 * @exception	IOException 	thrown if an IO error occurs during connection establishment
	 */
	protected URLConnection openConnection(URL url) throws IOException {
		String authority = url.getAuthority();
		// check to make sure that this URL was created using the
		// parseURL method.  This ensures the security check was done
		// at URL construction.
		if (!url.getAuthority().equals(SECURITY_AUTHORIZED)) {
			// No admin security check was made better check now.
			checkAdminPermission();
		}

		if (bundleEntry != null) {
			return (new BundleURLConnection(url, bundleEntry));
		} else {
			String bidString = url.getHost();
			if (bidString == null) {
				throw new IOException(AdaptorMsg.formatter.getString("URL_NO_BUNDLE_ID", url.toExternalForm())); //$NON-NLS-1$
			}
			AbstractBundle bundle = null;
			try {
				Long bundleID = new Long(bidString);
				bundle = (AbstractBundle) context.getBundle(bundleID.longValue());
			} catch (NumberFormatException nfe) {
				throw new MalformedURLException(AdaptorMsg.formatter.getString("URL_INVALID_BUNDLE_ID", bidString)); //$NON-NLS-1$
			}

			if (bundle == null) {
				throw new IOException(AdaptorMsg.formatter.getString("URL_NO_BUNDLE_FOUND", url.toExternalForm())); //$NON-NLS-1$
			}
			return (new BundleURLConnection(url, findBundleEntry(url, bundle)));
		}
	}

	/**
	 * Finds the bundle entry for this protocal.  This is handled
	 * differently for Bundle.gerResource() and Bundle.getEntry()
	 * because getResource uses the bundle classloader and getEntry
	 * only used the base bundle file.
	 * @param url The URL to find the BundleEntry for.
	 * @return
	 */
	abstract protected BundleEntry findBundleEntry(URL url, AbstractBundle bundle) throws IOException;

	/**
	 * Converts a bundle URL to a String.
	 *
	 * @param   url   the URL.
	 * @return  a string representation of the URL.
	 */
	protected String toExternalForm(URL url) {
		StringBuffer result = new StringBuffer(url.getProtocol());
		result.append("://"); //$NON-NLS-1$

		String bundleId = url.getHost();
		if ((bundleId != null) && (bundleId.length() > 0)) {
			result.append(bundleId);
		}

		String path = url.getPath();
		if (path != null) {
			if ((path.length() > 0) && (path.charAt(0) != '/')) /* if name doesn't have a leading slash */
			{
				result.append("/"); //$NON-NLS-1$
			}

			result.append(path);
		}

		return (result.toString());
	}

	public static void setContext(BundleContext context) {
		BundleResourceHandler.context = context;
	}

	protected int hashCode(URL url) {
		int hash = 0;
		String protocol = url.getProtocol();
		if (protocol != null)
			hash += protocol.hashCode();

		String host = url.getHost();
		if (host != null)
			hash += host.hashCode();

		String path = url.getPath();
		if (path != null)
			hash += path.hashCode();
		return hash;
	}

	protected boolean equals(URL url1, URL url2) {
		return sameFile(url1, url2);
	}

	protected synchronized InetAddress getHostAddress(URL url) {
		return null;
	}

	protected boolean hostsEqual(URL url1, URL url2) {
		String host1 = url1.getHost();
		String host2 = url2.getHost();
		if (host1 != null && host2 != null)
			return host1.equalsIgnoreCase(host2);
		else
			return (host1 == null && host2 == null);
	}

	protected boolean sameFile(URL url1, URL url2) {
		String p1 = url1.getProtocol();
		String p2 = url2.getProtocol();
		if (!((p1 == p2) || (p1 != null && p1.equalsIgnoreCase(p2))))
			return false;

		if (!hostsEqual(url1, url2))
			return false;

		String a1 = url1.getAuthority();
		String a2 = url2.getAuthority();
		if (!((a1 == a2) || (a1 != null && a1.equals(a2))))
			return false;

		String path1 = url1.getPath();
		String path2 = url2.getPath();
		if (!((path1 == path2) || (path1 != null && path1.equals(path2))))
			return false;

		return true;
	}

	protected void checkAdminPermission() {
		SecurityManager sm = System.getSecurityManager();

		if (sm != null) {
			if (adminPermission == null) {
				adminPermission = new AdminPermission();
			}

			sm.checkPermission(adminPermission);
		}
	}
}