project/com.nokia.carbide.cpp.project.ui/src/com/nokia/carbide/cpp/internal/project/ui/importWizards/ProjectPropertiesPage.java
author timkelly
Fri, 30 Oct 2009 14:12:42 -0500
changeset 553 fe69ca72a1a0
parent 0 fb279309251b
child 559 9198c2581dd6
permissions -rw-r--r--
Fix bug 9997. Warn against using project root at root && > 20,000 files exist under that root.

/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/
package com.nokia.carbide.cpp.internal.project.ui.importWizards;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
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.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;

import com.nokia.carbide.cdt.builder.EpocEngineHelper;
import com.nokia.carbide.cpp.internal.project.ui.ProjectUIHelpIds;
import com.nokia.carbide.cpp.sdk.core.ISymbianBuildContext;

public class ProjectPropertiesPage extends WizardPage implements Listener {
	
    private Text projectName;
    private Text rootDirectory;
    private Button browseButton;
    private long MAX_FILE_COUNT_UNDER_ROOT = 20000;
    
    String projectNameText = ""; //$NON-NLS-1$
    IPath rootDirectoryPath = null;
    IPath rootPathContainingAllProjectFiles = null;
    IPath rootPathContainingAllProjectAndSourceFiles = null;
    
    private BldInfImportWizard theWizard;

    // for caching
	private String parsedBldInfFile = null;
	private List<ISymbianBuildContext> parsedWithConfigs = null;
	private List<String> selectedMakMakeRefs = null;

	
	public ProjectPropertiesPage(BldInfImportWizard wizard) {
		super(Messages.ProjectPropertiesPage_title);
		setTitle(Messages.ProjectPropertiesPage_title);
		setDescription(Messages.ProjectPropertiesPage_description);
		theWizard = wizard;
	}

	public void createControl(Composite parent) {
		setPageComplete(false);
		setErrorMessage(null);
		setMessage(null);

		initializeDialogUnits(parent);
        
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(new GridLayout(3, false));
        
        createControls(composite);
        setControl(composite);        

        composite.setData(".uid", "ProjectPropertiesPage"); //$NON-NLS-1$ //$NON-NLS-2$
		getControl().setData("ProjectPropertiesPage", this); //$NON-NLS-1$
	}
	
	private void createControls(Composite parent) {
		Font font = parent.getFont();
		
		Label nameLabel = new Label(parent, SWT.NONE);
		nameLabel.setFont(font);
		nameLabel.setText(Messages.ProjectPropertiesPage_projectNameLabel);
		nameLabel.setToolTipText(Messages.ProjectPropertiesPage_projectNameTooltip);
		GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan = 1;
		nameLabel.setLayoutData(gd);

		projectName = new Text(parent, SWT.SINGLE | SWT.BORDER);
    	projectName.setFont(font);
    	projectName.setToolTipText(Messages.ProjectPropertiesPage_projectNameTooltip);
    	gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
		gd.horizontalSpan = 2;
    	projectName.setLayoutData(gd);
		projectName.addListener(SWT.Modify, this);
		projectName.setData(".uid", "projectName"); //$NON-NLS-1$ //$NON-NLS-2$
    	
		Label dirLabel = new Label(parent, SWT.NONE);
		dirLabel.setFont(font);
		dirLabel.setText(Messages.ProjectPropertiesPage_rootDirectoryLabel);
		dirLabel.setToolTipText(Messages.ProjectPropertiesPage_rootDirectoryTooltip);
		gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan = 1;
		dirLabel.setLayoutData(gd);

		rootDirectory = new Text(parent, SWT.SINGLE | SWT.BORDER);
		rootDirectory.setFont(font);
		rootDirectory.setToolTipText(Messages.ProjectPropertiesPage_rootDirectoryTooltip);
		gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
		gd.horizontalSpan = 1;
    	rootDirectory.setLayoutData(gd);
    	rootDirectory.addListener(SWT.Modify, this);
    	rootDirectory.setData(".uid", "rootDirectory"); //$NON-NLS-1$ //$NON-NLS-2$

    	browseButton = new Button(parent, SWT.PUSH);
    	browseButton.setFont(font);
    	browseButton.setText(Messages.ProjectPropertiesPage_browseButtonLabel);
    	browseButton.setToolTipText(Messages.ProjectPropertiesPage_browseButtonTooltip);
    	browseButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    	browseButton.addListener(SWT.Selection, this);
    	browseButton.setData(".uid", "browseButton"); //$NON-NLS-1$ //$NON-NLS-2$
    	
		Label helpText = new Label(parent, SWT.NONE);
		helpText.setFont(font);
		helpText.setText(Messages.ProjectPropertiesPage_helpText);
		gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan = 3;
		helpText.setLayoutData(gd);

		setButtonLayoutData(browseButton);
	}

