/*
 * Copyright 2010-2012 VMware and contributors
 *
 * 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.springsource.loaded.agent;

import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.ReloadEventProcessorPlugin;


/**
 * What does it do?
 * <p>
 * So far the GroovyPlugin can do two different things - configurable through the 'allowCompilableCallSites' flag.
 * 
 * <p>
 * If the flag is false: The plugin intercepts two of the main system types in groovy and turns OFF call site compilation. Without
 * this compilation the compiler will not be generating classes, it will instead be using reflection all the time. This is simpler
 * to handle (as we intercept reflection) but performance == thesuck.
 * <p>
 * If the flag is true: The plugin leaves groovy to compile call sites. We intercept the define method in the classloader used to
 * define these generated call site classes and ensure they are rewritten correctly. Note there is an alternative here of getting
 * the SpringLoadedPreProcessor to recognize these special classloaders and just instrument them that way. However, if we let the
 * plugin do it it is easier to test!
 * <p>
 * To see the difference in these approaches, check the numbers in the Groovy Benchmark tests.
 * 
 * @author Andy Clement
 * @since 0.7.0
 */
public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {

	boolean allowCompilableCallSites = true;

	// GroovySunClassLoader - can make the final field non final so it can be set to null (it is checked as part of the isCompilable methodin the callsitegenerator)
	// CallSiteGenerator - make isCompilable return false, which means we will never generate a direct call to a method that may not yet be on the target
	// implementing LoadtimeInstrumentationPlugin
	public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
		// TODO take classloader into account?
		if (slashedTypeName==null) {
			return false;
		}
		if (!allowCompilableCallSites) {
			return slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")
					|| slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/CallSiteGenerator");
		} else {
			if (slashedTypeName.equals("org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts")) {
				return true;
			}
		}
		return false;
	}

	public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
		if (allowCompilableCallSites) {
			return modifyDefineInClassLoaderForClassArtifacts(bytes);
		} else {
			// Deactivate compilation
			if (slashedClassName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")) {
				//		if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
				//			log.info("loadtime modifying " + slashedClassName);
				//		}
				ClassReader cr = new ClassReader(bytes);
				NonFinalizer ca = new NonFinalizer("sunVM");
				//		ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender("org/springsource/loaded/agent/SpringPlugin",
				//				"recordInstance");
				cr.accept(ca, 0);
				byte[] newbytes = ca.getBytes();
				return newbytes;
			} else {
				// must be the CallSiteGenerator
				ClassReader cr = new ClassReader(bytes);
				FalseReturner ca = new FalseReturner("isCompilable");
				cr.accept(ca, 0);
				byte[] newbytes = ca.getBytes();
				return newbytes;

			}
		}
	}

	private byte[] modifyDefineInClassLoaderForClassArtifacts(byte[] bytes) {
		ClassReader cr = new ClassReader(bytes);
		ModifyDefineInClassLoaderForClassArtifactsType ca = new ModifyDefineInClassLoaderForClassArtifactsType();
		cr.accept(ca, 0);
		byte[] newbytes = ca.getBytes();
		return newbytes;
	}

	// called by the modified code
	public static void recordInstance(Object obj) {
	}

	// implementing CallbackPlugin
	public void reloadEvent(String typename, Class<?> clazz, String versionsuffix) {
	}

	public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
		return false;
	}

}
