/**********************************************************************
 Copyright (c) 2002, 2005 IBM Corporation and others.
 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:
 Adrian Colyer, Andy Clement, Tracy Gardner - initial version
 Helen Hawkins - updated for new ajde interface (bug 148190)
 ...
 **********************************************************************/
package org.eclipse.ajdt.ui;

// --- imports ---

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;

import org.eclipse.ajdt.core.AJLog;
import org.eclipse.ajdt.core.AspectJPlugin;
import org.eclipse.ajdt.core.EclipseVersion;
import org.eclipse.ajdt.core.builder.AJBuilder;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnitManager;
import org.eclipse.ajdt.internal.builder.UIBuildListener;
import org.eclipse.ajdt.internal.core.ajde.ICompilerFactory;
import org.eclipse.ajdt.internal.javamodel.AJCompilationUnitResourceChangeListener;
import org.eclipse.ajdt.internal.ui.EnsureAJBuilder;
import org.eclipse.ajdt.internal.ui.ajde.UICompilerFactory;
import org.eclipse.ajdt.internal.ui.editor.AspectJTextTools;
import org.eclipse.ajdt.internal.ui.lazystart.Utils;
import org.eclipse.ajdt.internal.ui.preferences.AJCompilerPreferencePage;
import org.eclipse.ajdt.internal.ui.preferences.AspectJPreferences;
import org.eclipse.ajdt.internal.ui.text.UIMessages;
import org.eclipse.ajdt.internal.ui.tracing.EventTraceLogger;
import org.eclipse.ajdt.internal.utils.AJDTUtils;
import org.eclipse.contribution.jdt.itdawareness.ITDAwarenessAspect;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.text.templates.persistence.TemplatePersistenceData;
import org.eclipse.jface.text.templates.persistence.TemplateReaderWriter;
import org.eclipse.jface.text.templates.persistence.TemplateStore;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.internal.UIPlugin;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;

// --- end imports ---
/**
 * The main plugin class used in the desktop for AspectJ integration.
 * <p>
 * The AspectJUIPlugin (org.eclipse.ajdt.ui) provides the user interface and build
 * integration to enable use of Aspect-Oriented Software Development (AOSD)
 * using AspectJ within Eclipse.
 * </p>
 * <p>
 * This plugin depends on the org.aspectj.ajde (AJDE) plugin for the AspectJ
 * compiler and tools. The AJTools plugin is available from <a
 * href="http://www.aspectj.org">aspectj.org </a>
 */
public class AspectJUIPlugin extends org.eclipse.ui.plugin.AbstractUIPlugin {

	// the id of this plugin
	public static final String PLUGIN_ID = Utils.PLUGIN_ID; 

	public static final String ID_OUTLINE = PLUGIN_ID + ".ajoutlineview"; //$NON-NLS-1$

	private static final String AJDE_VERSION_KEY_PREVIOUS = "ajde.version.at.previous.startup"; //$NON-NLS-1$

	public static final String VISUALISER_ID = "org.eclipse.contribution.visualiser";  //$NON-NLS-1$

	private static final String XREF_CORE_ID = "org.eclipse.contribution.xref.core";  //$NON-NLS-1$

	private static final String XREF_UI_ID = "org.eclipse.contribution.xref.ui";  //$NON-NLS-1$

	/**
	 * Whether to use the Visualiser component
	 */
	public static boolean usingVisualiser = true;

	/**
	 * Whether to use the Cross References component
	 */
	public static boolean usingXref = true;

	/**
	 * shared single instance of the plugin
	 */
	private static AspectJUIPlugin plugin;

	/**
	 * The text tools to use for AspectJ aware editing
	 */
	private AspectJTextTools aspectJTextTools;

	/**
	 * The workbench Display for use by asynchronous UI updates
	 */
	private Display display;
	
	/**
	 * Listens for AspectJ builds
	 */
	private UIBuildListener buildListener;

	// custom attributes AJDT markers can have
	public static final String SOURCE_LOCATION_ATTRIBUTE = "sourceLocationOfAdvice"; //$NON-NLS-1$

