/* *******************************************************************
 * Copyright (c) 2004 IBM Corporation
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Matthew Webster, Adrian Colyer, John Kew + Lyor Goldstein (caching)
 *     Martin Lippert     initial implementation 
 * ******************************************************************/

package org.aspectj.weaver.tools;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.ProtectionDomain;
import java.util.*;

import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessage.Kind;
import org.aspectj.bridge.IMessageContext;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.IMessageHolder;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageHandler;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.MessageWriter;
import org.aspectj.bridge.Version;
import org.aspectj.bridge.WeaveMessage;
import org.aspectj.util.FileUtil;
import org.aspectj.util.LangUtil;
import org.aspectj.weaver.IClassFileProvider;
import org.aspectj.weaver.IUnwovenClassFile;
import org.aspectj.weaver.IWeaveRequestor;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.BcelObjectType;
import org.aspectj.weaver.bcel.BcelWeaver;
import org.aspectj.weaver.bcel.BcelWorld;
import org.aspectj.weaver.bcel.UnwovenClassFile;
import org.aspectj.weaver.tools.cache.CachedClassEntry;
import org.aspectj.weaver.tools.cache.CachedClassReference;
import org.aspectj.weaver.tools.cache.SimpleCache;
import org.aspectj.weaver.tools.cache.SimpleCacheFactory;
import org.aspectj.weaver.tools.cache.WeavedClassCache;

// OPTIMIZE add guards for all the debug/info/etc
/**
 * This adaptor allows the AspectJ compiler to be embedded in an existing system to facilitate load-time weaving. It provides an
 * interface for a weaving class loader to provide a classpath to be woven by a set of aspects. A callback is supplied to allow a
 * class loader to define classes generated by the compiler during the weaving process.
 * <p>
 * A weaving class loader should create a <code>WeavingAdaptor</code> before any classes are defined, typically during construction.
 * The set of aspects passed to the adaptor is fixed for the lifetime of the adaptor although the classpath can be augmented. A
 * system property can be set to allow verbose weaving messages to be written to the console.
 * 
 */
public class WeavingAdaptor implements IMessageContext {

	/**
	 * System property used to turn on verbose weaving messages
	 */
	public static final String WEAVING_ADAPTOR_VERBOSE = "aj.weaving.verbose";
	public static final String SHOW_WEAVE_INFO_PROPERTY = "org.aspectj.weaver.showWeaveInfo";
	public static final String TRACE_MESSAGES_PROPERTY = "org.aspectj.tracing.messages";

	private boolean enabled = false;
	protected boolean verbose = getVerbose();
	protected BcelWorld bcelWorld;
	protected BcelWeaver weaver;
	private IMessageHandler messageHandler;
	private WeavingAdaptorMessageHolder messageHolder;
	private boolean abortOnError = false;
	protected GeneratedClassHandler generatedClassHandler;
	protected Map<String, IUnwovenClassFile> generatedClasses = new HashMap<String, IUnwovenClassFile>();
	public BcelObjectType delegateForCurrentClass; // lazily initialized, should be used to prevent parsing bytecode multiple
	// times
	protected ProtectionDomain activeProtectionDomain;

	private boolean haveWarnedOnJavax = false;
    protected WeavedClassCache cache;

	private int weavingSpecialTypes = 0;
	private static final int INITIALIZED = 0x1;
	private static final int WEAVE_JAVA_PACKAGE = 0x2;
	private static final int WEAVE_JAVAX_PACKAGE = 0x4;

	private static Trace trace = TraceFactory.getTraceFactory().getTrace(WeavingAdaptor.class);

	protected WeavingAdaptor() {
	}