	public void handleEvent(Event event) {
		if (event.widget == browseButton) {
			handleBrowse();
		} else if (event.widget == rootDirectory && rootDirectory.isVisible()) {
			setPageComplete(validatePage());
		} else if (event.widget == projectName && projectName.isVisible()) {
			setPageComplete(validatePage());
		}
	}

	private void handleBrowse() {
	    String selectedDir = showBrowseDialog();
	    if (selectedDir != null) {
	        if (!selectedDir.equals(rootDirectory.getText())) {
	        	rootDirectory.setText(selectedDir);
	        }
	    }
	}

    private String showBrowseDialog() {
        DirectoryDialog dialog = new DirectoryDialog(rootDirectory.getShell(), SWT.OPEN);
        dialog.setText(Messages.ProjectPropertiesPage_browseDialogTitle);

        dialog.setFilterPath(rootDirectory.getText());

        return dialog.open();
    }

    private boolean validatePage() {
    	setMessage(null);
		setErrorMessage(null);

		projectNameText = projectName.getText().trim();

		if (projectNameText == null || projectNameText == "") { //$NON-NLS-1$
			setErrorMessage(Messages.ProjectPropertiesPage_noProjectSpecifiedError);
			return false;
		}
		
        IStatus nameStatus = ResourcesPlugin.getWorkspace().validateName(projectNameText, IResource.PROJECT);
        if (!nameStatus.isOK()) {
            setErrorMessage(nameStatus.getMessage());
            return false;
        }

        // builds don't work if the project name if greater than 32 characters for
        // some reason - see bug #2231.
        if (projectNameText.length() > 32) {
			setErrorMessage(Messages.ProjectPropertiesPage_projectNameTooLong);
			return false;
        }
        
        // make sure a project with that name doesn't already exist in the workspace (ignoring case)
        for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
        	if (project.getName().compareToIgnoreCase(projectNameText) == 0) {
    			setErrorMessage(Messages.ProjectPropertiesPage_projectExistsError);
    			return false;
        	}
        }
		
		// check the root directory
		if (rootDirectory.getText() == null || rootDirectory.getText() == "") { //$NON-NLS-1$
			setErrorMessage(Messages.ProjectPropertiesPage_noDirectorySpecifiedError);
			return false;
		}
		rootDirectoryPath = new Path(rootDirectory.getText());
		
		if (!rootDirectoryPath.toFile().exists()) {
			setErrorMessage(Messages.ProjectPropertiesPage_directoryDoesNotExitError);
			return false;
		}

		// make sure the root directory they chose at least contains all project files.  ignore case as well.
		if (rootPathContainingAllProjectFiles != null && !rootPathContainingAllProjectFiles.toOSString().toLowerCase().startsWith(rootDirectoryPath.removeTrailingSeparator().toOSString().toLowerCase())) {
			setErrorMessage(Messages.ProjectPropertiesPage_directoryDoesNotContainRequiredFiles);
			return false;
		}
		
		// see if there is already a .project file at this location.  note that above check only
		// checks for projects in the workspace.  this checks to see if there is another project
		// at the location where the .project file would be created.  this will see if a project
		// that the workspace knows about (open or close) exists in that location.  note that there
		// could be a .project file at that location in the file system.  we'll allow that as the
		// user will be prompted after they click finish if they want to overwrite that project.  but
		// if there is a project in the workbench for that .project file then we can't let them overwrite
		// it, so we post the error here.
		IContainer container = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(rootDirectoryPath);
		if (container != null && container.getType() == IResource.PROJECT) {
			setErrorMessage(String.format(Messages.ProjectPropertiesPage_projectFileExistsError, new Object[]{container.getName(), rootDirectoryPath.toOSString()}));
			return false;
		}

		// the max path length is 256 characters.  we will create {root}\.settings\org.eclipse.cdt.core.prefs, so we need to
		// make sure that the project root is not more than 221 characters.  we'll add a little extra wiggle room though so
		// call it 215.
		if  (rootDirectoryPath.toOSString().length() > 215) {
			setErrorMessage(Messages.ProjectPropertiesPage_directoryTooLong);
			return false;
		}
		
		// warn if the project root doesn't contain sources (bug 5527)
		if (rootPathContainingAllProjectAndSourceFiles != null && !rootPathContainingAllProjectAndSourceFiles.toOSString().toLowerCase().startsWith(rootDirectoryPath.removeTrailingSeparator().toOSString().toLowerCase())) {
			setMessage(Messages.ProjectPropertiesPage_directoryDoesNotContainSourceFiles, IStatus.WARNING);
			
			// still legal
			return true;
		}
		
