/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.instrument.classloading.oc4j;

import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.util.Assert;

/**
 * Reflective wrapper around a OC4J class loader. Used to
 * encapsulate the classloader-specific methods (discovered and
 * called through reflection) from the load-time weaver.
 *
 * @author Costin Leau
 */
class OC4JClassLoaderAdapter {

	private static final String CL_UTILS = "oracle.classloader.util.ClassLoaderUtilities";
	private static final String PREPROCESS_UTILS = "oracle.classloader.util.ClassPreprocessor";

	private final ClassLoader classLoader;
	private final Class<?> processorClass;
	private final Method addTransformer;
	private final Method copy;

	public OC4JClassLoaderAdapter(ClassLoader classLoader) {
		try {
			// Since OC4J 10.1.3's PolicyClassLoader is going to be removed,
			// we rely on the ClassLoaderUtilities API instead.
			Class<?> utilClass = classLoader.loadClass(CL_UTILS);
			this.processorClass = classLoader.loadClass(PREPROCESS_UTILS);

			this.addTransformer = utilClass.getMethod("addPreprocessor", new Class[] { ClassLoader.class,
					this.processorClass });
			this.copy = utilClass.getMethod("copy", new Class[] { ClassLoader.class });

		} catch (Exception ex) {
			throw new IllegalStateException(
					"Could not initialize OC4J LoadTimeWeaver because OC4J API classes are not available", ex);
		}

		this.classLoader = classLoader;
	}

	public void addTransformer(ClassFileTransformer transformer) {
		Assert.notNull(transformer, "ClassFileTransformer must not be null");
		try {
			OC4JClassPreprocessorAdapter adapter = new OC4JClassPreprocessorAdapter(transformer);
			Object adapterInstance = Proxy.newProxyInstance(this.processorClass.getClassLoader(),
					new Class[] { this.processorClass }, adapter);
			this.addTransformer.invoke(null, new Object[] { this.classLoader, adapterInstance });
		} catch (InvocationTargetException ex) {
			throw new IllegalStateException("OC4J addPreprocessor method threw exception", ex.getCause());
		} catch (Exception ex) {
			throw new IllegalStateException("Could not invoke OC4J addPreprocessor method", ex);
		}
	}

	public ClassLoader getClassLoader() {
		return this.classLoader;
	}

	public ClassLoader getThrowawayClassLoader() {
		try {
			return (ClassLoader) this.copy.invoke(null, new Object[] { this.classLoader });
		} catch (InvocationTargetException ex) {
			throw new IllegalStateException("OC4J copy method failed", ex.getCause());
		} catch (Exception ex) {
			throw new IllegalStateException("Could not copy OC4J classloader", ex);
		}
	}
}
