/*******************************************************************************
 * Copyright (c) 2005, 2015 BEA Systems, Inc.
 * 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:
 *   wharley@bea.com - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.apt.ui.internal.preferences;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.jdt.apt.core.internal.util.FactoryContainer;
import org.eclipse.jdt.apt.core.internal.util.FactoryPath;
import org.eclipse.jdt.apt.core.internal.util.FactoryPath.Attributes;
import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil;
import org.eclipse.jdt.apt.core.util.AptConfig;
import org.eclipse.jdt.apt.core.util.IFactoryPath;
import org.eclipse.jdt.apt.ui.internal.util.ExceptionHandler;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.CheckedListDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IListAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.jdt.ui.wizards.BuildPathDialogAccess;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;


/**
 * Data and controls for the Java Annotation Factory Path preference page.
 */
public class FactoryPathConfigurationBlock extends BaseConfigurationBlock {

	private static final int IDX_UP= 0;
	private static final int IDX_DOWN= 1;
	// 2
	private static final int IDX_ADDJAR= 3;
	private static final int IDX_ADDEXTJAR= 4;
	private static final int IDX_ADDVAR= 5;
	// 6
	private static final int IDX_EDIT= 7;
	private static final int IDX_ADVANCED= 8;
	private static final int IDX_REMOVE= 9;
	// 10
	private static final int IDX_ENABLEALL= 11;
	private static final int IDX_DISABLEALL= 12;

	private final static String[] buttonLabels = {
		Messages.FactoryPathConfigurationBlock_up,
		Messages.FactoryPathConfigurationBlock_down,
		null,                    // 2
		Messages.FactoryPathConfigurationBlock_addJars,
		Messages.FactoryPathConfigurationBlock_addExternalJars,
		Messages.FactoryPathConfigurationBlock_addVariable,
		null,                    // 6
		Messages.FactoryPathConfigurationBlock_edit,
		Messages.FactoryPathConfigurationBlock_advanced,
		Messages.FactoryPathConfigurationBlock_remove,
		null,                    // 10
		Messages.FactoryPathConfigurationBlock_enableAll,
		Messages.FactoryPathConfigurationBlock_disableAll
	};

	/**
	 * Event handler for factory path list control
	 */
	private class FactoryPathAdapter implements IListAdapter<FactoryPathEntry>, IDialogFieldListener {
        public void customButtonPressed(ListDialogField<FactoryPathEntry> field, int index) {
        	FactoryPathConfigurationBlock.this.customButtonPressed(index);
        }

        public void selectionChanged(ListDialogField<FactoryPathEntry> field) {
        	boolean enableRemove = canRemove();
        	field.enableButton(IDX_REMOVE, enableRemove);
        	boolean enableEdit = canEdit();
        	field.enableButton(IDX_EDIT, enableEdit);
        	boolean enableAdvanced = canAdvanced();
        	field.enableButton(IDX_ADVANCED, enableAdvanced);
        }

        /**
         * This method gets called when, among other things, a checkbox is
         * clicked.  However, it doesn't get any information about which
         * item it was whose checkbox was clicked.  We could hook into the
         * list control's CheckboxTableViewer event listener for changes to
         * individual checkboxes; but that does not get called for enableAll
         * and disableAll events.
         */
		public void dialogFieldChanged(DialogField field) {
			if (!fSettingListContents) {
				updateFactoryPathEntries();
			}
        }

		public void doubleClicked(ListDialogField<FactoryPathEntry> field) {
        	if (canEdit()) {
        		editSelectedItem();
        	}
        }

	}
	
	private class FactoryPathLabelProvider extends LabelProvider implements ITableLabelProvider {

		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		public String getColumnText(Object element, int columnIndex) {
			if (!(element instanceof FactoryPathEntry)) {
				return ""; //$NON-NLS-1$
			}
			FactoryPathEntry fpe = (FactoryPathEntry)element;
			if (columnIndex == 0) {
				return fpe._fc.toString();
			}
			else {
				return ""; //$NON-NLS-1$
			}
		}
	}
	
	/**
	 * The factory path is a list of containers, plus some information about
	 * each container.  That makes it a list of FactoryPathEntry.
	 */
	private static class FactoryPathEntry {
		/* shallow copies - beware! */
		public final FactoryContainer _fc;
		public FactoryPath.Attributes _attr;
		
		// CONSTRUCTORS
		public FactoryPathEntry(FactoryContainer fc, FactoryPath.Attributes attr) {
			_fc = fc;
			_attr = attr;
		}