		if (rootDirectoryPath.isRoot()){
			long resCount = countFilesRecursivelyWithMax(rootDirectoryPath.toFile(), 0);
			// Don't show warning, if by chance there's few sources under the root
			if (resCount >= MAX_FILE_COUNT_UNDER_ROOT){
				setMessage(Messages.ProjectPropertiesPage_directoryIsRoot, IStatus.WARNING);
			}
			return true;
		}
		
		return true;
    }
    
    /**
     * Recursively count the # of files under a given directory. Stops counting when count >= MAX_FILE_COUNT_UNDER_ROOT
     * @param directory - Start directory
     * @param start count, current count
     * @return
     */
    private long countFilesRecursivelyWithMax(File directory, long inCount) {
    	if (inCount >= MAX_FILE_COUNT_UNDER_ROOT) 
    		return MAX_FILE_COUNT_UNDER_ROOT;
    	if (directory.isDirectory()) {
            String[] children = directory.list();
            inCount = children.length + inCount; // increment the file count for this directory
            for (int i=0; i<children.length; i++) {
            	inCount = countFilesRecursivelyWithMax(new File(directory, children[i]), inCount);
            }

            return inCount; // return current file count
        }
    	return inCount; // return current file count

	}
    
    public String getProjectName() {
    	return projectNameText;
    }
    
    public IPath getRootDirectory() {
    	return rootDirectoryPath;
    }

	@Override
	public void setVisible(boolean visible) {
		// this gets called just before the page goes in or out of view.  if it's
		// going into view then get the project name and root directory.  when
		// we do save the bld.inf file and list of build configs we parsed with so we
		// know if we need to re-parse again if either of these changes, e.g. the user
		// hits back and selects a different bld.inf or set of configs
		if (visible) {
			if (parsedBldInfFile == null || parsedWithConfigs == null ||
					!parsedBldInfFile.equals(theWizard.getBldInfFile()) ||
					!parsedWithConfigs.equals(theWizard.getSelectedConfigs()) ||
					!selectedMakMakeRefs.equals(theWizard.getSelectedMakMakeReferences())) {
				parsedBldInfFile = theWizard.getBldInfFile();
				parsedWithConfigs = theWizard.getSelectedConfigs();
				selectedMakMakeRefs = theWizard.getSelectedMakMakeReferences();

				try {
					getContainer().run(true, true, new IRunnableWithProgress() {
						public void run(IProgressMonitor monitor) {
							List<IPath> projectRoots = EpocEngineHelper.getProjectRoots(new Path(parsedBldInfFile), parsedWithConfigs, monitor);
							rootDirectoryPath = projectRoots.get(0);
							rootPathContainingAllProjectFiles = projectRoots.get(1);
							rootPathContainingAllProjectAndSourceFiles = projectRoots.get(2);
							
							// do not use the group directory by default if possible
							if (rootDirectoryPath.segmentCount() > 1 && rootDirectoryPath.lastSegment().compareToIgnoreCase("group") == 0) { //$NON-NLS-1$
								rootDirectoryPath = rootDirectoryPath.removeLastSegments(1);
							}
							
							// do this in the ui thread
							getControl().getDisplay().asyncExec(new Runnable(){
								public void run() {
									rootDirectory.setText(rootDirectoryPath.toOSString());
									projectName.setText(getProposedProjectName());
									setPageComplete(validatePage());
								}
							});
						}
					});
				} catch (InvocationTargetException e) {
				} catch (InterruptedException e) {
					// Nothing to do if the user interrupts.
				}
			}
		}

        super.setVisible(visible);
	}
	
	private String getProposedProjectName() {
		// if there is only one makmake reference selected then use that name, otherwise
		// use the last segment of the proposed project root path.
		List<String> selectedMakMakeRefs = theWizard.getSelectedMakMakeReferences();
		String name = ""; //$NON-NLS-1$
		if (selectedMakMakeRefs.size() == 1) {
			name = selectedMakMakeRefs.get(0); 
			// remove file extension if present
			int loc = name.lastIndexOf("."); //$NON-NLS-1$
			if (loc > 0) {
				name = name.substring(0, loc);
			}
		} else {
			if (rootDirectoryPath.segmentCount() > 0) {
				name = rootDirectoryPath.lastSegment();
			}
		}
		return name;
	}

	@Override
	public void performHelp() {
        PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl().getShell(), ProjectUIHelpIds.PROJECT_PROPERTIES_PAGE);
	}
}