	/**
	 * Construct a WeavingAdaptor with a reference to a weaving class loader. The adaptor will automatically search the class loader
	 * hierarchy to resolve classes. The adaptor will also search the hierarchy for WeavingClassLoader instances to determine the
	 * set of aspects to be used for weaving.
	 * 
	 * @param loader instance of <code>ClassLoader</code>
	 */
	public WeavingAdaptor(WeavingClassLoader loader) {
		// System.err.println("? WeavingAdaptor.<init>(" + loader +"," + aspectURLs.length + ")");
		generatedClassHandler = loader;
		init((ClassLoader)loader, getFullClassPath((ClassLoader) loader), getFullAspectPath((ClassLoader) loader/* ,aspectURLs */));
	}

	/**
	 * Construct a WeavingAdaptor with a reference to a <code>GeneratedClassHandler</code>, a full search path for resolving classes
	 * and a complete set of aspects. The search path must include classes loaded by the class loader constructing the
	 * WeavingAdaptor and all its parents in the hierarchy.
	 * 
	 * @param handler <code>GeneratedClassHandler</code>
	 * @param classURLs the URLs from which to resolve classes
	 * @param aspectURLs the aspects used to weave classes defined by this class loader
	 */
	public WeavingAdaptor(GeneratedClassHandler handler, URL[] classURLs, URL[] aspectURLs) {
		// System.err.println("? WeavingAdaptor.<init>()");
		generatedClassHandler = handler;
		init(null, FileUtil.makeClasspath(classURLs), FileUtil.makeClasspath(aspectURLs));
	}

	protected List<String> getFullClassPath(ClassLoader loader) {
		List<String> list = new LinkedList<String>();
		for (; loader != null; loader = loader.getParent()) {
			if (loader instanceof URLClassLoader) {
				URL[] urls = ((URLClassLoader) loader).getURLs();
				list.addAll(0, FileUtil.makeClasspath(urls));
			} else {
				warn("cannot determine classpath");
			}
		}

		list.addAll(0, makeClasspath(System.getProperty("sun.boot.class.path")));

		return list;
	}

	private List<String> getFullAspectPath(ClassLoader loader) {
		List<String> list = new LinkedList<String>();
		for (; loader != null; loader = loader.getParent()) {
			if (loader instanceof WeavingClassLoader) {
				URL[] urls = ((WeavingClassLoader) loader).getAspectURLs();
				list.addAll(0, FileUtil.makeClasspath(urls));
			}
		}
		return list;
	}

	private static boolean getVerbose() {
		try {
			return Boolean.getBoolean(WEAVING_ADAPTOR_VERBOSE);
		} catch (Throwable t) {
			// security exception
			return false;
		}
	}

	/**
	 * Initialize the WeavingAdapter
	 * @param loader ClassLoader used by this adapter; which can be null
	 * @param classPath classpath of this adapter
	 * @param aspectPath list of aspect paths
	 */
	private void init(ClassLoader loader, List<String> classPath, List<String> aspectPath) {
		abortOnError = true;
		createMessageHandler();

		info("using classpath: " + classPath);
		info("using aspectpath: " + aspectPath);

		bcelWorld = new BcelWorld(classPath, messageHandler, null);
		bcelWorld.setXnoInline(false);
		bcelWorld.getLint().loadDefaultProperties();
		if (LangUtil.is15VMOrGreater()) {
			bcelWorld.setBehaveInJava5Way(true);
		}

		weaver = new BcelWeaver(bcelWorld);
		registerAspectLibraries(aspectPath);
		initializeCache(loader, aspectPath, null, getMessageHandler());
		enabled = true;
	}

	/**
	 * If the cache is enabled, initialize it and swap out the existing classhandler
	 * for the caching one -
	 *
	 * @param loader			   classloader for this adapter, may be null
	 * @param aspects			  List of strings representing aspects managed by the adapter; these could be urls or classnames
	 * @param existingClassHandler current class handler
	 * @param myMessageHandler	 current message handler
	 */
	protected void initializeCache(ClassLoader loader, List<String> aspects, GeneratedClassHandler existingClassHandler, IMessageHandler myMessageHandler) {
		if (WeavedClassCache.isEnabled()) {
			cache = WeavedClassCache.createCache(loader, aspects, existingClassHandler, myMessageHandler);
			// Wrap the existing class handler so that any generated classes are also cached
			if (cache != null) {
				this.generatedClassHandler = cache.getCachingClassHandler();
			}
		}
	}


