Fleshed out CategorizationPlan implementation.
Added findEntriesToRecategorize() to CategorizationPlan Refactored createNewCategory() to newCategory() on CategorizationPlan Refactored Category to use CategorizationPlans Created CatPlan for DescriptionBasedCategory Made Event cloneable. Refactored Category implementations to be aware of the single arg Category constructor. Created TwoLevelCategory, for entries which inherently have two levels of categorization in them. Created a CatPlan for TwoLevelCategory Removed the specialization implementation, ITHelpCategory: the new TwoLevelCategory is a more general version of the same. Created CatPlan for TicketCategory Fixed TicketCategory and TicketPlan to adjust for small difference between the old behavour of ITHelpCategory and new TwoLevelCategory Updated testing starter script.
This commit is contained in:
parent
24600b46d9
commit
ab1c7f2393
BIN
lib/compile/jcommon-1.0.16.jar
Normal file
BIN
lib/compile/jcommon-1.0.16.jar
Normal file
Binary file not shown.
Binary file not shown.
@ -5,5 +5,6 @@ import java.util.Map;
|
||||
|
||||
public interface CategorizationPlan {
|
||||
boolean deservesNewCategory(Event event, List<Entry> existingEntries);
|
||||
Map createNewCategory(Event event, List<Entry> existingEntries);
|
||||
Category newCategory(Event event, List<Entry> existingEntries);
|
||||
List<Entry> findEntriesToRecategorize(Event event, List<Entry> existingEntries);
|
||||
}
|
||||
|
@ -19,45 +19,25 @@ public abstract class Category implements Comparable<Category> {
|
||||
description = "Unnamed Category"
|
||||
}
|
||||
|
||||
public Category(String description) {
|
||||
entries = []
|
||||
categories = []
|
||||
categorizationPlans = []
|
||||
this.description = description
|
||||
}
|
||||
|
||||
public abstract boolean matchesEvent(Event e)
|
||||
|
||||
public Entry addEvent(Event e) {
|
||||
Entry entry
|
||||
|
||||
// see if we have a subcategory that will hold this event
|
||||
// see if we have or can create a subcategory that will hold this event
|
||||
entry = addToSubcategory(e)
|
||||
|
||||
// no, let's create a generic entry and add it
|
||||
if (!entry) {
|
||||
// if not, do we have a plan for categorizing this event?
|
||||
|
||||
// if not, do we have another entry that could be grouped with
|
||||
// this one to create a new category, based on the description?
|
||||
def existingEntry = entries.find
|
||||
{ it.description == e.description }
|
||||
|
||||
// yes
|
||||
if (existingEntry) {
|
||||
// create the new category
|
||||
def category = new DescriptionBasedCategory(e.description)
|
||||
|
||||
// add the new event to the category
|
||||
entry = category.addEvent(e)
|
||||
|
||||
// remove the existing entry from this category and
|
||||
// add it to the subcategory
|
||||
this.entries -= existingEntry
|
||||
category.entries << existingEntry
|
||||
existingEntry.category = category
|
||||
|
||||
// add the category to our list of subcategories
|
||||
categories << category
|
||||
}
|
||||
|
||||
// no, let's create a generic entry and add it
|
||||
else {
|
||||
entry = new Entry(this, e)
|
||||
entries << entry
|
||||
}
|
||||
entry = new Entry(this, e)
|
||||
entries << entry
|
||||
}
|
||||
|
||||
return entry
|
||||
@ -78,20 +58,27 @@ public abstract class Category implements Comparable<Category> {
|
||||
|
||||
if (matchingPlans) {
|
||||
// create the new category
|
||||
def result = matchingPlans.createNewCategory(e, entries)
|
||||
def newCategory = matchingPlans[0].newCategory(e, entries)
|
||||
|
||||
// add it to our list of cateogries
|
||||
categories << result.category
|
||||
categories << newCategory
|
||||
|
||||
// return new entry
|
||||
return result.entry
|
||||
// add the new event to the category
|
||||
def entry = newCategory.addEvent(e)
|
||||
|
||||
// move all the entries that match the new category over
|
||||
def existingEntries = matchingPlans[0].findEntriesToRecategorize(e, entries)
|
||||
entries -= existingEntries
|
||||
newCategory.entries.addAll(existingEntries)
|
||||
existingEntries.each { it.category = newCategory }
|
||||
|
||||
// return the new entry
|
||||
return entry
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public Entry
|
||||
|
||||
public Duration getDuration() {
|
||||
return categories.sum(new Duration(0)) { it.duration } +
|
||||
entries.sum(new Duration(0)) { it.duration }
|
||||
|
@ -0,0 +1,20 @@
|
||||
package com.jdbernard.timeanalyzer
|
||||
|
||||
public class DescriptionBasedCategorizationPlan implements CategorizationPlan {
|
||||
|
||||
public boolean deservesNewCategory(Event event, List<Entry> existingEntries) {
|
||||
return existingEntries.findAll {
|
||||
it.description == event.description }.size() > 0
|
||||
}
|
||||
|
||||
public Category newCategory(Event event,
|
||||
List<Entry> existingEntries) {
|
||||
return new DescriptionBasedCategory(event.description)
|
||||
}
|
||||
|
||||
public List<Entry> findEntriesToRecategorize(Event event,
|
||||
List<Entry> existingEntries) {
|
||||
return existingEntries.findAll { it.description == event.description }
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ import org.joda.time.Duration;
|
||||
import org.joda.time.format.PeriodFormat;
|
||||
import org.joda.time.format.PeriodFormatter;
|
||||
|
||||
public class Event {
|
||||
public class Event implements Cloneable {
|
||||
|
||||
public String description;
|
||||
public String notes;
|
||||
@ -19,4 +19,7 @@ public class Event {
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Object clone() throws CloneNotSupportedException
|
||||
{ return super.clone(); }
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ public class FilteredCategory extends Category{
|
||||
List<CategoryFilter> filters = []
|
||||
|
||||
public FilteredCategory(String description) {
|
||||
super()
|
||||
this.description = description
|
||||
super(description)
|
||||
}
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
|
@ -3,8 +3,11 @@ package com.jdbernard.timeanalyzer
|
||||
public class GeneralCategory extends Category {
|
||||
|
||||
public GeneralCategory() {
|
||||
super()
|
||||
description = "General"
|
||||
this("General")
|
||||
}
|
||||
|
||||
public GeneralCategory(String description) {
|
||||
super(description)
|
||||
}
|
||||
|
||||
public boolean matchesEvent(Event e) { true }
|
||||
|
@ -0,0 +1,21 @@
|
||||
package com.jdbernard.timeanalyzer
|
||||
|
||||
public class TwoLevelCategorizationPlan implements CategorizationPlan {
|
||||
|
||||
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
|
||||
|
||||
public boolean deservesNewCategory(Event event, List<Entry> el) {
|
||||
return event ==~ TWO_LEVEL_PATTERN
|
||||
}
|
||||
|
||||
public Category newCategory(Event event, List<Entry> el) {
|
||||
def m = event.description =~ TWO_LEVEL_PATTERN
|
||||
return new TwoLevelCategory(m[0][1])
|
||||
}
|
||||
|
||||
public List<Entry> findEntriesToRecategorize(Event event,
|
||||
List<Entry> existingEntries) {
|
||||
def m = event.description =~ TWO_LEVEL_PATTERN
|
||||
return existingEntries.findAll { it.description ==~ /${m[0][1]}:.*/ }
|
||||
}
|
||||
}
|
28
src/main/com/jdbernard/timeanalyzer/TwoLevelCategory.groovy
Normal file
28
src/main/com/jdbernard/timeanalyzer/TwoLevelCategory.groovy
Normal file
@ -0,0 +1,28 @@
|
||||
package com.jdbernard.timeanalyzer
|
||||
|
||||
public class TwoLevelCategory extends Category {
|
||||
|
||||
private final def descriptionPattern
|
||||
|
||||
public TwoLevelCategory(String heading) {
|
||||
super(heading)
|
||||
|
||||
descriptionPattern = ~/^${heading}:\s*(.*)/
|
||||
}
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return e.description ==~ descriptionPattern
|
||||
}
|
||||
|
||||
public Entry addEvent(Event e) {
|
||||
def event = e.clone()
|
||||
|
||||
def m = event.description =~ descriptionPattern
|
||||
|
||||
event.description = m[0][1]
|
||||
|
||||
def entry = super.addEvent(event)
|
||||
|
||||
return entry
|
||||
}
|
||||
}
|
@ -11,6 +11,6 @@ public class Util {
|
||||
DefaultPieDataset dpds = new DefaultPieDataset()
|
||||
|
||||
category.categories { subcat ->
|
||||
dpds.setValue(subcat.description, subcat.
|
||||
dpds.setValue(subcat.description, subcat.duration.getMillis()) }
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
package com.quantumdigital.ithelp.timeanalyzer
|
||||
|
||||
import com.jdbernard.timeanalyzer.Category
|
||||
import com.jdbernard.timeanalyzer.Entry
|
||||
import com.jdbernard.timeanalyzer.Event
|
||||
|
||||
public class ITHelpCategory extends Category {
|
||||
|
||||
private def ithelpPattern = ~/^ITHelp:\s*(.*)$/
|
||||
private def ticketPattern = ~/^ITHelp:.*#(\d+).*/
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return (e.description ==~ ithelpPattern)
|
||||
}
|
||||
|
||||
public Entry addEvent(Event e) {
|
||||
|
||||
Entry entry
|
||||
|
||||
// try to add to an existing category
|
||||
entry = addToSubcategory(e)
|
||||
|
||||
// no existing category matches
|
||||
if (!entry) {
|
||||
|
||||
// see if it is a new ticket entry
|
||||
def ticketMatch = e.description =~ ticketPattern
|
||||
|
||||
// if is a new ticket
|
||||
if(ticketMatch) {
|
||||
// parse the ticket number
|
||||
int ticketId = ticketMatch[0][1] as int
|
||||
|
||||
// create the category
|
||||
TicketCategory category = new TicketCategory(ticketId)
|
||||
|
||||
// add the category to our list
|
||||
categories << category
|
||||
|
||||
// add the event to the category
|
||||
entry = category.addEvent(e)
|
||||
}
|
||||
|
||||
// not a new ticket, use the general logic for adding
|
||||
else {
|
||||
|
||||
// add a general entry to this category
|
||||
entry = super.addEvent(e)
|
||||
|
||||
// adjust the description (remove the ITHelp tag)
|
||||
def m = entry.description =~ ithelpPattern
|
||||
entry.description = m[0][1]
|
||||
}
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.quantumdigital.ithelp.timeanalyzer
|
||||
|
||||
import com.jdbernard.timeanalyzer.Category
|
||||
import com.jdbernard.timeanalyzer.CategorizationPlan
|
||||
import com.jdbernard.timeanalyzer.Event
|
||||
import com.jdbernard.timeanalyzer.Entry
|
||||
|
||||
public class TicketCategorizationPlan implements CategorizationPlan {
|
||||
|
||||
private static def TICKET_PATTERN = ~/.*#(\d+).*/
|
||||
|
||||
public boolean deservesNewCategory(Event e, List<Entry> el) {
|
||||
return e.description ==~ TICKET_PATTERN
|
||||
}
|
||||
|
||||
public Category newCategory(Event e, List<Entry> el) {
|
||||
def m = e.description =~ TICKET_PATTERN
|
||||
|
||||
return new TicketCategory(m[0][1] as int)
|
||||
}
|
||||
|
||||
public List<Entry> findEntriesToRecategorize(Event e,
|
||||
List<Entry> existingEntries) {
|
||||
def m = e.description =~ TICKET_PATTERN
|
||||
int ticketId = m[0][1] as int
|
||||
|
||||
return existingEntries.findAll {
|
||||
it.description ==~ /.*#${ticketId}.*/ }
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ public class TicketCategory extends Category {
|
||||
}
|
||||
|
||||
public boolean matchesEvent(Event e) {
|
||||
return (e.description ==~ /ITHelp:.*#${ticketId}.*/)
|
||||
return (e.description ==~ /.*#${ticketId}.*/)
|
||||
}
|
||||
|
||||
public TicketEntry addEvent(Event e) {
|
||||
|
@ -10,8 +10,7 @@ public class TicketEntry extends Entry {
|
||||
public TicketEntry(TicketCategory category, Event e) {
|
||||
super(category, e)
|
||||
|
||||
def m = e.description =~ /^ITHelp:\s*(.*#(\d+).*)$/
|
||||
this.description = m[0][1]
|
||||
this.id = m[0][2] as int
|
||||
def m = e.description =~ /.*#(\d+).*/
|
||||
this.id = m[0][1] as int
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,24 @@ fileSource = new FileTimelineSource(new File("timeline.jdbernard.txt").toURI())
|
||||
timeline = fileSource.read()
|
||||
events = tep.process(timeline)
|
||||
|
||||
topcat = new FilteredCategory()
|
||||
topcat = new FilteredCategory("Top Category")
|
||||
topcat.filters << new TimeIntervalCategoryFilter(
|
||||
new DateTime(2011, 1, 2, 0, 0, 0, 0), new DateTime(2011, 1, 9, 0, 0, 0, 0))
|
||||
topcat.description = "Top Category"
|
||||
|
||||
ithelpcat = new ITHelpCategory()
|
||||
ithelpcat.description = "ITHelp"
|
||||
twoLevelCatPlan = new TwoLevelCategorizationPlan()
|
||||
descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan()
|
||||
topcat.categorizationPlans << twoLevelCatPlan
|
||||
topcat.categorizationPlans << descriptionBasedCatPlan
|
||||
|
||||
ithelpcat = new TwoLevelCategory("ITHelp")
|
||||
ticketCatPlan = new TicketCategorizationPlan()
|
||||
|
||||
ithelpcat.categorizationPlans << ticketCatPlan
|
||||
ithelpcat.categorizationPlans << descriptionBasedCatPlan
|
||||
|
||||
topcat.categories << ithelpcat
|
||||
|
||||
events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) }
|
||||
//events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) }
|
||||
|
||||
makePieDataset = { category ->
|
||||
DefaultPieDataset dpds = new DefaultPieDataset()
|
||||
@ -38,10 +46,11 @@ makePieDataset = { category ->
|
||||
return dpds
|
||||
}
|
||||
|
||||
topcatDataset = makePieDataset(topcat)
|
||||
ithelpDataset = makePieDataset(ithelpcat)
|
||||
//topcatDataset = makePieDataset(topcat)
|
||||
//ithelpDataset = makePieDataset(ithelpcat)
|
||||
|
||||
topcatFrame = new ChartFrame("Top Category",
|
||||
ChartFactory.createPieChart("Time Spent", topcatDataset, true, true, false))
|
||||
ithelpFrame = new ChartFrame("ITHelp",
|
||||
ChartFactory.createPieChart("Time Spent", ithelpDataset, true, true, false))
|
||||
//topcatFrame = new ChartFrame("Top Category",
|
||||
//ChartFactory.createPieChart("Time Spent", topcatDataset, true, true, false))
|
||||
//ithelpFrame = new ChartFrame("ITHelp",
|
||||
//ChartFactory.createPieChart("Time Spent", ithelpDataset, true, true, false))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user