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:
Jonathan Bernard 2011-01-12 17:21:39 -06:00
parent 24600b46d9
commit ab1c7f2393
16 changed files with 159 additions and 117 deletions

Binary file not shown.

View File

@ -5,5 +5,6 @@ import java.util.Map;
public interface CategorizationPlan { public interface CategorizationPlan {
boolean deservesNewCategory(Event event, List<Entry> existingEntries); 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);
} }

View File

@ -19,45 +19,25 @@ public abstract class Category implements Comparable<Category> {
description = "Unnamed Category" description = "Unnamed Category"
} }
public Category(String description) {
entries = []
categories = []
categorizationPlans = []
this.description = description
}
public abstract boolean matchesEvent(Event e) public abstract boolean matchesEvent(Event e)
public Entry addEvent(Event e) { public Entry addEvent(Event e) {
Entry entry 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) entry = addToSubcategory(e)
// no, let's create a generic entry and add it
if (!entry) { if (!entry) {
// if not, do we have a plan for categorizing this event? entry = new Entry(this, e)
entries << entry
// 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
}
} }
return entry return entry
@ -78,20 +58,27 @@ public abstract class Category implements Comparable<Category> {
if (matchingPlans) { if (matchingPlans) {
// create the new category // create the new category
def result = matchingPlans.createNewCategory(e, entries) def newCategory = matchingPlans[0].newCategory(e, entries)
// add it to our list of cateogries // add it to our list of cateogries
categories << result.category categories << newCategory
// return new entry // add the new event to the category
return result.entry 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 return null
} }
public Entry
public Duration getDuration() { public Duration getDuration() {
return categories.sum(new Duration(0)) { it.duration } + return categories.sum(new Duration(0)) { it.duration } +
entries.sum(new Duration(0)) { it.duration } entries.sum(new Duration(0)) { it.duration }

View File

@ -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 }
}
}

View File

@ -5,7 +5,7 @@ import org.joda.time.Duration;
import org.joda.time.format.PeriodFormat; import org.joda.time.format.PeriodFormat;
import org.joda.time.format.PeriodFormatter; import org.joda.time.format.PeriodFormatter;
public class Event { public class Event implements Cloneable {
public String description; public String description;
public String notes; public String notes;
@ -19,4 +19,7 @@ public class Event {
public String toString() { public String toString() {
return description; return description;
} }
public Object clone() throws CloneNotSupportedException
{ return super.clone(); }
} }

View File

@ -5,8 +5,7 @@ public class FilteredCategory extends Category{
List<CategoryFilter> filters = [] List<CategoryFilter> filters = []
public FilteredCategory(String description) { public FilteredCategory(String description) {
super() super(description)
this.description = description
} }
public boolean matchesEvent(Event e) { public boolean matchesEvent(Event e) {

View File

@ -3,8 +3,11 @@ package com.jdbernard.timeanalyzer
public class GeneralCategory extends Category { public class GeneralCategory extends Category {
public GeneralCategory() { public GeneralCategory() {
super() this("General")
description = "General" }
public GeneralCategory(String description) {
super(description)
} }
public boolean matchesEvent(Event e) { true } public boolean matchesEvent(Event e) { true }

View File

@ -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]}:.*/ }
}
}

View 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
}
}

View File

@ -11,6 +11,6 @@ public class Util {
DefaultPieDataset dpds = new DefaultPieDataset() DefaultPieDataset dpds = new DefaultPieDataset()
category.categories { subcat -> category.categories { subcat ->
dpds.setValue(subcat.description, subcat. dpds.setValue(subcat.description, subcat.duration.getMillis()) }
} }
} }

View File

@ -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
}
}

View File

@ -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}.*/ }
}
}

View File

@ -14,7 +14,7 @@ public class TicketCategory extends Category {
} }
public boolean matchesEvent(Event e) { public boolean matchesEvent(Event e) {
return (e.description ==~ /ITHelp:.*#${ticketId}.*/) return (e.description ==~ /.*#${ticketId}.*/)
} }
public TicketEntry addEvent(Event e) { public TicketEntry addEvent(Event e) {

View File

@ -10,8 +10,7 @@ public class TicketEntry extends Entry {
public TicketEntry(TicketCategory category, Event e) { public TicketEntry(TicketCategory category, Event e) {
super(category, e) super(category, e)
def m = e.description =~ /^ITHelp:\s*(.*#(\d+).*)$/ def m = e.description =~ /.*#(\d+).*/
this.description = m[0][1] this.id = m[0][1] as int
this.id = m[0][2] as int
} }
} }

View File

@ -16,16 +16,24 @@ fileSource = new FileTimelineSource(new File("timeline.jdbernard.txt").toURI())
timeline = fileSource.read() timeline = fileSource.read()
events = tep.process(timeline) events = tep.process(timeline)
topcat = new FilteredCategory() topcat = new FilteredCategory("Top Category")
topcat.filters << new TimeIntervalCategoryFilter( topcat.filters << new TimeIntervalCategoryFilter(
new DateTime(2011, 1, 2, 0, 0, 0, 0), new DateTime(2011, 1, 9, 0, 0, 0, 0)) 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() twoLevelCatPlan = new TwoLevelCategorizationPlan()
ithelpcat.description = "ITHelp" 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 topcat.categories << ithelpcat
events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) } //events.each { if (topcat.matchesEvent(it)) topcat.addEvent(it) }
makePieDataset = { category -> makePieDataset = { category ->
DefaultPieDataset dpds = new DefaultPieDataset() DefaultPieDataset dpds = new DefaultPieDataset()
@ -38,10 +46,11 @@ makePieDataset = { category ->
return dpds return dpds
} }
topcatDataset = makePieDataset(topcat) //topcatDataset = makePieDataset(topcat)
ithelpDataset = makePieDataset(ithelpcat) //ithelpDataset = makePieDataset(ithelpcat)
topcatFrame = new ChartFrame("Top Category", //topcatFrame = new ChartFrame("Top Category",
ChartFactory.createPieChart("Time Spent", topcatDataset, true, true, false)) //ChartFactory.createPieChart("Time Spent", topcatDataset, true, true, false))
ithelpFrame = new ChartFrame("ITHelp", //ithelpFrame = new ChartFrame("ITHelp",
ChartFactory.createPieChart("Time Spent", ithelpDataset, true, true, false)) //ChartFactory.createPieChart("Time Spent", ithelpDataset, true, true, false))