	protected void createMessageHandler() {
		messageHolder = new WeavingAdaptorMessageHolder(new PrintWriter(System.err));
		messageHandler = messageHolder;
		if (verbose) {
			messageHandler.dontIgnore(IMessage.INFO);
		}
		if (Boolean.getBoolean(SHOW_WEAVE_INFO_PROPERTY)) {
			messageHandler.dontIgnore(IMessage.WEAVEINFO);
		}
		info("AspectJ Weaver Version " + Version.text + " built on " + Version.time_text); //$NON-NLS-1$
	}

	protected IMessageHandler getMessageHandler() {
		return messageHandler;
	}

	public IMessageHolder getMessageHolder() {
		return messageHolder;
	}

	protected void setMessageHandler(IMessageHandler mh) {
		if (mh instanceof ISupportsMessageContext) {
			ISupportsMessageContext smc = (ISupportsMessageContext) mh;
			smc.setMessageContext(this);
		}
		if (mh != messageHolder) {
			messageHolder.setDelegate(mh);
		}
		messageHolder.flushMessages();
	}

	protected void disable() {
		if (trace.isTraceEnabled()) {
			trace.enter("disable", this);
		}

		enabled = false;
		messageHolder.flushMessages();

		if (trace.isTraceEnabled()) {
			trace.exit("disable");
		}
	}

	protected void enable() {
		enabled = true;
		messageHolder.flushMessages();
	}

	protected boolean isEnabled() {
		return enabled;
	}

	/**
	 * Appends URL to path used by the WeavingAdptor to resolve classes
	 * 
	 * @param url to be appended to search path
	 */
	public void addURL(URL url) {
		File libFile = new File(url.getPath());
		try {
			weaver.addLibraryJarFile(libFile);
		} catch (IOException ex) {
			warn("bad library: '" + libFile + "'");
		}
	}

	/**
	 * Weave a class using aspects previously supplied to the adaptor.
	 * 
	 * @param name the name of the class
	 * @param bytes the class bytes
	 * @return the woven bytes
	 * @exception IOException weave failed
	 */
	public byte[] weaveClass(String name, byte[] bytes) throws IOException {
		return weaveClass(name, bytes, false);
	}

	// Track if the weaver is already running on this thread - don't allow re-entrant calls
	private ThreadLocal<Boolean> weaverRunning = new ThreadLocal<Boolean>() {
		@Override
		protected Boolean initialValue() {
			return Boolean.FALSE;
		}
	};

