package com.metservice.kanban;
import static java.util.Arrays.asList;
import static org.apache.commons.io.FileUtils.writeStringToFile;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.apache.commons.io.filefilter.OrFileFilter;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;
import com.metservice.kanban.model.DefaultKanbanProject;
import com.metservice.kanban.model.KanbanBoardConfiguration;
import com.metservice.kanban.model.KanbanProject;
import com.metservice.kanban.model.KanbanProjectConfiguration;
import com.metservice.kanban.model.KanbanProjectConfigurationBuilder;
import com.metservice.kanban.model.TreeNode;
import com.metservice.kanban.model.WorkItemTree;
import com.metservice.kanban.model.WorkItemType;
import com.metservice.kanban.model.WorkItemTypeCollection;
import com.metservice.kanban.web.KanbanPersistence;
public class KanbanService {
//Sets the KANBAN_HOME_PROPERTY name
public static final String KANBAN_HOME_PROPERTY_NAME = "kanban.home";
//Filters the files that start with a . i.e '.home'
private static final NotFileFilter NO_DOT_DIRS_NO_FILES = new NotFileFilter(
new OrFileFilter(
new PrefixFileFilter("."),
FileFileFilter.FILE
));
//Sets the KANBAN_PROPERTIES_FILE name
private static final String KANBAN_PROPERTIES_FILE_NAME = "kanban.properties";
private final File home;
private final String version;
public KanbanService() {
this(getKanbanHomeFromSystemProperty());
}
/**
* This method is used as the constructor for {@link KanbanService}.
* <p>
* The kanbanHomePath is retrieved. If the path is <code>null</code> a File
* is created with the path 'user.home.kanban'.
* <p>
* If the path is not <code>null</code> then a new {@link File} is created
* using the pathname defined in the {@code KANBAN_HOME_PROPERTY_NAME}.
*
* @return Returns a {@link File} object.
*/
private static File getKanbanHomeFromSystemProperty() {
String kanbanHomePath = System.getProperty(KANBAN_HOME_PROPERTY_NAME);
if (kanbanHomePath == null) {
return new File(System.getProperty("user.home"), ".kanban");
} else {
return new File(kanbanHomePath);
}
}
/**
* Version number is retrieved from the version.txt file
*
* @return
* A {@link String} which is the version number of the program
* <p>
* <code>""</code>, if an {@link IOException} is thrown.
* @exception If
* an {@link IOException} is encountered a <code>""</code> is
* returned
*/
private String loadKanbanVersion() {
Properties versionProperties = new Properties();
try {
versionProperties.load(getClass().getResourceAsStream("/version.txt"));
} catch (IOException e) {
return "";
}
return versionProperties.getProperty("version");
}
/**
* Constructor for {@link KanbanService}.
*
* @param home
* - When passed the home File the <code>home</code> and
* <code>version</code> fields are set.
*/
public KanbanService(File home) {
this.home = home;
this.version = loadKanbanVersion();
}
/**
* @return {@link String} - returns the <code>home</code> field.
*/
public File getHome() {
return home;
}
/**
* @return {@link String} - returns the <code>version</code> details.
*/
public String getVersion() {
return version;
}
/**
* Returns a collection of projects who's names do not start with
* <code>"."</code>
*
* @return {@link Collection} - returns a <code>Collection(String)</code> of
* projects.
*/
public Collection<String> getProjects() {
if (home.exists()) {
List<String> result = asList(home.list(NO_DOT_DIRS_NO_FILES));
Collections.sort(result, String.CASE_INSENSITIVE_ORDER);
return result;
} else {
return new ArrayList<String>();
}
}
public Collection<String> getFilteredProjects() {
Collection<String> projects = getProjects();
Collection<String> filteredProjects = new ArrayList<String>();
for (String p : projects) {
if (!isFilteredProject(p)) {
filteredProjects.add(p);
}
}
return filteredProjects;
}
private boolean isFilteredProject(String p) {
return p.startsWith("_");
}
/**
* Retrieves project information of the specified project then takes the
* information and creates a {@link KanbanProject} object to be returned.
*
* @param projectName
* - {@link String} name of project to be retrieved.
* @return {@link KanbanProject} a new KanbanProject created from retrieved
* information.
* @throws IOException
* if an error is encountered an {@link IOException} is thrown.
*/
public KanbanProject getKanbanProject(String projectName) throws IOException {
KanbanProjectConfiguration configuration = getProjectConfiguration(projectName);
TreeNode<WorkItemType> rootWorkItemType = configuration.getRootWorkItemType();
KanbanBoardConfiguration phaseSequences = configuration.getPhaseSequences();
//Creates a new KanbanPersistence object using the configuration file.
//Then, creates a WorkItemTree using the persistence file.
KanbanPersistence persistence = new KanbanPersistence(configuration);
WorkItemTree tree = persistence.read();
WorkItemTypeCollection workItemTypes = createWorkItemTypeCollection(rootWorkItemType);
return new DefaultKanbanProject(workItemTypes, phaseSequences, tree, persistence, projectName);
}
/**
* Method to create a {@link WorkItemTypeCollection} from the WorkItemType
* {@link TreeNode}
*
* @param rootWorkItemType
* @return {@link WorkItemTypeCollection} - the newly created object.
*/
private WorkItemTypeCollection createWorkItemTypeCollection(TreeNode<WorkItemType> rootWorkItemType) {
return new WorkItemTypeCollection(rootWorkItemType);
}
/**
* Method to retrieve the Project configuration.
* <p>
* Creates a {@link KanbanProjectConfigurationBuilder} and then uses this to
* build the configuration.
* <p>
* The configuration is then returned.
*
* @param {@link String} projectName
* @return {@link KanbanProjectConfiguration} - the project configuration.
* @throws IOException
*/
public KanbanProjectConfiguration getProjectConfiguration(String projectName) throws IOException {
KanbanProjectConfigurationBuilder configurationBuilder = new KanbanProjectConfigurationBuilder(home,
projectName);
return configurationBuilder.buildConfiguration();
}
/**
* Creates a new project with the given Name and Settings.
* It creates a folder and saves the properties file in the folder.
* <p>
* If an existing project has the same name as <code>newProjectName</code>
* then an {@link IllegalArgumentException} is thrown and the project is not
* created.
* <p>
*
* @param {@link String} newProjectName
* @param {@link String} settings
* @throws IOException
*/
public void createProject(String newProjectName, String settings) throws IOException {
File newProjectHome = new File(home, newProjectName);
if (newProjectHome.exists()) {
throw new IllegalArgumentException("cannot create a project with the same name as an existing project: "
+ newProjectName);
}
//Creates the project folder and writes the settings to '[newProjectName].kanban.properties'
newProjectHome.mkdir();
File file = new File(newProjectHome, KANBAN_PROPERTIES_FILE_NAME);
writeStringToFile(file, settings);
}
/**
* Edits a given project with the given name and settings.
* It overwrites the existing .properties file for that project.
* If a .properties file for that project does not exist, the project is
* not edited.
*
* @param projectName
* - the project to edit
* @param settings
* - the new settings
* @throws IOException
*/
public void editProject(String projectName, String settings) throws IOException {
File projectHome = new File(home, projectName);
if (projectHome.exists()) {
File file = new File(projectHome, KANBAN_PROPERTIES_FILE_NAME);
writeStringToFile(file, settings);
} else {
throw new IllegalArgumentException("cannot edit a project that does not exist: " + projectName);
}
}
/**
* Rename a project.
*
* @param projectName
* the project name
* @param newProjectName
* the new project name
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public void renameProject(String projectName, String newProjectName) throws IOException {
File projectHome = new File(home, projectName);
File newProjectHome = new File(home, newProjectName);
if (newProjectHome.exists()) {
throw new IllegalArgumentException("project name already exists: " + newProjectName);
}
projectHome.renameTo(newProjectHome);
}
public void setColumnWipLimit(String projectName, WorkItemType workItemtype, String columnName, Integer wipLimit)
throws IOException {
getProjectConfiguration(projectName).getKanbanPropertiesFile().setColumnWipLimit(workItemtype, columnName,
wipLimit);
}
public boolean renameColumn(String projectName, WorkItemType workItemType, String columnName, String newColumnName)
throws IOException
{
// check if column exists
if (!workItemType.containsPhase(columnName)) {
return false;
}
if (workItemType.containsPhase(newColumnName)) {
return false;
}
// rename column in a properties file
try {
getProjectConfiguration(projectName).getKanbanPropertiesFile().renameColumn(workItemType, columnName,
newColumnName);
} catch (IllegalArgumentException e) {
return false;
}
// rename column in a CSV file
KanbanProjectConfiguration configuration = getProjectConfiguration(projectName);
CSVReader csvReader = new CSVReader(new FileReader(configuration.getDataFile(workItemType)));
List<String[]> items;
try {
items = csvReader.readAll();
for (int i = 0; i < items.get(0).length; i++) {
if (items.get(0)[i].equals(columnName)) {
items.get(0)[i] = newColumnName;
break;
}
}
} finally {
csvReader.close();
}
CSVWriter csvWriter = new CSVWriter(new FileWriter(configuration.getDataFile(workItemType)));
try {
csvWriter.writeAll(items);
} finally {
csvWriter.close();
}
return true;
}
}