package org.jboss.forge.addon.projects.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.jboss.forge.addon.convert.Converter;
import org.jboss.forge.addon.projects.BuildSystem;
import org.jboss.forge.addon.projects.BuildSystemFacet;
import org.jboss.forge.addon.projects.Project;
import org.jboss.forge.addon.projects.ProjectFacet;
import org.jboss.forge.addon.projects.ProjectFactory;
import org.jboss.forge.addon.projects.ProjectType;
import org.jboss.forge.addon.projects.facets.MetadataFacet;
import org.jboss.forge.addon.resource.DirectoryResource;
import org.jboss.forge.addon.resource.Resource;
import org.jboss.forge.addon.resource.ResourceFactory;
import org.jboss.forge.addon.ui.UIValidator;
import org.jboss.forge.addon.ui.context.UIBuilder;
import org.jboss.forge.addon.ui.context.UIContext;
import org.jboss.forge.addon.ui.context.UISelection;
import org.jboss.forge.addon.ui.context.UIValidationContext;
import org.jboss.forge.addon.ui.input.SingleValued;
import org.jboss.forge.addon.ui.input.UIInput;
import org.jboss.forge.addon.ui.input.UISelectOne;
import org.jboss.forge.addon.ui.metadata.UICommandMetadata;
import org.jboss.forge.addon.ui.metadata.WithAttributes;
import org.jboss.forge.addon.ui.result.NavigationResult;
import org.jboss.forge.addon.ui.result.Result;
import org.jboss.forge.addon.ui.result.Results;
import org.jboss.forge.addon.ui.util.Categories;
import org.jboss.forge.addon.ui.util.Metadata;
import org.jboss.forge.addon.ui.wizard.UIWizard;
import org.jboss.forge.furnace.services.Imported;
import org.jboss.forge.furnace.util.OperatingSystemUtils;
public class NewProjectWizard implements UIWizard
{
private static final Logger log = Logger.getLogger(NewProjectWizard.class.getName());
@Inject
private ProjectFactory projectFactory;
@Inject
private ResourceFactory resourceFactory;
@Inject
private Imported<BuildSystem> buildSystems;
@Inject
@WithAttributes(label = "Project name:", required = true)
private UIInput<String> named;
@Inject
@WithAttributes(label = "Top level package:")
private UIInput<String> topLevelPackage;
@Inject
@WithAttributes(label = "Version:", required = false)
private UIInput<String> version;
@Inject
@WithAttributes(label = "Project location:")
private UIInput<DirectoryResource> targetLocation;
@Inject
@WithAttributes(label = "Overwrite existing project location")
private UIInput<Boolean> overwrite;
@Inject
@WithAttributes(label = "Project Type:", required = true)
private UISelectOne<ProjectType> type;
@Inject
@WithAttributes(label = "Build System:", required = true)
private UISelectOne<BuildSystem> buildSystem;
@Override
public UICommandMetadata getMetadata(UIContext context)
{
return Metadata.forCommand(getClass()).name("New Project").description("Create a new project")
.category(Categories.create("Project", "Generation"));
}
@Override
public boolean isEnabled(UIContext context)
{
return buildSystem.getValueChoices().iterator().hasNext();
}
@Override
public void initializeUI(final UIBuilder builder) throws Exception
{
configureProjectNamedInput();
configureVersionInput();
configuteTargetLocationInput(builder);
configureOverwriteInput();
configureProjectTypeInput(builder);
configureTopLevelPackageInput();
configureBuildSystemInput();
builder.add(named).add(topLevelPackage).add(version).add(targetLocation).add(overwrite).add(type)
.add(buildSystem);
}
private void configureProjectNamedInput()
{
named.setValidator(new UIValidator()
{
@Override
public void validate(UIValidationContext context)
{
if (named.getValue() != null && named.getValue().matches(".*[^-_.a-zA-Z0-9].*"))
context.addValidationError(named, "Project name must not contain spaces or special characters.");
}
});
}
private void configureVersionInput()
{
version.setDefaultValue("1.0.0-SNAPSHOT");
}
private void configuteTargetLocationInput(final UIBuilder builder)
{
UISelection<Resource<?>> currentSelection = builder.getUIContext().getInitialSelection();
if (!currentSelection.isEmpty())
{
Resource<?> resource = currentSelection.get();
if (resource instanceof DirectoryResource)
{
targetLocation.setDefaultValue((DirectoryResource) resource);
}
}
else
{
targetLocation.setDefaultValue(resourceFactory.create(DirectoryResource.class,
OperatingSystemUtils.getUserHomeDir()));
}
}
private void configureOverwriteInput()
{
overwrite.setDefaultValue(false).setEnabled(new Callable<Boolean>()
{
@Override
public Boolean call() throws Exception
{
String projectName = named.getValue();
return targetLocation.getValue() != null && projectName != null
&& targetLocation.getValue().getChild(projectName).exists()
&& !targetLocation.getValue().getChild(projectName).listResources().isEmpty();
}
});
}
private void configureProjectTypeInput(final UIBuilder builder)
{
if (builder.getUIContext().getProvider().isGUI())
{
type.setItemLabelConverter(new Converter<ProjectType, String>()
{
@Override
public String convert(ProjectType source)
{
return source == null ? null : source.getType();
}
});
}
// Add Project types
List<ProjectType> projectTypes = new ArrayList<ProjectType>();
for (ProjectType projectType : type.getValueChoices())
{
boolean buildable = false;
for (BuildSystem buildSystem : buildSystems)
{
if (isProjectTypeBuildable(projectType, buildSystem))
{
projectTypes.add(projectType);
buildable = true;
break;
}
}
if (!buildable)
log.log(Level.FINE, "ProjectType [" + projectType.getType() + "] "
+ "deactivated because it cannot be built with any registered BuildSystem instances ["
+ buildSystems + "].");
}
Collections.sort(projectTypes, new Comparator<ProjectType>()
{
@Override
public int compare(ProjectType left, ProjectType right)
{
return new Integer(left.priority()).compareTo(right.priority());
}
});
if (!projectTypes.isEmpty())
{
type.setDefaultValue(projectTypes.get(0));
}
type.setValueChoices(projectTypes);
}
private void configureTopLevelPackageInput()
{
topLevelPackage.setDefaultValue(new Callable<String>()
{
@Override
public String call() throws Exception
{
String result = named.getValue();
if (result != null)
{
result = ("org." + result).replaceAll("\\W+", ".");
result = result.trim();
result = result.replaceAll("^\\.", "");
result = result.replaceAll("\\.$", "");
}
else
result = "org.example";
return result;
}
});
}
private void configureBuildSystemInput()
{
buildSystem.setRequired(true);
buildSystem.setItemLabelConverter(new Converter<BuildSystem, String>()
{
@Override
public String convert(BuildSystem source)
{
return source == null ? null : source.getType();
}
});
buildSystem.setValueChoices(new Callable<Iterable<BuildSystem>>()
{
@Override
public Iterable<BuildSystem> call() throws Exception
{
List<BuildSystem> result = new ArrayList<BuildSystem>();
for (BuildSystem buildSystemType : buildSystems)
{
ProjectType projectType = type.getValue();
if (projectType != null)
{
if (isProjectTypeBuildable(projectType, buildSystemType))
result.add(buildSystemType);
}
else
result.add(buildSystemType);
}
Collections.sort(result, new Comparator<BuildSystem>()
{
@Override
public int compare(BuildSystem left, BuildSystem right)
{
return new Integer(left.priority()).compareTo(right.priority());
}
});
return result;
}
});
buildSystem.setDefaultValue(new Callable<BuildSystem>()
{
@Override
public BuildSystem call() throws Exception
{
if (buildSystem.getValueChoices().iterator().hasNext())
{
return buildSystem.getValueChoices().iterator().next();
}
return null;
}
});
}
private boolean isProjectTypeBuildable(ProjectType type, BuildSystem buildSystem)
{
boolean result = false;
Iterable<Class<? extends BuildSystemFacet>> requiredFacets = getRequiredBuildSystemFacets(type);
if (requiredFacets == null || !requiredFacets.iterator().hasNext())
{
result = true;
}
else
{
for (Class<? extends BuildSystemFacet> required : requiredFacets)
{
result = false;
for (Class<? extends BuildSystemFacet> provided : buildSystem.getProvidedFacetTypes())
{
if (provided.isAssignableFrom(required))
result = true;
}
if (!result)
break;
}
}
return result;
}
@SuppressWarnings("unchecked")
private Iterable<Class<? extends BuildSystemFacet>> getRequiredBuildSystemFacets(ProjectType type)
{
Set<Class<? extends BuildSystemFacet>> result = new HashSet<Class<? extends BuildSystemFacet>>();
Iterable<Class<? extends ProjectFacet>> requiredFacets = type.getRequiredFacets();
if (requiredFacets != null)
{
for (Class<? extends ProjectFacet> facetType : requiredFacets)
{
if (BuildSystemFacet.class.isAssignableFrom(facetType))
{
result.add((Class<? extends BuildSystemFacet>) facetType);
}
}
}
return result;
}
@Override
public void validate(UIValidationContext context)
{
String packg = topLevelPackage.getValue();
if (packg != null && !packg.matches("(?i)(~\\.)?([a-z0-9_]+\\.?)+[a-z0-9_]"))
{
context.addValidationError(topLevelPackage, "Top level package must be a valid package name.");
}
if (overwrite.isEnabled() && overwrite.getValue() == false)
{
context.addValidationError(targetLocation, "Target location is not empty.");
}
}
@Override
public Result execute(UIContext context) throws Exception
{
Result result = Results.success("Project named '" + named.getValue() + "' has been created.");
DirectoryResource directory = targetLocation.getValue();
DirectoryResource targetDir = directory.getChildDirectory(named.getValue());
if (targetDir.mkdirs() || overwrite.getValue())
{
ProjectType value = type.getValue();
Project project = null;
if (value != null)
{
project = projectFactory.createProject(targetDir, buildSystem.getValue(), value.getRequiredFacets());
}
else
{
project = projectFactory.createProject(targetDir, buildSystem.getValue());
}
if (project != null)
{
MetadataFacet metadataFacet = project.getFacet(MetadataFacet.class);
metadataFacet.setProjectName(named.getValue());
metadataFacet.setProjectVersion(version.getValue());
metadataFacet.setTopLevelPackage(topLevelPackage.getValue());
context.setSelection(project.getProjectRoot());
context.setAttribute(Project.class, project);
}
else
result = Results.fail("Could not create project of type: [" + value + "]");
}
else
result = Results.fail("Could not create target location: " + targetDir);
return result;
}
public UIInput<String> getNamed()
{
return named;
}
public UIInput<DirectoryResource> getTargetLocation()
{
return targetLocation;
}
public UIInput<Boolean> getOverwrite()
{
return overwrite;
}
public UISelectOne<ProjectType> getType()
{
return type;
}
public SingleValued<UIInput<String>, String> getTopLevelPackage()
{
return topLevelPackage;
}
@Override
public NavigationResult next(UIContext context) throws Exception
{
if (type.getValue() != null)
{
return Results.navigateTo(type.getValue().getSetupFlow());
}
else
{
return null;
}
}
}