	/**
	 * Weave a class using aspects previously supplied to the adaptor.
	 * 
	 * @param name the name of the class
	 * @param bytes the class bytes
	 * @param mustWeave if true then this class *must* get woven (used for concrete aspects generated from XML)
	 * @return the woven bytes
	 * @exception IOException weave failed
	 */
	public byte[] weaveClass(String name, byte[] bytes, boolean mustWeave) throws IOException {
		if (trace == null) {
			// Pr231945: we are likely to be under tomcat and ENABLE_CLEAR_REFERENCES hasn't been set
			System.err
					.println("AspectJ Weaver cannot continue to weave, static state has been cleared.  Are you under Tomcat? In order to weave '"
							+ name
							+ "' during shutdown, 'org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false' must be set (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=231945).");
			return bytes;
		}
		if (weaverRunning.get()) {
			// System.out.println("AJC: avoiding re-entrant call to transform " + name);
			return bytes;
		}
		try {
			weaverRunning.set(true);
			if (trace.isTraceEnabled()) {
				trace.enter("weaveClass", this, new Object[] { name, bytes });
			}

			if (!enabled) {
				if (trace.isTraceEnabled()) {
					trace.exit("weaveClass", false);
				}
				return bytes;
			}

			boolean debugOn = !messageHandler.isIgnoring(Message.DEBUG);

			try {
				delegateForCurrentClass = null;
				name = name.replace('/', '.');
				if (couldWeave(name, bytes)) {
					if (accept(name, bytes)) {

						// Determine if we have the weaved class cached
						CachedClassReference cacheKey = null;
						final byte[] original_bytes = bytes;
						if (cache != null && !mustWeave) {
							cacheKey = cache.createCacheKey(name, original_bytes);
							CachedClassEntry entry = cache.get(cacheKey, original_bytes);
							if (entry != null) {
								// If the entry has been explicitly ignored
								// return the original bytes
								if (entry.isIgnored()) {
									return bytes;
								}
								return entry.getBytes();
							}
						}

						// TODO @AspectJ problem
						// Annotation style aspects need to be included regardless in order to get
						// a valid aspectOf()/hasAspect() generated in them. However - if they are excluded
						// (via include/exclude in aop.xml) they really should only get aspectOf()/hasAspect()
						// and not be included in the full set of aspects being applied by 'this' weaver
						if (debugOn) {
							debug("weaving '" + name + "'");
						}
						bytes = getWovenBytes(name, bytes);
						// temporarily out - searching for @Aspect annotated types is a slow thing to do - we should
						// expect the user to name them if they want them woven - just like code style
						// } else if (shouldWeaveAnnotationStyleAspect(name, bytes)) {
						// if (mustWeave) {
						// if (bcelWorld.getLint().mustWeaveXmlDefinedAspects.isEnabled()) {
						// bcelWorld.getLint().mustWeaveXmlDefinedAspects.signal(name, null);
						// }
						// }
						// // an @AspectJ aspect needs to be at least munged by the aspectOf munger
						// if (debugOn) {
						// debug("weaving '" + name + "'");
						// }
						// bytes = getAtAspectJAspectBytes(name, bytes);

						// Add the weaved class to the cache only if there
						// has been an actual change
						// JVK: Is there a better way to check if the class has
						// been transformed without carrying up some value
						// from the depths?
						if (cacheKey != null) {
							// If no transform has been applied, mark the class
							// as ignored.
							if (Arrays.equals(original_bytes, bytes)) {
								cache.ignore(cacheKey, original_bytes);
							} else {
								cache.put(cacheKey, original_bytes, bytes);
							}
						}
					} else if (debugOn) {
						debug("not weaving '" + name + "'");
					}
				} else if (debugOn) {
					debug("cannot weave '" + name + "'");
				}
			} finally {
				delegateForCurrentClass = null;
			}

			if (trace.isTraceEnabled()) {
				trace.exit("weaveClass", bytes);
			}
			return bytes;
		} finally {
			weaverRunning.set(false);
		}
	}

	/**
	 * @param name
	 * @return true if even valid to weave: either with an accept check or to munge it for @AspectJ aspectof support
	 */
	private boolean couldWeave(String name, byte[] bytes) {
		return !generatedClasses.containsKey(name) && shouldWeaveName(name);
	}

	// ATAJ
	protected boolean accept(String name, byte[] bytes) {
		return true;
	}

	protected boolean shouldDump(String name, boolean before) {
		return false;
	}

