Reorganized the code into new packages.

This commit is contained in:
Jonathan Bernard 2011-10-27 20:14:08 -05:00
parent 652cc8703a
commit d076b739f5
20 changed files with 533 additions and 454 deletions

View File

@ -1,5 +0,0 @@
package com.jdbernard.timeanalyzer;
public interface CategoryFilter {
public boolean matchesEvent(Event event);
}

View File

@ -1,107 +1,118 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
import org.joda.time.Duration import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.Duration
/**
* A category represents a collection of like events. /**
*/ * A `Category` represents a collection of like `Events` and sub-categories.
public abstract class Category implements Comparable<Category> { */
public abstract class Category implements Comparable<Category> {
public List events
public List categories /** List of events directly under this category. */
public List categorizationPlans public List events
public String description
/** List of sub-categories under this category. */
public Category() { public List categories
events = []
categories = [] /** List of `CategorizationPlan`s to use when adding new `Event`s to the
categorizationPlans = [] * category. */
description = "Unnamed Category" public List categorizationPlans
}
/** A end-user-friendly text description of the category.*/
public Category(String description) { public String description
events = []
categories = [] public Category() {
categorizationPlans = [] events = []
this.description = description categories = []
} categorizationPlans = []
description = "Unnamed Category" }
public abstract boolean matchesEvent(Event e)
public Category(String description) {
public Event addEvent(Event event) { events = []
categories = []
// see if we have or can create a subcategory that will hold this event categorizationPlans = []
Event addedEvent = addToSubcategory(event) this.description = description }
// no, let's just add it on ourself /**
if (!addedEvent) { * Does the given event belong in this category?
events << event * @param e `Event` being considered.
addedEvent = event * @return **`true`** if this event belongs in this category, **`false`**
} * otherwise.
*/
return addedEvent public abstract boolean matchesEvent(Event e)
}
/**
public Event addToSubcategory(Event e) { * Add an event to this category. This method does not check to see if the
* `Event` matches this category. It assumed that the check has already been
// find all matching subcategories * made and the `Event` matches.*/
def matchingCategories = categories.findAll { it.matchesEvent(e) } public Event addEvent(Event event) {
if (matchingCategories) // see if we have or can create a subcategory that will hold this event
return matchingCategories[0].addEvent(e) Event addedEvent = addToSubcategory(event)
// no matching subcategories, can we create a new one based on one // no, let's just add it on ourself
// of our plans? if (!addedEvent) {
def matchingPlans = categorizationPlans.findAll events << event
{ it.deservesNewCategory(e, events) } addedEvent = event }
if (matchingPlans) { return addedEvent }
// create the new category
def newCategory = matchingPlans[0].newCategory(e, events) public Event addToSubcategory(Event e) {
// add it to our list of cateogries // find all matching subcategories
categories << newCategory def matchingCategories = categories.findAll { it.matchesEvent(e) }
// add the new event to the category if (matchingCategories) {
def addedEvent = newCategory.addEvent(e) return matchingCategories[0].addEvent(e) }
// move all the events that match the new category over // no matching subcategories, can we create a new one based on one
def existingEvents = matchingPlans[0].findEventsToRecategorize(e, events) // of our plans?
events -= existingEvents def matchingPlans = categorizationPlans.findAll {
existingEvents.each { newCategory.addEvent(it) } it.deservesNewCategory(e, events) }
// return the new entry if (matchingPlans) {
return addedEvent // create the new category
} def newCategory = matchingPlans[0].newCategory(e, events)
return null // add it to our list of cateogries
} categories << newCategory
public Category filter(List<CategoryFilter> filters) { // add the new event to the category
def addedEvent = newCategory.addEvent(e)
// create new filtered category
FilteredCategory fc = new FilteredCategory(description) // move all the events that match the new category over
fc.filters = filters def existingEvents = matchingPlans[0].findEventsToRecategorize(e, events)
events -= existingEvents
// filter all events and add them to the category existingEvents.each { newCategory.addEvent(it) }
fc.events
// return the new entry
// TODO return addedEvent }
}
return null }
public Category filter(CategoryFilter filter) { return filter([filter]) }
public Category filter(List<CategoryFilter> filters) {
public Category filter(Closure c) { return filter(c as CategoryFilter) }
// create new filtered category
public Duration getDuration() { FilteredCategory fc = new FilteredCategory(description)
return categories.sum(new Duration(0)) { it.duration } + fc.filters = filters
events.sum(new Duration(0)) { it.duration }
} // filter all events and add them to the category
fc.events
public int compareTo(Category other) {
return this.getDuration().compareTo(other.getDuration()) // TODO
} }
public String toString() { return description } public Category filter(CategoryFilter filter) { return filter([filter]) }
} public Category filter(Closure c) { return filter(c as CategoryFilter) }
public Duration getDuration() {
return categories.sum(new Duration(0)) { it.duration } +
events.sum(new Duration(0)) { it.duration } }
public int compareTo(Category other) {
return this.getDuration().compareTo(other.getDuration()) }
public String toString() { return description }
}

