/******************************************************************************* * Copyright (c) 2015, 2016 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.wizard; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.preference.IPreferenceStore; import org.springframework.ide.eclipse.boot.core.IMavenCoordinates; import org.springframework.ide.eclipse.boot.core.ISpringBootProject; import org.springframework.ide.eclipse.boot.core.MavenId; import org.springframework.ide.eclipse.boot.core.SpringBootCore; import org.springframework.ide.eclipse.boot.core.SpringBootStarter; import org.springframework.ide.eclipse.boot.core.SpringBootStarters; import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec; import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency; import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.DependencyGroup; import org.springframework.ide.eclipse.boot.util.Log; import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel; import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression; import org.springsource.ide.eclipse.commons.livexp.ui.OkButtonHandler; import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; /** * @author Kris De Volder */ public class EditStartersModel implements OkButtonHandler { public static final Object JOB_FAMILY = "EditStartersModel.JOB_FAMILY"; public final DependencyFilterBox searchBox = new DependencyFilterBox(); private final ISpringBootProject project; private final PopularityTracker popularities; private final DefaultDependencies defaultDependencies; /** * Will be used to remember the set of initially selected dependencies (i.e. those that are already * present in the project when the dialog is opened. */ private final List<Dependency> initialDependencies = new ArrayList<>(); public final HierarchicalMultiSelectionFieldModel<Dependency> dependencies = new HierarchicalMultiSelectionFieldModel<>(Dependency.class, "dependencies") .label("Dependencies:"); private HashSet<MavenId> activeStarters; private SpringBootStarters starters; /** * Create EditStarters dialog model and initialize it based on a project selection. */ public EditStartersModel(IProject selectedProject, SpringBootCore springBootCore, IPreferenceStore store) throws Exception { this.popularities = new PopularityTracker(store); this.defaultDependencies = new DefaultDependencies(store); this.project = springBootCore.project(selectedProject); discoverOptions(dependencies); } public String getBootVersion() { return starters.getBootVersion(); } public String getProjectName() { return project.getProject().getName(); } public void performOk() { Job job = new Job("Modifying starters for "+getProjectName()) { @Override protected IStatus run(IProgressMonitor monitor) { try { List<Dependency> selected = dependencies.getCurrentSelection(); List<SpringBootStarter> selectedStarters = new ArrayList<>(selected.size()); for (Dependency dep : selected) { String id = dep.getId(); SpringBootStarter starter = starters.getStarter(id); if (starter!=null) { selectedStarters.add(starter); } } project.setStarters(selectedStarters); for (Dependency s : selected) { if (!initialDependencies.contains(s)) { popularities.incrementUsageCount(s); } } return Status.OK_STATUS; } catch (Exception e) { Log.log(e); return ExceptionUtil.status(e); } } @Override public boolean belongsTo(Object family) { return family==JOB_FAMILY; } }; job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); job.schedule(); } /** * Dynamically discover input fields and 'style' options by parsing initializr form. */ private void discoverOptions(HierarchicalMultiSelectionFieldModel<Dependency> dependencies) throws Exception { starters = project.getStarterInfos(); Set<MavenId> activeStarters = getActiveStarters(); if (starters!=null) { for (DependencyGroup dgroup : starters.getDependencyGroups()) { String catName = dgroup.getName(); // Setup template links variable values Map<String, String> variables = new HashMap<>(); variables.put(InitializrServiceSpec.BOOT_VERSION_LINK_TEMPLATE_VARIABLE, starters.getBootVersion()); for (Dependency dep : dgroup.getContent()) { if (starters.contains(dep.getId())) { dependencies.choice(catName, dep.getName(), dep, () -> DependencyHtmlContent.generateHtmlDocumentation(dep, variables), LiveExpression.constant(true)); MavenId mavenId = starters.getMavenId(dep.getId()); boolean selected = activeStarters.contains(mavenId); if (selected) { initialDependencies.add(dep); } dependencies.setSelection(catName, dep, selected); } } } } } private Set<MavenId> getActiveStarters() throws Exception { if (this.activeStarters==null) { this.activeStarters = new HashSet<>(); List<IMavenCoordinates> deps = project.getDependencies(); if (deps!=null) { for (IMavenCoordinates coords : deps) { String gid = coords.getGroupId(); String aid = coords.getArtifactId(); if (aid!=null && gid!=null) { this.activeStarters.add(new MavenId(gid, aid)); } } } } return activeStarters; } /** * Retrieves the most popular dependencies based on the number of times they have * been used to create a project. This similar to how it works in {@link NewSpringBootWizardModel} * except that we add the initially selected elements regardless of their usage count and * then use the usage count to backfill any remaining spots. * * @param howMany is an upper limit on the number of most popular items to be returned. * @return An array of the most popular dependencies. May return fewer items than requested. */ public List<CheckBoxModel<Dependency>> getMostPopular(int howMany) { ArrayList<CheckBoxModel<Dependency>> result = new ArrayList<>(); Set<Dependency> seen = new HashSet<>(); for (CheckBoxModel<Dependency> cb : dependencies.getAllBoxes()) { if (cb.getSelection().getValue()) { if (seen.add(cb.getValue())) { result.add(cb); } } } if (result.size() < howMany) { //space for adding some not yet selected 'popular' selections List<CheckBoxModel<Dependency>> popular = popularities.getMostPopular(dependencies, howMany); Iterator<CheckBoxModel<Dependency>> iter = popular.iterator(); while (result.size() < howMany && iter.hasNext()) { CheckBoxModel<Dependency> cb = iter.next(); if (seen.add(cb.getValue())) { result.add(cb); } } } return Collections.unmodifiableList(result); } /** * Retrieves currently set default dependencies * @return list of default dependencies check-box models */ public List<CheckBoxModel<Dependency>> getDefaultDependencies() { return defaultDependencies.getDependencies(dependencies); } /** * Retrieves frequently used dependencies. * * @param numberOfMostPopular max number of most popular dependencies * @return list of frequently used dependencies */ public List<CheckBoxModel<Dependency>> getFrequentlyUsedDependencies(int numberOfMostPopular) { List<CheckBoxModel<Dependency>> dependencies = getDefaultDependencies(); Set<String> defaultDependecyIds = defaultDependencies.getDependciesIdSet(); getMostPopular(numberOfMostPopular).stream().filter(checkboxModel -> { return !defaultDependecyIds.contains(checkboxModel.getValue().getId()); }).forEach(dependencies::add); // Sort alphabetically dependencies.sort(new Comparator<CheckBoxModel<Dependency>>() { @Override public int compare(CheckBoxModel<Dependency> d1, CheckBoxModel<Dependency> d2) { return d1.getLabel().compareTo(d2.getLabel()); } }); return dependencies; } /** * Convenience method for easier scripting of the wizard model (used in testing). Not used * by the UI itself. If the dependencyId isn't found in the wizard model then an IllegalArgumentException * will be raised. */ public void removeDependency(String dependencyId) { for (String catName : dependencies.getCategories()) { MultiSelectionFieldModel<Dependency> cat = dependencies.getContents(catName); for (Dependency dep : cat.getChoices()) { if (dependencyId.equals(dep.getId())) { cat.unselect(dep); return; //dep found and unselected } } } throw new IllegalArgumentException("No such dependency: "+dependencyId); } /** * Convenience method for easier scripting of the wizard model (used in testing). Not used * by the UI itself. If the dependencyId isn't found in the wizard model then an IllegalArgumentException * will be raised. */ public void addDependency(String dependencyId){ for (String catName : dependencies.getCategories()) { MultiSelectionFieldModel<Dependency> cat = dependencies.getContents(catName); for (Dependency dep : cat.getChoices()) { if (dependencyId.equals(dep.getId())) { cat.select(dep); return; //dep found and added to selection } } } throw new IllegalArgumentException("No such dependency: "+dependencyId); } }