/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.wizards.buildpaths;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
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.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;

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.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.preference.IPreferenceStore;
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.ui.PlatformUI;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
import org.eclipse.ui.views.navigator.ResourceComparator;

import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;

import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.launching.JavaRuntime;

import org.eclipse.jdt.ui.PreferenceConstants;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.dialogs.StatusUtil;
import org.eclipse.jdt.internal.ui.util.CoreUtility;
import org.eclipse.jdt.internal.ui.viewsupport.ImageDisposer;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
import org.eclipse.jdt.internal.ui.wizards.TypedElementSelectionValidator;
import org.eclipse.jdt.internal.ui.wizards.TypedViewerFilter;
import org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage.NewSourceContainerWorkbookPage;
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.IStringButtonAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringButtonDialogField;

public class BuildPathsBlock {

	public interface IRemoveOldBinariesQuery {

		/**
		 * Do the callback. Returns <code>true</code> if .class files should be removed from the
		 * old output location.
		 * @param removeLocation true if the folder at oldOutputLocation should be removed, false if only its content
		 * @param oldOutputLocation The old output location
		 * @return Returns true if .class files should be removed.
		 * @throws OperationCanceledException if the operation was canceled
		 */
		boolean doQuery(boolean removeLocation, IPath oldOutputLocation) throws OperationCanceledException;

	}


	private IWorkspaceRoot fWorkspaceRoot;

	private CheckedListDialogField<CPListElement> fClassPathList;
	private StringButtonDialogField fBuildPathDialogField;

	private StatusInfo fClassPathStatus;
	private StatusInfo fOutputFolderStatus;
	private StatusInfo fBuildPathStatus;

	private IJavaProject fCurrJProject;

	private IPath fOutputLocationPath;

	private IStatusChangeListener fContext;
	private Control fSWTWidget;
	private CTabFolder fTabFolder;

	private int fPageIndex;

	private BuildPathBasePage fSourceContainerPage;
	private ProjectsWorkbookPage fProjectsPage;
	private LibrariesWorkbookPage fLibrariesPage;
	private ModuleDependenciesPage fModulesPage;

	private BuildPathBasePage fCurrPage;

	private String fUserSettingsTimeStamp;
	private long fFileTimeStamp;

    private IRunnableContext fRunnableContext;

	private boolean fUseNewPage;

	private boolean fIs9OrHigher;

	private final IWorkbenchPreferenceContainer fPageContainer; // null when invoked from a non-property page context

	private final static int IDX_UP= 0;
	private final static int IDX_DOWN= 1;
	private final static int IDX_TOP= 3;
	private final static int IDX_BOTTOM= 4;
	private final static int IDX_SELECT_ALL= 6;
	private final static int IDX_UNSELECT_ALL= 7;

	public BuildPathsBlock(IRunnableContext runnableContext, IStatusChangeListener context, int pageToShow, boolean useNewPage, IWorkbenchPreferenceContainer pageContainer) {
		fPageContainer= pageContainer;
		fWorkspaceRoot= JavaPlugin.getWorkspace().getRoot();
		fContext= context;
		fUseNewPage= useNewPage;

		fPageIndex= pageToShow;

		fSourceContainerPage= null;
		fLibrariesPage= null;
		fProjectsPage= null;
		fCurrPage= null;
        fRunnableContext= runnableContext;

		BuildPathAdapter adapter= new BuildPathAdapter();

		String[] buttonLabels= new String[] {
			/* IDX_UP */ NewWizardMessages.BuildPathsBlock_classpath_up_button,
			/* IDX_DOWN */ NewWizardMessages.BuildPathsBlock_classpath_down_button,
			/* 2 */ null,
			/* IDX_TOP */ NewWizardMessages.BuildPathsBlock_classpath_top_button,
			/* IDX_BOTTOM */ NewWizardMessages.BuildPathsBlock_classpath_bottom_button,
			/* 5 */ null,
			/* IDX_SELECT_ALL */ NewWizardMessages.BuildPathsBlock_classpath_checkall_button,
			/* IDX_UNSELECT_ALL */ NewWizardMessages.BuildPathsBlock_classpath_uncheckall_button

		};

		fClassPathList= new CheckedListDialogField<>(adapter, buttonLabels, new CPListLabelProvider());
		fClassPathList.setDialogFieldListener(adapter);
		fClassPathList.setLabelText(NewWizardMessages.BuildPathsBlock_classpath_label);
		fClassPathList.setUpButtonIndex(IDX_UP);
		fClassPathList.setDownButtonIndex(IDX_DOWN);
		fClassPathList.setCheckAllButtonIndex(IDX_SELECT_ALL);
		fClassPathList.setUncheckAllButtonIndex(IDX_UNSELECT_ALL);

		fBuildPathDialogField= new StringButtonDialogField(adapter);
		fBuildPathDialogField.setButtonLabel(NewWizardMessages.BuildPathsBlock_buildpath_button);
		fBuildPathDialogField.setDialogFieldListener(adapter);
		fBuildPathDialogField.setLabelText(NewWizardMessages.BuildPathsBlock_buildpath_label);

		fBuildPathStatus= new StatusInfo();
		fClassPathStatus= new StatusInfo();
		fOutputFolderStatus= new StatusInfo();

		fCurrJProject= null;
	}

