Multiple sort criteria to filters, bugfix, and cleanup.
* Fixed a bug in the common build. This bug is fixed in version 1.9, but I am patching this bug locally in 1.6 until I have evaluated 1.9 with this project. * Moved `ExtendedPropertyHelp` to `com.jdbernard.pit` from `com.jdbernard.pit.file`. * Added a number of property types to `ExtendedPropertyHelp`. New additions are: * `java.util.Calendar` *object -> string value only* * `java.util.Date` *object -> string value only* * `java.lang.Long` *this replaces `java.util.Integer`* * `java.lang.Float` *object -> string value only* * `java.lang.Double` * Cleaned up the `matches(String)` and `matches(Class)` functions of `ExtendedPropertyHelp` * Modified `Filter` sorter behaviours. The `issueSorter` and `projectSorter` fields are now allowed to be either a closure or a list of closures. A single closure works as it did before. The list of closures allows the caller to specify multiple sort criteria. The individual criteria closures are applied in reverse order, so that the first item in the sorter list is the most significant criteria. For example, if the caller set the sorter to `[{it.category},{it.priority}]` then the issues would be sorted first by priority and then sorted again by category, meaning that the resulting data would be ordered first by the category of the issue and then by the priority for issues that share the same category. * Modified the methods in `Project` that use `Filter` objects to conform to the above behavior regarding sorting. It may be a better idea though to move the sort code all into `Filter` so that it is in one place. * Cleaned up the code in `Status` for matching status based on given symbols or partial status names.
This commit is contained in:
parent
47cf3cf0a4
commit
6f58a83ad4
@ -59,7 +59,9 @@
|
|||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!--======== LIBRARY TARGETS ========-->
|
<!--======== LIBRARY TARGETS ========-->
|
||||||
<target name="lib" depends="-lib-local,-lib-ivy"/>
|
<target name="-lib" depends="-lib-local,-lib-ivy,lib"/>
|
||||||
|
|
||||||
|
<target name="lib"/>
|
||||||
|
|
||||||
<target name="-lib-ivy" unless="${lib.local}"/>
|
<target name="-lib-ivy" unless="${lib.local}"/>
|
||||||
|
|
||||||
@ -96,7 +98,7 @@
|
|||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!--======== COMPILATION TARGETS ========-->
|
<!--======== COMPILATION TARGETS ========-->
|
||||||
<target name="-compile-groovy" depends="-init,-init-groovy,lib">
|
<target name="-compile-groovy" depends="-init,-init-groovy,-lib">
|
||||||
<mkdir dir="${build.dir}/main/classes"/>
|
<mkdir dir="${build.dir}/main/classes"/>
|
||||||
<groovyc srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
<groovyc srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||||
includeAntRuntime="false" fork="yes">
|
includeAntRuntime="false" fork="yes">
|
||||||
@ -109,7 +111,7 @@
|
|||||||
</groovyc>
|
</groovyc>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="-compile-java" depends="-init,lib">
|
<target name="-compile-java" depends="-init,-lib">
|
||||||
<mkdir dir="${build.dir}/main/classes"/>
|
<mkdir dir="${build.dir}/main/classes"/>
|
||||||
<javac srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
<javac srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||||
includeAntRuntime="false" classpathref="compile-libs"/>
|
includeAntRuntime="false" classpathref="compile-libs"/>
|
||||||
|
@ -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
|
#Sat Apr 24 17:08:00 CDT 2010
|
||||||
build.dir=build
|
build.dir=build
|
||||||
src.dir=src
|
src.dir=src
|
||||||
lib.shared.dir=../shared-libs
|
lib.shared.dir=../shared-libs
|
||||||
test.dir=test
|
test.dir=test
|
||||||
build.number=25
|
build.number=8
|
||||||
version=3.0.0
|
version=3.1.0
|
||||||
name=libpit
|
name=libpit
|
||||||
lib.dir=lib
|
lib.dir=lib
|
||||||
lib.local=true
|
lib.local=true
|
||||||
|
@ -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() }
|
||||||
|
}
|
@ -8,8 +8,8 @@ class Filter {
|
|||||||
List<String> ids = null
|
List<String> ids = null
|
||||||
int priority = 9
|
int priority = 9
|
||||||
boolean acceptProjects = true
|
boolean acceptProjects = true
|
||||||
Closure issueSorter = defaultIssueSorter
|
def issueSorter = defaultIssueSorter
|
||||||
Closure projectSorter = defaultProjectSorter
|
def projectSorter = defaultProjectSorter
|
||||||
|
|
||||||
public static Closure defaultIssueSorter = { it.id.toInteger() }
|
public static Closure defaultIssueSorter = { it.id.toInteger() }
|
||||||
public static Closure defaultProjectSorter = { it.name }
|
public static Closure defaultProjectSorter = { it.name }
|
||||||
|
@ -10,30 +10,32 @@ public abstract class Project {
|
|||||||
|
|
||||||
public void eachIssue(Filter filter = null, Closure c) {
|
public void eachIssue(Filter filter = null, Closure c) {
|
||||||
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
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))
|
if (!filter || filter.accept(i))
|
||||||
c.call(i)
|
c.call(i) }
|
||||||
}
|
|
||||||
|
|
||||||
public void eachProject(Filter filter = null, Closure c) {
|
public void eachProject(Filter filter = null, Closure c) {
|
||||||
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
|
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))
|
if (!filter || filter.accept(p))
|
||||||
c.call(p)
|
c.call(p) }
|
||||||
}
|
|
||||||
|
|
||||||
// walk every issue and project in this project recursively and execute the
|
// walk every issue and project in this project recursively and execute the
|
||||||
// given closure on each issue that meets the filter criteria
|
// given closure on each issue that meets the filter criteria
|
||||||
public void walkProject(Filter filter, Closure c) {
|
public void walkProject(Filter filter, Closure c) {
|
||||||
this.eachIssue(filter, 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
|
// This get all issues, including subissues
|
||||||
public List getAllIssues(Filter filter = null) {
|
public List getAllIssues(Filter filter = null) {
|
||||||
List result = this.issues.findAll { filter.accept(it) }
|
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||||
this.eachProject(filter) { p -> result += p.getAllIssues(filter) }
|
|
||||||
}
|
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 }
|
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 deleteIssue(Issue issue)
|
||||||
|
|
||||||
public abstract boolean deleteProject(Project project)
|
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) }}}
|
||||||
}
|
}
|
||||||
|
@ -12,25 +12,25 @@ public enum Status {
|
|||||||
protected Status(String s) { symbol = s }
|
protected Status(String s) { symbol = s }
|
||||||
|
|
||||||
public static Status toStatus(String str) {
|
public static Status toStatus(String str) {
|
||||||
Status retVal = null
|
// Try to match based on symbol
|
||||||
for(status in Status.values()) {
|
def match = Status.values().find {it.symbol.equalsIgnoreCase(str)}
|
||||||
if (status.symbol.equalsIgnoreCase(str) ||
|
if (match) { return match }
|
||||||
status.name().startsWith(str.toUpperCase())) {
|
|
||||||
|
|
||||||
if (retVal != null)
|
// No match on the symbol, look for the status name (or abbreviations)
|
||||||
|
match = Status.values().findAll {
|
||||||
|
it.name().startsWith(str.toUpperCase()) }
|
||||||
|
|
||||||
|
// No matching status, oops.
|
||||||
|
if (match.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("No status matches '${str}'") }
|
||||||
|
|
||||||
|
// More than one matching status, oops.
|
||||||
|
else if (match.size() > 1) {
|
||||||
throw new IllegalArgumentException("Request string is" +
|
throw new IllegalArgumentException("Request string is" +
|
||||||
" ambigous, '${str}' could represent ${retVal} or " +
|
" ambigous, '${str}' could represent any of ${match}.")}
|
||||||
"${status}, possibly others.")
|
|
||||||
|
|
||||||
retVal = status
|
// Only one matching status, yay!
|
||||||
}
|
else { return match[0] }}
|
||||||
}
|
|
||||||
|
|
||||||
if (retVal == null)
|
|
||||||
throw new IllegalArgumentException("No status matches '${str}'")
|
|
||||||
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
def words = name().split("_")
|
def words = name().split("_")
|
||||||
|
@ -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() }
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user