	private boolean shouldWeaveName(String name) {
		if ("osj".indexOf(name.charAt(0)) != -1) {
			if ((weavingSpecialTypes & INITIALIZED) == 0) {
				weavingSpecialTypes |= INITIALIZED;
				// initialize it
				Properties p = weaver.getWorld().getExtraConfiguration();
				if (p != null) {
					boolean b = p.getProperty(World.xsetWEAVE_JAVA_PACKAGES, "false").equalsIgnoreCase("true");
					if (b) {
						weavingSpecialTypes |= WEAVE_JAVA_PACKAGE;
					}
					b = p.getProperty(World.xsetWEAVE_JAVAX_PACKAGES, "false").equalsIgnoreCase("true");
					if (b) {
						weavingSpecialTypes |= WEAVE_JAVAX_PACKAGE;
					}
				}
			}
			if (name.startsWith("org.aspectj.")) {
				return false;
			}
			if (name.startsWith("sun.reflect.")) {// JDK reflect
				return false;
			}
			if (name.startsWith("javax.")) {
				if ((weavingSpecialTypes & WEAVE_JAVAX_PACKAGE) != 0) {
					return true;
				} else {
					if (!haveWarnedOnJavax) {
						haveWarnedOnJavax = true;
						warn("javax.* types are not being woven because the weaver option '-Xset:weaveJavaxPackages=true' has not been specified");
					}
					return false;
				}
			}
			if (name.startsWith("java.")) {
				if ((weavingSpecialTypes & WEAVE_JAVA_PACKAGE) != 0) {
					return true;
				} else {
					return false;
				}
			}
		}
		// boolean should = !(name.startsWith("org.aspectj.")
		// || (name.startsWith("java.") && (weavingSpecialTypes & WEAVE_JAVA_PACKAGE) == 0)
		// || (name.startsWith("javax.") && (weavingSpecialTypes & WEAVE_JAVAX_PACKAGE) == 0)
		// // || name.startsWith("$Proxy")//JDK proxies//FIXME AV is that 1.3 proxy ? fe. ataspect.$Proxy0 is a java5 proxy...
		// || name.startsWith("sun.reflect."));
		return true;
	}

	/**
	 * We allow @AJ aspect weaving so that we can add aspectOf() as part of the weaving (and not part of the source compilation)
	 * 
	 * @param name
	 * @param bytes bytecode (from classloader), allow to NOT lookup stuff on disk again during resolve
	 * @return true if @Aspect
	 */
	private boolean shouldWeaveAnnotationStyleAspect(String name, byte[] bytes) {
		if (delegateForCurrentClass == null) {
			// if (weaver.getWorld().isASMAround()) return asmCheckAnnotationStyleAspect(bytes);
			// else
			ensureDelegateInitialized(name, bytes);
		}
		return (delegateForCurrentClass.isAnnotationStyleAspect());
	}

	// private boolean asmCheckAnnotationStyleAspect(byte[] bytes) {
	// IsAtAspectAnnotationVisitor detector = new IsAtAspectAnnotationVisitor();
	//
	// ClassReader cr = new ClassReader(bytes);
	// try {
	// cr.accept(detector, true);//, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
	// } catch (Exception spe) {
	// // if anything goes wrong, e.g., an NPE, then assume it's NOT an @AspectJ aspect...
	// System.err.println("Unexpected problem parsing bytes to discover @Aspect annotation");
	// spe.printStackTrace();
	// return false;
	// }
	//
	// return detector.isAspect();
	// }

	protected void ensureDelegateInitialized(String name, byte[] bytes) {
		if (delegateForCurrentClass == null) {
			BcelWorld world = (BcelWorld) weaver.getWorld();
			delegateForCurrentClass = world.addSourceObjectType(name, bytes, false);
		}
	}

	/**
	 * Weave a set of bytes defining a class.
	 * 
	 * @param name the name of the class being woven
	 * @param bytes the bytes that define the class
	 * @return byte[] the woven bytes for the class
	 * @throws IOException
	 */
	private byte[] getWovenBytes(String name, byte[] bytes) throws IOException {
		WeavingClassFileProvider wcp = new WeavingClassFileProvider(name, bytes);
		weaver.weave(wcp);
		return wcp.getBytes();
	}