		// CONVERSION TO/FROM INDIVIDUAL ELEMENTS
		public static Map<FactoryContainer, Attributes> pathMapFromList(List<FactoryPathEntry> list) {
			Map<FactoryContainer, FactoryPath.Attributes> map =
				new LinkedHashMap<>(list.size());
			for (FactoryPathEntry fpe : list) {
				map.put(fpe._fc, fpe._attr);
			}
			return map;
		}
		public static List<FactoryPathEntry> pathListFromMap(Map<FactoryContainer, Attributes> map) {
			List<FactoryPathEntry> list = new ArrayList<>(map.size());
			for (Map.Entry<FactoryContainer, Attributes> entry : map.entrySet()) {
				FactoryPathEntry fpe = new FactoryPathEntry(entry.getKey(), entry.getValue());
				list.add(fpe);
			}
			return list;
		}

		// SUPPORT FOR COMPARISON
		public boolean equals(Object obj) {
			if (!(obj instanceof FactoryPathEntry))
				return false;
			FactoryPathEntry fpe = (FactoryPathEntry)obj;
			return _fc.equals(fpe._fc) && _attr.equals(fpe._attr);
		}
		public int hashCode() {
			return _fc.hashCode() ^ _attr.hashCode();
		}
		
	}
	
	private PixelConverter fPixelConverter;
	private Composite fBlockControl; // the control representing the entire configuration block

	/**
	 * The factory path at the time this pref pane was launched.
	 * Use this to see if anything changed at save time.
	 */
	private List<FactoryPathEntry> fOriginalPath;
	
	private final IJavaProject fJProj;

	/**
	 * The GUI control representing the factory path.  Its data items
	 * are of type FactoryPathEntry.
	 */
	private CheckedListDialogField<FactoryPathEntry> fFactoryPathList;
	
	/**
	 * True while inside setListContents().  Used in order to efficiently
	 * and correctly add new elements to the factory list: short-circuits
	 * the factory list field listener code.
	 */
	private boolean fSettingListContents = false;

	public FactoryPathConfigurationBlock(IStatusChangeListener context,
			IProject project, IWorkbenchPreferenceContainer container) {
		super(context, project, new Key[] {}, container);
		
		fJProj = JavaCore.create(project);
		
		FactoryPathAdapter adapter= new FactoryPathAdapter();
		FactoryPathLabelProvider labelProvider = new FactoryPathLabelProvider();
		
		fFactoryPathList= new CheckedListDialogField<>(adapter, buttonLabels, labelProvider);
		fFactoryPathList.setDialogFieldListener(adapter);
		fFactoryPathList.setLabelText(Messages.FactoryPathConfigurationBlock_pluginsAndJars);
		fFactoryPathList.setUpButtonIndex(IDX_UP);
		fFactoryPathList.setDownButtonIndex(IDX_DOWN);
		fFactoryPathList.setRemoveButtonIndex(IDX_REMOVE);
		fFactoryPathList.setCheckAllButtonIndex(IDX_ENABLEALL);
		fFactoryPathList.setUncheckAllButtonIndex(IDX_DISABLEALL);
	}

	/**
	 * Respond to the user checking the "enabled" checkbox of an entry
	 * in the factory path control, by replacing the FactoryPathEntry
	 * with a new one with the correct "enabled" value.
	 * We don't have information about which entry was changed, so we
	 * have to look at all of them.  This is inefficient - somewhere
	 * around O(n log n) depending on how the list is implemented - but
	 * hopefully the list is not so huge that it's a problem.
	 */
	private void updateFactoryPathEntries() {
		for (FactoryPathEntry fpe : getListContents()) {
			boolean checked = fFactoryPathList.isChecked(fpe);
			if (checked != fpe._attr.isEnabled()) {
				fpe._attr.setEnabled(checked);
			}
		}
	}

	/**
	 * Respond to a button in the button bar.
	 * Most buttons are handled by code in CheckedListDialogField;
	 * this method is for the rest, e.g., Add External Jar.
	 * @param index
	 */
	public void customButtonPressed(int index) {
		FactoryPathEntry[] newEntries = null;
		switch (index) {
		case IDX_ADDJAR: // add jars in project
			newEntries= openJarFileDialog(null);
			addEntries(newEntries);
			break;
			
		case IDX_ADDEXTJAR: // add external jars
			newEntries= openExtJarFileDialog(null);
			addEntries(newEntries);
			break;
			
		case IDX_ADDVAR: // add jar from classpath variable
			newEntries= openVariableSelectionDialog(null);
			addEntries(newEntries);
			break;
			
		case IDX_EDIT: // edit selected item
			if (canEdit()) {
				editSelectedItem();
			}
			break;
			
		case IDX_ADVANCED: // advanced options
			advancedOptionsDialog();
			break;
		}
		
	}
	