	public static final String RELATED_LOCATIONS_ATTRIBUTE_PREFIX = "relatedLocations-"; //$NON-NLS-1$

	public static final String ACCKIND_ATTRIBUTE = "acckind"; //$NON-NLS-1$
	
	public static final int PROGRESS_MONITOR_MAX = 100;

    private EnsureAJBuilder ajProjectListener;

    /**
     * Creates an AspectJPlugin instance and initializes the supporting Ajde
     * tools - Compatible with Eclipse 3.0. Note the rest of the contents of the
     * 2.x constructor now resides in the start(BundleContext) method.
     */
    public AspectJUIPlugin() {
        super();
        AspectJUIPlugin.plugin = this;
    }

    /**
	 * Return the single default instance of this plugin
	 */
	public static AspectJUIPlugin getDefault() {
		return plugin;
	}

	/**
	 * Converts the given project to be an AspectJ project
	 * @param project
	 * @throws CoreException
	 */
	public static void convertToAspectJProject(IProject project) throws CoreException {
		AJDTUtils.addAspectJNature(project, false);
	}

	/**
	 * Removes the AspectJ capability from a project
	 * @param project
	 * @throws CoreException
	 */
	public static void convertFromAspectJProject(IProject project) throws CoreException {
		AJDTUtils.removeAspectJNature(project);
	}


	/**
	 * This function checks to see if the workbench is starting with a new
	 * version of AJDE (it persists the version previously used by the workbench
	 * in a preference) - if a new version of AJDE is in use then it goes
	 * through the projects and for any AspectJ project, it tries to ensure the
	 * path to any aspectjrt.jar that it references is correct. This fixes a
	 * migration issue we have where the path includes the AJDE version -
	 * without this method you have to do it manually by either editing the
	 * aspectjrt.jar entry or removing/readding the nature. We also add the
	 * AspectJ code templates here.
	 * 
	 */
	private void checkAspectJVersion() {
		IPreferenceStore store = AspectJUIPlugin.getDefault()
				.getPreferenceStore();

		// Version of AJDE now installed.
		String currentAjdeVersion = UIMessages.ajde_version;

		// Version that the projects in this workspace used on the previous
		// execution of eclipse.
		String previousAjdeVersion = store.getString(AJDE_VERSION_KEY_PREVIOUS);
		if (previousAjdeVersion == null
				|| !currentAjdeVersion.equals(previousAjdeVersion)) {
			AJLog.log("New version of AJDE detected (now:" //$NON-NLS-1$
					+ currentAjdeVersion
					+ ") - checking aspectjrt.jar for each project."); //$NON-NLS-1$

			IProject[] projects = AspectJPlugin.getWorkspace().getRoot()
					.getProjects();
			for (int i = 0; i < projects.length; i++) {
				if (projects[i].isOpen()) {
					IProject current = projects[i];
					if (AspectJPlugin.isAJProject(current)) {
						AJDTUtils.verifyAjrtVersion(current);
					}
				}
			}

			checkTemplatesInstalled();
			checkProblemMarkersVisible();
			store.putValue(AJDE_VERSION_KEY_PREVIOUS, currentAjdeVersion);
		}
	}

	private void checkProblemMarkersVisible() {
		String TAG_DIALOG_SECTION = "org.eclipse.ui.views.problem"; //$NON-NLS-1$
		String problemMarker = "org.eclipse.ajdt.ui.problemmarker:"; //$NON-NLS-1$
		AbstractUIPlugin plugin = UIPlugin.getDefault();
		IDialogSettings workbenchSettings = plugin.getDialogSettings();
		IDialogSettings settings = workbenchSettings
				.getSection(TAG_DIALOG_SECTION);
		if (settings != null) {
			IDialogSettings filterSettings = settings.getSection("filter"); //$NON-NLS-1$
			if (filterSettings != null) {
				String enabledMarkers = filterSettings.get("selectedType"); //$NON-NLS-1$
				if ((enabledMarkers != null)
						&& enabledMarkers.indexOf(problemMarker) == -1) {
					enabledMarkers = enabledMarkers + problemMarker;
					filterSettings.put("selectedType", enabledMarkers); //$NON-NLS-1$
				}
			}
		}
	}