	/**
	 * Weave a set of bytes defining a class for only what is needed to turn @AspectJ aspect in a usefull form ie with aspectOf
	 * method - see #113587
	 * 
	 * @param name the name of the class being woven
	 * @param bytes the bytes that define the class
	 * @return byte[] the woven bytes for the class
	 * @throws IOException
	 */
	private byte[] getAtAspectJAspectBytes(String name, byte[] bytes) throws IOException {
		WeavingClassFileProvider wcp = new WeavingClassFileProvider(name, bytes);
		wcp.setApplyAtAspectJMungersOnly();
		weaver.weave(wcp);
		return wcp.getBytes();
	}

	private void registerAspectLibraries(List aspectPath) {
		// System.err.println("? WeavingAdaptor.registerAspectLibraries(" + aspectPath + ")");
		for (Iterator i = aspectPath.iterator(); i.hasNext();) {
			String libName = (String) i.next();
			addAspectLibrary(libName);
		}

		weaver.prepareForWeave();
	}

	/*
	 * Register an aspect library with this classloader for use during weaving. This class loader will also return (unmodified) any
	 * of the classes in the library in response to a <code>findClass()</code> request. The library is not required to be on the
	 * weavingClasspath given when this classloader was constructed.
	 * 
	 * @param aspectLibraryJarFile a jar file representing an aspect library
	 * 
	 * @throws IOException
	 */
	private void addAspectLibrary(String aspectLibraryName) {
		File aspectLibrary = new File(aspectLibraryName);
		if (aspectLibrary.isDirectory() || (FileUtil.isZipFile(aspectLibrary))) {
			try {
				info("adding aspect library: '" + aspectLibrary + "'");
				weaver.addLibraryJarFile(aspectLibrary);
			} catch (IOException ex) {
				error("exception adding aspect library: '" + ex + "'");
			}
		} else {
			error("bad aspect library: '" + aspectLibrary + "'");
		}
	}

	private static List<String> makeClasspath(String cp) {
		List<String> ret = new ArrayList<String>();
		if (cp != null) {
			StringTokenizer tok = new StringTokenizer(cp, File.pathSeparator);
			while (tok.hasMoreTokens()) {
				ret.add(tok.nextToken());
			}
		}
		return ret;
	}

	protected boolean debug(String message) {
		return MessageUtil.debug(messageHandler, message);
	}

	protected boolean info(String message) {
		return MessageUtil.info(messageHandler, message);
	}

	protected boolean warn(String message) {
		return MessageUtil.warn(messageHandler, message);
	}

	protected boolean warn(String message, Throwable th) {
		return messageHandler.handleMessage(new Message(message, IMessage.WARNING, th, null));
	}

	protected boolean error(String message) {
		return MessageUtil.error(messageHandler, message);
	}

	protected boolean error(String message, Throwable th) {
		return messageHandler.handleMessage(new Message(message, IMessage.ERROR, th, null));
	}

	public String getContextId() {
		return "WeavingAdaptor";
	}

	/**
	 * Dump the given bytcode in _dump/... (dev mode)
	 * 
	 * @param name
	 * @param b
	 * @param before whether we are dumping before weaving
	 * @throws Throwable
	 */
	protected void dump(String name, byte[] b, boolean before) {
		String dirName = getDumpDir();

		if (before) {
			dirName = dirName + File.separator + "_before";
		}

		String className = name.replace('.', '/');
		final File dir;
		if (className.indexOf('/') > 0) {
			dir = new File(dirName + File.separator + className.substring(0, className.lastIndexOf('/')));
		} else {
			dir = new File(dirName);
		}
		dir.mkdirs();
		String fileName = dirName + File.separator + className + ".class";
		try {
			// System.out.println("WeavingAdaptor.dump() fileName=" + new File(fileName).getAbsolutePath());
			FileOutputStream os = new FileOutputStream(fileName);
			os.write(b);
			os.close();
		} catch (IOException ex) {
			warn("unable to dump class " + name + " in directory " + dirName, ex);
		}
	}

	/**
	 * @return the directory in which to dump - default is _ajdump but it
	 */
	protected String getDumpDir() {
		return "_ajdump";
	}