View File

@ -0,0 +1,7 @@
package com.jdbernard.timeanalyzer.categories;
import com.jdbernard.timeanalyzer.events.Event;
public interface CategoryFilter {
public boolean matchesEvent(Event event);
}

View File

@ -1,17 +1,15 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
import org.joda.time.Duration import com.jdbernard.timeanalyzer.events.Event
public class DescriptionBasedCategory extends Category { public class DescriptionBasedCategory extends Category {
public DescriptionBasedCategory(String description) { public DescriptionBasedCategory(String description) {
super() super()
this.description = description.replaceAll(/\p{Punct}/, '') this.description = description.replaceAll(/\p{Punct}/, '') }
}
public boolean matchesEvent(Event e) {
public boolean matchesEvent(Event e) { return e.description.replaceAll(/\p{Punct}/, '').toLowerCase() ==
return e.description.replaceAll(/\p{Punct}/, '').toLowerCase() == description.toLowerCase() }
description.toLowerCase()
} }
}

View File

@ -1,15 +1,15 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
public class FilteredCategory extends Category{ import com.jdbernard.timeanalyzer.events.Event
List<CategoryFilter> filters = [] public class FilteredCategory extends Category {
public FilteredCategory(String description) { List<CategoryFilter> filters = []
super(description)
} public FilteredCategory(String description) {
super(description) }
public boolean matchesEvent(Event e) {
return filters.every { filter -> filter.matchesEvent(e) } public boolean matchesEvent(Event e) {
} return filters.every { filter -> filter.matchesEvent(e) } }
} }

View File

@ -1,15 +1,17 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
public class GeneralCategory extends Category { import com.jdbernard.timeanalyzer.events.Event
public GeneralCategory() { public class GeneralCategory extends Category {
this("General")
} public GeneralCategory() {
this("General")
public GeneralCategory(String description) { }
super(description)
} public GeneralCategory(String description) {
super(description)
public boolean matchesEvent(Event e) { true } }
} public boolean matchesEvent(Event e) { true }
}

View File

@ -0,0 +1,18 @@
package com.jdbernard.timeanalyzer.categories
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.Interval
public class TimeIntervalCategory extends Category {
private final Interval interval
public TimeIntervalCategory(String desc, Interval interval) {
super(desc)
this.interval = interval }
public boolean matchesEvent(Event e) {
Interval eventIv = new Interval(e.start, e.duration)
return interval.contains(eventIv) }
}

View File

@ -1,23 +1,25 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
import org.joda.time.Interval import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.ReadableInstant
import org.joda.time.ReadableInterval import org.joda.time.Interval
import org.joda.time.ReadableInstant
public class TimeIntervalCategoryFilter implements CategoryFilter { import org.joda.time.ReadableInterval
ReadableInterval interval public class TimeIntervalCategoryFilter implements CategoryFilter {
public TimeIntervalCategoryFilter(ReadableInterval interval) { ReadableInterval interval
this.interval = interval
} public TimeIntervalCategoryFilter(ReadableInterval interval) {
this.interval = interval
public TimeIntervalCategoryFilter(ReadableInstant start, }
ReadableInstant end) {
this.interval = new Interval(start, end) public TimeIntervalCategoryFilter(ReadableInstant start,
} ReadableInstant end) {
this.interval = new Interval(start, end)
public boolean matchesEvent(Event event) { }
return interval.contains(event.start)
} public boolean matchesEvent(Event event) {
} return interval.contains(event.start)
}
}

View File

@ -1,24 +1,23 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categories
public class TwoLevelCategory extends Category { import com.jdbernard.timeanalyzer.events.Event
private final def descriptionPattern public class TwoLevelCategory extends Category {
public TwoLevelCategory(String heading) { private final def descriptionPattern
super(heading)
public TwoLevelCategory(String heading) {
descriptionPattern = ~/^${heading}:\s*(.*)/ super(heading)
}
descriptionPattern = ~/^${heading}:\s*(.*)/ }
public boolean matchesEvent(Event e) {
return e.description ==~ descriptionPattern public boolean matchesEvent(Event e) {
} return e.description ==~ descriptionPattern }
public Event addEvent(Event e) { public Event addEvent(Event e) {
def m = e.description =~ descriptionPattern def m = e.description =~ descriptionPattern
e = new Event(e, description: m[0][1]) e = new Event(e, description: m[0][1])
super.addEvent(e) super.addEvent(e) }
} }
}