	/**
	 * Install the AspectJ code templates. We'd like to do this by an extension
	 * point, but there doesn't seem to be one.
	 */
	private void checkTemplatesInstalled() {
	    TemplateStore codeTemplates = null;
	    try {
    		codeTemplates = JavaPlugin.getDefault()
    				.getTemplateStore();
	    } catch (Exception e) {
	        // a problem occurred while loading templates (Bug 259033)
	        // just ignore and try the next time.
	        return;
	    }
		// bug 90791: don't add templates if they are already there
		// bug 125998: using pertypewithin because it was the most recently added
		if (codeTemplates.findTemplate("pertypewithin") == null) { //$NON-NLS-1$
			try {
				URL loc = getBundle().getEntry("/aspectj_code_templates.xml"); //$NON-NLS-1$
				TemplateReaderWriter trw = new TemplateReaderWriter();
				TemplatePersistenceData[] templates = trw.read(
						loc.openStream(), null);
				if ((templates == null) || (templates.length == 0)) {
					AJLog.log(UIMessages.codeTemplates_couldNotLoad);
				} else {
					for (int i = 0; i < templates.length; i++) {
						// bug 125998: Check that the individual template has not already been added
						if (codeTemplates.findTemplate(templates[i].getTemplate().getName()) == null) {
							codeTemplates.add(templates[i]);
						}
					}
					codeTemplates.save();
				}
			} catch (IOException fnf) {
				AJLog.log(UIMessages.codeTemplates_couldNotLoad);
			}
		}
	}
	
	public void start(BundleContext context) throws Exception {
		super.start(context);

		// listen for builds of AJ projects
		buildListener = new UIBuildListener();
        AJBuilder.addAJBuildListener(buildListener);
		
		// BUG 23955. getCurrent() returned null if invoked from a menu.
		display = Display.getDefault();
		
		// BUG 249045 and BUG 261045 don't do this any more
//		if (!AspectJPlugin.USING_CU_PROVIDER) {
//		    insertAJCompilationUnitDocumentProvider();
//		}
		

		// Create and register the resource change listener if necessary, it
		// will be notified if resources are added/deleted or their content changed.

		// listener for aspectj model
		AspectJPlugin.getWorkspace().addResourceChangeListener(
				new AJCompilationUnitResourceChangeListener(),
				IResourceChangeEvent.PRE_CLOSE
						| IResourceChangeEvent.PRE_DELETE
						| IResourceChangeEvent.POST_CHANGE
						| IResourceChangeEvent.PRE_BUILD);
		
		// set the UI version of core operations
		AspectJPlugin.getDefault().setAJLogger(new EventTraceLogger());
		
		// set the compiler factory to be the ui one
		setCompilerFactory(new UICompilerFactory());
		
        ajProjectListener = new EnsureAJBuilder();
        AspectJPlugin.getWorkspace().addResourceChangeListener(ajProjectListener, IResourceChangeEvent.PRE_BUILD);

		checkEclipseVersion();

		// 126728: don't try to use the Visualiser / Xref components if the
		// required plugins are not available
		if (Platform.getBundle(VISUALISER_ID)==null) {
			usingVisualiser = false;
		}
		if ((Platform.getBundle(XREF_CORE_ID)==null)
				|| (Platform.getBundle(XREF_UI_ID)==null)) {
			usingXref = false;
		}
		
		checkAspectJVersion();

		if (!AspectJPlugin.USING_CU_PROVIDER) {
		    Job cuInitJob = new Job("Initialize CompilationUnit Manager") {
                protected IStatus run(IProgressMonitor monitor) {
                    // bug 278425 --- see if m2eclipse exists and ensure it is started before continuing
                    startM2Eclipse();
                    AJCompilationUnitManager.INSTANCE
                            .initCompilationUnits(AspectJPlugin.getWorkspace());
                    AJDTUtils.refreshPackageExplorer();
                    return Status.OK_STATUS;
                }
            };
            
            cuInitJob.setPriority(Job.SHORT);
            cuInitJob.schedule();
		    
		} else {
		    ITDAwarenessAspect.provider = new AJDTNameEnvironmentProvider();
		}
	}
	
