content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.wizards.buildpaths;
  
  import java.util.ArrayList;
  
  import org.eclipse.core.resources.ResourcesPlugin;
  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.Status;
  import org.eclipse.jface.dialogs.MessageDialog;
  import org.eclipse.swt.widgets.Shell;
  import org.eclipse.wst.jsdt.core.JsGlobalScopeContainerInitializer;
  import org.eclipse.wst.jsdt.core.IIncludePathAttribute;
  import org.eclipse.wst.jsdt.core.IJsGlobalScopeContainer;
  import org.eclipse.wst.jsdt.core.IIncludePathEntry;
  import org.eclipse.wst.jsdt.core.IJavaScriptModel;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.wizards.NewWizardMessages;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  
  /**
   *
   */
  public class BuildPathSupport {
  	
  	public static final String JRE_PREF_PAGE_ID= "org.eclipse.wst.jsdt.debug.ui.preferences.VMPreferencePage"; //$NON-NLS-1$
  
  	
  	private BuildPathSupport() {
  		super();
  	}
  	
  	/**
  	 * Returns a deprecation message for a classpath variable name.
  	 * 
  	 * @param variableName classpath variable name
  	 * @return the deprecation message, or <code>null</code> iff
  	 *         <code>variableName</code> is not a classpath variable or the
  	 *         variable is not deprecated
  	 */
  	public static String getDeprecationMessage(String variableName) {
  		String deprecationMessage= JavaScriptCore.getIncludepathVariableDeprecationMessage(variableName);
  		if (deprecationMessage == null	)
  			return null;
  		else
  			return Messages.format(NewWizardMessages.BuildPathSupport_deprecated,
  					new Object[] {variableName, deprecationMessage});
  	}
  
  	/**
  	 * Finds a source attachment for a new archive in the existing classpaths.
  	 * @param elem The new classpath entry
  	 * @return A path to be taken for the source attachment or <code>null</code>
  	 */
  	public static IPath guessSourceAttachment(CPListElement elem) {
  		if (elem.getEntryKind() == IIncludePathEntry.CPE_CONTAINER) {
  			return null;
  		}
  		IJavaScriptProject currProject= elem.getJavaProject(); // can be null
  		try {
  			// try if the jar itself contains the source
  			IJavaScriptModel jmodel= JavaScriptCore.create(ResourcesPlugin.getWorkspace().getRoot());
  			IJavaScriptProject[] jprojects= jmodel.getJavaScriptProjects();
  			for (int i= 0; i < jprojects.length; i++) {
  				IJavaScriptProject curr= jprojects[i];
  				if (!curr.equals(currProject)) {
  					IIncludePathEntry[] entries= curr.getRawIncludepath();
  					for (int k= 0; k < entries.length; k++) {
  						IIncludePathEntry entry= entries[k];
  						if (entry.getEntryKind() == elem.getEntryKind()
  							&& entry.getPath().equals(elem.getPath())) {
  							IPath attachPath= entry.getSourceAttachmentPath();
  							if (attachPath != null && !attachPath.isEmpty()) {
  								return attachPath;
  							}
  						}
  					}
  				}
  			}
  		} catch (JavaScriptModelException e) {
  			JavaScriptPlugin.log(e.getStatus());
  		}
  		return null;
  	}
  	
  	/**
  	 * Finds a javadoc location for a new archive in the existing classpaths.
  	 * @param elem The new classpath entry
  	 * @return A javadoc location found in a similar classpath entry or <code>null</code>.
  	 */
  	public static String guessJavadocLocation(CPListElement elem) {
  		if (elem.getEntryKind() == IIncludePathEntry.CPE_CONTAINER) {
  			return null;
  		}
  		IJavaScriptProject currProject= elem.getJavaProject(); // can be null
  		try {
  			// try if the jar itself contains the source
  			IJavaScriptModel jmodel= JavaScriptCore.create(ResourcesPlugin.getWorkspace().getRoot());
  			IJavaScriptProject[] jprojects= jmodel.getJavaScriptProjects();
  			for (int i= 0; i < jprojects.length; i++) {
  				IJavaScriptProject curr= jprojects[i];
  				if (!curr.equals(currProject)) {
  					IIncludePathEntry[] entries= curr.getRawIncludepath();
  					for (int k= 0; k < entries.length; k++) {
  						IIncludePathEntry entry= entries[k];
  						if (entry.getEntryKind() == elem.getEntryKind() && entry.getPath().equals(elem.getPath())) {
  							IIncludePathAttribute[] attributes= entry.getExtraAttributes();
  							for (int n= 0; n < attributes.length; n++) {
  								IIncludePathAttribute attrib= attributes[n];
  								if (IIncludePathAttribute.JSDOC_LOCATION_ATTRIBUTE_NAME.equals(attrib.getName())) {
  									return attrib.getValue();
  								}
  							}
  						}
  					}
  				}
  			}
  		} catch (JavaScriptModelException e) {
  			JavaScriptPlugin.log(e.getStatus());
  		}
  		return null;
  	}
  	
  	private static class UpdatedJsGlobalScopeContainer implements IJsGlobalScopeContainer {
  
  		private IIncludePathEntry[] fNewEntries;
  		private IJsGlobalScopeContainer fOriginal;
  
  		public UpdatedJsGlobalScopeContainer(IJsGlobalScopeContainer original, IIncludePathEntry[] newEntries) {
  			fNewEntries= newEntries;
  			fOriginal= original;
  		}
  
  		/**
  		 * @deprecated Use {@link #getIncludepathEntries()} instead
  		 */
  		public IIncludePathEntry[] getClasspathEntries() {
  			return getIncludepathEntries();
  		}
  
  		public IIncludePathEntry[] getIncludepathEntries() {
  			return fNewEntries;
  		}
  
  		public String getDescription() {
  			return fOriginal.getDescription();
  		}
  
  		public int getKind() {
  			return fOriginal.getKind();
  		}
  
  		public IPath getPath() {
  			return fOriginal.getPath();
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.wst.jsdt.core.IJsGlobalScopeContainer#resolvedLibraryImport(java.lang.String)
  		 */
  		public String[] resolvedLibraryImport(String a) {
  				return null;
  		}
  	}
  
  	/**
  	 * Apply a modified classpath entry to the classpath. The classpath entry can also be from a classpath container.
  	 * @param shell If not null and the entry could not be found on the projects classpath, a dialog will ask to put the entry on the classpath
  	 * @param newEntry The modified entry. The entry's kind or path must be unchanged.
  	 * @param changedAttributes The attibutes that have changed. See {@link CPListElement} for constants values.
  	 * @param jproject Project where the entry belongs to
  	 * @param containerPath The path of the entry's parent container or <code>null</code> if the entry is not in a container
  	 * @param monitor The progress monitor to use
  	 * @throws CoreException
  	 */
  	public static void modifyClasspathEntry(Shell shell, IIncludePathEntry newEntry, String[] changedAttributes, IJavaScriptProject jproject, IPath containerPath, IProgressMonitor monitor) throws CoreException {
  		if (containerPath != null) {
  			updateContainerClasspath(jproject, containerPath, newEntry, changedAttributes, monitor);
  		} else {
  			updateProjectClasspath(shell, jproject, newEntry, changedAttributes, monitor);
  		}
  	}
  	
  	
  	/**
  	 * Apply a modified classpath entry to the classpath. The classpath entry can also be from a classpath container.
  	 * @param shell If not null and the entry could not be found on the projects classpath, a dialog will ask to put the entry on the classpath
  	 * @param newEntry The modified entry. The entry's kind or path must be unchanged.
  	 * @param jproject Project where the entry belongs to
  	 * @param containerPath The path of the entry's parent container or <code>null</code> if the entry is not in a container
  	 * @param monitor The progress monitor to use
  	 * @throws CoreException
  	 */
  	public static void modifyClasspathEntry(Shell shell, IIncludePathEntry newEntry, IJavaScriptProject jproject, IPath containerPath, IProgressMonitor monitor) throws CoreException {
  		modifyClasspathEntry(shell, newEntry, null, jproject, containerPath, monitor);
  	}
  
  	private static void updateContainerClasspath(IJavaScriptProject jproject, IPath containerPath, IIncludePathEntry newEntry, String[] changedAttributes, IProgressMonitor monitor) throws CoreException {
  		IJsGlobalScopeContainer container= JavaScriptCore.getJsGlobalScopeContainer(containerPath, jproject);
  		if (container == null) {
  			throw new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, IStatus.ERROR, "Container " + containerPath + " cannot be resolved", null));  //$NON-NLS-1$//$NON-NLS-2$
  		}
  		IIncludePathEntry[] entries= container.getIncludepathEntries();
  		IIncludePathEntry[] newEntries= new IIncludePathEntry[entries.length];
  		for (int i= 0; i < entries.length; i++) {
  			IIncludePathEntry curr= entries[i];
  			if (curr.getEntryKind() == newEntry.getEntryKind() && curr.getPath().equals(newEntry.getPath())) {
  				newEntries[i]= getUpdatedEntry(curr, newEntry, changedAttributes, jproject);
  			} else {
  				newEntries[i]= curr;
  			}
  		}
  		requestContainerUpdate(jproject, container, newEntries);
  		monitor.worked(1);
  	}
  
  	private static IIncludePathEntry getUpdatedEntry(IIncludePathEntry currEntry, IIncludePathEntry updatedEntry, String[] updatedAttributes, IJavaScriptProject jproject) {
  		if (updatedAttributes == null) {
  			return updatedEntry; // used updated entry 'as is'
  		}
  		CPListElement currElem= CPListElement.createFromExisting(currEntry, jproject);
  		CPListElement newElem= CPListElement.createFromExisting(updatedEntry, jproject);
  		for (int i= 0; i < updatedAttributes.length; i++) {
  			String attrib= updatedAttributes[i];
  			currElem.setAttribute(attrib, newElem.getAttribute(attrib));
  		}
  		return currElem.getClasspathEntry();
  	}
  
  	/**
  	 * Request a container update.
  	 * @param jproject The project of the container
  	 * @param container The container to requesta  change to
  	 * @param newEntries The updated entries
  	 * @throws CoreException
  	 */
  	public static void requestContainerUpdate(IJavaScriptProject jproject, IJsGlobalScopeContainer container, IIncludePathEntry[] newEntries) throws CoreException {
  		IPath containerPath= container.getPath();
  		IJsGlobalScopeContainer updatedContainer= new UpdatedJsGlobalScopeContainer(container, newEntries);
  		JsGlobalScopeContainerInitializer initializer= JavaScriptCore.getJsGlobalScopeContainerInitializer(containerPath.segment(0));
  		if (initializer != null) {
  			initializer.requestJsGlobalScopeContainerUpdate(containerPath, jproject, updatedContainer);
  		}
  	}
  
  	private static void updateProjectClasspath(Shell shell, IJavaScriptProject jproject, IIncludePathEntry newEntry, String[] changedAttributes, IProgressMonitor monitor) throws JavaScriptModelException {
  		IIncludePathEntry[] oldClasspath= jproject.getRawIncludepath();
  		int nEntries= oldClasspath.length;
  		ArrayList newEntries= new ArrayList(nEntries + 1);
  		int entryKind= newEntry.getEntryKind();
  		IPath jarPath= newEntry.getPath();
  		boolean found= false;
  		for (int i= 0; i < nEntries; i++) {
  			IIncludePathEntry curr= oldClasspath[i];
  			if (curr.getEntryKind() == entryKind && curr.getPath().equals(jarPath)) {
  				// add modified entry
  				newEntries.add(getUpdatedEntry(curr, newEntry, changedAttributes, jproject));
  				found= true;
  			} else {
  				newEntries.add(curr);
  			}
  		}
  		if (!found) {
  			if (!putJarOnClasspathDialog(shell)) {
  				return;
  			}
  			// add new
  			newEntries.add(newEntry);			
  		}
  		IIncludePathEntry[] newClasspath= (IIncludePathEntry[]) newEntries.toArray(new IIncludePathEntry[newEntries.size()]);
  		jproject.setRawIncludepath(newClasspath, monitor);
  	}
  	
  	private static boolean putJarOnClasspathDialog(final Shell shell) {
  		if (shell == null) {
  			return false;
  		}
  		
  		final boolean[] result= new boolean[1];
  		shell.getDisplay().syncExec(new Runnable() {
  			public void run() {
  				String title= NewWizardMessages.BuildPathSupport_putoncpdialog_title; 
  				String message= NewWizardMessages.BuildPathSupport_putoncpdialog_message; 
  				result[0]= MessageDialog.openQuestion(shell, title, message);
  			}
  		});
  		return result[0];
  	}
  }
  