	// -------- UI creation ---------

	public Control createControl(Composite parent) {
		fSWTWidget= parent;

		Composite composite= new Composite(parent, SWT.NONE);
		composite.setFont(parent.getFont());

		GridLayout layout= new GridLayout();
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		layout.numColumns= 1;
		composite.setLayout(layout);

		CTabFolder folder= new CTabFolder(composite, SWT.NONE);
		folder.setLayoutData(new GridData(GridData.FILL_BOTH));
		folder.setFont(composite.getFont());

		CTabItem item;
        item= new CTabItem(folder, SWT.NONE);
        folder.setSelection(0);
        item.setText(NewWizardMessages.BuildPathsBlock_tab_source);
        item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_PACKFRAG_ROOT));

        if (fUseNewPage) {
			fSourceContainerPage= new NewSourceContainerWorkbookPage(fClassPathList, fBuildPathDialogField, fRunnableContext, this);
        } else {
			fSourceContainerPage= new SourceContainerWorkbookPage(fClassPathList, fBuildPathDialogField);
        }
        item.setData(fSourceContainerPage);
        item.setControl(fSourceContainerPage.getControl(folder));

		Image projectImage= PlatformUI.getWorkbench().getSharedImages().getImage(IDE.SharedImages.IMG_OBJ_PROJECT);

		fProjectsPage= new ProjectsWorkbookPage(fClassPathList, fPageContainer);
		item= new CTabItem(folder, SWT.NONE);
		item.setText(NewWizardMessages.BuildPathsBlock_tab_projects);
		item.setImage(projectImage);
		item.setData(fProjectsPage);
		item.setControl(fProjectsPage.getControl(folder));

		fLibrariesPage= new LibrariesWorkbookPage(fClassPathList, fPageContainer);
		item= new CTabItem(folder, SWT.NONE);
		item.setText(NewWizardMessages.BuildPathsBlock_tab_libraries);
		item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_LIBRARY));
		item.setData(fLibrariesPage);
		item.setControl(fLibrariesPage.getControl(folder));

		// a non shared image
		Image cpoImage= JavaPluginImages.DESC_TOOL_CLASSPATH_ORDER.createImage();
		composite.addDisposeListener(new ImageDisposer(cpoImage));

		ClasspathOrderingWorkbookPage ordpage= new ClasspathOrderingWorkbookPage(fClassPathList);
		item= new CTabItem(folder, SWT.NONE);
		item.setText(NewWizardMessages.BuildPathsBlock_tab_order);
		item.setImage(cpoImage);
		item.setData(ordpage);
		item.setControl(ordpage.getControl(folder));

		fModulesPage= new ModuleDependenciesPage(fContext, fClassPathList);
		item= new CTabItem(folder, SWT.NONE);
		item.setText(NewWizardMessages.BuildPathsBlock_tab_modules);
		item.setImage(JavaPluginImages.get(JavaPluginImages.IMG_OBJS_MODULE));
		item.setData(fModulesPage);
		item.setControl(fModulesPage.getControl(folder));

		if (fCurrJProject != null) {
			fSourceContainerPage.init(fCurrJProject);
			fLibrariesPage.init(fCurrJProject);
			fProjectsPage.init(fCurrJProject);
			fModulesPage.init(fCurrJProject);
			fIs9OrHigher= JavaModelUtil.is9OrHigher(fCurrJProject);
		}

		folder.setSelection(fPageIndex);
		fCurrPage= (BuildPathBasePage) folder.getItem(fPageIndex).getData();
		folder.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				tabChanged(e.item);
			}
		});
		fTabFolder= folder;

		Dialog.applyDialogFont(composite);
		return composite;
	}

	private Shell getShell() {
		if (fSWTWidget != null) {
			return fSWTWidget.getShell();
		}
		return JavaPlugin.getActiveWorkbenchShell();
	}

	/**
	 * Initializes the classpath for the given project. Multiple calls to init are allowed,
	 * but all existing settings will be cleared and replace by the given or default paths.
	 * @param jproject The java project to configure. Does not have to exist.
	 * @param outputLocation The output location to be set in the page. If <code>null</code>
	 * is passed, jdt default settings are used, or - if the project is an existing Java project- the
	 * output location of the existing project
	 * @param classpathEntries The classpath entries to be set in the page. If <code>null</code>
	 * is passed, jdt default settings are used, or - if the project is an existing Java project - the
	 * classpath entries of the existing project
	 */
	public void init(IJavaProject jproject, IPath outputLocation, IClasspathEntry[] classpathEntries) {
		fCurrJProject= jproject;
		List<CPListElement> newClassPath= null;
		IProject project= fCurrJProject.getProject();
		boolean projectExists= (project.exists() && project.getFile(".classpath").exists()); //$NON-NLS-1$
		IClasspathEntry[] existingEntries= null;
		if  (projectExists) {
			if (outputLocation == null) {
				outputLocation=  fCurrJProject.readOutputLocation();
			}
			existingEntries= fCurrJProject.readRawClasspath();
			if (classpathEntries == null) {
				classpathEntries= existingEntries;
			}
		}
		if (outputLocation == null) {
			outputLocation= getDefaultOutputLocation(jproject);
		}

		if (classpathEntries != null) {
			newClassPath= getCPListElements(classpathEntries, existingEntries);
		}
		if (newClassPath == null) {
			newClassPath= getDefaultClassPath(jproject);
		}

		List<CPListElement> exportedEntries = new ArrayList<>();
		for (int i= 0; i < newClassPath.size(); i++) {
			CPListElement curr= newClassPath.get(i);
			if (curr.isExported() || curr.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
				exportedEntries.add(curr);
			}
		}

		// inits the dialog field
		fBuildPathDialogField.setText(outputLocation.makeRelative().toString());
		fBuildPathDialogField.enableButton(project.exists());
		fClassPathList.setElements(newClassPath);
		fClassPathList.setCheckedElements(exportedEntries);

		fClassPathList.selectFirstElement();

		if (fSourceContainerPage != null) {
			fSourceContainerPage.init(fCurrJProject);
			fProjectsPage.init(fCurrJProject);
			fLibrariesPage.init(fCurrJProject);
			// fModulesPage will be unconditionally initialized in updateUI() below
			fIs9OrHigher= JavaModelUtil.is9OrHigher(fCurrJProject);
		}

		initializeTimeStamps();
		updateUI();
	}

	protected void updateUI() {
		if (fSWTWidget == null || fSWTWidget.isDisposed()) {
			return;
		}

		if (Display.getCurrent() != null) {
			doUpdateUI();
		} else {
			Display.getDefault().asyncExec(() -> {
				if (fSWTWidget == null || fSWTWidget.isDisposed()) {
					return;
				}
				doUpdateUI();
			});
		}
	}

	protected void doUpdateUI() {
		if (fModulesPage.needReInit()) {
			init(fCurrJProject, null, null); // extent of system modules was changed, re-init fClassPathList
		}
		fBuildPathDialogField.refresh();
		fClassPathList.refresh();
		boolean is9OrHigherAfter= JavaModelUtil.is9OrHigher(fCurrJProject);
		if (is9OrHigherAfter != fIs9OrHigher) {
			// update the library and project page if fis9OrHigher changed
			fLibrariesPage.init(fCurrJProject);
			fProjectsPage.init(fCurrJProject);
			fIs9OrHigher= is9OrHigherAfter;
		}
		fModulesPage.init(fCurrJProject); // always update, Apply might have made more modules visible
		doStatusLineUpdate();
	}

	private String getEncodedSettings() {
		StringBuffer buf= new StringBuffer();
		CPListElement.appendEncodePath(fOutputLocationPath, buf).append(';');

		int nElements= fClassPathList.getSize();
		buf.append('[').append(nElements).append(']');
		for (int i= 0; i < nElements; i++) {
			CPListElement elem= fClassPathList.getElement(i);
			elem.appendEncodedSettings(buf);
		}
		return buf.toString();
	}

	public boolean hasChangesInDialog() {
		String currSettings= getEncodedSettings();
		return !currSettings.equals(fUserSettingsTimeStamp);
	}

	public boolean hasChangesInClasspathFile() {
		IFile file= fCurrJProject.getProject().getFile(".classpath"); //$NON-NLS-1$
		return fFileTimeStamp != file.getModificationStamp();
	}

	public boolean isClassfileMissing() {
		return !fCurrJProject.getProject().getFile(".classpath").exists(); //$NON-NLS-1$
	}

	public void initializeTimeStamps() {
		IFile file= fCurrJProject.getProject().getFile(".classpath"); //$NON-NLS-1$
		fFileTimeStamp= file.getModificationStamp();
		fUserSettingsTimeStamp= getEncodedSettings();
	}

	private ArrayList<CPListElement> getCPListElements(IClasspathEntry[] classpathEntries, IClasspathEntry[] existingEntries) {
		List<IClasspathEntry> existing= existingEntries == null ? Collections.<IClasspathEntry>emptyList() : Arrays.asList(existingEntries);
		ArrayList<CPListElement> newClassPath= new ArrayList<>();
		for (IClasspathEntry curr : classpathEntries) {
			newClassPath.add(CPListElement.create(curr, ! existing.contains(curr), fCurrJProject));
		}
		return newClassPath;
	}

	// -------- public api --------

	/**
	 * @return Returns the Java project. Can return <code>null<code> if the page has not
	 * been initialized.
	 */
	public IJavaProject getJavaProject() {
		return fCurrJProject;
	}

	/**
	 *  @return Returns the current output location. Note that the path returned must not be valid.
	 */
	public IPath getOutputLocation() {
		return new Path(fBuildPathDialogField.getText()).makeAbsolute();
	}

	/**
	 *  @return Returns the current class path (raw). Note that the entries returned must not be valid.
	 */
	public IClasspathEntry[] getRawClassPath() {
		List<CPListElement> elements=  fClassPathList.getElements();
		int nElements= elements.size();
		IClasspathEntry[] entries= new IClasspathEntry[elements.size()];

		for (int i= 0; i < nElements; i++) {
			CPListElement currElement= elements.get(i);
			entries[i]= currElement.getClasspathEntry();
		}
		return entries;
	}

	public int getPageIndex() {
		return fPageIndex;
	}


	// -------- evaluate default settings --------

	private List<CPListElement> getDefaultClassPath(IJavaProject jproj) {
		List<CPListElement> list= new ArrayList<>();
		IResource srcFolder;
		IPreferenceStore store= PreferenceConstants.getPreferenceStore();
		String sourceFolderName= store.getString(PreferenceConstants.SRCBIN_SRCNAME);
		if (store.getBoolean(PreferenceConstants.SRCBIN_FOLDERS_IN_NEWPROJ) && sourceFolderName.length() > 0) {
			srcFolder= jproj.getProject().getFolder(sourceFolderName);
		} else {
			srcFolder= jproj.getProject();
		}

		list.add(new CPListElement(jproj, IClasspathEntry.CPE_SOURCE, srcFolder.getFullPath(), srcFolder));

		IClasspathEntry[] jreEntries= PreferenceConstants.getDefaultJRELibrary();
		list.addAll(getCPListElements(jreEntries, null));
		return list;
	}

	public static IPath getDefaultOutputLocation(IJavaProject jproj) {
		IPreferenceStore store= PreferenceConstants.getPreferenceStore();
		if (store.getBoolean(PreferenceConstants.SRCBIN_FOLDERS_IN_NEWPROJ)) {
			String outputLocationName= store.getString(PreferenceConstants.SRCBIN_BINNAME);
			return jproj.getProject().getFullPath().append(outputLocationName);
		} else {
			return jproj.getProject().getFullPath();
		}
	}

	private class BuildPathAdapter implements IStringButtonAdapter, IDialogFieldListener, IListAdapter<CPListElement> {

		// -------- IStringButtonAdapter --------
		@Override
		public void changeControlPressed(DialogField field) {
			buildPathChangeControlPressed(field);
		}

		// ---------- IDialogFieldListener --------
		@Override
		public void dialogFieldChanged(DialogField field) {
			buildPathDialogFieldChanged(field);
		}

		// ---------- IListAdapter --------
		@Override
		public void customButtonPressed(ListDialogField<CPListElement> field, int index) {
			buildPathCustomButtonPressed(field, index);
		}

		@Override
		public void doubleClicked(ListDialogField<CPListElement> field) {
		}

		@Override
		public void selectionChanged(ListDialogField<CPListElement> field) {
			updateTopButtonEnablement();
		}
	}

	private void buildPathChangeControlPressed(DialogField field) {
		if (field == fBuildPathDialogField) {
			IContainer container= chooseContainer();
			if (container != null) {
				fBuildPathDialogField.setText(container.getFullPath().makeRelative().toString());
			}
		}
	}

	public void updateTopButtonEnablement() {
		fClassPathList.enableButton(IDX_BOTTOM, fClassPathList.canMoveDown());
		fClassPathList.enableButton(IDX_TOP, fClassPathList.canMoveUp());
	}

	public void buildPathCustomButtonPressed(ListDialogField<CPListElement> field, int index) {
		List<CPListElement> elems= field.getSelectedElements();
		field.removeElements(elems);
		if (index == IDX_BOTTOM) {
			field.addElements(elems);
		} else if (index == IDX_TOP) {
			field.addElements(elems, 0);
		}
	}

	private void buildPathDialogFieldChanged(DialogField field) {
		if (field == fClassPathList) {
			updateClassPathStatus();
			updateTopButtonEnablement();
		} else if (field == fBuildPathDialogField) {
			updateOutputLocationStatus();
		}
		doStatusLineUpdate();
	}



	// -------- verification -------------------------------

	private void doStatusLineUpdate() {
		if (Display.getCurrent() != null) {
			IStatus res= findMostSevereStatus();
			fContext.statusChanged(res);
		}
	}

	private IStatus findMostSevereStatus() {
		return StatusUtil.getMostSevere(new IStatus[] { fClassPathStatus, fOutputFolderStatus, fBuildPathStatus });
	}


	/**
	 * Validates the build path.
	 */
	public void updateClassPathStatus() {
		fClassPathStatus.setOK();

		List<CPListElement> elements= fClassPathList.getElements();

		CPListElement entryMissing= null;
		CPListElement entryDeprecated= null;
		int nEntriesMissing= 0;

		for (int i= elements.size()-1 ; i >= 0 ; i--) {
			CPListElement currElement= elements.get(i);
			boolean isChecked= fClassPathList.isChecked(currElement);
			if (currElement.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
				if (!isChecked) {
					fClassPathList.setCheckedWithoutUpdate(currElement, true);
				}
				if (!fClassPathList.isGrayed(currElement)) {
					fClassPathList.setGrayedWithoutUpdate(currElement, true);
				}
			} else {
				currElement.setExported(isChecked);
			}

			if (currElement.isMissing()) {
				nEntriesMissing++;
				if (entryMissing == null) {
					entryMissing= currElement;
				}
			}
			if (entryDeprecated == null && currElement.isDeprecated()) {
				entryDeprecated= currElement;
			}
		}

		if (nEntriesMissing > 0) {
			if (nEntriesMissing == 1) {
				fClassPathStatus.setWarning(Messages.format(NewWizardMessages.BuildPathsBlock_warning_EntryMissing, BasicElementLabels.getPathLabel(entryMissing.getPath(), false)));
			} else {
				fClassPathStatus.setWarning(Messages.format(NewWizardMessages.BuildPathsBlock_warning_EntriesMissing, String.valueOf(nEntriesMissing)));
			}
		} else if (entryDeprecated != null) {
			fClassPathStatus.setInfo(entryDeprecated.getDeprecationMessage());
		}

		updateBuildPathStatus();
	}

	/**
	 * Validates output location & build path.
	 */
	private void updateOutputLocationStatus() {
		fOutputLocationPath= null;

		String text= fBuildPathDialogField.getText();
		if ("".equals(text)) { //$NON-NLS-1$
			fOutputFolderStatus.setError(NewWizardMessages.BuildPathsBlock_error_EnterBuildPath);
			return;
		}
		IPath path= getOutputLocation();
		fOutputLocationPath= path;

		IResource res= fWorkspaceRoot.findMember(path);
		if (res != null) {
			// if exists, must be a folder or project
			if (res.getType() == IResource.FILE) {
				fOutputFolderStatus.setError(NewWizardMessages.BuildPathsBlock_error_InvalidBuildPath);
				return;
			}
		}

		fOutputFolderStatus.setOK();

		String pathStr= fBuildPathDialogField.getText();
		Path outputPath= (new Path(pathStr));
		pathStr= outputPath.lastSegment();
		if (".settings".equals(pathStr) && outputPath.segmentCount() == 2) { //$NON-NLS-1$
			fOutputFolderStatus.setWarning(NewWizardMessages.OutputLocation_SettingsAsLocation);
		}

		if (pathStr.charAt(0) == '.' && pathStr.length() > 1) {
			fOutputFolderStatus.setWarning(Messages.format(NewWizardMessages.OutputLocation_DotAsLocation, BasicElementLabels.getResourceName(pathStr)));
		}

		updateBuildPathStatus();
	}

	private void updateBuildPathStatus() {
		List<CPListElement> elements= fClassPathList.getElements();
		IClasspathEntry[] entries= new IClasspathEntry[elements.size()];

		for (int i= elements.size()-1 ; i >= 0 ; i--) {
			CPListElement currElement= elements.get(i);
			entries[i]= currElement.getClasspathEntry();
		}

		IJavaModelStatus status= JavaConventions.validateClasspath(fCurrJProject, entries, fOutputLocationPath);
		if (!status.isOK()) {
			fBuildPathStatus.setError(status.getMessage());
			return;
		}
		fBuildPathStatus.setOK();
	}

	// -------- creation -------------------------------

	public static void createProject(IProject project, URI locationURI, IProgressMonitor monitor) throws CoreException {
		if (monitor == null) {
			monitor= new NullProgressMonitor();
		}
		monitor.beginTask(NewWizardMessages.BuildPathsBlock_operationdesc_project, 10);

		// create the project
		try {
			if (!project.exists()) {
				IProjectDescription desc= project.getWorkspace().newProjectDescription(project.getName());
				if (locationURI != null && ResourcesPlugin.getWorkspace().getRoot().getLocationURI().equals(locationURI)) {
					locationURI= null;
				}
				desc.setLocationURI(locationURI);
				project.create(desc, monitor);
				monitor= null;
			}
			if (!project.isOpen()) {
				project.open(monitor);
				monitor= null;
			}
		} finally {
			if (monitor != null) {
				monitor.done();
			}
		}
	}

	public static void addJavaNature(IProject project, IProgressMonitor monitor) throws CoreException {
		if (monitor != null && monitor.isCanceled()) {
			throw new OperationCanceledException();
		}
		if (!project.hasNature(JavaCore.NATURE_ID)) {
			IProjectDescription description = project.getDescription();
			String[] prevNatures= description.getNatureIds();
			String[] newNatures= new String[prevNatures.length + 1];
			System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
			newNatures[prevNatures.length]= JavaCore.NATURE_ID;
			description.setNatureIds(newNatures);
			project.setDescription(description, monitor);
		} else {
			if (monitor != null) {
				monitor.worked(1);
			}
		}
	}

	public void configureJavaProject(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		configureJavaProject(null, monitor);
	}

	public void configureJavaProject(String newProjectCompliance, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		flush(fClassPathList.getElements(), getOutputLocation(), getJavaProject(), newProjectCompliance, monitor);
		initializeTimeStamps();

		updateUI();
	}

	/**
	 * Sets the configured build path and output location to the given Java project.
	 * If the project already exists, only build paths are updated.
	 * <p>
	 * If the classpath contains an Execution Environment entry, the EE's compiler compliance options
	 * are used as project-specific options (unless the classpath already contained the same Execution Environment)
	 *
	 * @param classPathEntries the new classpath entries (list of {@link CPListElement})
	 * @param outputLocation the output location
	 * @param javaProject the Java project
	 * @param newProjectCompliance compliance to set for a new project, can be <code>null</code>
	 * @param monitor a progress monitor, or <code>null</code>
	 * @throws CoreException if flushing failed
	 * @throws OperationCanceledException if flushing has been cancelled
	 */
	public static void flush(List<CPListElement> classPathEntries, IPath outputLocation, IJavaProject javaProject, String newProjectCompliance, IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		if (monitor == null) {
			monitor= new NullProgressMonitor();
		}
		monitor.setTaskName(NewWizardMessages.BuildPathsBlock_operationdesc_java);
		monitor.beginTask("", classPathEntries.size() * 4 + 4); //$NON-NLS-1$
		try {

			IProject project= javaProject.getProject();
			IPath projPath= project.getFullPath();

			IPath oldOutputLocation;
			try {
				oldOutputLocation= javaProject.getOutputLocation();
			} catch (CoreException e) {
				oldOutputLocation= projPath.append(PreferenceConstants.getPreferenceStore().getString(PreferenceConstants.SRCBIN_BINNAME));
			}

			if (oldOutputLocation.equals(projPath) && !outputLocation.equals(projPath)) {
				if (BuildPathsBlock.hasClassfiles(project)) {
					if (BuildPathsBlock.getRemoveOldBinariesQuery(JavaPlugin.getActiveWorkbenchShell()).doQuery(false, projPath)) {
						BuildPathsBlock.removeOldClassfiles(project);
					}
				}
			} else if (!outputLocation.equals(oldOutputLocation)) {
				IFolder folder= ResourcesPlugin.getWorkspace().getRoot().getFolder(oldOutputLocation);
				if (folder.exists()) {
					if (folder.members().length == 0
							|| BuildPathsBlock.getRemoveOldBinariesQuery(JavaPlugin.getActiveWorkbenchShell()).doQuery(folder.isDerived(), oldOutputLocation)) {
						BuildPathsBlock.removeOldClassfiles(folder);
					}
				}
			}

			monitor.worked(1);

			IWorkspaceRoot fWorkspaceRoot= JavaPlugin.getWorkspace().getRoot();

			//create and set the output path first
			if (!fWorkspaceRoot.exists(outputLocation)) {
				IFolder folder= fWorkspaceRoot.getFolder(outputLocation);
				CoreUtility.createDerivedFolder(folder, true, true, new SubProgressMonitor(monitor, 1));
			} else {
				monitor.worked(1);
			}
			if (monitor.isCanceled()) {
				throw new OperationCanceledException();
			}

			int nEntries= classPathEntries.size();
			IClasspathEntry[] classpath= new IClasspathEntry[nEntries];
			int i= 0;

			for (CPListElement entry : classPathEntries) {
				if(entry.isRootNodeForPath()){
					continue;
				}
				classpath[i]= entry.getClasspathEntry();
				i++;

				IResource res= entry.getResource();
				//1 tick
				if (res instanceof IFolder && entry.getLinkTarget() == null && !res.exists()) {
					CoreUtility.createFolder((IFolder)res, true, true, new SubProgressMonitor(monitor, 1));
				} else {
					monitor.worked(1);
				}

				//3 ticks
				if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
					IPath folderOutput= (IPath) entry.getAttribute(CPListElement.OUTPUT);
					if (folderOutput != null && folderOutput.segmentCount() > 1) {
						IFolder folder= fWorkspaceRoot.getFolder(folderOutput);
						CoreUtility.createDerivedFolder(folder, true, true, new SubProgressMonitor(monitor, 1));
					} else {
						monitor.worked(1);
					}

					IPath path= entry.getPath();
					if (projPath.equals(path)) {
						monitor.worked(2);
						continue;
					}

					if (projPath.isPrefixOf(path)) {
						path= path.removeFirstSegments(projPath.segmentCount());
					}
					IFolder folder= project.getFolder(path);
					IPath orginalPath= entry.getOrginalPath();
					if (orginalPath == null) {
						if (!folder.exists()) {
							//New source folder needs to be created
							if (entry.getLinkTarget() == null) {
								CoreUtility.createFolder(folder, true, true, new SubProgressMonitor(monitor, 2));
							} else {
								folder.createLink(entry.getLinkTarget(), IResource.ALLOW_MISSING_LOCAL, new SubProgressMonitor(monitor, 2));
							}
						}
					} else {
						if (projPath.isPrefixOf(orginalPath)) {
							orginalPath= orginalPath.removeFirstSegments(projPath.segmentCount());
						}
						IFolder orginalFolder= project.getFolder(orginalPath);
						if (entry.getLinkTarget() == null) {
							if (!folder.exists()) {
								//Source folder was edited, move to new location
								IPath parentPath= entry.getPath().removeLastSegments(1);
								if (projPath.isPrefixOf(parentPath)) {
									parentPath= parentPath.removeFirstSegments(projPath.segmentCount());
								}
								if (parentPath.segmentCount() > 0) {
									IFolder parentFolder= project.getFolder(parentPath);
									if (!parentFolder.exists()) {
										CoreUtility.createFolder(parentFolder, true, true, new SubProgressMonitor(monitor, 1));
									} else {
										monitor.worked(1);
									}
								} else {
									monitor.worked(1);
								}
								orginalFolder.move(entry.getPath(), true, true, new SubProgressMonitor(monitor, 1));
							}
						} else {
							if (!folder.exists() || !entry.getLinkTarget().equals(entry.getOrginalLinkTarget())) {
								orginalFolder.delete(true, new SubProgressMonitor(monitor, 1));
								folder.createLink(entry.getLinkTarget(), IResource.ALLOW_MISSING_LOCAL, new SubProgressMonitor(monitor, 1));
							}
						}
					}
				} else {
					if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
						IPath path= entry.getPath();
						if (! path.equals(entry.getOrginalPath())) {
							String eeID= JavaRuntime.getExecutionEnvironmentId(path);
							if (eeID != null) {
								BuildPathSupport.setEEComplianceOptions(javaProject, eeID, newProjectCompliance);
								newProjectCompliance= null; // don't set it again below
							}
						}
						if (newProjectCompliance != null) {
							Map<String, String> options= javaProject.getOptions(false);
							JavaModelUtil.setComplianceOptions(options, newProjectCompliance);
							JavaModelUtil.setDefaultClassfileOptions(options, newProjectCompliance); // complete compliance options
							javaProject.setOptions(options);
						}
					}
					monitor.worked(3);
				}
				if (monitor.isCanceled()) {
					throw new OperationCanceledException();
				}
			}

			javaProject.setRawClasspath(classpath, outputLocation, new SubProgressMonitor(monitor, 2));
		} finally {
			monitor.done();
		}
	}

	public static boolean hasClassfiles(IResource resource) throws CoreException {
		if (resource.isDerived()) {
			return true;
		}
		if (resource instanceof IContainer) {
			for (IResource member : ((IContainer) resource).members()) {
				if (hasClassfiles(member)) {
					return true;
				}
			}
		}
		return false;
	}


	public static void removeOldClassfiles(IResource resource) throws CoreException {
		if (resource.isDerived()) {
			resource.delete(false, null);
		} else if (resource instanceof IContainer) {
			for (IResource member : ((IContainer) resource).members()) {
				removeOldClassfiles(member);
			}
		}
	}

	public static IRemoveOldBinariesQuery getRemoveOldBinariesQuery(final Shell shell) {
		return (removeFolder, oldOutputLocation) -> {
			final int[] res= new int[] { 1 };
			Display.getDefault().syncExec(() -> {
				Shell sh= shell != null ? shell : JavaPlugin.getActiveWorkbenchShell();
				String title= NewWizardMessages.BuildPathsBlock_RemoveBinariesDialog_title;
				String message;
				String pathLabel= BasicElementLabels.getPathLabel(oldOutputLocation, false);
				if (removeFolder) {
					message= Messages.format(NewWizardMessages.BuildPathsBlock_RemoveOldOutputFolder_description, pathLabel);
				} else {
					message= Messages.format(NewWizardMessages.BuildPathsBlock_RemoveBinariesDialog_description, pathLabel);
				}
				MessageDialog dialog= new MessageDialog(sh, title, null, message, MessageDialog.QUESTION, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
				res[0]= dialog.open();
			});
			if (res[0] == 0) {
				return true;
			} else if (res[0] == 1) {
				return false;
			}
			throw new OperationCanceledException();
		};
	}


	// ---------- util method ------------

	private IContainer chooseContainer() {
		Class<?>[] acceptedClasses= new Class[] { IProject.class, IFolder.class };
		ISelectionStatusValidator validator= new TypedElementSelectionValidator(acceptedClasses, false);
		IProject[] allProjects= fWorkspaceRoot.getProjects();
		ArrayList<IProject> rejectedElements= new ArrayList<>(allProjects.length);
		IProject currProject= fCurrJProject.getProject();
		for (IProject project : allProjects) {
			if (!project.equals(currProject)) {
				rejectedElements.add(project);
			}
		}
		ViewerFilter filter= new TypedViewerFilter(acceptedClasses, rejectedElements.toArray());

		ILabelProvider lp= new WorkbenchLabelProvider();
		ITreeContentProvider cp= new WorkbenchContentProvider();

		IResource initSelection= null;
		if (fOutputLocationPath != null) {
			initSelection= fWorkspaceRoot.findMember(fOutputLocationPath);
		}

		FolderSelectionDialog dialog= new FolderSelectionDialog(getShell(), lp, cp);
		dialog.setTitle(NewWizardMessages.BuildPathsBlock_ChooseOutputFolderDialog_title);
		dialog.setValidator(validator);
		dialog.setMessage(NewWizardMessages.BuildPathsBlock_ChooseOutputFolderDialog_description);
		dialog.addFilter(filter);
		dialog.setInput(fWorkspaceRoot);
		dialog.setInitialSelection(initSelection);
		dialog.setComparator(new ResourceComparator(ResourceComparator.NAME));

		if (dialog.open() == Window.OK) {
			return (IContainer)dialog.getFirstResult();
		}
		return null;
	}

	// -------- tab switching ----------

	private void tabChanged(Widget widget) {
		if (widget instanceof CTabItem) {
			CTabItem tabItem= (CTabItem) widget;
			BuildPathBasePage newPage= (BuildPathBasePage) tabItem.getData();
			if (fCurrPage != null) {
				List<?> selection= fCurrPage.getSelection();
				if (!selection.isEmpty()) {
					newPage.setSelection(selection, false);
				}
			}
			fCurrPage= newPage;
			fPageIndex= tabItem.getParent().getSelectionIndex();
		}
	}

	private int getPageIndex(int entryKind) {
		switch (entryKind) {
			case IClasspathEntry.CPE_CONTAINER:
			case IClasspathEntry.CPE_LIBRARY:
			case IClasspathEntry.CPE_VARIABLE:
				return 2;
			case IClasspathEntry.CPE_PROJECT:
				return 1;
			case IClasspathEntry.CPE_SOURCE:
				return 0;
		}
		return 0;
	}

	private CPListElement findElement(IClasspathEntry entry) {
		CPListElement prefixMatch= null;
		int entryKind= entry.getEntryKind();
		for (int i= 0, len= fClassPathList.getSize(); i < len; i++) {
			CPListElement curr= fClassPathList.getElement(i);
			if (curr.getEntryKind() == entryKind) {
				IPath entryPath= entry.getPath();
				IPath currPath= curr.getPath();
				if (currPath.equals(entryPath)) {
					return curr;
				}
				// in case there's no full match, look for a similar container (same ID segment):
				if (prefixMatch == null && entryKind == IClasspathEntry.CPE_CONTAINER) {
					int n= entryPath.segmentCount();
					if (n > 0) {
						IPath genericContainerPath= n == 1 ? entryPath : entryPath.removeLastSegments(n - 1);
						if (n > 1 && genericContainerPath.isPrefixOf(currPath)) {
							prefixMatch= curr;
						}
					}
				}
			}
		}
		return prefixMatch;
	}

	public void setElementToReveal(IClasspathEntry entry, String attributeKey) {
		int pageIndex= getPageIndex(entry.getEntryKind());
		if (fTabFolder == null) {
			fPageIndex= pageIndex;
		} else {
			fTabFolder.setSelection(pageIndex);
			CPListElement element= findElement(entry);
			if (element != null) {
				Object elementToSelect= element;

				if (attributeKey != null) {
					Object attrib= element.findAttributeElement(attributeKey);
					if (attrib != null) {
						elementToSelect= attrib;
					}
				}
				BuildPathBasePage page= (BuildPathBasePage) fTabFolder.getItem(pageIndex).getData();
				List<Object> selection= new ArrayList<>(1);
				selection.add(elementToSelect);
				page.setSelection(selection, true);
			}
		}
	}

	public void addElement(IClasspathEntry entry) {
		int pageIndex= getPageIndex(entry.getEntryKind());
		if (fTabFolder == null) {
			fPageIndex= pageIndex;
		} else {
			fTabFolder.setSelection(pageIndex);

			Object page=  fTabFolder.getItem(pageIndex).getData();
			if (page instanceof LibrariesWorkbookPage) {
				CPListElement element= CPListElement.create(entry, true, fCurrJProject);
				((LibrariesWorkbookPage) page).addElement(element);
			}
		}
	}

	public void dispose() {
		if (fSourceContainerPage instanceof NewSourceContainerWorkbookPage) {
			((NewSourceContainerWorkbookPage)fSourceContainerPage).dispose();
			fSourceContainerPage= null;
		}
    }

	public boolean isOKStatus() {
	    return findMostSevereStatus().isOK();
    }

	public void setFocus() {
		fSourceContainerPage.setFocus();
    }
}