	/**
	 * Processes messages arising from weaver operations. Tell weaver to abort on any message more severe than warning.
	 */
	protected class WeavingAdaptorMessageHolder extends MessageHandler {

		private IMessageHandler delegate;
		private List<IMessage> savedMessages;

		protected boolean traceMessages = Boolean.getBoolean(TRACE_MESSAGES_PROPERTY);

		public WeavingAdaptorMessageHolder(PrintWriter writer) {

			this.delegate = new WeavingAdaptorMessageWriter(writer);
			super.dontIgnore(IMessage.WEAVEINFO);
		}

		private void traceMessage(IMessage message) {
			if (message instanceof WeaveMessage) {
				trace.debug(render(message));
			} else if (message.isDebug()) {
				trace.debug(render(message));
			} else if (message.isInfo()) {
				trace.info(render(message));
			} else if (message.isWarning()) {
				trace.warn(render(message), message.getThrown());
			} else if (message.isError()) {
				trace.error(render(message), message.getThrown());
			} else if (message.isFailed()) {
				trace.fatal(render(message), message.getThrown());
			} else if (message.isAbort()) {
				trace.fatal(render(message), message.getThrown());
			} else {
				trace.error(render(message), message.getThrown());
			}
		}

		protected String render(IMessage message) {
			return "[" + getContextId() + "] " + message.toString();
		}

		public void flushMessages() {
			if (savedMessages == null) {
				savedMessages = new ArrayList<IMessage>();
				savedMessages.addAll(super.getUnmodifiableListView());
				clearMessages();
				for (IMessage message : savedMessages) {
					delegate.handleMessage(message);
				}
			}
			// accumulating = false;
			// messages.clear();
		}

		public void setDelegate(IMessageHandler messageHandler) {
			delegate = messageHandler;
		}

		/*
		 * IMessageHandler
		 */

		@Override
		public boolean handleMessage(IMessage message) throws AbortException {
			if (traceMessages) {
				traceMessage(message);
			}

			super.handleMessage(message);

			if (abortOnError && 0 <= message.getKind().compareTo(IMessage.ERROR)) {
				throw new AbortException(message);
			}
			// if (accumulating) {
			// boolean result = addMessage(message);
			// if (abortOnError && 0 <= message.getKind().compareTo(IMessage.ERROR)) {
			// throw new AbortException(message);
			// }
			// return result;
			// }
			// else return delegate.handleMessage(message);

			if (savedMessages != null) {
				delegate.handleMessage(message);
			}
			return true;
		}

		@Override
		public boolean isIgnoring(Kind kind) {
			return delegate.isIgnoring(kind);
		}

		@Override
		public void dontIgnore(IMessage.Kind kind) {
			if (null != kind && delegate != null) {
				delegate.dontIgnore(kind);
			}
		}

		@Override
		public void ignore(Kind kind) {
			if (null != kind && delegate != null) {
				delegate.ignore(kind);
			}
		}

		/*
		 * IMessageHolder
		 */

		@Override
		public List<IMessage> getUnmodifiableListView() {
			// System.err.println("? WeavingAdaptorMessageHolder.getUnmodifiableListView() savedMessages=" + savedMessages);
			List<IMessage> allMessages = new ArrayList<IMessage>();
			allMessages.addAll(savedMessages);
			allMessages.addAll(super.getUnmodifiableListView());
			return allMessages;
		}
	}

	protected class WeavingAdaptorMessageWriter extends MessageWriter {

		private final Set<IMessage.Kind> ignoring = new HashSet<IMessage.Kind>();
		private final IMessage.Kind failKind;

		public WeavingAdaptorMessageWriter(PrintWriter writer) {
			super(writer, true);

			ignore(IMessage.WEAVEINFO);
			ignore(IMessage.DEBUG);
			ignore(IMessage.INFO);
			this.failKind = IMessage.ERROR;
		}

