content: /*******************************************************************************
   * Copyright (c) 2000, 2009 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.io.File;
  import java.lang.reflect.InvocationTargetException;
  import java.util.List;
  
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IWorkspaceRoot;
  import org.eclipse.core.resources.ResourcesPlugin;
  import org.eclipse.core.runtime.Assert;
  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.Path;
  import org.eclipse.jface.dialogs.Dialog;
  import org.eclipse.jface.operation.IRunnableWithProgress;
  import org.eclipse.jface.viewers.ILabelProvider;
  import org.eclipse.jface.viewers.ITreeContentProvider;
  import org.eclipse.jface.viewers.ViewerFilter;
  import org.eclipse.jface.window.Window;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.layout.GridData;
  import org.eclipse.swt.layout.GridLayout;
  import org.eclipse.swt.widgets.Composite;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.DirectoryDialog;
  import org.eclipse.swt.widgets.FileDialog;
  import org.eclipse.swt.widgets.Label;
  import org.eclipse.swt.widgets.Shell;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.ui.model.WorkbenchContentProvider;
  import org.eclipse.ui.model.WorkbenchLabelProvider;
  import org.eclipse.wst.jsdt.core.IIncludePathEntry;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.dialogs.StatusInfo;
  import org.eclipse.wst.jsdt.internal.ui.dialogs.StatusUtil;
  import org.eclipse.wst.jsdt.internal.ui.util.PixelConverter;
  import org.eclipse.wst.jsdt.internal.ui.wizards.IStatusChangeListener;
  import org.eclipse.wst.jsdt.internal.ui.wizards.NewWizardMessages;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.DialogField;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.IStringButtonAdapter;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.LayoutUtil;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.SelectionButtonDialogField;
  import org.eclipse.wst.jsdt.internal.ui.wizards.dialogfields.StringButtonDialogField;
  
  /**
   * UI to set the source attachment archive and root.
   * Same implementation for both setting attachments for libraries from
   * variable entries and for normal (internal or external) jar.
   */
  public class SourceAttachmentBlock {
  		
  	private IStatusChangeListener fContext;
  	
  	private StringButtonDialogField fFileNameField;
  	private SelectionButtonDialogField fWorkspaceButton;
  	private SelectionButtonDialogField fExternalFolderButton;
  	
  	private IStatus fNameStatus;
  	
  	/**
  	 * The path to which the archive variable points.
  	 * Null if invalid path or not resolvable. Must not exist.
  	 */	
  	private IPath fFileVariablePath;
  		
  	private IWorkspaceRoot fWorkspaceRoot;
  	
  	private Control fSWTWidget;
  	private Label fFullPathResolvedLabel;
  	
  	private IJavaScriptProject fProject;
  	private IIncludePathEntry fEntry;
  	private IPath fContainerPath;
  
  
  	/**
  	 * @param context listeners for status updates
  	 * @param entry The entry to edit
  	 */
  	public SourceAttachmentBlock(IStatusChangeListener context, IIncludePathEntry entry) {
  		Assert.isNotNull(entry);
  		
  		fContext= context;
  		fEntry= entry;
  
  		
  		int kind= entry.getEntryKind();
  		Assert.isTrue(kind == IIncludePathEntry.CPE_LIBRARY || kind == IIncludePathEntry.CPE_VARIABLE);
  				
  		fWorkspaceRoot= ResourcesPlugin.getWorkspace().getRoot();
  		
  		fNameStatus= new StatusInfo();
  		
  		SourceAttachmentAdapter adapter= new SourceAttachmentAdapter();
  		
  		fFileNameField= new StringButtonDialogField(adapter);
  		fFileNameField.setDialogFieldListener(adapter);
  		fFileNameField.setLabelText(NewWizardMessages.SourceAttachmentBlock_filename_label); 
  		fFileNameField.setButtonLabel(NewWizardMessages.SourceAttachmentBlock_filename_externalfile_button); 
  	
  		fWorkspaceButton= new SelectionButtonDialogField(SWT.PUSH);
  		fWorkspaceButton.setDialogFieldListener(adapter);
  		fWorkspaceButton.setLabelText(NewWizardMessages.SourceAttachmentBlock_filename_internal_button); 
  
  		fExternalFolderButton= new SelectionButtonDialogField(SWT.PUSH);
  		fExternalFolderButton.setDialogFieldListener(adapter);
  		fExternalFolderButton.setLabelText(NewWizardMessages.SourceAttachmentBlock_filename_externalfolder_button); 
  		
  		// set the old settings
  		setDefaults();
  	}
  	
  	/**
  	 * @deprecated Use API {@link org.eclipse.wst.jsdt.ui.wizards.BuildPathDialogAccess#configureSourceAttachment(Shell, IIncludePathEntry)}
  	 */
  	public SourceAttachmentBlock(IStatusChangeListener context, IIncludePathEntry entry, IPath containerPath, IJavaScriptProject project) {
  		this(context, entry);
  		fContainerPath= containerPath;
  		fProject= project;
  	}
  	
  	public void setDefaults() {
  		if (fEntry.getSourceAttachmentPath() != null) {
  			fFileNameField.setText(fEntry.getSourceAttachmentPath().toString());
  		} else {
  			fFileNameField.setText(""); //$NON-NLS-1$
  		}	
  	}
  	
  	private boolean isVariableEntry() {
  		return fEntry.getEntryKind() == IIncludePathEntry.CPE_VARIABLE;
  	}
  	
  	
  	/**
  	 * Gets the source attachment path chosen by the user
  	 */
  	public IPath getSourceAttachmentPath() {
  		if (fFileNameField.getText().length() == 0) {
  			return null;
  		}
  		return getFilePath();
  	}
  
  	/**
  	 * Gets the source attachment root chosen by the user
  	 * Returns null to let JCore automatically detect the root.
  	 */
  	public IPath getSourceAttachmentRootPath() {
  		return null;
  	}
  	
  	public IIncludePathEntry getNewEntry() {
  		CPListElement elem= CPListElement.createFromExisting(fEntry, fProject);
  		return elem.getClasspathEntry();
  	}
  		
  	/**
  	 * Creates the control
  	 */
  	public Control createControl(Composite parent) {
  		PixelConverter converter= new PixelConverter(parent);
  		
  		fSWTWidget= parent;
  		
  		Composite composite= new Composite(parent, SWT.NONE);	
  		
  		GridLayout layout= new GridLayout();
  		layout.marginHeight= 0;
  		layout.marginWidth= 0;
  		layout.numColumns= 4;		
  		composite.setLayout(layout);
  		
  		
  		if (isVariableEntry()) {
  			int widthHint= converter.convertWidthInCharsToPixels(40);
  			int labelWidthHint= widthHint * 2;
  			
  			Label message= new Label(composite, SWT.WRAP);
  			GridData gd= new GridData(GridData.FILL, GridData.BEGINNING, false, false, 4, 1);
  			message.setLayoutData(gd);
  			message.setText(Messages.format(NewWizardMessages.SourceAttachmentBlock_message, fEntry.getPath().lastSegment())); 
  			
  			//DialogField.createEmptySpace(composite, 1);
  
  			Label desc= new Label(composite, SWT.WRAP);
  			gd= new GridData(GridData.FILL, GridData.BEGINNING, false, false, 4, 1);
  			gd.widthHint= labelWidthHint;
  			desc.setLayoutData(gd);
  			desc.setText(NewWizardMessages.SourceAttachmentBlock_filename_description);
  			
  			fFileNameField.doFillIntoGrid(composite, 4);
  			LayoutUtil.setWidthHint(fFileNameField.getTextControl(null), widthHint);
  
  			// label that shows the resolved path for variable jars
  			//DialogField.createEmptySpace(composite, 1);	
  			fFullPathResolvedLabel= new Label(composite, SWT.WRAP);
  			fFullPathResolvedLabel.setText(getResolvedLabelString());
  			gd= new GridData(GridData.FILL, GridData.BEGINNING, false, false, 4, 1);
  			gd.widthHint= labelWidthHint;
  			fFullPathResolvedLabel.setLayoutData(gd);
  			
  			LayoutUtil.setHorizontalGrabbing(fFileNameField.getTextControl(null));
  		} else {
  			int widthHint= converter.convertWidthInCharsToPixels(60);
  			
  			GridData gd= new GridData(GridData.FILL, GridData.BEGINNING, false, false, 3, 1);
  			gd.widthHint= converter.convertWidthInCharsToPixels(50);
  
  			Label message= new Label(composite, SWT.LEFT + SWT.WRAP);
  			message.setLayoutData(gd);
  			message.setText(Messages.format(NewWizardMessages.SourceAttachmentBlock_message, fEntry.getPath().lastSegment())); 
  			
  			fWorkspaceButton.doFillIntoGrid(composite, 1);
  			((GridData) fWorkspaceButton.getSelectionButton(null).getLayoutData()).verticalAlignment= SWT.END;
  			
  			
  			// archive name field
  			fFileNameField.doFillIntoGrid(composite, 4);
  			LayoutUtil.setWidthHint(fFileNameField.getTextControl(null), widthHint);
  			LayoutUtil.setHorizontalGrabbing(fFileNameField.getTextControl(null));
  
  			// Additional 'browse workspace' button for normal jars
  			DialogField.createEmptySpace(composite, 3);
  			
  			fExternalFolderButton.doFillIntoGrid(composite, 1);
  		}
  				
  		fFileNameField.postSetFocusOnDialogField(parent.getDisplay());
  		
  		Dialog.applyDialogFont(composite);
  				
  		PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IJavaHelpContextIds.SOURCE_ATTACHMENT_BLOCK);
  		return composite;
  	}
  	
  		
  	private class SourceAttachmentAdapter implements IStringButtonAdapter, IDialogFieldListener {
  		
  		// -------- IStringButtonAdapter --------
  		public void changeControlPressed(DialogField field) {
  			attachmentChangeControlPressed(field);			
  		}
  		
  		// ---------- IDialogFieldListener --------
  		public void dialogFieldChanged(DialogField field) {
  			attachmentDialogFieldChanged(field);
  		}
  	}
  	
  	private void attachmentChangeControlPressed(DialogField field) {
  		if (field == fFileNameField) {
  			IPath jarFilePath= isVariableEntry() ? chooseExtension() : chooseExtJarFile();
  			if (jarFilePath != null) {
  				fFileNameField.setText(jarFilePath.toString());
  			}
  		}	
  	}
  	
  	// ---------- IDialogFieldListener --------
  
  	private void attachmentDialogFieldChanged(DialogField field) {
  		if (field == fFileNameField) {
  			fNameStatus= updateFileNameStatus();
  		} else if (field == fWorkspaceButton) {
  			IPath jarFilePath= chooseInternal();
  			if (jarFilePath != null) {
  				fFileNameField.setText(jarFilePath.toString());
  			}
  			return;
  		} else if (field == fExternalFolderButton) {
  			IPath folderPath= chooseExtFolder();
  			if (folderPath != null) {
  				fFileNameField.setText(folderPath.toString());
  			}
  			return;
  		}
  		doStatusLineUpdate();
  	}	
  		
  	private void doStatusLineUpdate() {
  		fFileNameField.enableButton(canBrowseFileName());
  		
  		// set the resolved path for variable jars
  		if (fFullPathResolvedLabel != null) {
  			fFullPathResolvedLabel.setText(getResolvedLabelString());
  		}
  		
  		IStatus status= StatusUtil.getMostSevere(new IStatus[] { fNameStatus });
  		fContext.statusChanged(status);
  	}
  	
  	private boolean canBrowseFileName() {
  		if (!isVariableEntry()) {
  			return true;
  		}
  		// to browse with a variable JAR, the variable name must point to a directory
  		if (fFileVariablePath != null) {
  			return fFileVariablePath.toFile().isDirectory();
  		}
  		return false;
  	}
  	
  	private String getResolvedLabelString() {
  		IPath resolvedPath= getResolvedPath(getFilePath());
  		if (resolvedPath != null) {
  			return resolvedPath.toOSString();
  		}
  		return ""; //$NON-NLS-1$
  	}	
  	
  	private IPath getResolvedPath(IPath path) {
  		if (path != null) {
  			String varName= path.segment(0);
  			if (varName != null) {
  				IPath varPath= JavaScriptCore.getIncludepathVariable(varName);
  				if (varPath != null) {
  					return varPath.append(path.removeFirstSegments(1));
  				}
  			}
  		}
  		return null;
  	}	
  	
  	private IStatus updateFileNameStatus() {
  		StatusInfo status= new StatusInfo();
  		fFileVariablePath= null;
  		
  		String fileName= fFileNameField.getText();
  		if (fileName.length() == 0) {
  			// no source attachment
  			return status;
  		} else {
  			if (!Path.EMPTY.isValidPath(fileName)) {
  				status.setError(NewWizardMessages.SourceAttachmentBlock_filename_error_notvalid); 
  				return status;
  			}
  			IPath filePath= Path.fromOSString(fileName);
  			IPath resolvedPath;
  			if (isVariableEntry()) {
  				if (filePath.getDevice() != null) {
  					status.setError(NewWizardMessages.SourceAttachmentBlock_filename_error_deviceinpath); 
  					return status;
  				}
  				String varName= filePath.segment(0);
  				if (varName == null) {
  					status.setError(NewWizardMessages.SourceAttachmentBlock_filename_error_notvalid); 
  					return status;
  				}
  				fFileVariablePath= JavaScriptCore.getIncludepathVariable(varName);
  				if (fFileVariablePath == null) {
  					status.setError(NewWizardMessages.SourceAttachmentBlock_filename_error_varnotexists); 
  					return status;
  				}
  				resolvedPath= fFileVariablePath.append(filePath.removeFirstSegments(1));
  				
  				if (resolvedPath.isEmpty()) {
  					status.setWarning(NewWizardMessages.SourceAttachmentBlock_filename_warning_varempty); 
  					return status;
  				}
  				File file= resolvedPath.toFile();
  				if (!file.exists()) {				
  					String message= Messages.format(NewWizardMessages.SourceAttachmentBlock_filename_error_filenotexists, resolvedPath.toOSString()); 
  					status.setWarning(message);
  					return status;
  				}
  				if (!resolvedPath.isAbsolute()) {
  					String message=  Messages.format(NewWizardMessages.SourceAttachmentBlock_filename_error_notabsolute, filePath.toString()); 
  					status.setError(message);
  					return status;
  				}
  				
  				String deprecationMessage= BuildPathSupport.getDeprecationMessage(varName);
  				if (deprecationMessage != null) {
  					status.setWarning(deprecationMessage); 
  					return status;
  				}
  				
  			} else {
  				// JDT/Core only supports source attachments in archives on the
  				// local file system. So using getLocation is save here. 
  				File file= filePath.toFile();
  				IResource res= fWorkspaceRoot.findMember(filePath);
  				if (res != null && res.getLocation() != null) {
  					file= res.getLocation().toFile();
  				}
  				if (!file.exists()) {
  					String message=  Messages.format(NewWizardMessages.SourceAttachmentBlock_filename_error_filenotexists, filePath.toString()); 
  					status.setError(message);
  					return status;
  				}
  				if (res == null) {
  					if (!filePath.isAbsolute()) {
  						String message=  Messages.format(NewWizardMessages.SourceAttachmentBlock_filename_error_notabsolute, filePath.toString()); 
  						status.setError(message);
  						return status;
  					}
  				}
  			}
  			
  		}
  		return status;
  	}
  	
  	private IPath getFilePath() {
  		return Path.fromOSString(fFileNameField.getText()).makeAbsolute();
  	}
  	
  	private IPath chooseExtension() {
  		IPath currPath= getFilePath();
  		if (currPath.segmentCount() == 0) {
  			currPath= fEntry.getPath();
  		}		
  	
  		IPath resolvedPath= getResolvedPath(currPath);
  		File initialSelection= resolvedPath != null ? resolvedPath.toFile() : null;
  			
  		String currVariable= currPath.segment(0);
  		JARFileSelectionDialog dialog= new JARFileSelectionDialog(getShell(), false, true);
  		dialog.setTitle(NewWizardMessages.SourceAttachmentBlock_extvardialog_title); 
  		dialog.setMessage(NewWizardMessages.SourceAttachmentBlock_extvardialog_description); 
  		dialog.setInput(fFileVariablePath.toFile());
  		dialog.setInitialSelection(initialSelection);
  		if (dialog.open() == Window.OK) {
  			File result= (File) dialog.getResult()[0];
  			IPath returnPath= Path.fromOSString(result.getPath()).makeAbsolute();
  			return modifyPath(returnPath, currVariable);
  		}
  		return null;
  	}
  	
  	/*
  	 * Opens a dialog to choose a jar from the file system.
  	 */
  	private IPath chooseExtJarFile() {
  		IPath currPath= getFilePath();
  		if (currPath.segmentCount() == 0) {
  			currPath= fEntry.getPath();
  		}		
  			
  		if (ArchiveFileFilter.isArchivePath(currPath)) {
  			currPath= currPath.removeLastSegments(1);
  		}
  	
  		FileDialog dialog= new FileDialog(getShell());
  		dialog.setText(NewWizardMessages.SourceAttachmentBlock_extjardialog_text); 
  		dialog.setFilterExtensions(new String[] {"*.jar;*.zip;*.js"}); //$NON-NLS-1$
  		dialog.setFilterPath(currPath.toOSString());
  		String res= dialog.open();
  		if (res != null) {
  			return Path.fromOSString(res).makeAbsolute();
  		}
  		return null;
  	}
  	
  	private IPath chooseExtFolder() {
  		IPath currPath= getFilePath();
  		if (currPath.segmentCount() == 0) {
  			currPath= fEntry.getPath();
  		}
  		if (ArchiveFileFilter.isArchivePath(currPath)) {
  			currPath= currPath.removeLastSegments(1);
  		}
  	
  		DirectoryDialog dialog= new DirectoryDialog(getShell());
  		dialog.setMessage(NewWizardMessages.SourceAttachmentBlock_extfolderdialog_message);
  		dialog.setText(NewWizardMessages.SourceAttachmentBlock_extfolderdialog_text); 
  		dialog.setFilterPath(currPath.toOSString());
  		String res= dialog.open();
  		if (res != null) {
  			return Path.fromOSString(res).makeAbsolute();
  		}
  		return null;
  	}	
  	
  	/*
  	 * Opens a dialog to choose an internal jar.
  	 */	
  	private IPath chooseInternal() {
  		String initSelection= fFileNameField.getText();
  		
  		ViewerFilter filter= new ArchiveFileFilter((List) null, false);
  
  		ILabelProvider lp= new WorkbenchLabelProvider();
  		ITreeContentProvider cp= new WorkbenchContentProvider();
  
  		IResource initSel= null;
  		if (initSelection.length() > 0) {
  			initSel= fWorkspaceRoot.findMember(new Path(initSelection));
  		}
  		if (initSel == null) {
  			initSel= fWorkspaceRoot.findMember(fEntry.getPath());
  		}
  
  		FolderSelectionDialog dialog= new FolderSelectionDialog(getShell(), lp, cp);
  		dialog.setAllowMultiple(false);
  		dialog.addFilter(filter);
  		dialog.setTitle(NewWizardMessages.SourceAttachmentBlock_intjardialog_title); 
  		dialog.setMessage(NewWizardMessages.SourceAttachmentBlock_intjardialog_message); 
  		dialog.setInput(fWorkspaceRoot);
  		dialog.setInitialSelection(initSel);
  		if (dialog.open() == Window.OK) {
  			IResource res= (IResource) dialog.getFirstResult();
  			return res.getFullPath();
  		}
  		return null;
  	}
  	
  	private Shell getShell() {
  		if (fSWTWidget != null) {
  			return fSWTWidget.getShell();
  		}
  		return JavaScriptPlugin.getActiveWorkbenchShell();			
  	}
  	
  	/**
  	 * Takes a path and replaces the beginning with a variable name
  	 * (if the beginning matches with the variables value)
  	 */
  	private IPath modifyPath(IPath path, String varName) {
  		if (varName == null || path == null) {
  			return null;
  		}
  		if (path.isEmpty()) {
  			return new Path(varName);
  		}
  		
  		IPath varPath= JavaScriptCore.getIncludepathVariable(varName);
  		if (varPath != null) {
  			if (varPath.isPrefixOf(path)) {
  				path= path.removeFirstSegments(varPath.segmentCount());
  			} else {
  				path= new Path(path.lastSegment());
  			}
  		} else {
  			path= new Path(path.lastSegment());
  		}
  		return new Path(varName).append(path);
  	}
  
  
  	/**
  	 * Creates a runnable that sets the source attachment by modifying the project's classpath.
  	 */
  	public static IRunnableWithProgress getRunnable(final Shell shell, final IIncludePathEntry newEntry, final IJavaScriptProject jproject, final IPath containerPath) {
  		return new IRunnableWithProgress() {
  			public void run(IProgressMonitor monitor) throws InvocationTargetException {				
  				try {
  					String[] changedAttributes= {};
  					BuildPathSupport.modifyClasspathEntry(shell, newEntry, changedAttributes, jproject, containerPath, monitor);
  				} catch (CoreException e) {
  					throw new InvocationTargetException(e);
  				}
  			}
  		};
  	}
  
  	/**
  	 * @deprecated Use {@link #getRunnable(Shell, IIncludePathEntry, IJavaScriptProject, IPath)}
  	 */
  	public IRunnableWithProgress getRunnable(final Shell shell) {
  		return getRunnable(shell, getNewEntry(), fProject, fContainerPath);
  	}	
  	
  	
  }
  