View File

@ -1,10 +1,13 @@
package com.jdbernard.timeanalyzer; package com.jdbernard.timeanalyzer.categorizationplans;
import java.util.List; import com.jdbernard.timeanalyzer.categories.Category;
import java.util.Map; import com.jdbernard.timeanalyzer.events.Event;
public interface CategorizationPlan { import java.util.List;
boolean deservesNewCategory(Event event, List<Event> existingEvents); import java.util.Map;
Category newCategory(Event event, List<Event> existingEvents);
List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents); public interface CategorizationPlan {
} boolean deservesNewCategory(Event event, List<Event> existingEvents);
Category newCategory(Event event, List<Event> existingEvents);
List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents);
}

View File

@ -0,0 +1,35 @@
package com.jdbernard.timeanalyzer.categorizationplans;
import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.DateTime
import org.joda.time.Interval
import org.joda.time.Period
public class DailyCategorizationPlan {
boolean deservesNewCategory(Event event, List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
Interval eventIv = new Interval(
event.start, event.duration)
return fullDay.contains(eventIv) }
Category newCategory(Event event, List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
return TimeIntervalCategory(
event.start.toString("EEE, MMM dd", fullday)) }
List<Event> findEventsToRecategorize(Event event,
List<Event> existingEvents) {
Interval fullday = new Interval(
event.start.toDateMidnight(), Period.days(1))
return existingEvents.findAll {
Interval iv = new Interval(it.start, it.duration)
fullday.contains(iv) } }
}

View File

