/* * Copyright (c) 2015 the original author or authors. * 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: * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation */ package org.eclipse.buildship.ui.wizard.project; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.gradle.tooling.ProgressListener; import org.gradle.util.GradleVersion; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.gradleware.tooling.toolingmodel.OmniBuildEnvironment; import com.gradleware.tooling.toolingmodel.OmniGradleBuild; import com.gradleware.tooling.toolingmodel.OmniGradleProjectStructure; import com.gradleware.tooling.toolingmodel.util.Pair; import com.gradleware.tooling.toolingutils.binding.Property; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.buildship.core.GradlePluginsRuntimeException; import org.eclipse.buildship.core.gradle.MissingFeatures; import org.eclipse.buildship.core.i18n.CoreMessages; import org.eclipse.buildship.core.projectimport.ProjectImportConfiguration; import org.eclipse.buildship.core.util.gradle.GradleDistributionFormatter; import org.eclipse.buildship.core.util.gradle.GradleDistributionWrapper; import org.eclipse.buildship.core.util.progress.DelegatingProgressListener; import org.eclipse.buildship.ui.UiPlugin; import org.eclipse.buildship.ui.util.font.FontUtils; import org.eclipse.buildship.ui.util.layout.LayoutUtils; import org.eclipse.buildship.ui.util.widget.UiBuilder; /** * Page in the {@link ProjectImportWizard} showing a preview about the project about to be imported. */ public final class ProjectPreviewWizardPage extends AbstractWizardPage { private final ProjectPreviewLoader projectPreviewLoader; private final Font keyFont; private final Font valueFont; private final String pageContextInformation; private Label projectDirLabel; private Label gradleUserHomeLabel; private Label gradleDistributionLabel; private Label gradleVersionLabel; private Label gradleVersionWarningLabel; private Label javaHomeLabel; private Tree projectPreviewTree; public ProjectPreviewWizardPage(ProjectImportConfiguration configuration, ProjectPreviewLoader previewLoader) { this(configuration, previewLoader, ProjectWizardMessages.Title_PreviewImportWizardPage, ProjectWizardMessages.InfoMessage_GradlePreviewWizardPageDefault, ProjectWizardMessages.InfoMessage_GradlePreviewWizardPageContext); } public ProjectPreviewWizardPage(ProjectImportConfiguration configuration, ProjectPreviewLoader previewLoader, String title, String defaultMessage, String pageContextInformation) { super("ProjectPreview", title, defaultMessage, configuration, ImmutableList.<Property<?>> of()); //$NON-NLS-1$ this.projectPreviewLoader = Preconditions.checkNotNull(previewLoader); this.keyFont = FontUtils.getCustomDialogFont(SWT.BOLD); this.valueFont = FontUtils.getCustomDialogFont(SWT.NONE); this.pageContextInformation = pageContextInformation; } @Override protected void createWidgets(Composite root) { root.setLayout(createLayout()); createContent(root); } private Layout createLayout() { GridLayout layout = LayoutUtils.newGridLayout(2); layout.horizontalSpacing = 4; layout.verticalSpacing = 4; return layout; } private void createContent(Composite root) { createSummaryLabels(root); createPreviewGroup(root); updatePreviewLabels(getConfiguration()); } private void createSummaryLabels(Composite container) { UiBuilder.UiBuilderFactory uiBuilderFactory = getUiBuilderFactory(); uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_ProjectRootDirectory + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ this.projectDirLabel = uiBuilderFactory.newLabel(container).alignFillHorizontal().disabled().font(this.valueFont).control(); createSpacingRow(container, 2); uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_GradleUserHome + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ this.gradleUserHomeLabel = uiBuilderFactory.newLabel(container).alignFillHorizontal().disabled().font(this.valueFont).control(); uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_GradleDistribution + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ this.gradleDistributionLabel = uiBuilderFactory.newLabel(container).alignFillHorizontal().disabled().font(this.valueFont).control(); uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_GradleVersion + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ Composite gradleVersionContainer = new Composite(container, SWT.NONE); GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(gradleVersionContainer); GridLayoutFactory.swtDefaults().margins(0, 0).extendedMargins(0, 0, 0, 0).spacing(0, 0).numColumns(2).applyTo(gradleVersionContainer); this.gradleVersionLabel = uiBuilderFactory.newLabel(gradleVersionContainer).alignLeft().disabled().font(this.valueFont).control(); this.gradleVersionWarningLabel = uiBuilderFactory.newLabel(gradleVersionContainer).alignLeft().control(); this.gradleVersionWarningLabel.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_WARN_TSK)); this.gradleVersionWarningLabel.setCursor(gradleVersionContainer.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); this.gradleVersionWarningLabel.setToolTipText(ProjectWizardMessages.Missing_Features_Tooltip); this.gradleVersionWarningLabel.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { String version = ProjectPreviewWizardPage.this.gradleVersionLabel.getText(); MissingFeatures limitations = new MissingFeatures(GradleVersion.version(version)); FluentIterable<String> limitationMessages = FluentIterable.from(limitations.getMissingFeatures()).transform(new Function<Pair<GradleVersion, String>, String>() { @Override public String apply(Pair<GradleVersion, String> limitation) { return limitation.getSecond(); } }); String message = NLS.bind(ProjectWizardMessages.Missing_Features_Details_0_1, version, Joiner.on('\n').join(limitationMessages)); MessageDialog.openInformation(getShell(), ProjectWizardMessages.Title_Dialog_Missing_Features, message); } }); createSpacingRow(container, 2); uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_JavaHome + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ this.javaHomeLabel = uiBuilderFactory.newLabel(container).alignFillHorizontal().disabled().font(this.valueFont).control(); } private void createPreviewGroup(Composite container) { UiBuilder.UiBuilderFactory uiBuilderFactory = getUiBuilderFactory(); // add spacing between the summary labels and the preview tree createSpacingRow(container, 2); // create an empty row and then a label uiBuilderFactory.newLabel(container).text(ProjectWizardMessages.Label_ProjectStructure + ":").font(this.keyFont).alignLeft(); //$NON-NLS-1$ // create an info icon explaining that the preview can deviate from actual values Label previewStructureInfoLabel = uiBuilderFactory.newLabel(container).alignLeft().control(); previewStructureInfoLabel.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_INFO_TSK)); previewStructureInfoLabel.setCursor(container.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); previewStructureInfoLabel.setToolTipText(ProjectWizardMessages.PreviewStructureInfo_Tooltip); previewStructureInfoLabel.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { MessageDialog.openInformation(getShell(), ProjectWizardMessages.Title_Dialog_PreviewStructureInfo, ProjectWizardMessages.PreviewStructureInfo_Details); } }); // add the preview tree this.projectPreviewTree = uiBuilderFactory.newTree(container).alignFillBoth(2).control(); } private void createSpacingRow(Composite container, int horizontalSpan) { GridData data = new GridData(SWT.LEFT, SWT.CENTER, false, false, horizontalSpan, 1); data.heightHint = 8; new Label(container, SWT.NONE).setLayoutData(data); } private void updatePreviewLabels(ProjectImportConfiguration configuration) { updateFileLabel(this.projectDirLabel, configuration.getProjectDir(), CoreMessages.Value_UseGradleDefault); updateGradleDistributionLabel(this.gradleDistributionLabel, configuration.getGradleDistribution(), CoreMessages.Value_UseGradleDefault); updateGradleVersionLabel(this.gradleVersionLabel, configuration.getGradleDistribution(), CoreMessages.Value_Unknown); this.gradleUserHomeLabel.setText(CoreMessages.Value_Unknown); this.javaHomeLabel.setText(CoreMessages.Value_Unknown); updateGradleVersionWarningLabel(); } private void updateFileLabel(Label target, Property<File> source, String defaultMessage) { File file = source.getValue(); target.setText(file != null ? file.getAbsolutePath() : defaultMessage); } private void updateGradleDistributionLabel(Label target, Property<GradleDistributionWrapper> gradleDistribution, String defaultMessage) { GradleDistributionWrapper gradleDistributionWrapper = gradleDistribution.getValue(); target.setText(gradleDistributionWrapper != null ? GradleDistributionFormatter.toString(gradleDistributionWrapper) : defaultMessage); } private void updateGradleVersionLabel(Label target, Property<GradleDistributionWrapper> gradleDistribution, String defaultMessage) { // set the version to 'unknown' until the effective version is // available from the BuildEnvironment model GradleDistributionWrapper gradleDistributionWrapper = gradleDistribution.getValue(); if (gradleDistributionWrapper == null) { target.setText(defaultMessage); return; } switch (gradleDistributionWrapper.getType()) { case WRAPPER: case LOCAL_INSTALLATION: case REMOTE_DISTRIBUTION: target.setText(defaultMessage); break; case VERSION: target.setText(gradleDistributionWrapper.getConfiguration()); break; default: throw new GradlePluginsRuntimeException("Unrecognized Gradle distribution type: " + gradleDistributionWrapper.getType()); //$NON-NLS-1$ } // if the length of the text is changed and the version warning is visible then we have to // adjust their horizontal alignment target.getParent().layout(); } private void updateGradleVersionWarningLabel() { try { GradleVersion version = GradleVersion.version(this.gradleVersionLabel.getText()); MissingFeatures missingFeatures = new MissingFeatures(version); this.gradleVersionWarningLabel.setVisible(!missingFeatures.getMissingFeatures().isEmpty()); } catch (IllegalArgumentException e) { this.gradleVersionWarningLabel.setVisible(false); } ProjectPreviewWizardPage.this.gradleVersionLabel.getParent().layout(); } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { // whenever the page becomes visible, set the initial preview labels to the values of // the wizard model updatePreviewLabels(getConfiguration()); // schedule the loading of the project preview asynchronously, otherwise the UI will not // update until the job has finished PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { scheduleProjectPreviewJob(); } }); } } private void scheduleProjectPreviewJob() { IWizardContainer container = getContainer(); if (container == null) { return; } try { // once cancellation has been requested by the user, do not block any longer // this way, the user can continue with the import wizard even if the preview is still // loading a no-longer-of-interest model in the background container.run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InterruptedException { monitor.beginTask("Loading project preview", IProgressMonitor.UNKNOWN); //$NON-NLS-1$ final CountDownLatch latch = new CountDownLatch(1); final ProgressListener listener = DelegatingProgressListener.withFullOutput(monitor); final Job job = ProjectPreviewWizardPage.this.projectPreviewLoader.loadPreview(new ProjectPreviewJobResultHandler(latch), ImmutableList.<ProgressListener> of(listener)); while (!latch.await(500, TimeUnit.MILLISECONDS)) { // regularly check if the job was cancelled until // the job has either finished successfully or failed if (monitor.isCanceled()) { job.cancel(); throw new InterruptedException(); } } } }); } catch (InvocationTargetException e) { UiPlugin.logger().error("Failed to load preview.", e.getCause()); //$NON-NLS-1$ } catch (InterruptedException ignored) { } } /** * Loads the Gradle project data required to populate the preview page. Having the logic to load * the data outside of the actual project preview wizard page allows the wizard that is using * the preview page to do additional things in preparation of showing the preview. */ public interface ProjectPreviewLoader { /** * Loads the Gradle project data required to populate the preview page. * * @param resultHandler the handler that is called once the project data has been loaded or * a failure occurred * @param listeners the progress listeners to register when calling Gradle * @return the job in which the Gradle project data is loaded */ Job loadPreview(FutureCallback<Pair<OmniBuildEnvironment, OmniGradleBuild>> resultHandler, List<ProgressListener> listeners); } /** * Updates the project preview once the necessary Gradle models have been loaded. */ private final class ProjectPreviewJobResultHandler implements FutureCallback<Pair<OmniBuildEnvironment, OmniGradleBuild>> { private final CountDownLatch latch; private ProjectPreviewJobResultHandler(CountDownLatch latch) { this.latch = latch; } @Override public void onSuccess(Pair<OmniBuildEnvironment, OmniGradleBuild> result) { // the job has already taken care of logging the success this.latch.countDown(); updateSummary(result.getFirst()); populateTree(result.getSecond()); } @Override public void onFailure(Throwable t) { // the job has already taken care of logging and displaying the error this.latch.countDown(); clearTree(); } private void updateSummary(final OmniBuildEnvironment buildEnvironment) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!getControl().isDisposed()) { // update Gradle user home if (buildEnvironment.getGradle().getGradleUserHome().isPresent()) { String gradleUserHome = buildEnvironment.getGradle().getGradleUserHome().get().getAbsolutePath(); ProjectPreviewWizardPage.this.gradleUserHomeLabel.setText(gradleUserHome); } // update Gradle version String gradleVersion = buildEnvironment.getGradle().getGradleVersion(); ProjectPreviewWizardPage.this.gradleVersionLabel.setText(gradleVersion); updateGradleVersionWarningLabel(); // update Java home String javaHome = buildEnvironment.getJava().getJavaHome().getAbsolutePath(); ProjectPreviewWizardPage.this.javaHomeLabel.setText(javaHome); } } }); } private void populateTree(final OmniGradleBuild buildStructure) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!getControl().isDisposed()) { ProjectPreviewWizardPage.this.projectPreviewTree.removeAll(); populateRecursively(buildStructure, ProjectPreviewWizardPage.this.projectPreviewTree); } } }); } private void clearTree() { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!getControl().isDisposed()) { ProjectPreviewWizardPage.this.projectPreviewTree.removeAll(); } } }); } private void populateRecursively(OmniGradleBuild gradleBuild, Tree parent) { OmniGradleProjectStructure rootProject = gradleBuild.getRootProject(); TreeItem rootTreeItem = new TreeItem(ProjectPreviewWizardPage.this.projectPreviewTree, SWT.NONE); rootTreeItem.setExpanded(true); rootTreeItem.setText(rootProject.getName()); populateRecursively(rootProject, rootTreeItem); for (OmniGradleBuild includedBuilds : gradleBuild.getIncludedBuilds()) { populateRecursively(includedBuilds, parent); } } private void populateRecursively(OmniGradleProjectStructure gradleProjectStructure, TreeItem parent) { for (OmniGradleProjectStructure childProject : gradleProjectStructure.getChildren()) { TreeItem treeItem = new TreeItem(parent, SWT.NONE); treeItem.setText(childProject.getName()); populateRecursively(childProject, treeItem); } } } @Override protected String getPageContextInformation() { return this.pageContextInformation; } @Override public void dispose() { this.keyFont.dispose(); this.valueFont.dispose(); super.dispose(); } }