	private void startM2Eclipse() {
	    Bundle m2eclipseBundle = Platform.getBundle("org.maven.ide.eclipse");
	    if (m2eclipseBundle != null) {
	        try {
	            // start bundle, but don't force bundle to be restarted next time
	            m2eclipseBundle.start(Bundle.START_TRANSIENT);
	        } catch (BundleException e) {
	            // bundle couldn't be started for some reason
	        }
	    }
	}

    public void stop(BundleContext context) throws Exception {
	    super.stop(context);
	    AspectJPlugin.getWorkspace().removeResourceChangeListener(ajProjectListener);
	    AJBuilder.removeAJBuildListener(buildListener);
	}
	

	/**
	 * use reflection to insert our own
	 * document provider so that 
	 * AJ CUs are properly created from .aj files (sometimes)
	 * Only need if JDT Weaving is turned off
	 * If weaving is on, then this happens automatically from the weaver
	 * 
	 * XXX this method is not used any more...can delete
	 */
//	private void insertAJCompilationUnitDocumentProvider() {
//	    try {
//	        
//	        Field javaPluginDocumentProviderField = JavaPlugin.class.getDeclaredField("fCompilationUnitDocumentProvider");
//	        javaPluginDocumentProviderField.setAccessible(true);
//	        ICompilationUnitDocumentProvider oldProvider = (ICompilationUnitDocumentProvider) javaPluginDocumentProviderField.get(JavaPlugin.getDefault());
//	        if (oldProvider != null) {
//	            oldProvider.shutdown();
//	        }
//	        ICompilationUnitDocumentProvider newProvider = new AJCompilationUnitDocumentProvider();
//	        javaPluginDocumentProviderField.set(JavaPlugin.getDefault(), newProvider);
//
//            WorkingCopyManager manager = (WorkingCopyManager) JavaPlugin.getDefault().getWorkingCopyManager();
//            Field managerDocumentProviderField = manager.getClass().getDeclaredField("fDocumentProvider");
//            managerDocumentProviderField.setAccessible(true);
//            oldProvider = (ICompilationUnitDocumentProvider) managerDocumentProviderField.get(manager);
//            if (! (oldProvider instanceof AJCompilationUnitDocumentProvider)) {
//                oldProvider.shutdown();
//                managerDocumentProviderField.set(manager, newProvider);
//            }
//        } catch (SecurityException e) {
//        } catch (IllegalArgumentException e) {
//        } catch (NoSuchFieldException e) {
//        } catch (IllegalAccessException e) {
//        }
//    }
	
	private void checkEclipseVersion() {
		Bundle bundle = Platform.getBundle("org.eclipse.jdt.core"); //$NON-NLS-1$
		String versionStr = (String) bundle.getHeaders().get(
				Constants.BUNDLE_VERSION);
		Version version = new Version(versionStr);
		if ((version.getMajor() != EclipseVersion.MAJOR_VERSION)
				|| (version.getMinor() != EclipseVersion.MINOR_VERSION)) {
			MessageDialog.openError(null,
					UIMessages.ajdtErrorDialogTitle,
					NLS.bind(UIMessages.wrong_eclipse_version,
							new String[] {
									EclipseVersion.MAJOR_VERSION + "." //$NON-NLS-1$
											+ EclipseVersion.MINOR_VERSION,
									version.getMajor() + "." //$NON-NLS-1$
											+ version.getMinor() }));
		}
	}

	/**
	 * get the active window in the workbench, or null if no window is active
	 */
	public IWorkbenchWindow getActiveWorkbenchWindow() {
		return plugin.getWorkbench().getActiveWorkbenchWindow();

	}

	/**
	 * get the main workbench display
	 */
	public Display getDisplay() {
		return display;
	}