	/**
	 * Can't remove a selection that contains a plugin.
	 */
	private boolean canRemove() {
		List<FactoryPathEntry> selected= getSelectedListContents();
		boolean containsPlugin= false;
		for (FactoryPathEntry fpe : selected) {
			if (fpe._fc.getType() == FactoryContainer.FactoryType.PLUGIN) {
				containsPlugin = true;
				break;
			}
		}
		return !containsPlugin;
	}
	
	/**
	 * Can only edit a single item at a time.  Can't edit plugins.
	 */
	private boolean canEdit() {
		List<FactoryPathEntry> selected= getSelectedListContents();
		if (selected.size() != 1) {
			return false;
		}
		FactoryContainer fc = selected.get(0)._fc;
		return (fc.getType() != FactoryContainer.FactoryType.PLUGIN);
	}

	/**
	 * Can only launch the 'advanced' dialog on a single item at a time.
	 */
	private boolean canAdvanced() {
		List<FactoryPathEntry> selected= getSelectedListContents();
		return (selected.size() == 1);
	}

	/**
	 * Edit the item selected.
	 * Precondition: exactly one item is selected in the list,
	 * and it is an editable item (not a plugin).
	 * @param field a listbox of FactoryContainers.
	 */
	private void editSelectedItem() {
		List<FactoryPathEntry> selected= getSelectedListContents();
		if (selected.size() != 1) {
			return;
		}
		FactoryPathEntry original = selected.get(0);
		FactoryPathEntry[] edited = null;
		switch (original._fc.getType()) {
		case PLUGIN:
			return;
		case EXTJAR:
			edited= openExtJarFileDialog(original);
			break;
		case VARJAR:
			edited= openVariableSelectionDialog(original);
			break;
		case WKSPJAR:
			edited= openJarFileDialog(original);
			break;
		}
		if (edited != null && edited.length > 0) {
			fFactoryPathList.replaceElement(original, edited[0]);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.apt.ui.internal.preferences.BaseConfigurationBlock#createContents(org.eclipse.swt.widgets.Composite)
	 */
	protected Control createContents(Composite parent) {
		setShell(parent.getShell());
		
		fPixelConverter= new PixelConverter(parent);
		
		fBlockControl= new Composite(parent, SWT.NONE);
		fBlockControl.setFont(parent.getFont());

		Dialog.applyDialogFont(fBlockControl);
		
		LayoutUtil.doDefaultLayout(fBlockControl, new DialogField[] { fFactoryPathList }, true, SWT.DEFAULT, SWT.DEFAULT);
		LayoutUtil.setHorizontalGrabbing(fFactoryPathList.getListControl(null));

		fFactoryPathList.enableButton(IDX_ADDJAR, (fJProj != null));
		// bugzilla 139101: only enable Advanced and Edit buttons if there is a selection
		fFactoryPathList.enableButton(IDX_ADVANCED, false);
		fFactoryPathList.enableButton(IDX_EDIT, false);
		int buttonBarWidth= fPixelConverter.convertWidthInCharsToPixels(24);
		fFactoryPathList.setButtonsMinWidth(buttonBarWidth);
		
		return fBlockControl;
	}
	
	@Override
	public boolean hasProjectSpecificOptionsNoCache(IProject project) {
		return (project == null) ? false : AptConfig.hasProjectSpecificFactoryPath(JavaCore.create(project));
	}

	/**
	 * Initialize the user interface based on the cached original settings.
	 */
	@Override
	protected void initContents() {
		setListContents(fOriginalPath);
	}

	/**
	 * Save reference copies of the settings, so we can see if anything changed.
	 * This must stay in sync with the actual saved values for the rebuild logic
	 * to work; so be sure to call this any time you save (eg in performApply()).
	 */
	@Override
	protected void cacheOriginalValues() {
		IFactoryPath ifp = AptConfig.getFactoryPath(fJProj);
		// we'll risk this downcast because we're such good buddies with apt.core.
		FactoryPath fp = (FactoryPath)ifp;
		Map<FactoryContainer, FactoryPath.Attributes> path = fp.getAllContainers();
		fOriginalPath = FactoryPathEntry.pathListFromMap(path);
		super.cacheOriginalValues();
	}
	
	/*
	 * Helper method to get rid of unchecked conversion warning
	 */
	private List<FactoryPathEntry> getListContents() {
		List<FactoryPathEntry> contents= fFactoryPathList.getElements();
		return contents;
	}
	
	/*
	 * Helper method to get rid of unchecked conversion warning
	 */
	private List<FactoryPathEntry> getSelectedListContents() {
		List<FactoryPathEntry> contents= fFactoryPathList.getSelectedElements();
		return contents;
	}
	
	/**
	 * Add new entries to the list control.  Differs from setListContents()
	 * in that the list is not cleared first, and the entries are not copied
	 * before being added to the list.
	 * @param entries can be null.
	 */
	private void addEntries(FactoryPathEntry[] entries) {
		if (null == entries) {
			return;
		}
		int insertAt;
		List<FactoryPathEntry> selectedElements= getSelectedListContents();
		if (selectedElements.size() == 1) {
			insertAt= fFactoryPathList.getIndexOfElement(selectedElements.get(0)) + 1;
		} else {
			insertAt= fFactoryPathList.getSize();
		}
		try {
			fSettingListContents = true;
			for (int i = 0; i < entries.length; ++i) {
				fFactoryPathList.addElement(entries[i], insertAt + i);
				fFactoryPathList.setChecked(entries[i], entries[i]._attr.isEnabled());
			}
		}
		finally {
			fSettingListContents = false;
		}
	}
	
	/**
	 * Set the contents of the list control.  The FPEs in the input list
	 * will be copied; the originals are left untouched, so that if the
	 * list control's contents are modified they can be compared with the
	 * original.
	 * @param fpeList can be null.
	 */
	private void setListContents(List<FactoryPathEntry> fpeList) {
		try {
			fSettingListContents = true;
			fFactoryPathList.removeAllElements();
			if (fpeList == null) {
				return;
			}
			for (FactoryPathEntry originalFpe : fpeList) {
				// clone, because we may later want to compare with the original.
				FactoryPathEntry fpe = new FactoryPathEntry(originalFpe._fc, new Attributes(originalFpe._attr));
				fFactoryPathList.addElement(fpe);
				fFactoryPathList.setChecked(fpe, fpe._attr.isEnabled());
			}
		}
		finally {
			fSettingListContents = false;
		}
	}

	/**
	 * Get all the containers of a certain type currently on the list.
	 * The format of the returned paths will depend on the container type:
	 * for EXTJAR it will be an absolute path; for WKSPJAR it will be a
	 * path relative to the workspace root; for VARJAR it will be a path
	 * whose first segment is the name of a classpath variable.
	 * @param type may not be PLUGIN
	 * @param ignore null, or an item to not put on the list (used when
	 * editing an existing item).
	 * @return an array, possibly empty (but never null)
	 */
	private IPath[] getExistingPaths(FactoryContainer.FactoryType type, FactoryContainer ignore) {
		if (type == FactoryContainer.FactoryType.PLUGIN) {
			throw new IllegalArgumentException();
		}
		List<FactoryPathEntry> all = getListContents();
		// find out how many entries there are of this type
		int countType = 0;
		for (FactoryPathEntry fpe : all) {
			FactoryContainer fc = fpe._fc;
			if (fc.getType() == type && fc != ignore) {
				++countType;
			}
		}
		// create an array of paths, one per entry of this type
		IPath[] some = new IPath[countType];
		int i = 0;
		for (FactoryPathEntry fpe : all) {
			FactoryContainer fc = fpe._fc;
			if (fc.getType() == type && fc != ignore) {
				some[i++] = new Path(fc.getId());
			}
		}
		return some;
	}
	
	/**
	 * Launch the "advanced options" dialog, which displays the factory classes
	 * contained by the selected container and allows the user to specify
	 * options that are needed only in certain special cases.
	 * 
	 * We treat advanced options as an attribute of the factory path, not of the
	 * container; the same container may have different advanced options in different
	 * projects.  We treat advanced options the same way as the "enabled" flag.
	 */
	private void advancedOptionsDialog() {
		List<FactoryPathEntry> selected= getSelectedListContents();
		if (selected.size() != 1) {
			return;
		}
		FactoryPathEntry original= selected.get(0);
		AdvancedFactoryPathOptionsDialog dialog=
			new AdvancedFactoryPathOptionsDialog(getShell(), original._fc, original._attr);
		if (dialog.open() == Window.OK) {
			original._attr = dialog.getResult();
			// If the dialog could change the enabled attribute, we would also
			// need to update the checkbox in the GUI here.  But it doesn't.
		}
	}

	/**
	 * Add or edit a project-relative jar file.  Only possible when editing
	 * project properties; this method is disabled in workspace prefs.
	 * @param original null, or an existing list entry to be edited
	 * @return a list of additional factory path entries to be added
	 */
	private FactoryPathEntry[] openJarFileDialog(FactoryPathEntry original) {
		if (fJProj == null) {
			return null;
		}
		IWorkspaceRoot root= fJProj.getProject().getWorkspace().getRoot();
		
		if (original == null) {
			IPath[] results= BuildPathDialogAccess.chooseJAREntries(getShell(), fJProj.getPath(), new IPath[0]);
			if (results == null) {
				return null;
			}
			ArrayList<FactoryPathEntry> res= new ArrayList<>();
			for (int i= 0; i < results.length; i++) {
				IResource resource= root.findMember(results[i]);
				if (resource instanceof IFile) {
					FactoryContainer fc = FactoryPathUtil.newWkspJarFactoryContainer(results[i]);
					// assume defaults of enabled=true, runInAptMode=false
					FactoryPath.Attributes attr = new FactoryPath.Attributes(true, false);
					FactoryPathEntry fpe = new FactoryPathEntry(fc, attr);
					res.add(fpe);
				}
				//TODO: handle missing jars
			}
			return res.toArray(new FactoryPathEntry[res.size()]);
		}
		else {
			IPath[] existingPaths = getExistingPaths(FactoryContainer.FactoryType.WKSPJAR, original._fc);
			IPath result= BuildPathDialogAccess.configureJAREntry(getShell(), new Path(original._fc.getId()), existingPaths);
			if (result == null) {
				return null;
			}
			IResource resource= root.findMember(result);
			if (resource instanceof IFile) {
				FactoryPathEntry[] edited = new FactoryPathEntry[1];
				FactoryContainer fc= FactoryPathUtil.newWkspJarFactoryContainer(result);
				// Use prior value for isEnabled.  Assume default of runInAptMode=false
				FactoryPath.Attributes attr = new FactoryPath.Attributes(original._attr.isEnabled(), false);
				edited[0]= new FactoryPathEntry(fc, attr);
				return edited;
			}
			//TODO: handle missing jars
			return null;
 		}
	}

	/**
	 * Add or edit an external (not project-relative) jar file.
	 * @param original null, or an existing list entry to be edited
	 * @return a list of additional factory path entries to be added
	 */
	private FactoryPathEntry[] openExtJarFileDialog(FactoryPathEntry original) {
		if (original == null) {
			IPath[] selected= BuildPathDialogAccess.chooseExternalJAREntries(getShell());
			if (selected == null) {
				return null;
			}
			ArrayList<FactoryPathEntry> res= new ArrayList<>();
			for (int i= 0; i < selected.length; i++) {
				FactoryContainer fc = FactoryPathUtil.newExtJarFactoryContainer(selected[i].toFile());
				// assume defaults of enabled=true, runInAptMode=false
				FactoryPath.Attributes attr = new FactoryPath.Attributes(true, false);
				FactoryPathEntry fpe = new FactoryPathEntry(fc, attr);
				res.add(fpe);
			}
			return res.toArray(new FactoryPathEntry[res.size()]);
		}
		else {
			IPath result= BuildPathDialogAccess.configureExternalJAREntry(getShell(), new Path(original._fc.getId()));
			if (result == null) {
				return null;
			}
			FactoryPathEntry[] edited= new FactoryPathEntry[1];
			FactoryContainer fc= FactoryPathUtil.newExtJarFactoryContainer(result.toFile());
			// Use prior value for isEnabled.  Assume default of runInAptMode=false
			FactoryPath.Attributes attr = new FactoryPath.Attributes(original._attr.isEnabled(), false);
			edited[0]= new FactoryPathEntry(fc, attr);
			return edited;
		}
	}
	
	/**
	 * Add or edit an external (not project-relative) jar file whose
	 * location includes a classpath variable name.
	 * @param original null, or an existing list entry to be edited
	 * @return a list of additional factory path entries to be added
	 */
	private FactoryPathEntry[] openVariableSelectionDialog(FactoryPathEntry original) {
		if (original == null) {
			IPath[] selected= BuildPathDialogAccess.chooseVariableEntries(getShell(), new IPath[0]);
			if (selected == null) {
				return null;
			}
			ArrayList<FactoryPathEntry> res= new ArrayList<>();
			for (int i= 0; i < selected.length; i++) {
				FactoryContainer fc= FactoryPathUtil.newVarJarFactoryContainer(selected[i]);
				// assume defaults of enabled=true, runInAptMode=false
				FactoryPath.Attributes attr = new FactoryPath.Attributes(true, false);
				FactoryPathEntry fpe = new FactoryPathEntry(fc, attr);
				res.add(fpe);
			}
			return res.toArray(new FactoryPathEntry[res.size()]);
		}
		else {
			IPath[] existingPaths = getExistingPaths(FactoryContainer.FactoryType.VARJAR, original._fc);
			IPath result= BuildPathDialogAccess.configureVariableEntry(getShell(), new Path(original._fc.getId()), existingPaths);
			if (result == null) {
				return null;
			}
			FactoryPathEntry[] edited= new FactoryPathEntry[1];
			FactoryContainer fc= FactoryPathUtil.newVarJarFactoryContainer(result);
			// Use prior value for isEnabled.  Assume default of runInAptMode=false
			FactoryPath.Attributes attr = new FactoryPath.Attributes(original._attr.isEnabled(), false);
			edited[0]= new FactoryPathEntry(fc, attr);
			return edited;
		}
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jdt.apt.ui.internal.preferences.BaseConfigurationBlock#updateModel(org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField)
	 */
	protected void updateModel(DialogField field) {
		// We don't use IEclipsePreferences for this pane, so no need to do anything.
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jdt.apt.ui.internal.preferences.BaseConfigurationBlock#validateSettings(org.eclipse.jdt.internal.ui.preferences.OptionsConfigurationBlock.Key, java.lang.String, java.lang.String)
	 */
	protected void validateSettings(Key changedKey, String oldValue, String newValue) {
		// TODO: validate that all the specified factory containers exist?
	}
	
	protected void saveSettings() {
		FactoryPath fp;
		if ((fJProj != null) && !fBlockControl.isEnabled()) {
			// We're in a project properties pane but the entire configuration
			// block control is disabled.  That means the per-project settings checkbox
			// is unchecked.  To save that state, we'll delete the settings file.
			fp = null;
		}
		else {
			List<FactoryPathEntry> containers;
			Map<FactoryContainer, FactoryPath.Attributes> map;
			containers = getListContents();
			map = FactoryPathEntry.pathMapFromList(containers);
			fp = new FactoryPath();
			fp.setContainers(map);
		}
		
		try {
			AptConfig.setFactoryPath(fJProj, fp);
		}
		catch (CoreException e) {
			final String title = Messages.FactoryPathConfigurationBlock_unableToSaveFactorypath_title;
			final String message = Messages.FactoryPathConfigurationBlock_unableToSaveFactorypath_message;
			ExceptionHandler.handle(e, fBlockControl.getShell(), title, message);
		}
		
		super.saveSettings();
	}
	
	/**
	 * If per-project, restore list contents to current workspace settings;
	 * the per-project settings checkbox will be cleared for us automatically.
	 * If workspace, restore list contents to factory-default settings.
	 */
	public void performDefaults() {
		IFactoryPath ifp = AptConfig.getDefaultFactoryPath(fJProj);
		// we'll risk this downcast because we're such good buddies with apt.core.
		FactoryPath fp = (FactoryPath)ifp;
		Map<FactoryContainer, FactoryPath.Attributes> map = fp.getAllContainers();
		List<FactoryPathEntry> defaults = FactoryPathEntry.pathListFromMap(map);
		setListContents(defaults);
		super.performDefaults();
	}
	
	/**
	 * @return true if settings or project-specificness changed since
	 * the pane was launched - that is, if there is anything to save.
	 */
	@Override
	protected boolean settingsChanged(IScopeContext currContext) {
		if (fOriginalPath == null) {
			// shouldn't happen, but just in case it does, consider it a change.
			return true;
		}
		// Is the new path the same size, containing the same items
		// in the same order?  We rely on FactoryPathEntry.equals() here.
		List<FactoryPathEntry> newPath = getListContents();
		return !fOriginalPath.equals(newPath);
	}
	
}
