Refactored GitProjectsImportPage
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / clone / GitProjectsImportPage.java
blob14bf0d853269aaacc77a7c63c181128acee3f69d
1 package org.eclipse.egit.ui.internal.clone;
3 /*******************************************************************************
4 * Copyright (c) 2004, 2008 IBM Corporation and others.
5 * Copyright (C) 2007, Martin Oberhuber (martin.oberhuber@windriver.com)
6 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
7 * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua>
8 * Copyright (C) 2010, Wim Jongman <wim.jongman@remainsoftware.com>
10 * All rights reserved. This program and the accompanying materials
11 * are made available under the terms of the Eclipse Public License v1.0
12 * which accompanies this distribution, and is available at
13 * http://www.eclipse.org/legal/epl-v10.html
14 *******************************************************************************/
16 import java.io.File;
17 import java.io.IOException;
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Set;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IProjectDescription;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.resources.IWorkspace;
30 import org.eclipse.core.resources.ResourcesPlugin;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IProgressMonitor;
34 import org.eclipse.core.runtime.IStatus;
35 import org.eclipse.core.runtime.OperationCanceledException;
36 import org.eclipse.core.runtime.Path;
37 import org.eclipse.core.runtime.Platform;
38 import org.eclipse.core.runtime.Status;
39 import org.eclipse.core.runtime.SubProgressMonitor;
40 import org.eclipse.egit.ui.Activator;
41 import org.eclipse.egit.ui.UIText;
42 import org.eclipse.jface.dialogs.Dialog;
43 import org.eclipse.jface.operation.IRunnableWithProgress;
44 import org.eclipse.jface.viewers.ITreeContentProvider;
45 import org.eclipse.jface.viewers.LabelProvider;
46 import org.eclipse.jface.viewers.TreeViewer;
47 import org.eclipse.jface.viewers.Viewer;
48 import org.eclipse.jface.viewers.ViewerComparator;
49 import org.eclipse.jface.wizard.WizardPage;
50 import org.eclipse.osgi.util.NLS;
51 import org.eclipse.swt.SWT;
52 import org.eclipse.swt.events.MouseAdapter;
53 import org.eclipse.swt.events.MouseEvent;
54 import org.eclipse.swt.events.SelectionAdapter;
55 import org.eclipse.swt.events.SelectionEvent;
56 import org.eclipse.swt.graphics.Point;
57 import org.eclipse.swt.layout.GridData;
58 import org.eclipse.swt.layout.GridLayout;
59 import org.eclipse.swt.widgets.Button;
60 import org.eclipse.swt.widgets.Composite;
61 import org.eclipse.swt.widgets.Display;
62 import org.eclipse.swt.widgets.Label;
63 import org.eclipse.swt.widgets.Tree;
64 import org.eclipse.swt.widgets.TreeItem;
65 import org.eclipse.ui.actions.WorkspaceModifyOperation;
66 import org.eclipse.ui.dialogs.FilteredTree;
67 import org.eclipse.ui.dialogs.PatternFilter;
68 import org.eclipse.ui.statushandlers.StatusManager;
69 import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
71 /**
72 * The GitWizardProjectsImportPage is the page that allows the user to import
73 * projects from a particular location. This is a modified copy of the
74 * WizardProjectsImportPage class from the org.eclipse.ui.ide bundle.
76 public class GitProjectsImportPage extends WizardPage {
78 /**
79 * The name of the folder containing metadata information for the workspace.
81 public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
83 private IImportStructureProvider structureProvider;
85 private TreeViewer projectsList;
87 private ProjectRecord[] selectedProjects = new ProjectRecord[0];
89 final private HashSet<Object> checkedItems = new HashSet<Object>();
91 private IProject[] wsProjects;
93 // The last selected path to minimize searches
94 private String lastPath;
96 // The last time that the file or folder at the selected path was modified
97 // to minimize searches
98 private long lastModified;
100 private Button selectAll;
102 private Button deselectAll;
105 * Creates a new project creation wizard page.
107 public GitProjectsImportPage() {
108 super(GitProjectsImportPage.class.getName());
109 setPageComplete(false);
110 setTitle(UIText.WizardProjectsImportPage_ImportProjectsTitle);
111 setDescription(UIText.WizardProjectsImportPage_ImportProjectsDescription);
114 public void createControl(Composite parent) {
116 initializeDialogUnits(parent);
118 Composite workArea = new Composite(parent, SWT.NONE);
119 setControl(workArea);
121 workArea.setLayout(new GridLayout());
122 workArea.setLayoutData(new GridData(GridData.FILL_BOTH
123 | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL));
125 createProjectsRoot(workArea);
126 createProjectsList(workArea);
127 Dialog.applyDialogFont(workArea);
132 * Create the checkbox list for the found projects.
134 * @param workArea
136 private void createProjectsList(Composite workArea) {
138 checkedItems.clear();
140 Label title = new Label(workArea, SWT.NONE);
141 title.setText(UIText.WizardProjectsImportPage_ProjectsListTitle);
143 Composite listComposite = new Composite(workArea, SWT.NONE);
144 GridLayout layout = new GridLayout();
145 layout.numColumns = 2;
146 layout.marginWidth = 0;
147 layout.makeColumnsEqualWidth = false;
148 listComposite.setLayout(layout);
150 listComposite.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
151 | GridData.GRAB_VERTICAL | GridData.FILL_BOTH));
153 PatternFilter filter = new PatternFilter() {
155 @Override
156 public boolean isElementVisible(Viewer viewer, Object element) {
158 if (checkedItems.contains(element)) {
159 return true;
162 return super.isElementVisible(viewer, element);
165 @Override
166 public void setPattern(String patternString) {
167 super.setPattern(patternString);
168 // TODO: is there a better way to react on changes in the tree?
169 // disable select all button when tree becomes empty due to
170 // filtering
171 Display.getDefault().asyncExec(new Runnable() {
172 public void run() {
173 enableSelectAllButtons();
180 // we have to use the old constructor in order to be 3.4 compatible
181 // TODO once we drop 3.4 support, we should change to the new constructor
182 FilteredTree filteredTree = new FilteredTree(listComposite, SWT.CHECK
183 | SWT.BORDER, filter);
184 filteredTree.setInitialText(UIText.WizardProjectsImportPage_filterText);
185 projectsList = filteredTree.getViewer();
186 GridData listData = new GridData(GridData.GRAB_HORIZONTAL
187 | GridData.GRAB_VERTICAL | GridData.FILL_BOTH);
188 projectsList.getControl().setLayoutData(listData);
190 projectsList.setContentProvider(new ITreeContentProvider() {
192 public Object[] getChildren(Object parentElement) {
193 return null;
196 public Object[] getElements(Object inputElement) {
197 return getValidProjects();
200 public boolean hasChildren(Object element) {
201 return false;
204 public Object getParent(Object element) {
205 return null;
208 public void dispose() {
209 // ignore
212 public void inputChanged(Viewer viewer, Object oldInput,
213 Object newInput) {
214 // ignore
219 projectsList.getTree().addMouseListener(new MouseAdapter() {
220 @Override
221 public void mouseUp(MouseEvent e) {
222 if (e.widget instanceof Tree) {
223 TreeItem item = ((Tree) e.widget).getItem(new Point(e.x,
224 e.y));
225 if (item != null) {
226 if (item.getChecked())
227 checkedItems.add(item.getData());
228 else
229 checkedItems.remove(item.getData());
230 setPageComplete(!checkedItems.isEmpty());
236 projectsList.setLabelProvider(new LabelProvider() {
237 public String getText(Object element) {
238 // Need to set the checked item state. FIXME This is clumsy.
239 for (final TreeItem item : projectsList.getTree().getItems()) {
240 if (checkedItems.contains(item.getData()))
241 item.setChecked(true);
242 else
243 item.setChecked(false);
245 return ((ProjectRecord) element).getProjectLabel(structureProvider);
249 projectsList.setInput(this);
250 projectsList.setComparator(new ViewerComparator());
251 createSelectionButtons(listComposite);
255 * Create the selection buttons in the listComposite.
257 * @param listComposite
259 private void createSelectionButtons(Composite listComposite) {
260 Composite buttonsComposite = new Composite(listComposite, SWT.NONE);
261 GridLayout layout = new GridLayout();
262 layout.marginWidth = 0;
263 layout.marginHeight = 0;
264 buttonsComposite.setLayout(layout);
266 buttonsComposite.setLayoutData(new GridData(
267 GridData.VERTICAL_ALIGN_BEGINNING));
269 selectAll = new Button(buttonsComposite, SWT.PUSH);
270 selectAll.setText(UIText.WizardProjectsImportPage_selectAll);
271 selectAll.addSelectionListener(new SelectionAdapter() {
272 public void widgetSelected(SelectionEvent e) {
273 checkedItems.clear();
274 // only the root has children
275 for (final TreeItem item : projectsList.getTree().getItems()) {
276 item.setChecked(true);
277 checkedItems.add(item.getData());
279 setPageComplete(true);
282 Dialog.applyDialogFont(selectAll);
283 setButtonLayoutData(selectAll);
285 deselectAll = new Button(buttonsComposite, SWT.PUSH);
286 deselectAll.setText(UIText.WizardProjectsImportPage_deselectAll);
287 deselectAll.addSelectionListener(new SelectionAdapter() {
288 public void widgetSelected(SelectionEvent e) {
289 checkedItems.clear();
290 // only the root has children
291 for (final TreeItem item : projectsList.getTree().getItems()) {
292 item.setChecked(false);
294 projectsList.setInput(this); // filter away selected projects
295 setPageComplete(false);
298 Dialog.applyDialogFont(deselectAll);
299 setButtonLayoutData(deselectAll);
304 * Create the area where you select the root directory for the projects.
306 * @param workArea
307 * Composite
309 private void createProjectsRoot(Composite workArea) {
311 // project specification group
312 Composite projectGroup = new Composite(workArea, SWT.NONE);
313 GridLayout layout = new GridLayout();
314 layout.numColumns = 3;
315 layout.makeColumnsEqualWidth = false;
316 layout.marginWidth = 0;
317 projectGroup.setLayout(layout);
318 projectGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
322 public void setVisible(boolean visible) {
323 super.setVisible(visible);
327 * Update the list of projects based on path. This will not check any
328 * projects.
330 * @param path
332 void setProjectsList(final String path) {
333 // on an empty path empty selectedProjects
334 if (path == null || path.length() == 0) {
335 selectedProjects = new ProjectRecord[0];
336 projectsList.refresh(true);
337 setPageComplete(checkedItems.size() > 0);
338 lastPath = path;
339 setErrorMessage(UIText.GitProjectsImportPage_NoProjectsMessage);
340 return;
343 final File directory = new File(path);
344 long modified = directory.lastModified();
345 if (path.equals(lastPath) && lastModified == modified) {
346 // since the file/folder was not modified and the path did not
347 // change, no refreshing is required
348 return;
351 setErrorMessage(null);
353 lastPath = path;
354 lastModified = modified;
356 try {
357 getContainer().run(true, true, new IRunnableWithProgress() {
359 public void run(IProgressMonitor monitor) {
361 monitor.beginTask(
362 UIText.WizardProjectsImportPage_SearchingMessage,
363 100);
364 selectedProjects = new ProjectRecord[0];
365 Collection<File> files = new ArrayList<File>();
366 monitor.worked(10);
367 if (directory.isDirectory()) {
369 if (!collectProjectFilesFromDirectory(files, directory,
370 null, monitor)) {
371 return;
373 Iterator<File> filesIterator = files.iterator();
374 selectedProjects = new ProjectRecord[files.size()];
375 int index = 0;
376 monitor.worked(50);
377 monitor
378 .subTask(UIText.WizardProjectsImportPage_ProcessingMessage);
379 while (filesIterator.hasNext()) {
380 File file = filesIterator.next();
381 selectedProjects[index] = new ProjectRecord(file);
382 checkedItems.add(selectedProjects[index]);
383 index++;
386 if (files.isEmpty())
387 setErrorMessage(UIText.GitProjectsImportPage_NoProjectsMessage);
388 } else {
389 monitor.worked(60);
391 monitor.done();
395 } catch (InvocationTargetException e) {
396 Activator.logError(e.getMessage(), e);
397 } catch (InterruptedException e) {
398 // Nothing to do if the user interrupts.
401 projectsList.refresh(true);
402 if (getValidProjects().length < selectedProjects.length) {
403 setMessage(UIText.WizardProjectsImportPage_projectsInWorkspace,
404 WARNING);
405 } else {
406 setMessage(UIText.WizardProjectsImportPage_ImportProjectsDescription);
408 enableSelectAllButtons();
409 setPageComplete(checkedItems.size() > 0);
412 private void enableSelectAllButtons() {
413 if (projectsList.getTree().getItemCount() > 0) {
414 selectAll.setEnabled(true);
415 deselectAll.setEnabled(true);
416 } else {
417 selectAll.setEnabled(false);
418 deselectAll.setEnabled(false);
423 * Collect the list of .project files that are under directory into files.
425 * @param files
426 * @param directory
427 * @param directoriesVisited
428 * Set of canonical paths of directories, used as recursion guard
429 * @param monitor
430 * The monitor to report to
431 * @return boolean <code>true</code> if the operation was completed.
433 private boolean collectProjectFilesFromDirectory(Collection<File> files,
434 File directory, Set<String> directoriesVisited,
435 IProgressMonitor monitor) {
437 if (monitor.isCanceled()) {
438 return false;
440 monitor.subTask(NLS.bind(
441 UIText.WizardProjectsImportPage_CheckingMessage, directory
442 .getPath()));
443 File[] contents = directory.listFiles();
444 if (contents == null)
445 return false;
447 // Initialize recursion guard for recursive symbolic links
448 if (directoriesVisited == null) {
449 directoriesVisited = new HashSet<String>();
450 try {
451 directoriesVisited.add(directory.getCanonicalPath());
452 } catch (IOException exception) {
453 StatusManager.getManager().handle(
454 new Status(IStatus.ERROR, Activator.getPluginId(),
455 exception.getLocalizedMessage(), exception));
459 // first look for project description files
460 final String dotProject = IProjectDescription.DESCRIPTION_FILE_NAME;
461 for (int i = 0; i < contents.length; i++) {
462 File file = contents[i];
463 if (file.isFile() && file.getName().equals(dotProject)) {
464 files.add(file);
465 // don't search sub-directories since we can't have nested
466 // projects
467 return true;
470 // no project description found, so recurse into sub-directories
471 for (int i = 0; i < contents.length; i++) {
472 if (contents[i].isDirectory()) {
473 if (!contents[i].getName().equals(METADATA_FOLDER)) {
474 try {
475 String canonicalPath = contents[i].getCanonicalPath();
476 if (!directoriesVisited.add(canonicalPath)) {
477 // already been here --> do not recurse
478 continue;
480 } catch (IOException exception) {
481 StatusManager.getManager().handle(
482 new Status(IStatus.ERROR, Activator
483 .getPluginId(), exception
484 .getLocalizedMessage(), exception));
487 collectProjectFilesFromDirectory(files, contents[i],
488 directoriesVisited, monitor);
492 return true;
496 * Create the selected projects
498 * @return boolean <code>true</code> if all project creations were
499 * successful.
501 boolean createProjects() {
502 final Object[] selected = checkedItems.toArray();
503 WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
504 protected void execute(IProgressMonitor monitor)
505 throws InvocationTargetException, InterruptedException {
506 try {
507 monitor.beginTask("", selected.length); //$NON-NLS-1$
508 if (monitor.isCanceled()) {
509 throw new OperationCanceledException();
511 for (int i = 0; i < selected.length; i++) {
512 createExistingProject((ProjectRecord) selected[i],
513 new SubProgressMonitor(monitor, 1));
515 } finally {
516 monitor.done();
520 // run the new project creation operation
521 try {
522 getContainer().run(true, true, op);
523 } catch (InterruptedException e) {
524 return false;
525 } catch (InvocationTargetException e) {
526 // one of the steps resulted in a core exception
527 Throwable t = e.getTargetException();
528 Activator.handleError(UIText.WizardProjectImportPage_errorMessage,
529 t, true);
530 return false;
532 return true;
536 * Create the project described in record. If it is successful return true.
538 * @param record
539 * @param monitor
540 * @return boolean <code>true</code> if successful
541 * @throws InvocationTargetException
542 * @throws InterruptedException
544 private boolean createExistingProject(final ProjectRecord record,
545 IProgressMonitor monitor) throws InvocationTargetException,
546 InterruptedException {
547 String projectName = record.getProjectName();
548 final IWorkspace workspace = ResourcesPlugin.getWorkspace();
549 final IProject project = workspace.getRoot().getProject(projectName);
550 if (record.description == null) {
551 // error case
552 record.description = workspace.newProjectDescription(projectName);
553 IPath locationPath = new Path(record.projectSystemFile
554 .getAbsolutePath());
556 // If it is under the root use the default location
557 if (Platform.getLocation().isPrefixOf(locationPath)) {
558 record.description.setLocation(null);
559 } else {
560 record.description.setLocation(locationPath);
562 } else {
563 record.description.setName(projectName);
566 try {
567 monitor.beginTask(
568 UIText.WizardProjectsImportPage_CreateProjectsTask, 100);
569 project.create(record.description, new SubProgressMonitor(monitor,
570 30));
571 project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
572 monitor, 50));
573 } catch (CoreException e) {
574 throw new InvocationTargetException(e);
575 } finally {
576 monitor.done();
579 return true;
583 * Method used for test suite.
585 * @return CheckboxTreeViewer the viewer containing all the projects found
587 public TreeViewer getProjectsList() {
588 return projectsList;
592 * Retrieve all the projects in the current workspace.
594 * @return IProject[] array of IProject in the current workspace
596 private IProject[] getProjectsInWorkspace() {
597 if (wsProjects == null) {
598 wsProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
600 return wsProjects;
604 * Get the array of valid project records that can be imported from the
605 * source workspace or archive, selected by the user. If a project with the
606 * same name exists in both the source workspace and the current workspace,
607 * it will not appear in the list of projects to import and thus cannot be
608 * selected for import.
610 * Method declared public for test suite.
612 * @return ProjectRecord[] array of projects that can be imported into the
613 * workspace
615 public ProjectRecord[] getValidProjects() {
616 List<ProjectRecord> validProjects = new ArrayList<ProjectRecord>();
617 for (int i = 0; i < selectedProjects.length; i++) {
618 if (!isProjectInWorkspace(selectedProjects[i].getProjectName())) {
619 validProjects.add(selectedProjects[i]);
622 return validProjects.toArray(new ProjectRecord[validProjects.size()]);
626 * Determine if the project with the given name is in the current workspace.
628 * @param projectName
629 * String the project name to check
630 * @return boolean true if the project with the given name is in this
631 * workspace
633 private boolean isProjectInWorkspace(String projectName) {
634 if (projectName == null) {
635 return false;
637 IProject[] workspaceProjects = getProjectsInWorkspace();
638 for (int i = 0; i < workspaceProjects.length; i++) {
639 if (projectName.equals(workspaceProjects[i].getName())) {
640 return true;
643 return false;