ctakes-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From seanfi...@apache.org
Subject svn commit: r1660963 [18/19] - in /ctakes/sandbox/timelanes: META-INF/ edu/ edu/mayo/ edu/mayo/bmi/ edu/mayo/bmi/annotation/ edu/mayo/bmi/annotation/knowtator/ org/ org/chboston/ org/chboston/cnlp/ org/chboston/cnlp/anafora/ org/chboston/cnlp/anafora/a...
Date Thu, 19 Feb 2015 18:06:17 GMT
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timeline/TimelineFactory.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timeline/TimelineFactory.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timeline/TimelineFactory.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timeline/TimelineFactory.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,343 @@
+package org.chboston.cnlp.timeline.timeline;
+
+import org.chboston.cnlp.iaa.evaluator.temporal.TLinkTypeArray3;
+import org.chboston.cnlp.iaa.evaluator.temporal.TlinkType;
+import org.chboston.cnlp.nlp.annotation.annotation.Annotation;
+import org.chboston.cnlp.nlp.annotation.annotation.AnnotationSpanComparator;
+import org.chboston.cnlp.nlp.annotation.annotation.store.AnnotationStore;
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefaultAttribute;
+import org.chboston.cnlp.nlp.annotation.attribute.DefinedAttributeType;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.CoreferenceChain;
+import org.chboston.cnlp.nlp.annotation.coreference.EntityIdStore;
+import org.chboston.cnlp.nlp.annotation.coreference.UniqueEntityIdStore;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.event.DocTimeRel;
+import org.chboston.cnlp.nlp.annotation.relation.DefaultRelation;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.nlp.util.CollectionMap;
+import org.chboston.cnlp.nlp.util.HashSetMap;
+import org.chboston.cnlp.nlp.util.SetMap;
+import org.chboston.cnlp.timeline.chronic.parser.AtParser;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanFactory;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+final public class TimelineFactory {
+
+
+   static private final Logger LOGGER = Logger.getLogger( "TimelineFactory" );
+
+   static private final Attribute CLOSURE_CREATOR = new DefaultAttribute( DefinedAttributeType.CREATOR, "Closure" );
+   static private final Attribute DOC_TIME_REL_CREATOR
+         = new DefaultAttribute( DefinedAttributeType.CREATOR, "DocTimeRel" );
+
+
+   private TimelineFactory() {
+   }
+
+
+   static public CompoundTimeline createCompoundTimeline( final String title,
+                                                          final Map<String, AnnotationStore> annotationCollections ) {
+      final Collection<Timeline> timelines = new HashSet<>( annotationCollections.size() );
+      for ( Map.Entry<String, AnnotationStore> annotationCollection : annotationCollections.entrySet() ) {
+         timelines.add( createTimeline( annotationCollection.getKey(), annotationCollection.getValue() ) );
+      }
+      long maxReferenceMillis = Long.MIN_VALUE;
+      for ( Timeline timeline : timelines ) {
+         maxReferenceMillis = Math.max( maxReferenceMillis, timeline.getReferenceMillis() );
+      }
+      return new CompoundTimeline( title, maxReferenceMillis, timelines );
+   }
+
+
+   static public Timeline createTimeline( final String title, final AnnotationStore annotationStore ) {
+      final List<Entity> temporalEntities = new ArrayList<>();
+      final List<Entity> nonTemporalEntities = new ArrayList<>();
+      final Collection<Entity> coreferenceLinks = new HashSet<>();
+      for ( CoreferenceChain chain : annotationStore.getCoreferenceChains() ) {
+         if ( chain.isClassType( TemporalClassType.TIMEX ) || chain.isClassType( TemporalClassType.DOCTIME ) ) {
+            continue;
+         } else if ( chain.isClassType( TemporalClassType.EVENT ) ) {
+            temporalEntities.add( chain );
+         } else {
+            nonTemporalEntities.add( chain );
+         }
+         for ( Entity link : chain ) {
+            coreferenceLinks.add( link );
+         }
+      }
+      for ( Entity event : annotationStore.getEvents() ) {
+         if ( !coreferenceLinks.contains( event ) ) {
+            temporalEntities.add( event );
+         }
+      }
+      temporalEntities.addAll( annotationStore.getTimes() );
+      for ( Entity namedEntity : annotationStore.getNamedEntities() ) {
+         if ( !coreferenceLinks.contains( namedEntity ) ) {
+            nonTemporalEntities.add( namedEntity );
+         }
+      }
+      Collections.sort( temporalEntities, AnnotationSpanComparator.getInstance() );
+      Collections.sort( nonTemporalEntities, AnnotationSpanComparator.getInstance() );
+      return createTimeline3( title, temporalEntities, nonTemporalEntities,
+            annotationStore.getUmlsRelations(),
+            annotationStore.getTimes(),
+            annotationStore.getTimeRelations() );
+   }
+
+
+   static private Collection<Integer> getDocRelTimeUniqueIds( final EntityIdStore entityIdCollection,
+                                                              final Map<Integer, PointedTimeSpan> uniqueIdTimeSpanMap ) {
+      final Collection<Integer> docRelTimeUniqueIds = new HashSet<>();
+      final Collection<Integer> usedUniqueIds = new HashSet<>();
+      for ( Integer timeSpanUniqueId : uniqueIdTimeSpanMap.keySet() ) {
+         usedUniqueIds.addAll( entityIdCollection.getAllUniqueEntityIds( timeSpanUniqueId ) );
+      }
+      for ( Integer uniqueId : entityIdCollection.getAllUniqueEntityIds() ) {
+         if ( usedUniqueIds.contains( uniqueId ) ) {
+            // Already has time span
+            continue;
+         }
+         final Collection<Entity> entities = entityIdCollection.getUniqueEntities( uniqueId );
+         if ( entities == null || entities.isEmpty() ) {
+            continue;
+         }
+         for ( Entity entity : entities ) {
+            if ( entity.isClassType( TemporalClassType.EVENT ) ) {
+               docRelTimeUniqueIds.add( uniqueId );
+               break;
+            }
+         }
+      }
+      return docRelTimeUniqueIds;
+   }
+
+
+   static private void doTemporalStuff( final Map<Entity, TimeSpan> timexTimeSpanMap,
+                                        final Collection<Entity> temporalEntities,
+                                        final Collection<Entity> timex3s,
+                                        final List<Relation> tlinks,
+                                        final Entity docTimex,
+                                        final CollectionMap<PointedTimeSpan, Entity> allTimeSpanEntityMap,
+                                        final CollectionMap<Entity, Relation> allEntityTlinkMap ) {
+      final EntityIdStore entityIdCollection = new UniqueEntityIdStore( temporalEntities );
+      final TLinkTypeArray3 tLinkTypeArray3 = new TLinkTypeArray3( tlinks, entityIdCollection );
+      final List<Relation> uniqueClosedTLinks = tLinkTypeArray3.getUniqueClosedTlinks();
+
+      final Map<Integer, PointedTimeSpan> uniqueIdTimeSpanMap
+            = TimeSpanFactory.getUniqueIdTimeSpans( timexTimeSpanMap, uniqueClosedTLinks, entityIdCollection );
+      final Collection<Integer> timexReferences = new HashSet<>( timex3s.size() );
+      for ( Entity timex : timex3s ) {
+         timexReferences.add( entityIdCollection.getUniqueEntityId( timex ) );
+      }
+      // Distribute Events to each timeSpan - for fast lookup by time span
+      final SetMap<PointedTimeSpan, Entity> timeSpanEntityMap = new HashSetMap<>();
+      for ( Map.Entry<Integer, PointedTimeSpan> entityTimeSpan : uniqueIdTimeSpanMap.entrySet() ) {
+         final Collection<Entity> uniqueEntities = entityIdCollection.getUniqueEntities( entityTimeSpan.getKey() );
+         final PointedTimeSpan timeSpanPlus = entityTimeSpan.getValue();
+         timeSpanEntityMap.addAll( timeSpanPlus, uniqueEntities );
+      }
+      // Associate TLinks with Events - useful later for display and interrogation
+      final SetMap<Integer, Relation> uniqueIdTlinkMap = new HashSetMap<>();
+      for ( Relation uniqueClosedTlink : uniqueClosedTLinks ) {
+         final Integer uniqueEntityId = entityIdCollection.getUniqueEntityId( uniqueClosedTlink.getFirstEntity() );
+         if ( timexReferences.contains( uniqueEntityId ) ) {
+            continue;
+         }
+         uniqueIdTlinkMap.place( uniqueEntityId, uniqueClosedTlink );
+      }
+
+      // Associate orphaned events (not in tlink with timex) with a timespan made from doctimerel
+      if ( docTimex != null ) {
+         final Collection<Integer> docRelTimeUniqueIds
+               = getDocRelTimeUniqueIds( entityIdCollection, uniqueIdTimeSpanMap );
+         addDocTimeRelLinks( docTimex, docRelTimeUniqueIds, entityIdCollection, timeSpanEntityMap, uniqueIdTlinkMap );
+      }
+      for ( Map.Entry<PointedTimeSpan, Set<Entity>> timeSpanEntities : timeSpanEntityMap.entrySet() ) {
+         allTimeSpanEntityMap.addAll( timeSpanEntities.getKey(), timeSpanEntities.getValue() );
+      }
+
+      // Cull Overlaps relations if something better (more precise) exists
+      refineTLinkMap( uniqueIdTlinkMap );
+
+      // switch to map of entities to tlinks
+      for ( Map.Entry<Integer, Set<Relation>> uniqueIdTlinks : uniqueIdTlinkMap.entrySet() ) {
+         final Collection<Entity> uniqueEntities = entityIdCollection.getUniqueEntities( uniqueIdTlinks.getKey() );
+         for ( Entity uniqueEntity : uniqueEntities ) {
+            for ( Relation tlink : uniqueIdTlinks.getValue() ) {
+               allEntityTlinkMap.place( uniqueEntity,
+                     createClosureTlink( uniqueEntity, tlink.getSecondEntity(),
+                           tlink.getAttribute( DefinedAttributeType.RELATION_TYPE.getName() ) ) );
+            }
+         }
+      }
+   }
+
+
+   static public Timeline createTimeline3( final String title,
+                                           final Collection<Entity> temporalEntities,
+                                           final Collection<Entity> nonTemporalEntities,
+                                           final Iterable<Relation> umlsRelations,
+                                           final Collection<Entity> timex3s,
+                                           final List<Relation> tlinks ) {
+      final Entity docTimex = AtParser.getDocTimex( nonTemporalEntities, timex3s );
+      final Map<Entity, TimeSpan> timexTimeSpanMap = TimeSpanFactory.getTimexTimeSpanMap( docTimex, timex3s );
+
+      final CollectionMap<PointedTimeSpan, Entity> allTimeSpanEntityMap = new HashSetMap<>();
+      final CollectionMap<Entity, Relation> allEntityTlinkMap = new HashSetMap<>();
+      doTemporalStuff( timexTimeSpanMap, temporalEntities, timex3s, tlinks, docTimex,
+            allTimeSpanEntityMap, allEntityTlinkMap );
+
+      // Add non-temporal entities
+      allTimeSpanEntityMap.addAll( TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS, nonTemporalEntities );
+
+      // TODO add umls relations
+//      for ( Relation relation : umlsRelations ) {
+//         final Entity entity1 = relation.getFirstEntity();
+//         final Entity entity2 = relation.getSecondEntity();
+//         final int id1 = entityIdCollection.getUniqueEntityId( entity1 );
+//         final int id2 = entityIdCollection.getUniqueEntityId( entity2 );
+//         LOGGER.info( id1 + " " + relation.getAttribute( DefinedAttributeType.RELATION_TYPE.getName() ).getValue()
+//                      + " " + id2 );
+//      }
+      // TODO add umls relations
+      return new DefaultTimeline( title, AtParser
+            .getTimexMillis( docTimex ), allTimeSpanEntityMap, allEntityTlinkMap, timex3s );
+   }
+
+
+   static private void refineTLinkMap( final SetMap<Integer, Relation> entityTlinkMap ) {
+      for ( Map.Entry<Integer, Set<Relation>> entityTLinks : entityTlinkMap.entrySet() ) {
+         removeLesserTLinks( entityTLinks.getValue() );
+      }
+   }
+
+   // Overlap is not very specific, so if an event has a non-overlap tlink to a certain event, discard the overlap tlink
+   static private void removeLesserTLinks( final Collection<Relation> tlinks ) {
+      final Map<Entity, Map<TlinkType, Relation>> secondEntityMap = new HashMap<>();
+      final Collection<Relation> removalLinks = new HashSet<>();
+      for ( Relation tlink : tlinks ) {
+         if ( tlink.getFirstEntity().equals( tlink.getSecondEntity() ) ) {
+            // Through closure and repeat annotations this can actually happen, but we want to get rid of it
+            removalLinks.add( tlink );
+            continue;
+         }
+         Map<TlinkType, Relation> reverseTLinks = secondEntityMap.get( tlink.getSecondEntity() );
+         if ( reverseTLinks == null ) {
+            reverseTLinks = new EnumMap<>( TlinkType.class );
+            secondEntityMap.put( tlink.getSecondEntity(), reverseTLinks );
+         }
+         final TlinkType type = TlinkType.getTlinkType( tlink );
+         if ( type == TlinkType.OVERLAP && !reverseTLinks.isEmpty() ) {
+            removalLinks.add( tlink );
+         } else if ( type != TlinkType.OVERLAP && reverseTLinks.containsKey( TlinkType.OVERLAP ) ) {
+            removalLinks.add( reverseTLinks.get( TlinkType.OVERLAP ) );
+         }
+         reverseTLinks.put( type, tlink );
+      }
+      tlinks.removeAll( removalLinks );
+   }
+
+
+   static private void addDocTimeRelLinks( final Entity docTimex, final Iterable<Integer> docRelUniqueIds,
+                                           final EntityIdStore entityIdCollection,
+                                           final CollectionMap<PointedTimeSpan, Entity> timeSpanEntityMap,
+                                           final CollectionMap<Integer, Relation> uniqueIdTlinkMap ) {
+      final Collection<Relation> docRelTlinks = new ArrayList<>();
+      for ( Integer uniqueId : docRelUniqueIds ) {
+         final Collection<Attribute> tlinkTypes = createDocTimeRelTlinkType( docTimex, uniqueId, entityIdCollection );
+         if ( tlinkTypes.isEmpty() ) {
+            continue;
+         }
+         final Collection<Entity> entities = entityIdCollection.getAllEntities( uniqueId );
+         int i = 0;
+         PointedTimeSpan timeSpanPlus = TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS;
+         for ( Entity entity : entities ) {
+            docRelTlinks.clear();
+            if ( i == 0 ) {
+               for ( Attribute tlinkType : tlinkTypes ) {
+                  final Relation tlink = createDocTimeRelTlink( docTimex, entity, tlinkType );
+                  uniqueIdTlinkMap.place( uniqueId, tlink );
+                  docRelTlinks.add( tlink );
+               }
+               timeSpanPlus = TimeSpanFactory.getUniqueIdTimeSpan( docTimex, docRelTlinks );
+            }
+            timeSpanEntityMap.place( timeSpanPlus, entity );
+            i++;
+         }
+      }
+   }
+
+
+   static private Collection<Attribute> createDocTimeRelTlinkType( final Entity docTimex, final Integer uniqueId,
+                                                                   final EntityIdStore entityIdCollection ) {
+      if ( docTimex == null ) {
+         return Collections.emptySet();
+      }
+      final Collection<Entity> entities = entityIdCollection.getAllEntities( uniqueId );
+      final Collection<DocTimeRel> docTimeRels = EnumSet.noneOf( DocTimeRel.class );
+      final int docTimeRelsSize = DocTimeRel.values().length;
+      for ( Entity entity : entities ) {
+         docTimeRels.addAll( getDocRelTimes( entity ) );
+         if ( docTimeRels.size() == docTimeRelsSize ) {
+            // Have all possible docTimeRel represented
+            break;
+         }
+      }
+      if ( docTimeRels.isEmpty() ) {
+         return Collections.emptySet();
+      }
+      if ( docTimeRels.contains( DocTimeRel.BEFORE ) && docTimeRels.contains( DocTimeRel.AFTER ) ) {
+         docTimeRels.remove( DocTimeRel.AFTER );
+         docTimeRels.add( DocTimeRel.OVERLAP );
+      }
+      final Collection<Attribute> tlinkTypes = new HashSet<>( docTimeRels.size() );
+      for ( DocTimeRel docTimeRel : docTimeRels ) {
+         tlinkTypes.add( new DefaultAttribute( DefinedAttributeType.RELATION_TYPE, docTimeRel.toString() ) );
+      }
+      return tlinkTypes;
+   }
+
+
+   static private Relation createDocTimeRelTlink( final Entity docTimex, final Entity entity,
+                                                  final Attribute tlinkType ) {
+      return new DefaultRelation( entity, docTimex, TemporalClassType.TLINK, tlinkType, DOC_TIME_REL_CREATOR );
+   }
+
+   static private Relation createClosureTlink( final Entity firstEntity, final Entity secondEntity,
+                                               final Attribute tlinkType ) {
+      return new DefaultRelation( firstEntity, secondEntity, TemporalClassType.TLINK, tlinkType, CLOSURE_CREATOR );
+   }
+
+
+   static private Collection<DocTimeRel> getDocRelTimes( final Annotation entity ) {
+      if ( entity instanceof CoreferenceChain ) {
+         final Collection<DocTimeRel> docTimeRels = EnumSet.noneOf( DocTimeRel.class );
+         for ( Annotation element : (CoreferenceChain)entity ) {
+            docTimeRels.addAll( getDocRelTimes( element ) );
+         }
+         return docTimeRels;
+      }
+      final DocTimeRel docTimeRel = DocTimeRel.getDocTimeRel( entity );
+      if ( docTimeRel == null ) {
+         return Collections.emptySet();
+      }
+      if ( docTimeRel == DocTimeRel.BEFORE_OVERLAP ) {
+         return Arrays.asList( DocTimeRel.BEFORE, DocTimeRel.OVERLAP );
+      }
+      return Collections.singleton( docTimeRel );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/AbstractTimeSpan.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/AbstractTimeSpan.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/AbstractTimeSpan.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/AbstractTimeSpan.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,228 @@
+package org.chboston.cnlp.timeline.timespan;
+
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/31/12
+ */
+abstract public class AbstractTimeSpan implements TimeSpan {
+
+   //   static private final DateFormat LONG_DATE_FORMAT = DateFormat.getDateInstance( DateFormat.LONG );
+   static private final DateFormat YEAR_DATE_FORMAT = new SimpleDateFormat( "yyyy" );
+   static private final DateFormat SHORT_MONTH_DATE_FORMAT = new SimpleDateFormat( "M/yyyy" );
+   static private final DateFormat SHORT_DATE_FORMAT = new SimpleDateFormat( "M/d/yyyy" );
+   static private final DateFormat LONG_MONTH_DATE_FORMAT = new SimpleDateFormat( "MMMM, yyyy" );
+   static private final DateFormat LONG_DATE_FORMAT = new SimpleDateFormat( "EEEE, MMMM d, yyyy" );
+   static private final DateFormat SHORT_DATE_TIME_FORMAT = new SimpleDateFormat( "M/d/yyyy h:mm a" );
+   static private final DateFormat LONG_DATE_TIME_FORMAT = new SimpleDateFormat( "EEEE, MMMM d, yyyy h:mm a" );
+
+
+   private Collection<ChangeListener> _changeListeners;
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public Calendar getStartCalendar() {
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( getStartMillis() );
+      return calendar;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public Calendar getEndCalendar() {
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( getStopMillis() );
+      return calendar;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isSingleDate() {
+      return getStartMillis() == getStopMillis();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public long getWidthMillis() {
+      return getStopMillis() - getStartMillis();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public boolean containsMilli( final long value ) {
+      return getStartMillis() <= value && getStopMillis() >= value;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public boolean contains( final TimeSpan timeSpan ) {
+      return getStartMillis() <= timeSpan.getStartMillis() && timeSpan.getStopMillis() <= getStopMillis();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   final public boolean overlaps( final TimeSpan timeSpan ) {
+      return containsMilli( timeSpan.getStartMillis() ) || containsMilli( timeSpan.getStopMillis() )
+             || timeSpan.contains( this );
+   }
+
+   /**
+    * Adds a ChangeListener to the model's listener list.
+    *
+    * @param listener the ChangeListener to add
+    * @see #removeChangeListener
+    */
+   public void addChangeListener( final ChangeListener listener ) {
+      if ( _changeListeners == null ) {
+         _changeListeners = new ArrayList<>();
+      }
+      _changeListeners.add( listener );
+   }
+
+
+   /**
+    * Removes a ChangeListener from the model's listener list.
+    *
+    * @param listener the ChangeListener to remove
+    * @see #addChangeListener
+    */
+   public void removeChangeListener( final ChangeListener listener ) {
+      if ( _changeListeners != null ) {
+         _changeListeners.remove( listener );
+      }
+   }
+
+
+   /**
+    * Runs each <code>ChangeListener</code>'s <code>stateChanged</code> method.
+    */
+   protected void fireStateChanged() {
+      if ( _changeListeners == null ) {
+         return;
+      }
+      final ChangeEvent event = new ChangeEvent( this );
+      for ( ChangeListener listener : _changeListeners ) {
+         listener.stateChanged( event );
+      }
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String toString() {
+      final String fuzzyText = isFuzzyDate() ? "(Roughly) " : "";
+      final Date beginDate = new Date( getStartMillis() );
+      if ( isSingleDate() ) {
+         if ( !CalendarUtil.isMidnight( getStartCalendar() ) ) {
+            return fuzzyText + LONG_DATE_TIME_FORMAT.format( beginDate );
+         }
+         return fuzzyText + LONG_DATE_FORMAT.format( beginDate );
+      }
+      if ( CalendarUtil.isFullYear( getStartCalendar(), getEndCalendar() ) ) {
+         return fuzzyText + YEAR_DATE_FORMAT.format( beginDate );
+      } else if ( CalendarUtil.isMultipleYears( getStartCalendar(), getEndCalendar() ) ) {
+         final Date endDate = new Date( getStopMillis() );
+         return fuzzyText + YEAR_DATE_FORMAT.format( beginDate ) + " through " + YEAR_DATE_FORMAT.format( endDate );
+      }
+      if ( CalendarUtil.isFullMonth( getStartCalendar(), getEndCalendar() ) ) {
+         return fuzzyText + LONG_MONTH_DATE_FORMAT.format( beginDate );
+      } else if ( CalendarUtil.isMultipleMonths( getStartCalendar(), getEndCalendar() ) ) {
+         final Date endDate = new Date( getStopMillis() );
+         return fuzzyText + LONG_MONTH_DATE_FORMAT.format( beginDate ) + " through " +
+                LONG_MONTH_DATE_FORMAT.format( endDate );
+      }
+      String beginText = LONG_DATE_FORMAT.format( beginDate );
+      if ( CalendarUtil.isFullDay( getStartCalendar(), getEndCalendar() ) ) {
+         return fuzzyText + beginText;
+//      } else if ( CalendarUtil.isMultipleDays( getStartCalendar(), getEndCalendar() ) ) {
+//         final Date endDate = new Date( getStopMillis() );
+//         return fuzzyText + beginText + " to " + LONG_DATE_FORMAT.format( endDate );
+      }
+      if ( !CalendarUtil.isMidnight( getStartCalendar() ) ) {
+         beginText = LONG_DATE_TIME_FORMAT.format( beginDate );
+      }
+      final Date endDate = new Date( getStopMillis() );
+      String endText = LONG_DATE_FORMAT.format( endDate );
+      if ( !CalendarUtil.isMidnight( getEndCalendar() ) ) {
+         beginText = LONG_DATE_TIME_FORMAT.format( beginDate );
+         endText = LONG_DATE_TIME_FORMAT.format( endDate );
+      }
+      return fuzzyText + beginText + " through " + endText;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int hashCode() {
+      long bits = isFuzzyDate() ? 1 : 0;
+      bits += getStartMillis() * 31;
+      if ( !isSingleDate() ) {
+         bits += getStopMillis() * 43;
+      }
+      return (((int)bits) ^ ((int)(bits >> 32)));
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean equals( final Object object ) {
+      return object instanceof TimeSpan
+             && ((TimeSpan)object).isFuzzyDate() == isFuzzyDate()
+             && ((TimeSpan)object).getStartMillis() == getStartMillis()
+             && ((TimeSpan)object).getStopMillis() == getStopMillis();
+   }
+
+
+   //   static public Span getUnion( final Collection<Span> sketchableDateSpans ) {
+   //      return getUnion( sketchableDateSpans.toArray( new Span[sketchableDateSpans.size()] ) );
+   //   }
+   //
+   //   static public Span getUnion( final Span... sketchableDateSpans ) {
+   //      if ( sketchableDateSpans.length == 0 ) {
+   //         return new Span( Calendar.getInstance(), Calendar.getInstance() );
+   //      }
+   //      long minMillis = Long.MAX_VALUE;
+   //      long maxMillis = Long.MIN_VALUE;
+   //      for ( Span textspan : sketchableDateSpans ) {
+   //         final long beginMillis = textspan.getBeginTime().getTimeInMillis();
+   //         if ( beginMillis < minMillis ) {
+   //            minMillis = beginMillis;
+   //         }
+   //         final long endMillis = textspan.getEndTime().getTimeInMillis();
+   //         if ( endMillis > maxMillis ) {
+   //            maxMillis = endMillis;
+   //         }
+   //      }
+   //      return new Span( minMillis, maxMillis );
+   //   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/CalendarUtil.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/CalendarUtil.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/CalendarUtil.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/CalendarUtil.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,143 @@
+package org.chboston.cnlp.timeline.timespan;
+
+import java.util.Calendar;
+
+import static java.util.Calendar.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/13/12
+ */
+final public class CalendarUtil {
+
+   // TODO duplicate in JTimelineCalendar, combine
+   static private final long HOUR_MILLIS = 60 * 60 * 1000;
+   static private final long DAY_MILLIS = 24 * HOUR_MILLIS;
+   static private final long WEEK_MILLIS = 7 * DAY_MILLIS;
+   static private final long MONTH_MILLIS = (long)(30.4375 * DAY_MILLIS);
+   static private final long YEAR_MILLIS = (long)(365.25 * DAY_MILLIS);
+   static private final long DECADE_MILLIS = 10 * YEAR_MILLIS;
+
+   static public enum TIME_MILLIS {
+      HOUR( HOUR_MILLIS ), DAY( DAY_MILLIS ), WEEK( WEEK_MILLIS ), MONTH( MONTH_MILLIS ),
+      YEAR( YEAR_MILLIS ), DECADE( DECADE_MILLIS );
+      final public long __millis;
+
+      private TIME_MILLIS( final long millis ) {
+         __millis = millis;
+      }
+   }
+
+   private CalendarUtil() {
+   }
+
+   static public boolean isMidnight( final Calendar calendar ) {
+      return (calendar.get( HOUR_OF_DAY ) == 23 && calendar.get( MINUTE ) == 59 && calendar.get( SECOND ) >= 59) ||
+             (calendar.get( HOUR_OF_DAY ) == 0 && calendar.get( MINUTE ) == 0 && calendar.get( SECOND ) <= 1);
+   }
+
+   static public boolean isNoon( final Calendar calendar ) {
+      return calendar.get( HOUR_OF_DAY ) == 11 && calendar.get( MINUTE ) == 0 && calendar.get( SECOND ) <= 1;
+   }
+
+   static public boolean shouldBeDay( final Calendar calendar ) {
+      return isMidnight( calendar );
+   }
+
+   static private boolean checkEqual( final Calendar calendar, final Calendar endCalendar ) {
+      return calendar.getTimeInMillis() >= endCalendar.getTimeInMillis() - 1
+             && calendar.getTimeInMillis() <= endCalendar.getTimeInMillis() + 1;
+//      if ( calendar.compareTo( endCalendar ) == 0 ) {
+//         return true;
+//      }
+//      calendar.add( SECOND, 1 );
+//      if ( calendar.compareTo( endCalendar ) == 0 ) {
+//         return true;
+//      }
+//      calendar.add( SECOND, 1 );
+//      return calendar.compareTo( endCalendar ) == 0;
+   }
+
+   static public boolean isFullDay( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar ) ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      calendar.add( DAY_OF_YEAR, 1 );
+      return checkEqual( calendar, endCalendar );
+   }
+
+   static public boolean isMultipleDays( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar ) ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      while ( calendar.compareTo( endCalendar ) < 0 ) {
+         calendar.add( DAY_OF_YEAR, 1 );
+         if ( checkEqual( calendar, endCalendar ) ) {
+            return true;
+         }
+      }
+      return false;
+   }
+
+   static public boolean isFullMonth( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar )
+           || startCalendar.get( DAY_OF_MONTH ) != 1 ) {//|| endCalendar.get( DAY_OF_MONTH ) != 1 ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      calendar.add( MONTH, 1 );
+      return checkEqual( calendar, endCalendar );
+   }
+
+   static public boolean isMultipleMonths( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar )
+           || startCalendar.get( DAY_OF_MONTH ) != 1 ) {//|| endCalendar.get( DAY_OF_MONTH ) != 1 ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      while ( calendar.compareTo( endCalendar ) < 0 ) {
+         calendar.add( MONTH, 1 );
+         if ( checkEqual( calendar, endCalendar ) ) {
+            return true;
+         }
+      }
+      return false;
+   }
+
+
+   static public boolean isFullYear( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar )
+           || startCalendar.get( DAY_OF_YEAR ) != 1 ) {//|| endCalendar.get( DAY_OF_YEAR ) != 1 ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      calendar.add( YEAR, 1 );
+      return checkEqual( calendar, endCalendar );
+   }
+
+   static public boolean isMultipleYears( final Calendar startCalendar, final Calendar endCalendar ) {
+      if ( !isMidnight( startCalendar ) || !isMidnight( endCalendar )
+           || startCalendar.get( DAY_OF_YEAR ) != 1 ) {//|| endCalendar.get( DAY_OF_YEAR ) != 1 ) {
+         return false;
+      }
+      final Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis( startCalendar.getTimeInMillis() );
+      while ( calendar.compareTo( endCalendar ) < 0 ) {
+         calendar.add( YEAR, 1 );
+         if ( checkEqual( calendar, endCalendar ) ) {
+            return true;
+         }
+      }
+      return false;
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/DefaultTimeSpan.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/DefaultTimeSpan.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/DefaultTimeSpan.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/DefaultTimeSpan.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,94 @@
+package org.chboston.cnlp.timeline.timespan;
+
+import com.mdimension.jchronic.utils.Span;
+import net.jcip.annotations.Immutable;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/30/12
+ */
+@Immutable
+public class DefaultTimeSpan extends AbstractTimeSpan {
+
+   final private long _startMillis;
+   final private long _stopMillis;
+   final private boolean _isFuzzyDate;
+
+   public DefaultTimeSpan( final Span chronicSpan ) {
+      this( chronicSpan, false );
+   }
+
+   public DefaultTimeSpan( final Span chronicSpan, boolean isFuzzyDate ) {
+      this( chronicSpan.getBegin() * 1000, chronicSpan.getEnd() * 1000 - 1, isFuzzyDate );
+   }
+
+   public DefaultTimeSpan( final Calendar startDate, final Calendar stopDate ) {
+      this( startDate, stopDate, false );
+   }
+
+   public DefaultTimeSpan( final Calendar startDate, final Calendar stopDate, final boolean isFuzzyDate ) {
+      this( startDate.getTimeInMillis(), stopDate.getTimeInMillis(), isFuzzyDate );
+   }
+
+   public DefaultTimeSpan( final long startMillis, final long stopMillis ) {
+      this( startMillis, stopMillis, false );
+   }
+
+   public DefaultTimeSpan( final TimeSpan timeSpan ) {
+      this( timeSpan.getStartMillis(), timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+   }
+
+   public DefaultTimeSpan( final long startMillis, final long stopMillis, final boolean isFuzzyDate ) {
+      _startMillis = Math.min( startMillis, stopMillis );
+      _stopMillis = Math.max( startMillis, stopMillis );
+      _isFuzzyDate = isFuzzyDate;
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public long getStartMillis() {
+      return _startMillis;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public long getStopMillis() {
+      return _stopMillis;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean isFuzzyDate() {
+      return _isFuzzyDate;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String toString() {
+      return DateFormat.getDateTimeInstance().format( _startMillis )
+             + " .. " + DateFormat.getDateTimeInstance().format( _stopMillis );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean equals( final Object object ) {
+      return object instanceof DefaultTimeSpan && super.equals( object );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/EndPointer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/EndPointer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/EndPointer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/EndPointer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,45 @@
+package org.chboston.cnlp.timeline.timespan;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/27/13
+ */
+public enum EndPointer {
+   BEFORE( "before", -1, -1 ), AFTER( "after", -1, 1 ), OVERLAP( "overlapping", 1, 0 ), EQUAL( "equal to", 4, 0 );
+   final private String __name;
+   final private int __weight;
+   final private int __order;
+
+   EndPointer( final String name, final int weight, final int order ) {
+      __name = name;
+      __weight = weight;
+      __order = order;
+   }
+
+   public String getName() {
+      return __name;
+   }
+
+   public int getWeight( final boolean pointerFromLeft ) {
+      // EQUAL is always more exact than any other, therefore most preferred.
+      // If the pointer applies to the left, then AFTER is more exact than before
+      // OVERLAP is the least exact pointer
+      if ( this == AFTER ) {
+         if ( pointerFromLeft ) {
+            return 3;
+         }
+         return 2;
+      } else if ( this == BEFORE ) {
+         if ( !pointerFromLeft ) {
+            return 3;
+         }
+         return 2;
+      }
+      return __weight;
+   }
+
+   public int getOrder() {
+      return __order;
+   }
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpan.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpan.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpan.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpan.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,51 @@
+package org.chboston.cnlp.timeline.timespan;
+
+import javax.swing.event.ChangeListener;
+import java.util.Calendar;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/31/12
+ */
+public interface TimeSpan {
+
+   Calendar getStartCalendar();
+
+   Calendar getEndCalendar();
+
+   long getStartMillis();
+
+   long getStopMillis();
+
+   boolean isFuzzyDate();
+
+   boolean isSingleDate();
+
+   long getWidthMillis();
+
+   boolean containsMilli( final long value );
+
+   boolean contains( TimeSpan timeSpan );
+
+   boolean overlaps( TimeSpan timeSpan );
+
+   // TODO get rid of the listeners, events.  There should be no changes.  Move to MutableTimeSpan
+
+   /**
+    * Adds a ChangeListener to the model's listener list.
+    *
+    * @param listener the ChangeListener to add
+    * @see #removeChangeListener
+    */
+   void addChangeListener( final ChangeListener listener );
+
+   /**
+    * Removes a ChangeListener from the model's listener list.
+    *
+    * @param listener the ChangeListener to remove
+    * @see #addChangeListener
+    */
+   void removeChangeListener( final ChangeListener listener );
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpanComparator.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpanComparator.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpanComparator.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/TimeSpanComparator.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,40 @@
+package org.chboston.cnlp.timeline.timespan;
+
+import java.util.Comparator;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/11/13
+ */
+public enum TimeSpanComparator implements Comparator<TimeSpan> {
+   INSTANCE;
+
+   static public TimeSpanComparator getInstance() {
+      return INSTANCE;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int compare( final TimeSpan timeSpan1, final TimeSpan timeSpan2 ) {
+      final long start1 = timeSpan1.getStartMillis();
+      final long start2 = timeSpan2.getStartMillis();
+      if ( start1 < start2 ) {
+         return -1;
+      } else if ( start2 < start1 ) {
+         return 1;
+      }
+      final long stop1 = timeSpan1.getStopMillis();
+      final long stop2 = timeSpan2.getStopMillis();
+      if ( stop1 < stop2 ) {
+         return -1;
+      } else if ( stop2 < stop1 ) {
+         return 1;
+      }
+      return 0;
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeSpan.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeSpan.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeSpan.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeSpan.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,20 @@
+package org.chboston.cnlp.timeline.timespan.plus;
+
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 9/24/2014
+ */
+public interface PointedTimeSpan extends TimeSpan {
+
+   public TimeEndPoint getStartTime();
+
+   public TimeEndPoint getStopTime();
+
+   public String getRelationText();
+
+   public String getSpanText();
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeline.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeline.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeline.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/PointedTimeline.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,175 @@
+//package org.chboston.cnlp.timeline.timespan.plus;
+//
+//import org.chboston.cnlp.nlp.annotation.classtype.ClassTypeUtil;
+//import org.chboston.cnlp.nlp.annotation.entity.Entity;
+//import org.chboston.cnlp.nlp.annotation.relation.Relation;
+//import org.chboston.cnlp.nlp.annotation.timex.Timex;
+//import org.chboston.cnlp.timeline.timeline.Timeline;
+//import org.chboston.cnlp.timeline.timespan.AbstractTimeSpan;
+//
+//import java.util.*;
+//import java.util.logging.Logger;
+//
+//import static org.chboston.cnlp.timeline.timespan.EndPointer.AFTER;
+//import static org.chboston.cnlp.timeline.timespan.EndPointer.BEFORE;
+//import static org.chboston.cnlp.timeline.timespan.EndPointer.EQUAL;
+//
+//import static org.chboston.cnlp.timeline.timespan.plus.TimeSpanFactory.EndPoint;
+//
+///**
+// * @author SPF , chip-nlp
+// * @version %I%
+// * @since 12/10/2014
+// */
+//final public class PointedTimeline extends AbstractTimeSpan implements PointedTimeSpan, Timeline {
+//
+//   static private final Logger LOGGER = Logger.getLogger( "PointedTimeline" );
+//
+//   final private String _title;
+//   final private TimeSpanPlus _summaryTimeSpanPlus;
+//   final private Timeline _internalTimeline;
+//
+//   public PointedTimeline( final String title, Map<EndPoint, Collection<TimeEndPoint>> entityTimes ) {
+//      _title = title;
+//
+//      final Collection<TimeEndPoint> startRemovals = new HashSet<>();
+//      final Collection<TimeEndPoint> stopRemovals = new HashSet<>();
+//      startRemovals.clear();
+//      stopRemovals.clear();
+//      for ( TimeEndPoint startTime : entityTimes.get( TimeSpanFactory.EndPoint.START ) ) {
+//         if ( startTime.getPointer() != AFTER && startTime.getPointer() != EQUAL ) {
+//            continue;
+//         }
+//         for ( TimeEndPoint stopTime : entityTimes.get( EndPoint.STOP ) ) {
+//            if ( startTime.getMillis() >= stopTime.getMillis()
+//                 && (stopTime.getPointer() == BEFORE || stopTime.getPointer() == EQUAL ) ) {
+//               startRemovals.add( startTime );
+//               break;
+//            }
+//         }
+//      }
+//      for ( TimeEndPoint stopTime : entityTimes.get( EndPoint.STOP ) ) {
+//         if ( stopTime.getPointer() != BEFORE && stopTime.getPointer() != EQUAL ) {
+//            continue;
+//         }
+//         for ( TimeEndPoint startTime : entityTimes.get( EndPoint.START ) ) {
+//            if ( stopTime.getMillis() <= startTime.getMillis()
+//                 && (startTime.getPointer() == AFTER || startTime.getPointer() == EQUAL ) ) {
+//               stopRemovals.add( stopTime );
+//               break;
+//            }
+//         }
+//         entityTimes.get( EndPoint.START ).removeAll( startRemovals );
+//         entityTimes.get( EndPoint.STOP ).removeAll( stopRemovals );
+//      }
+//
+//   }
+//
+//   public TimeEndPoint getStartTime() {
+//      return _summaryTimeSpanPlus.getStartTime();
+//   }
+//
+//   public TimeEndPoint getStopTime() {
+//      return _summaryTimeSpanPlus.getStopTime();
+//   }
+//
+//   public String getRelationText() {
+//      return _summaryTimeSpanPlus.getRelationText();
+//   }
+//
+//   public String getSpanText() {
+//      return _summaryTimeSpanPlus.getSpanText();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public String getTitle() {
+//      return _title;
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public long getReferenceMillis() {
+//      return _internalTimeline.getReferenceMillis();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public List<PointedTimeSpan> getTimeSpans() {
+//      return _internalTimeline.getTimeSpans();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public Collection<Relation> getEntityEntityRelations( final Entity entity ) {
+//      return _internalTimeline.getEntityEntityRelations( entity );
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public Collection<Entity> getEntities( final PointedTimeSpan timeSpan ) {
+//      return _internalTimeline.getEntities( timeSpan );
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public Collection<Entity> getEntities() {
+//      return _internalTimeline.getEntities();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public Collection<Timex> getTimes() {
+//      return _internalTimeline.getTimes();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public long getStartMillis() {
+//      return _internalTimeline.getStartMillis();
+//   }
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public long getStopMillis() {
+//      return _internalTimeline.getStopMillis();
+//   }
+//
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public boolean isFuzzyDate() {
+//      return _internalTimeline.isFuzzyDate();
+//   }
+//
+//
+//   /**
+//    * {@inheritDoc}
+//    */
+//   @Override
+//   public Iterator<PointedTimeSpan> iterator() {
+//      return _internalTimeline.iterator();
+//   }
+//
+//
+//}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeEndPoint.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeEndPoint.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeEndPoint.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeEndPoint.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,79 @@
+package org.chboston.cnlp.timeline.timespan.plus;
+
+import net.jcip.annotations.Immutable;
+import org.chboston.cnlp.timeline.timespan.EndPointer;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/30/13
+ */
+@Immutable
+final public class TimeEndPoint {
+
+   static public final TimeEndPoint NULL_END_POINT = new TimeEndPoint( EndPointer.EQUAL, Long.MAX_VALUE, false );
+
+
+   private final EndPointer _pointer;
+   private final long _millis;
+   private final boolean _fuzzy;
+   private final int _hashcode;
+
+   public TimeEndPoint( final EndPointer pointer, final long millis, final boolean fuzzy ) {
+      _pointer = pointer;
+      _millis = millis;
+      _fuzzy = fuzzy;
+      long bits = _fuzzy ? 1 : 0;
+      bits += 3 * _pointer.hashCode();
+      bits += 5 * _millis;
+      _hashcode = (int)bits;
+   }
+
+   public EndPointer getPointer() {
+      return _pointer;
+   }
+
+   public long getMillis() {
+      return _millis;
+   }
+
+   public boolean isFuzzy() {
+      return _fuzzy;
+   }
+
+
+   static private final DateFormat LONG_DATE_FORMAT = new SimpleDateFormat( "EEEE, MMMM d, yyyy" );
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String toString() {
+      final Date endDate = new Date( _millis );
+      return (_fuzzy ? "roughly " : "") + _pointer.getName() + " " + LONG_DATE_FORMAT.format( endDate );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int hashCode() {
+      return _hashcode;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean equals( final Object object ) {
+      return object instanceof TimeEndPoint
+             && ((TimeEndPoint)object)._millis == _millis
+             && ((TimeEndPoint)object)._pointer == _pointer
+             && ((TimeEndPoint)object)._fuzzy == _fuzzy;
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanFactory.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanFactory.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanFactory.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanFactory.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,633 @@
+package org.chboston.cnlp.timeline.timespan.plus;
+
+import com.mdimension.jchronic.Options;
+import com.mdimension.jchronic.tags.Pointer;
+import com.mdimension.jchronic.utils.Span;
+import org.chboston.cnlp.nlp.annotation.annotation.Annotation;
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+import org.chboston.cnlp.nlp.annotation.attribute.TlinkAttributeValue;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.EntityIdStore;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.chronic.parser.AtParser;
+import org.chboston.cnlp.timeline.chronic.parser.StringDateParser;
+import org.chboston.cnlp.timeline.timespan.DefaultTimeSpan;
+import org.chboston.cnlp.timeline.timespan.EndPointer;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+
+import java.util.*;
+import java.util.logging.Logger;
+
+import static org.chboston.cnlp.timeline.timespan.EndPointer.*;
+import static org.chboston.cnlp.timeline.timespan.plus.TimeEndPoint.NULL_END_POINT;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/30/13
+ */
+final public class TimeSpanFactory {
+
+   static private final boolean DEBUG = false;
+
+   static private final Logger LOGGER = Logger.getLogger( "TimeSpanFactory" );
+
+   private TimeSpanFactory() {
+   }
+
+   static public enum EndPoint {
+      START, STOP
+   }
+
+
+   // ENTRY POINT
+   static public PointedTimeSpan getUniqueIdTimeSpan( final Entity docTimex, final Iterable<Relation> tlinks ) {
+      final Map<Entity, TimeSpan> timexTimeSpanMap
+            = getTimexTimeSpanMap( docTimex, Collections.singletonList( docTimex ) );
+      return getUniqueIdTimeSpan( timexTimeSpanMap, tlinks );
+   }
+
+   static private PointedTimeSpan getUniqueIdTimeSpan( final Map<Entity, TimeSpan> timexTimeSpanMap,
+                                                       final Iterable<Relation> tlinks ) {
+      final Map<EndPoint, Collection<TimeEndPoint>> endPointMap
+            = getTimeEndPoints2( timexTimeSpanMap, tlinks );
+      final Collection<TimeEndPoint> startTimes = endPointMap.get( EndPoint.START );
+      final Collection<TimeEndPoint> stopTimes = endPointMap.get( EndPoint.STOP );
+      if ( startTimes.isEmpty() || stopTimes.isEmpty() ) {
+         return TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS;
+      }
+      boolean haveStartBefore = false;
+      for ( TimeEndPoint startTime : startTimes ) {
+         if ( startTime.getPointer() == BEFORE ) {
+            haveStartBefore = true;
+            break;
+         }
+      }
+      boolean haveStopAfter = false;
+      for ( TimeEndPoint stopTime : stopTimes ) {
+         if ( stopTime.getPointer() == AFTER ) {
+            haveStopAfter = true;
+            break;
+         }
+      }
+      TimeEndPoint bestStart = NULL_END_POINT;
+      TimeEndPoint bestStop = NULL_END_POINT;
+      while ( bestStart == NULL_END_POINT || bestStop == NULL_END_POINT ) {
+         bestStart = getBestStartTime( startTimes, haveStopAfter );
+         bestStop = getBestStopTime( stopTimes, haveStartBefore );
+      }
+      if ( bestStart == NULL_END_POINT || bestStop == NULL_END_POINT ) {
+         LOGGER.warning( "Could not optimize best span" );
+      }
+      return new TimeSpanPlus( bestStart, bestStop );
+   }
+
+
+   // ENTRY POINT
+   static public <T extends Entity> Map<Integer, PointedTimeSpan> getUniqueIdTimeSpans(
+         final Map<Entity, TimeSpan> timexTimeSpanMap,
+         final Iterable<Relation> tlinks,
+         final EntityIdStore entityIdCollection ) {
+      final Map<Integer, Map<EndPoint, Collection<TimeEndPoint>>> endPointMap
+            = getTimeEndPoints( timexTimeSpanMap, tlinks, entityIdCollection );
+
+      final Map<Integer, PointedTimeSpan> uniqueIdTimeSpanMap = new HashMap<>( endPointMap.size() );
+      for ( Map.Entry<Integer, Map<EndPoint, Collection<TimeEndPoint>>> uniqueIdEntry : endPointMap.entrySet() ) {
+         final Iterable<TimeEndPoint> startTimes = uniqueIdEntry.getValue().get( EndPoint.START );
+         final Iterable<TimeEndPoint> stopTimes = uniqueIdEntry.getValue().get( EndPoint.STOP );
+         boolean haveStartBefore = false;
+         for ( TimeEndPoint startTime : startTimes ) {
+            if ( startTime.getPointer() == BEFORE ) {
+               haveStartBefore = true;
+               break;
+            }
+         }
+         boolean haveStopAfter = false;
+         for ( TimeEndPoint stopTime : stopTimes ) {
+            if ( stopTime.getPointer() == AFTER ) {
+               haveStopAfter = true;
+               break;
+            }
+         }
+         TimeEndPoint bestStart = NULL_END_POINT;
+         TimeEndPoint bestStop = NULL_END_POINT;
+         while ( bestStart == NULL_END_POINT || bestStop == NULL_END_POINT ) {
+            bestStart = getBestStartTime( startTimes, haveStopAfter );
+            bestStop = getBestStopTime( stopTimes, haveStartBefore );
+         }
+         if ( bestStart == NULL_END_POINT || bestStop == NULL_END_POINT ) {
+            LOGGER.warning( "Could not optimize best span" );
+            continue;
+         }
+         final PointedTimeSpan timeSpanPlus = new TimeSpanPlus( bestStart, bestStop );
+         uniqueIdTimeSpanMap.put( uniqueIdEntry.getKey(), timeSpanPlus );
+
+//         if ( DEBUG ) {
+//            LOGGER.info( "" );
+//            for ( Relation tlink : tlinks ) {
+//               if ( !isSourceClosure( tlink )
+//                    && (tlink.getFirstEntity().equals( entityEntry.getKey() )
+//                        || tlink.getSecondEntity().equals( entityEntry.getKey() ) ) ) {
+//                  LOGGER.info( "Source   " + tlink.getFirstEntity().getSpannedTextRepresentation()
+//                               + " " + AttributeUtil.getTlinkType( tlink ).getName()
+//                               + " " + tlink.getSecondEntity().getSpannedTextRepresentation()
+//                               + " : " + tlink.getFirstEntity().getTextSpan().getStartIndex()
+//                               + "-" + tlink.getFirstEntity().getTextSpan().getEndIndex()
+//                               + " " + tlink.getSecondEntity().getTextSpan().getStartIndex()
+//                               + "-" + tlink.getSecondEntity().getTextSpan().getEndIndex() );
+//               }
+//            }
+//            for ( Relation tlink : tlinks ) {
+//               if ( isSourceClosure( tlink )
+//                    && tlink.getFirstEntity().equals( entityEntry.getKey() )
+//                    && tlink.getSecondEntity() instanceof Timex ) {
+//                  LOGGER.info( "Closed   " + tlink.getFirstEntity().getSpannedTextRepresentation()
+//                               + " " + AttributeUtil.getTlinkType( tlink ).getName()
+//                               + " " + tlink.getSecondEntity().getSpannedTextRepresentation()
+//                               + " : " + tlink.getFirstEntity().getTextSpan().getStartIndex()
+//                               + "-" + tlink.getFirstEntity().getTextSpan().getEndIndex()
+//                               + " " + tlink.getSecondEntity().getTextSpan().getStartIndex()
+//                               + "-" + tlink.getSecondEntity().getTextSpan().getEndIndex() );
+//               }
+//            }
+//            LOGGER.info( "Result   " + entityEntry.getKey().getSpannedTextRepresentation() + " " + timeSpanPlus );
+//         }
+      }
+      return uniqueIdTimeSpanMap;
+   }
+
+
+   static private Map<Integer, Map<EndPoint, Collection<TimeEndPoint>>> getTimeEndPoints(
+         final Map<Entity, TimeSpan> timexTimeSpanMap,
+         final Iterable<Relation> tlinks,
+         final EntityIdStore entityIdCollection ) {
+      final Map<Integer, Map<EndPoint, Collection<TimeEndPoint>>> allUniqueIdTimes = new HashMap<>();
+      for ( Relation tlink : tlinks ) {
+         // Use Event as Related to Timex --> makes the eventual display of events wrt timex (rash before 1972) easier
+         final Entity entity1 = tlink.getFirstEntity();
+         if ( entity1.isClassType( TemporalClassType.TIMEX ) || entity1.isClassType( TemporalClassType.DOCTIME ) ) {
+//            if ( DEBUG ) {
+//               boolean haveOtherOption = false;
+//               for ( Relation tlink2 : tlinks ) {
+//                  if ( tlink2.getFirstEntity().equals( tlink.getSecondEntity() ) &&
+//                       tlink2.getSecondEntity() instanceof Timex ) {
+//                     haveOtherOption = true;
+//                     break;
+//                  }
+//               }
+//               if ( !haveOtherOption ) {
+//                  LOGGER.warning( "No other option for " + tlink.toString() );
+//               }
+//            }
+            // Don't want Timex as related to Event
+            continue;
+         }
+         final Entity entity2 = tlink.getSecondEntity();
+         if ( entity2.isClassType( TemporalClassType.TIMEX ) || entity1.isClassType( TemporalClassType.DOCTIME ) ) {
+            final TimeSpan timeSpan = timexTimeSpanMap.get( entity2 );
+            if ( timeSpan == null ) {
+               continue;
+            }
+            final Integer uniqueEntityId = entityIdCollection.getUniqueEntityId( entity1 );
+            Map<EndPoint, Collection<TimeEndPoint>> times = allUniqueIdTimes.get( uniqueEntityId );
+            if ( times == null ) {
+               times = new EnumMap<>( EndPoint.class );
+               times.put( EndPoint.START, new HashSet<TimeEndPoint>() );
+               times.put( EndPoint.STOP, new HashSet<TimeEndPoint>() );
+               allUniqueIdTimes.put( uniqueEntityId, times );
+            }
+            final TlinkAttributeValue tlinkType = AttributeUtil.getTlinkType( tlink );
+            final TimeEndPoint startTime = getStartTimeEndPoint( timeSpan, tlinkType );
+            final TimeEndPoint stopTime = getStopTimeEndPoint( timeSpan, tlinkType );
+            times.get( EndPoint.START ).add( startTime );
+            times.get( EndPoint.STOP ).add( stopTime );
+         }
+      }
+      // The following corrects for discontiguous time spans, such as might be found with coreferences
+      // TODO -- should really separate truly discontiguous spans [contains] [contains] into separate events
+      // TODO -- would be cooler to have a separate "Coreference Timeline" made from discontiguous time spans
+      // TODO -- cranky, this would require a separate "per coreference chain" iteration and store
+      // TODO -- could make a PointedTimeSpan that is basically a Timeline ...  Started PointedTimeline
+      final Collection<TimeEndPoint> startRemovals = new HashSet<>();
+      final Collection<TimeEndPoint> stopRemovals = new HashSet<>();
+      for ( Map<EndPoint, Collection<TimeEndPoint>> entityTimes : allUniqueIdTimes.values() ) {
+         startRemovals.clear();
+         stopRemovals.clear();
+         for ( TimeEndPoint startTime : entityTimes.get( EndPoint.START ) ) {
+            if ( startTime.getPointer() != AFTER && startTime.getPointer() != EQUAL ) {
+               continue;
+            }
+            for ( TimeEndPoint stopTime : entityTimes.get( EndPoint.STOP ) ) {
+               if ( startTime.getMillis() >= stopTime.getMillis()
+                    && (stopTime.getPointer() == BEFORE || stopTime.getPointer() == EQUAL) ) {
+                  startRemovals.add( startTime );
+                  break;
+               }
+            }
+         }
+         for ( TimeEndPoint stopTime : entityTimes.get( EndPoint.STOP ) ) {
+            if ( stopTime.getPointer() != BEFORE && stopTime.getPointer() != EQUAL ) {
+               continue;
+            }
+            for ( TimeEndPoint startTime : entityTimes.get( EndPoint.START ) ) {
+               if ( stopTime.getMillis() <= startTime.getMillis()
+                    && (startTime.getPointer() == AFTER || startTime.getPointer() == EQUAL) ) {
+                  stopRemovals.add( stopTime );
+                  break;
+               }
+            }
+         }
+         entityTimes.get( EndPoint.START ).removeAll( startRemovals );
+         entityTimes.get( EndPoint.STOP ).removeAll( stopRemovals );
+      }
+      return allUniqueIdTimes;
+   }
+
+
+   static private Map<EndPoint, Collection<TimeEndPoint>> getTimeEndPoints2(
+         final Map<Entity, TimeSpan> timexTimeSpanMap,
+         final Iterable<Relation> tlinks ) {
+      final Map<EndPoint, Collection<TimeEndPoint>> times = new EnumMap<>( EndPoint.class );
+      times.put( EndPoint.START, new HashSet<TimeEndPoint>() );
+      times.put( EndPoint.STOP, new HashSet<TimeEndPoint>() );
+      for ( Relation tlink : tlinks ) {
+         // Use Event as Related to Timex --> makes the eventual display of events wrt timex (rash before 1972) easier
+         final Entity entity1 = tlink.getFirstEntity();
+         if ( entity1.isClassType( TemporalClassType.TIMEX ) || entity1.isClassType( TemporalClassType.DOCTIME ) ) {
+            if ( DEBUG ) {
+               boolean haveOtherOption = false;
+               for ( Relation tlink2 : tlinks ) {
+                  if ( tlink2.getFirstEntity().equals( tlink.getSecondEntity() )
+                       && ( tlink2.getSecondEntity().isClassType( TemporalClassType.TIMEX )
+                            || entity1.isClassType( TemporalClassType.DOCTIME ) )) {
+                     haveOtherOption = true;
+                     break;
+                  }
+               }
+               if ( !haveOtherOption ) {
+                  LOGGER.warning( "No other option for " + tlink.toString() );
+               }
+            }
+            // Don't want Timex as related to Event
+            continue;
+         }
+         final Entity entity2 = tlink.getSecondEntity();
+         if ( entity2.isClassType( TemporalClassType.TIMEX ) || entity2.isClassType( TemporalClassType.DOCTIME ) ) {
+            final TimeSpan timeSpan = timexTimeSpanMap.get( entity2 );
+            if ( timeSpan == null ) {
+               continue;
+            }
+            final TlinkAttributeValue tlinkType = AttributeUtil.getTlinkType( tlink );
+            final TimeEndPoint startTime = getStartTimeEndPoint( timeSpan, tlinkType );
+            final TimeEndPoint stopTime = getStopTimeEndPoint( timeSpan, tlinkType );
+            times.get( EndPoint.START ).add( startTime );
+            times.get( EndPoint.STOP ).add( stopTime );
+         }
+      }
+      // The following corrects for discontiguous time spans, such as might be found with coreferences
+      final Collection<TimeEndPoint> startRemovals = new HashSet<>();
+      final Collection<TimeEndPoint> stopRemovals = new HashSet<>();
+      for ( TimeEndPoint startTime : times.get( EndPoint.START ) ) {
+         if ( startTime.getPointer() != AFTER && startTime.getPointer() != EQUAL ) {
+            continue;
+         }
+         for ( TimeEndPoint stopTime : times.get( EndPoint.STOP ) ) {
+            if ( startTime.getMillis() >= stopTime.getMillis()
+                 && (stopTime.getPointer() == BEFORE || stopTime.getPointer() == EQUAL) ) {
+               startRemovals.add( startTime );
+               break;
+            }
+         }
+      }
+      for ( TimeEndPoint stopTime : times.get( EndPoint.STOP ) ) {
+         if ( stopTime.getPointer() != BEFORE && stopTime.getPointer() != EQUAL ) {
+            continue;
+         }
+         for ( TimeEndPoint startTime : times.get( EndPoint.START ) ) {
+            if ( stopTime.getMillis() <= startTime.getMillis()
+                 && (startTime.getPointer() == AFTER || startTime.getPointer() == EQUAL) ) {
+               stopRemovals.add( stopTime );
+               break;
+            }
+         }
+      }
+      times.get( EndPoint.START ).removeAll( startRemovals );
+      times.get( EndPoint.STOP ).removeAll( stopRemovals );
+      return times;
+   }
+
+
+   static private TimeEndPoint getBetterStart( final TimeEndPoint time1, final TimeEndPoint time2,
+                                               final boolean haveStopAfter ) {
+      if ( time1.equals( TimeEndPoint.NULL_END_POINT ) ) {
+         return time2;
+      }
+      final EndPointer pointer1 = time1.getPointer();
+      final EndPointer pointer2 = time2.getPointer();
+      if ( pointer1 == pointer2 ) {
+         switch ( pointer1 ) {
+            case AFTER:
+               return getLater( time1, time2 );
+            case BEFORE:
+               return getEarlier( time1, time2 );
+            case EQUAL:
+               return getLater( time1, time2 );
+            case OVERLAP:
+               return getEarlier( time1, time2 );
+         }
+      }
+      // check for an equal pointer - trumps all other pointers
+      if ( pointer1 == EQUAL ) {
+         return time1;
+      } else if ( pointer2 == EQUAL ) {
+         return time2;
+      }
+      if ( time1.getMillis() == time2.getMillis() && pointer1 == OVERLAP
+           && (pointer2 == BEFORE || pointer2 == AFTER) ) {
+         // Before / Overlap and After / Overlap are more descriptive than Overlap
+         return time2;
+      }
+      if ( time1.getMillis() <= time2.getMillis() ) {
+         return getBetterEarlierStart( time1, time2, haveStopAfter );
+      }
+      return getBetterEarlierStart( time2, time1, haveStopAfter );
+   }
+
+   static private TimeEndPoint getBetterEarlierStart( final TimeEndPoint earlierTime, final TimeEndPoint laterTime,
+                                                      final boolean haveStopAfter ) {
+      final EndPointer earlierPointer = earlierTime.getPointer();
+      final EndPointer laterPointer = laterTime.getPointer();
+      switch ( earlierPointer ) {
+         // refinement >-
+         case AFTER:
+            return earlierTime;
+         case BEFORE: {
+            switch ( laterPointer ) {
+               // redefinition : <- & -> = ~~~
+               case AFTER:
+                  return new TimeEndPoint( OVERLAP, earlierTime.getMillis(), (earlierTime.isFuzzy() ||
+                                                                              laterTime.isFuzzy()) );
+               // conservative <- & ~~ = <-
+               case OVERLAP:
+                  return earlierTime;
+            }
+         }
+         case OVERLAP:
+            if ( haveStopAfter && laterPointer != BEFORE ) {
+               // refinement >-
+               // Some tlink of AFTER exists, so it should be safe to use the after
+               return laterTime;
+            }
+            // conservative ~~ & <-  || ~~ & -> = ~~~
+            return earlierTime;
+      }
+      // Left with AFTER where AFTER time is wrong ?
+      return getEarlier( earlierTime, laterTime );
+   }
+
+   static private TimeEndPoint getBetterStop( final TimeEndPoint time1, final TimeEndPoint time2, final boolean haveStartBefore ) {
+      if ( time1.equals( TimeEndPoint.NULL_END_POINT ) ) {
+         return time2;
+      }
+      final EndPointer pointer1 = time1.getPointer();
+      final EndPointer pointer2 = time2.getPointer();
+      if ( pointer1 == pointer2 ) {
+         switch ( pointer1 ) {
+            case AFTER:
+               return getLater( time1, time2 );
+            case BEFORE:
+               return getEarlier( time1, time2 );
+            case EQUAL:
+               return getEarlier( time1, time2 );
+            case OVERLAP:
+               return getLater( time1, time2 );
+         }
+      }
+      // check for an equal pointer - trumps all other pointers
+      if ( pointer1 == EQUAL ) {
+         return time1;
+      } else if ( pointer2 == EQUAL ) {
+         return time2;
+      }
+      if ( time1.getMillis() == time2.getMillis() && pointer1 == OVERLAP
+           && (pointer2 == BEFORE || pointer2 == AFTER) ) {
+         // Before / Overlap and After / Overlap are more descriptive than Overlap
+         return time2;
+      }
+      if ( time1.getMillis() >= time2.getMillis() ) {
+         return getBetterLaterStop( time1, time2, haveStartBefore );
+      }
+      return getBetterLaterStop( time2, time1, haveStartBefore );
+   }
+
+
+   static private TimeEndPoint getBetterLaterStop( final TimeEndPoint laterTime, final TimeEndPoint earlierTime,
+                                                   final boolean haveStartBefore ) {
+      final EndPointer earlierPointer = earlierTime.getPointer();
+      final EndPointer laterPointer = laterTime.getPointer();
+      switch ( laterPointer ) {
+         case AFTER: {
+            switch ( earlierPointer ) {
+               case BEFORE:
+                  return new TimeEndPoint( OVERLAP, laterTime.getMillis(),
+                        (laterTime.isFuzzy() || earlierTime.isFuzzy()) );
+               case OVERLAP:
+                  return laterTime;
+            }
+         }
+         case BEFORE:
+            return laterTime;
+         case OVERLAP:
+            if ( haveStartBefore && earlierPointer != AFTER  ) {
+               // Some tlink of BEFORE exists, so it should be safe to use the before
+               return earlierTime;
+            }
+            return laterTime;
+      }
+      // Left with AFTER where AFTER time is wrong ?
+      return getEarlier( laterTime, earlierTime );
+   }
+
+
+   static private TimeEndPoint getEarlier( final TimeEndPoint time1, final TimeEndPoint time2 ) {
+      if ( time1.getMillis() == time2.getMillis() ) {
+         return getLessFuzzy( time1, time2 );
+      }
+      if ( time1.getMillis() < time2.getMillis() ) {
+         return time1;
+      }
+      return time2;
+   }
+
+   static private TimeEndPoint getLater( final TimeEndPoint time1, final TimeEndPoint time2 ) {
+      if ( time1.getMillis() == time2.getMillis() ) {
+         return getLessFuzzy( time1, time2 );
+      }
+      if ( time1.getMillis() > time2.getMillis() ) {
+         return time1;
+      }
+      return time2;
+   }
+
+
+   static private TimeEndPoint getLessFuzzy( final TimeEndPoint time1, final TimeEndPoint time2 ) {
+      if ( time1.isFuzzy() && !time2.isFuzzy() ) {
+         return time2;
+      }
+      return time1;
+   }
+
+
+   static private TimeEndPoint getBestStartTime( final Iterable<TimeEndPoint> startTimes, final boolean haveStopAfter ) {
+      TimeEndPoint bestTime = TimeEndPoint.NULL_END_POINT;
+      for ( TimeEndPoint time : startTimes ) {
+         bestTime = getBetterStart( bestTime, time, haveStopAfter );
+      }
+      if ( bestTime.getPointer() == BEFORE ) {
+
+      }
+      return bestTime;
+   }
+
+   static private TimeEndPoint getBestStopTime( final Iterable<TimeEndPoint> stopTimes, final boolean haveStartBefore ) {
+      TimeEndPoint bestTime = TimeEndPoint.NULL_END_POINT;
+      for ( TimeEndPoint time : stopTimes ) {
+         bestTime = getBetterStop( bestTime, time, haveStartBefore );
+      }
+      return bestTime;
+   }
+
+
+   static public <T extends Entity> Map<Entity, TimeSpan> getTimexTimeSpanMap( final Entity docTimex,
+                                                                               final Collection<Entity> timex3s ) {
+      Calendar docTime = Calendar.getInstance();
+      if ( docTimex != null ) {
+         docTime = AtParser.getTimexCalendar( docTimex );
+      }
+      docTime.set( Calendar.HOUR_OF_DAY, 0 );
+      docTime.set( Calendar.MINUTE, 0 );
+      return getTimexTimeSpanMap( docTime, timex3s );
+   }
+
+   static public <T extends Entity> Map<Entity, TimeSpan> getTimexTimeSpanMap( final Calendar docTime,
+                                                                               final Collection<Entity> timex3s ) {
+      // Get all Timex entities
+      final Collection<Entity> validTimes = AtParser.getValidTimes( timex3s );
+      final Options chronicOptions = new Options( Pointer.PointerType.NONE, docTime, false, 0 );
+      final Options pastOptions = new Options( Pointer.PointerType.PAST, docTime, false, 0 );
+      final long docTimeSeconds = docTime.getTimeInMillis() / 1000 + 60 * 60 * 24;
+      final Map<Entity, TimeSpan> timexTimeSpans = new HashMap<>( validTimes.size() );
+      for ( Entity timex : validTimes ) {
+         final TimeSpan timeSpan = getTimeSpan( timex, chronicOptions, pastOptions, docTimeSeconds );
+         if ( timeSpan != null ) {
+            timexTimeSpans.put( timex, timeSpan );
+         }
+      }
+      return timexTimeSpans;
+   }
+
+   // TODO consolidate with AtParser
+
+   static private TimeSpan getTimeSpan( final Entity timex, final Options chronicOptions,
+                                        final Options pastOptions, final long docTimeSeconds ) {
+      // when ambiguous, don't assume that times are in the past or the future
+      // all times are relative to docTime
+      // do not use midpoints instead of time spans
+      // there is no "within textspan" time range
+      final String text = getTimexText( timex );
+      if ( text != null && text.length() > 0 ) {
+         final Span chronicSpan = StringDateParser.getChronicSpan( chronicOptions, text );
+         if ( chronicSpan != null ) {
+            if ( chronicSpan.getBegin() > docTimeSeconds ) {
+               // Kludge for some past dates (months) being placed in the future
+               final Span pastSpan = StringDateParser.getChronicSpan( pastOptions, text );
+               if ( pastSpan != null ) {
+                  return new DefaultTimeSpan( pastSpan, StringDateParser.isFuzzy( text ) );
+               }
+            }
+            return new DefaultTimeSpan( chronicSpan, StringDateParser.isFuzzy( text ) );
+         } else {
+            LOGGER.warning( "Chronic Couldn't Parse " + text );
+         }
+      }
+      return null;
+   }
+
+   static private String getTimexText( final Annotation timex ) {
+      if ( timex.isClassType( TemporalClassType.DOCTIME ) ) {
+         return timex.getSpannedText();
+      }
+      final List<String> attributeNames = timex.getAttributeNames();
+      for ( String name : attributeNames ) {
+         final Attribute attribute = timex.getAttribute( name );
+         if ( attribute.getValue().equalsIgnoreCase( "DATE" ) ) {
+            return timex.getSpannedText();
+         } else if ( attribute.getValue().equalsIgnoreCase( "TIME" ) ) {
+            return timex.getSpannedText();
+         } else if ( attribute.getValue().equalsIgnoreCase( "DURATION" )
+                     && timex.getSpannedText().contains( "last few" ) ) {
+            // kludge
+            return timex.getSpannedText();
+         } else if ( attribute.getValue().equals( "DURATION" )
+                     && (timex.getSpannedText().startsWith( "the last " )
+                         || timex.getSpannedText().startsWith( "the next " )) ) {
+            // Kludge because annotations often have duration of "last few XXX", which can become a parsed timespan
+            return timex.getSpannedText();
+            //         } else if ( attribute.getValue().equalsIgnoreCase( "QUANTIFIER" ) ) {
+            //            System.out.println("QUANTIFIER " + timex );
+            //            return timex.getSpannedText();
+         } else if ( attribute.getValue().equalsIgnoreCase( "PREPOSTEXP" ) ) {
+            return timex.getSpannedText();
+            //         } else if ( attribute.getValue().equalsIgnoreCase( "SET" ) ) {
+            //            System.out.println("SET " + timex );
+            //            return timex.getSpannedText();
+         }
+      }
+      return "";
+   }
+
+
+   static private TimeEndPoint getStartTimeEndPoint( final TimeSpan timeSpan, final TlinkAttributeValue tlinkType ) {
+      if ( tlinkType == TlinkAttributeValue.AFTER ) {
+         return new TimeEndPoint( AFTER, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.BEFORE ) {
+         return new TimeEndPoint( BEFORE, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.CONTAINED_BY ) {
+         return new TimeEndPoint( AFTER, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.CONTAINS ) {
+         return new TimeEndPoint( BEFORE, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.ENDS_ON ) {
+         return new TimeEndPoint( BEFORE, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.BEGINS_ON ) {
+         return new TimeEndPoint( EQUAL, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.OVERLAP ) {
+         return new TimeEndPoint( OVERLAP, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      }
+      // Don't know what else to do, so return overlap
+      return new TimeEndPoint( OVERLAP, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+   }
+
+   static private TimeEndPoint getStopTimeEndPoint( final TimeSpan timeSpan, final TlinkAttributeValue tlinkType ) {
+      if ( tlinkType == TlinkAttributeValue.AFTER ) {
+         return new TimeEndPoint( AFTER, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.BEFORE ) {
+         return new TimeEndPoint( BEFORE, timeSpan.getStartMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.CONTAINED_BY ) {
+         return new TimeEndPoint( BEFORE, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.CONTAINS ) {
+         return new TimeEndPoint( AFTER, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.ENDS_ON ) {
+         return new TimeEndPoint( EQUAL, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.BEGINS_ON ) {
+         return new TimeEndPoint( AFTER, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      } else if ( tlinkType == TlinkAttributeValue.OVERLAP ) {
+         return new TimeEndPoint( OVERLAP, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+      }
+      // Don't know what else to do, so return overlap
+      return new TimeEndPoint( OVERLAP, timeSpan.getStopMillis(), timeSpan.isFuzzyDate() );
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanLengthComparator.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanLengthComparator.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanLengthComparator.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/timespan/plus/TimeSpanLengthComparator.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,31 @@
+package org.chboston.cnlp.timeline.timespan.plus;
+
+import java.util.Comparator;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/7/13
+ */
+public enum TimeSpanLengthComparator implements Comparator<PointedTimeSpan> {
+   INSTANCE;
+
+   static public TimeSpanLengthComparator getInstance() {
+      return INSTANCE;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int compare( final PointedTimeSpan timeSpan1, final PointedTimeSpan timeSpan2 ) {
+      final long width1 = timeSpan1.getWidthMillis();
+      final long width2 = timeSpan2.getWidthMillis();
+      if ( width1 > width2 * 2l ) {
+         return -1;
+      } else if ( width2 > width1 * 2l ) {
+         return 1;
+      }
+      return TimeSpanPlusComparator.getInstance().compare( timeSpan1, timeSpan2 );
+   }
+}



Mime
View raw message