		@Override
		public boolean handleMessage(IMessage message) throws AbortException {
			// boolean result =
			super.handleMessage(message);
			if (abortOnError && 0 <= message.getKind().compareTo(failKind)) {
				throw new AbortException(message);
			}
			return true;
		}

		@Override
		public boolean isIgnoring(Kind kind) {
			return ((null != kind) && (ignoring.contains(kind)));
		}

		/**
		 * Set a message kind to be ignored from now on
		 */
		@Override
		public void ignore(IMessage.Kind kind) {
			if ((null != kind) && (!ignoring.contains(kind))) {
				ignoring.add(kind);
			}
		}

		/**
		 * Remove a message kind from the list of those ignored from now on.
		 */
		@Override
		public void dontIgnore(IMessage.Kind kind) {
			if (null != kind) {
				ignoring.remove(kind);
			}
		}

		@Override
		protected String render(IMessage message) {
			return "[" + getContextId() + "] " + super.render(message);
		}
	}

	private class WeavingClassFileProvider implements IClassFileProvider {

		private final UnwovenClassFile unwovenClass;
		private final List<UnwovenClassFile> unwovenClasses = new ArrayList<UnwovenClassFile>();
		private IUnwovenClassFile wovenClass;
		private boolean isApplyAtAspectJMungersOnly = false;

		public WeavingClassFileProvider(String name, byte[] bytes) {
			ensureDelegateInitialized(name, bytes);
			this.unwovenClass = new UnwovenClassFile(name, delegateForCurrentClass.getResolvedTypeX().getName(), bytes);
			this.unwovenClasses.add(unwovenClass);

			if (shouldDump(name.replace('/', '.'), true)) {
				dump(name, bytes, true);
			}

		}

		public void setApplyAtAspectJMungersOnly() {
			isApplyAtAspectJMungersOnly = true;
		}

		public boolean isApplyAtAspectJMungersOnly() {
			return isApplyAtAspectJMungersOnly;
		}

		public byte[] getBytes() {
			if (wovenClass != null) {
				return wovenClass.getBytes();
			} else {
				return unwovenClass.getBytes();
			}
		}

		public Iterator<UnwovenClassFile> getClassFileIterator() {
			return unwovenClasses.iterator();
		}

		public IWeaveRequestor getRequestor() {
			return new IWeaveRequestor() {

				public void acceptResult(IUnwovenClassFile result) {
					if (wovenClass == null) {
						wovenClass = result;
						String name = result.getClassName();
						if (shouldDump(name.replace('/', '.'), false)) {
							dump(name, result.getBytes(), false);
						}
					} else {
						// Classes generated by weaver e.g. around closure advice
						String className = result.getClassName();
						byte[]	resultBytes = result.getBytes();

						if (SimpleCacheFactory.isEnabled()) {
							SimpleCache lacache=SimpleCacheFactory.createSimpleCache();
							lacache.put(result.getClassName(), wovenClass.getBytes(), result.getBytes());
							lacache.addGeneratedClassesNames(wovenClass.getClassName(), wovenClass.getBytes(), result.getClassName());
						}

						generatedClasses.put(className, result);
						generatedClasses.put(wovenClass.getClassName(), result);
						generatedClassHandler.acceptClass(className, null, resultBytes);
					}
				}

				public void processingReweavableState() {
				}

				public void addingTypeMungers() {
				}

				public void weavingAspects() {
				}

				public void weavingClasses() {
				}

				public void weaveCompleted() {
					// ResolvedType.resetPrimitives();
					if (delegateForCurrentClass != null) {
						delegateForCurrentClass.weavingCompleted();
					}
					// ResolvedType.resetPrimitives();
					// bcelWorld.discardType(typeBeingProcessed.getResolvedTypeX()); // work in progress
				}
			};
		}
	}

	public void setActiveProtectionDomain(ProtectionDomain protectionDomain) {
		activeProtectionDomain = protectionDomain;
	}
}