	/**
	 * get the text tools to be used by the AspectJ editor
	 */
	public AspectJTextTools getAspectJTextTools() {
		IPreferenceStore textToolPreferences;

		if (aspectJTextTools == null) {
			// text tools deliberately use the JavaPlugin settings
			textToolPreferences = JavaPlugin.getDefault().getPreferenceStore();
			aspectJTextTools = new AspectJTextTools(textToolPreferences);
		}

		return aspectJTextTools;
	}

	/**
	 * initialize the default preferences for this plugin
	 */
	protected void initializeDefaultPreferences(IPreferenceStore store) {
		AspectJPreferences.initDefaults(store);
		AJCompilerPreferencePage.initDefaults(store);
	}

	/**
	 * initialize the plug-in image registry
	 */
	protected void initializeImageRegistry(ImageRegistry reg) {
	}

	// Implementation of ISelectionListener follows
	
	/**
	 * Attempt to update the project's build classpath with the AspectJ runtime
	 * library.
	 * 
	 * @param project
	 */
	public static void addAjrtToBuildPath(IProject project) {
		IJavaProject javaProject = JavaCore.create(project);
		try {
			IClasspathEntry[] originalCP = javaProject.getRawClasspath();
			IPath ajrtPath = new Path(AspectJPlugin.ASPECTJRT_CONTAINER);
			boolean found = false;
			for (int i = 0; i < originalCP.length; i++) {
                if (originalCP[i].getPath().equals(ajrtPath)) {
                    found = true;
                    break;
                }
            }
			if (!found) {
    			IClasspathEntry ajrtLIB = JavaCore.newContainerEntry(
    					ajrtPath, false);
    			// Update the raw classpath with the new ajrtCP entry.
    			int originalCPLength = originalCP.length;
    			IClasspathEntry[] newCP = new IClasspathEntry[originalCPLength + 1];
    			System.arraycopy(originalCP, 0, newCP, 0, originalCPLength);
    			newCP[originalCPLength] = ajrtLIB;
    			javaProject.setRawClasspath(newCP, new NullProgressMonitor());
			}
		} catch (JavaModelException e) {
		}
	}

	/**
	 * Attempt to update the project's build classpath by removing any occurance
	 * of the AspectJ runtime library.
	 * 
	 * @param project
	 */
	public static void removeAjrtFromBuildPath(IProject project) {
		IJavaProject javaProject = JavaCore.create(project);
		try {
			IClasspathEntry[] originalCP = javaProject.getRawClasspath();
			ArrayList tempCP = new ArrayList();

			// Go through each current classpath entry one at a time. If it
			// is not a reference to the aspectjrt.jar then do not add it
			// to the collection of new classpath entries.
			for (int i = 0; i < originalCP.length; i++) {
				IPath path = originalCP[i].getPath();
				boolean keep = true;
				if (path.toOSString().endsWith("ASPECTJRT_LIB") //$NON-NLS-1$
						|| path.toOSString().endsWith("aspectjrt.jar")) { //$NON-NLS-1$
					keep = false;
				}
				if (originalCP[i].getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
					if (path.segment(0).equals(AspectJPlugin.ASPECTJRT_CONTAINER)) {
						keep = false;
					}
				}
				if (keep) {			
					tempCP.add(originalCP[i]);
				}
			}// end for

			// Set the classpath with only those elements that survived the
			// above filtration process.
			if (originalCP.length != tempCP.size()) {
				IClasspathEntry[] newCP = (IClasspathEntry[]) tempCP
						.toArray(new IClasspathEntry[tempCP.size()]);
				javaProject.setRawClasspath(newCP, new NullProgressMonitor());
			}// end if at least one classpath element removed
		} catch (JavaModelException e) {
		}
	}
	
	public ICompilerFactory getCompilerFactory() {
		return AspectJPlugin.getDefault().getCompilerFactory();
	}

	public void setCompilerFactory(ICompilerFactory compilerFactory) {
		AspectJPlugin.getDefault().setCompilerFactory(compilerFactory);
	}

}