@ -1,23 +1,27 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categorizationplans
public class DescriptionBasedCategorizationPlan implements CategorizationPlan { import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.DescriptionBasedCategory
public boolean deservesNewCategory(Event event, List<Event> existingEvents) { import com.jdbernard.timeanalyzer.events.Event
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
return existingEvents.any { public class DescriptionBasedCategorizationPlan implements CategorizationPlan {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
} public boolean deservesNewCategory(Event event, List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
public Category newCategory(Event event, return existingEvents.any {
List<Event> existingEvents) { it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
return new DescriptionBasedCategory(event.description) }
}
public Category newCategory(Event event,
public List<Event> findEventsToRecategorize(Event event, List<Event> existingEvents) {
List<Event> existingEvents) { return new DescriptionBasedCategory(event.description)
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase() }
return existingEvents.findAll {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc } public List<Event> findEventsToRecategorize(Event event,
} List<Event> existingEvents) {
def desc = event.description.replaceAll(/\p{Punct}/, '').toLowerCase()
} return existingEvents.findAll {
it.description.replaceAll(/\p{Punct}/, '').toLowerCase() == desc }
}
}

View File

@ -1,21 +1,25 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.categorizationplans
public class TwoLevelCategorizationPlan implements CategorizationPlan { import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.categories.TwoLevelCategory
private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/ import com.jdbernard.timeanalyzer.events.Event
public boolean deservesNewCategory(Event event, List<Event> el) { public class TwoLevelCategorizationPlan implements CategorizationPlan {
return event ==~ TWO_LEVEL_PATTERN
} private static final def TWO_LEVEL_PATTERN = ~/(.+?):(.*)/
public Category newCategory(Event event, List<Event> el) { public boolean deservesNewCategory(Event event, List<Event> el) {
def m = event.description =~ TWO_LEVEL_PATTERN return event ==~ TWO_LEVEL_PATTERN
return new TwoLevelCategory(m[0][1]) }
}
public Category newCategory(Event event, List<Event> el) {
public List<Event> findEventsToRecategorize(Event event, def m = event.description =~ TWO_LEVEL_PATTERN
List<Event> existingEvents) { return new TwoLevelCategory(m[0][1])
def m = event.description =~ TWO_LEVEL_PATTERN }
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ }
} public List<Event> findEventsToRecategorize(Event event,
} List<Event> existingEvents) {
def m = event.description =~ TWO_LEVEL_PATTERN
return existingEvents.findAll { it.description ==~ /${m[0][1]}:.*/ }
}
}

View File

@ -1,16 +1,16 @@
package com.jdbernard.timeanalyzer.chart package com.jdbernard.timeanalyzer.chart
import com.jdbernard.timeanalyzer.Category import com.jdbernard.timeanalyzer.categories.Category
import org.jfree.data.general.DefaultPieDataset import org.jfree.data.general.DefaultPieDataset
import org.jfree.data.general.PieDataset import org.jfree.data.general.PieDataset
import org.jfree.util.SortOrder import org.jfree.util.SortOrder
public class Util { public class Util {
public static PieDataset makePieDataset(Category category) { public static PieDataset makePieDataset(Category category) {
DefaultPieDataset dpds = new DefaultPieDataset() DefaultPieDataset dpds = new DefaultPieDataset()
category.categories { subcat -> category.categories { subcat ->
dpds.setValue(subcat.description, subcat.duration.getMillis()) } dpds.setValue(subcat.description, subcat.duration.getMillis()) }
} }
} }

View File

@ -1,41 +1,41 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.events
import org.joda.time.DateTime import org.joda.time.ReadableDateTime
import org.joda.time.Duration 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 implements Cloneable { public class Event implements Cloneable {
public final String description public final String description
public final String notes public final String notes
public final DateTime start public final ReadableDateTime start
public Duration duration // bit of a hack, allows modification for the public Duration duration // bit of a hack, allows modification for the
// TimelineEventProcessor // TimelineEventProcessor
public static PeriodFormatter periodFormatter = PeriodFormat.getDefault() public static PeriodFormatter periodFormatter = PeriodFormat.getDefault()
public Event(String desc, String notes, DateTime start, Duration duration) { public Event(String desc, String notes, ReadableDateTime start, Duration duration) {
this.description = desc this.description = desc
this.notes = notes this.notes = notes
this.start = start this.start = start
this.duration = duration this.duration = duration
} }
public Event(Map params) { public Event(Map params) {
this.description = params.description ?: "" this.description = params.description ?: ""
this.notes = params.notes ?: "" this.notes = params.notes ?: ""
this.start = params.start this.start = params.start
this.duration = params.duration this.duration = params.duration
} }
public Event(Map params, Event e) { public Event(Map params, Event e) {
this.description = params.description ?: e.description this.description = params.description ?: e.description
this.notes = params.notes ?: e.notes this.notes = params.notes ?: e.notes
this.start = params.start ?: e.start this.start = params.start ?: e.start
this.duration = params.duration ?: e.duration this.duration = params.duration ?: e.duration
} }
public String toString() { return description } public String toString() { return description }
} }

View File

@ -1,45 +1,46 @@
package com.jdbernard.timeanalyzer package com.jdbernard.timeanalyzer.processors
import com.jdbernard.timestamper.core.Timeline import com.jdbernard.timeanalyzer.events.Event
import org.joda.time.DateTime import com.jdbernard.timestamper.core.Timeline
import org.joda.time.Duration import org.joda.time.DateTime
import org.joda.time.Duration
public class TimelineEventProcessor {
public class TimelineEventProcessor {
/** Events whose description matches one of these regex strings or
* patterns are ignored. */ /** Events whose description matches one of these regex strings or
List exclusions = [] * patterns are ignored. */
List exclusions = []
public TimelineEventProcessor() {}
public TimelineEventProcessor() {}
public TimelineEventProcessor(List exclusions) {
this.exclusions = exclusions public TimelineEventProcessor(List exclusions) {
} this.exclusions = exclusions
}
public List<Event> process(Timeline timeline) {
List<Event> events = [] public List<Event> process(Timeline timeline) {
List<Event> events = []
timeline.each { marker ->
Event e = new Event( timeline.each { marker ->
description: marker.mark, Event e = new Event(
notes: marker.notes, description: marker.mark,
start: new DateTime(marker.timestamp), notes: marker.notes,
duration: new Duration(0)) start: new DateTime(marker.timestamp),
duration: new Duration(0))
// if this is not the first event, then we need to update the
// duration of the previous event // if this is not the first event, then we need to update the
if (events.size > 0) { // duration of the previous event
Event lastEvent = events[-1] if (events.size > 0) {
lastEvent.duration = new Duration(lastEvent.start, e.start) Event lastEvent = events[-1]
} lastEvent.duration = new Duration(lastEvent.start, e.start)
}
events << e
} events << e
}
def excluded = events.findAll { event ->
exclusions.any { exclusion -> event.description ==~ exclusion } def excluded = events.findAll { event ->
} exclusions.any { exclusion -> event.description ==~ exclusion }
}
return events - excluded
} return events - excluded
} }
}

View File

@ -1,29 +1,29 @@
package com.quantumdigital.ithelp.timeanalyzer package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Category import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.CategorizationPlan import com.jdbernard.timeanalyzer.categorizationplans.CategorizationPlan
import com.jdbernard.timeanalyzer.Event import com.jdbernard.timeanalyzer.events.Event
public class TicketCategorizationPlan implements CategorizationPlan { public class TicketCategorizationPlan implements CategorizationPlan {
private static def TICKET_PATTERN = ~/.*#(\d+).*/ private static def TICKET_PATTERN = ~/.*#(\d+).*/
public boolean deservesNewCategory(Event e, List<Event> el) { public boolean deservesNewCategory(Event e, List<Event> el) {
return e.description ==~ TICKET_PATTERN return e.description ==~ TICKET_PATTERN
} }
public Category newCategory(Event e, List<Event> el) { public Category newCategory(Event e, List<Event> el) {
def m = e.description =~ TICKET_PATTERN def m = e.description =~ TICKET_PATTERN
return new TicketCategory(m[0][1] as int) return new TicketCategory(m[0][1] as int)
} }
public List<Event> findEventsToRecategorize(Event e, public List<Event> findEventsToRecategorize(Event e,
List<Event> existingEvents) { List<Event> existingEvents) {
def m = e.description =~ TICKET_PATTERN def m = e.description =~ TICKET_PATTERN
int ticketId = m[0][1] as int int ticketId = m[0][1] as int
return existingEvents.findAll { return existingEvents.findAll {
it.description ==~ /.*#${ticketId}.*/ } it.description ==~ /.*#${ticketId}.*/ }
} }
} }

View File

@ -1,25 +1,25 @@
package com.quantumdigital.ithelp.timeanalyzer package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Category import com.jdbernard.timeanalyzer.categories.Category
import com.jdbernard.timeanalyzer.Event import com.jdbernard.timeanalyzer.events.Event
public class TicketCategory extends Category { public class TicketCategory extends Category {
public final int ticketId public final int ticketId
public TicketCategory(int ticketId) { public TicketCategory(int ticketId) {
super() super()
this.ticketId = ticketId this.ticketId = ticketId
this.description = "Ticket #${ticketId}" this.description = "Ticket #${ticketId}"
} }
public boolean matchesEvent(Event e) { public boolean matchesEvent(Event e) {
return (e.description ==~ /.*#${ticketId}.*/) return (e.description ==~ /.*#${ticketId}.*/)
} }
public Event addEvent(Event e) { public Event addEvent(Event e) {
TicketEvent te = new TicketEvent(e) TicketEvent te = new TicketEvent(e)
events << te events << te
return te return te
} }
} }

View File

@ -1,37 +1,37 @@
package com.quantumdigital.ithelp.timeanalyzer package com.quantumdigital.ithelp.timeanalyzer
import com.jdbernard.timeanalyzer.Event import com.jdbernard.timeanalyzer.events.Event
public class TicketEvent extends Event { public class TicketEvent extends Event {
public final int id public final int id
public TicketEvent(String desc, String notes, String start, String duration) { public TicketEvent(String desc, String notes, String start, String duration) {
super(desc, notes, start, duration) super(desc, notes, start, duration)
def m = desc =~ /.*#(\d+).*/ def m = desc =~ /.*#(\d+).*/
this.id = m[0][1] as int this.id = m[0][1] as int
} }
public TicketEvent(Map params) { public TicketEvent(Map params) {
super(params) super(params)
def m = description =~ /.*#(\d+).*/ def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int this.id = m[0][1] as int
} }
public TicketEvent(Map params, Event e) { public TicketEvent(Map params, Event e) {
super(params, e) super(params, e)
def m = description =~ /.*#(\d+).*/ def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int this.id = m[0][1] as int
} }
public TicketEvent(Event e) { public TicketEvent(Event e) {
super([:], e) super([:], e)
def m = description =~ /.*#(\d+).*/ def m = description =~ /.*#(\d+).*/
this.id = m[0][1] as int this.id = m[0][1] as int
} }
} }

View File

@ -18,7 +18,7 @@ events = tep.process(timeline)
topcat = new FilteredCategory("Top Category") 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, 24, 0, 0, 0, 0), new DateTime(2011, 1, 25, 0, 0, 0, 0))
twoLevelCatPlan = new TwoLevelCategorizationPlan() twoLevelCatPlan = new TwoLevelCategorizationPlan()
descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan() descriptionBasedCatPlan = new DescriptionBasedCategorizationPlan()