diff --git a/jdb-build-1.6.xml b/jdb-build-1.6.xml
index da3fa52..760c821 100644
--- a/jdb-build-1.6.xml
+++ b/jdb-build-1.6.xml
@@ -59,7 +59,9 @@
-
+
+
+
@@ -96,7 +98,7 @@
-
+
@@ -109,7 +111,7 @@
-
+
diff --git a/libpit/project.properties b/libpit/project.properties
index a85e2f0..8e11113 100755
--- a/libpit/project.properties
+++ b/libpit/project.properties
@@ -1,11 +1,11 @@
-#Tue, 22 Nov 2011 14:32:12 -0600
+#Wed, 07 Dec 2011 17:53:14 -0600
#Sat Apr 24 17:08:00 CDT 2010
build.dir=build
src.dir=src
lib.shared.dir=../shared-libs
test.dir=test
-build.number=25
-version=3.0.0
+build.number=8
+version=3.1.0
name=libpit
lib.dir=lib
lib.local=true
diff --git a/libpit/src/main/com/jdbernard/pit/ExtendedPropertyHelp.groovy b/libpit/src/main/com/jdbernard/pit/ExtendedPropertyHelp.groovy
new file mode 100644
index 0000000..5aeb92a
--- /dev/null
+++ b/libpit/src/main/com/jdbernard/pit/ExtendedPropertyHelp.groovy
@@ -0,0 +1,81 @@
+package com.jdbernard.pit
+
+import org.joda.time.DateMidnight
+import org.joda.time.DateTime
+
+import java.text.SimpleDateFormat
+
+public enum ExtendedPropertyHelp {
+
+ // Property types should be ordered here in order of decreasing specificity.
+ // That is, subclasses should come before the more general class so that
+ // objects are converted using the most specific class that
+ // ExtendedPropertyHelp knows how to work with.
+ DATE_MIDNIGHT(/^\d{4}-\d{2}-\d{2}$/, DateMidnight,
+ { v -> DateMidnight.parse(v) },
+ { d -> d.toString("YYYY-MM-dd") }),
+ DATETIME(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/, DateTime,
+ { v -> DateTime.parse(v) },
+ { d -> d.toString("YYYY-MM-dd'T'HH:mm:ss") }),
+ // We never want to parse a value into a java.util.Date or
+ // java.util.Calendar object (we are using Joda Time instead of the
+ // standard Java Date and Calendar objects) but we do want to be able to
+ // handle if someone gives us a Date or Calendar object.
+ DATE(NEVER_MATCH, Date,
+ { v -> v }, // never called
+ { d -> dateFormat.format(d) }),
+ CALENDAR(NEVER_MATCH, Calendar,
+ { v -> v }, // never called
+ { c ->
+ def df = dateFormat.clone()
+ df.calendar = c
+ df.format(c.time) }),
+
+ INTEGER(NEVER_MATCH, Integer,
+ { v -> v as Integer }, // never called
+ { i -> i as String }),
+ LONG(/^\d+$/, Long,
+ { v -> v as Long },
+ { l -> l as String }),
+ FLOAT(NEVER_MATCH, Float,
+ { v -> v as Float}, // never called
+ { f -> f as String}),
+ DOUBLE(/^\d+\.\d+$/, Double,
+ { v -> v as Double },
+ { d -> d as String });
+
+ String pattern;
+ Class klass;
+ def parseFun, formatFun;
+
+ private static SimpleDateFormat dateFormat =
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+
+ // This pattern for can never match (is uses negative lookahead to
+ // contradict itself).
+ private static String NEVER_MATCH = /(?!x)x/;
+
+
+ public ExtendedPropertyHelp(String pattern, Class klass, def parseFun,
+ def formatFun) {
+ this.pattern = pattern
+ this.klass = klass
+ this.parseFun = parseFun
+ this.formatFun = formatFun }
+
+ public boolean matches(String prop) { return prop ==~ pattern }
+
+ public boolean matches(Class klass) { return this.klass == klass }
+
+ public static Object parse(String value) {
+ def propertyType = ExtendedPropertyHelp.values().find {
+ it.matches(value) }
+
+ return propertyType ? propertyType.parseFun(value) : value }
+
+ public static String format(def object) {
+ def propertyType = ExtendedPropertyHelp.values().find {
+ it.klass.isInstance(object) }
+
+ return propertyType ? propertyType.formatFun(object) : object.toString() }
+}
diff --git a/libpit/src/main/com/jdbernard/pit/Filter.groovy b/libpit/src/main/com/jdbernard/pit/Filter.groovy
index 239bb6f..2bbcfaa 100755
--- a/libpit/src/main/com/jdbernard/pit/Filter.groovy
+++ b/libpit/src/main/com/jdbernard/pit/Filter.groovy
@@ -8,8 +8,8 @@ class Filter {
List ids = null
int priority = 9
boolean acceptProjects = true
- Closure issueSorter = defaultIssueSorter
- Closure projectSorter = defaultProjectSorter
+ def issueSorter = defaultIssueSorter
+ def projectSorter = defaultProjectSorter
public static Closure defaultIssueSorter = { it.id.toInteger() }
public static Closure defaultProjectSorter = { it.name }
diff --git a/libpit/src/main/com/jdbernard/pit/Project.groovy b/libpit/src/main/com/jdbernard/pit/Project.groovy
index 9796e9c..9135f23 100755
--- a/libpit/src/main/com/jdbernard/pit/Project.groovy
+++ b/libpit/src/main/com/jdbernard/pit/Project.groovy
@@ -10,30 +10,32 @@ public abstract class Project {
public void eachIssue(Filter filter = null, Closure c) {
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
- for (i in issues.values().sort(sorter))
+ for (i in sort(issues.values(), sorter))
if (!filter || filter.accept(i))
- c.call(i)
- }
+ c.call(i) }
public void eachProject(Filter filter = null, Closure c) {
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
- for (p in projects.values().sort(sorter))
+ for (p in sort(projects.values(), sorter))
if (!filter || filter.accept(p))
- c.call(p)
- }
+ c.call(p) }
// walk every issue and project in this project recursively and execute the
// given closure on each issue that meets the filter criteria
public void walkProject(Filter filter, Closure c) {
this.eachIssue(filter, c)
- this.eachProject(filter) { p -> p.walkProject(filter, c) }
- }
+ this.eachProject(filter) { p -> p.walkProject(filter, c) } }
// This get all issues, including subissues
public List getAllIssues(Filter filter = null) {
- List result = this.issues.findAll { filter.accept(it) }
- this.eachProject(filter) { p -> result += p.getAllIssues(filter) }
- }
+ def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
+
+ List allIssues = this.issues.values().findAll {
+ filter ? filter.accept(it) : true }
+
+ this.eachProject(filter) { p -> allIssues += p.getAllIssues(filter) }
+
+ return sort(allIssues, sorter) }
public void setName(String name) { this.name = name }
@@ -49,4 +51,10 @@ public abstract class Project {
public abstract boolean deleteIssue(Issue issue)
public abstract boolean deleteProject(Project project)
+
+ protected List sort(def collection, def sorter) {
+ if (sorter instanceof Closure) {
+ return collection.sort(sorter) }
+ else if (sorter instanceof List) {
+ return sorter.reverse().inject(collection) { c, s -> c.sort(s) }}}
}
diff --git a/libpit/src/main/com/jdbernard/pit/Status.groovy b/libpit/src/main/com/jdbernard/pit/Status.groovy
index d1a227b..1779370 100755
--- a/libpit/src/main/com/jdbernard/pit/Status.groovy
+++ b/libpit/src/main/com/jdbernard/pit/Status.groovy
@@ -12,25 +12,25 @@ public enum Status {
protected Status(String s) { symbol = s }
public static Status toStatus(String str) {
- Status retVal = null
- for(status in Status.values()) {
- if (status.symbol.equalsIgnoreCase(str) ||
- status.name().startsWith(str.toUpperCase())) {
+ // Try to match based on symbol
+ def match = Status.values().find {it.symbol.equalsIgnoreCase(str)}
+ if (match) { return match }
- if (retVal != null)
- throw new IllegalArgumentException("Request string is" +
- " ambigous, '${str}' could represent ${retVal} or " +
- "${status}, possibly others.")
+ // No match on the symbol, look for the status name (or abbreviations)
+ match = Status.values().findAll {
+ it.name().startsWith(str.toUpperCase()) }
- retVal = status
- }
- }
+ // No matching status, oops.
+ if (match.size() == 0) {
+ throw new IllegalArgumentException("No status matches '${str}'") }
- if (retVal == null)
- throw new IllegalArgumentException("No status matches '${str}'")
+ // More than one matching status, oops.
+ else if (match.size() > 1) {
+ throw new IllegalArgumentException("Request string is" +
+ " ambigous, '${str}' could represent any of ${match}.")}
- return retVal
- }
+ // Only one matching status, yay!
+ else { return match[0] }}
public String toString() {
def words = name().split("_")
diff --git a/libpit/src/main/com/jdbernard/pit/file/ExtendedPropertyHelp.groovy b/libpit/src/main/com/jdbernard/pit/file/ExtendedPropertyHelp.groovy
deleted file mode 100644
index cd5bf13..0000000
--- a/libpit/src/main/com/jdbernard/pit/file/ExtendedPropertyHelp.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.jdbernard.pit.file
-
-import org.joda.time.DateMidnight
-import org.joda.time.DateTime
-
-public enum ExtendedPropertyHelp {
-
- DATE(/^\d{4}-\d{2}-\d{2}$/, DateMidnight,
- { v -> DateMidnight.parse(v) },
- { d -> d.toString("YYYY-MM-dd") }),
- DATETIME(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/, DateTime,
- { v -> DateTime.parse(v) },
- { d -> d.toString("YYYY-MM-dd'T'HH:mm:ss") }),
- INTEGER(/^\d+$/, Integer,
- { v -> v as Integer },
- { i -> i as String });
-
- String pattern;
- Class klass;
- def parseFun, formatFun;
-
- public ExtendedPropertyHelp(String pattern, Class klass, def parseFun,
- def formatFun) {
- this.pattern = pattern
- this.klass = klass
- this.parseFun = parseFun
- this.formatFun = formatFun }
-
- public boolean matches(String prop) { return prop ==~ pattern }
-
- public static Object parse(String value) {
- def result = null
- ExtendedPropertyHelp.values().each { propType ->
- if (propType.matches(value)) { result = propType.parseFun(value) }}
-
- return result ?: value }
-
- public static String format(def object) {
- def result = null
- ExtendedPropertyHelp.values().each { propType ->
- if (!result && propType.klass.isInstance(object)) {
- result = propType.formatFun(object) }}
-
- return result ?: object.toString() }
-}