Return-Path: X-Original-To: apmail-ctakes-commits-archive@www.apache.org Delivered-To: apmail-ctakes-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 87734108AB for ; Thu, 19 Feb 2015 18:06:21 +0000 (UTC) Received: (qmail 79496 invoked by uid 500); 19 Feb 2015 18:06:21 -0000 Delivered-To: apmail-ctakes-commits-archive@ctakes.apache.org Received: (qmail 79409 invoked by uid 500); 19 Feb 2015 18:06:21 -0000 Mailing-List: contact commits-help@ctakes.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ctakes.apache.org Delivered-To: mailing list commits@ctakes.apache.org Received: (qmail 78839 invoked by uid 99); 19 Feb 2015 18:06:21 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 19 Feb 2015 18:06:21 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id F1115AC02E5 for ; Thu, 19 Feb 2015 18:06:20 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: commits@ctakes.apache.org From: seanfinan@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20150219180620.F1115AC02E5@hades.apache.org> 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 annotationCollections ) { + final Collection timelines = new HashSet<>( annotationCollections.size() ); + for ( Map.Entry 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 temporalEntities = new ArrayList<>(); + final List nonTemporalEntities = new ArrayList<>(); + final Collection 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 getDocRelTimeUniqueIds( final EntityIdStore entityIdCollection, + final Map uniqueIdTimeSpanMap ) { + final Collection docRelTimeUniqueIds = new HashSet<>(); + final Collection 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 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 timexTimeSpanMap, + final Collection temporalEntities, + final Collection timex3s, + final List tlinks, + final Entity docTimex, + final CollectionMap allTimeSpanEntityMap, + final CollectionMap allEntityTlinkMap ) { + final EntityIdStore entityIdCollection = new UniqueEntityIdStore( temporalEntities ); + final TLinkTypeArray3 tLinkTypeArray3 = new TLinkTypeArray3( tlinks, entityIdCollection ); + final List uniqueClosedTLinks = tLinkTypeArray3.getUniqueClosedTlinks(); + + final Map uniqueIdTimeSpanMap + = TimeSpanFactory.getUniqueIdTimeSpans( timexTimeSpanMap, uniqueClosedTLinks, entityIdCollection ); + final Collection 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 timeSpanEntityMap = new HashSetMap<>(); + for ( Map.Entry entityTimeSpan : uniqueIdTimeSpanMap.entrySet() ) { + final Collection 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 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 docRelTimeUniqueIds + = getDocRelTimeUniqueIds( entityIdCollection, uniqueIdTimeSpanMap ); + addDocTimeRelLinks( docTimex, docRelTimeUniqueIds, entityIdCollection, timeSpanEntityMap, uniqueIdTlinkMap ); + } + for ( Map.Entry> 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> uniqueIdTlinks : uniqueIdTlinkMap.entrySet() ) { + final Collection 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 temporalEntities, + final Collection nonTemporalEntities, + final Iterable umlsRelations, + final Collection timex3s, + final List tlinks ) { + final Entity docTimex = AtParser.getDocTimex( nonTemporalEntities, timex3s ); + final Map timexTimeSpanMap = TimeSpanFactory.getTimexTimeSpanMap( docTimex, timex3s ); + + final CollectionMap allTimeSpanEntityMap = new HashSetMap<>(); + final CollectionMap 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 entityTlinkMap ) { + for ( Map.Entry> 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 tlinks ) { + final Map> secondEntityMap = new HashMap<>(); + final Collection 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 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 docRelUniqueIds, + final EntityIdStore entityIdCollection, + final CollectionMap timeSpanEntityMap, + final CollectionMap uniqueIdTlinkMap ) { + final Collection docRelTlinks = new ArrayList<>(); + for ( Integer uniqueId : docRelUniqueIds ) { + final Collection tlinkTypes = createDocTimeRelTlinkType( docTimex, uniqueId, entityIdCollection ); + if ( tlinkTypes.isEmpty() ) { + continue; + } + final Collection 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 createDocTimeRelTlinkType( final Entity docTimex, final Integer uniqueId, + final EntityIdStore entityIdCollection ) { + if ( docTimex == null ) { + return Collections.emptySet(); + } + final Collection entities = entityIdCollection.getAllEntities( uniqueId ); + final Collection 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 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 getDocRelTimes( final Annotation entity ) { + if ( entity instanceof CoreferenceChain ) { + final Collection 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 _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 ChangeListener's stateChanged 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 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 { + 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> entityTimes ) { +// _title = title; +// +// final Collection startRemovals = new HashSet<>(); +// final Collection 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 getTimeSpans() { +// return _internalTimeline.getTimeSpans(); +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public Collection getEntityEntityRelations( final Entity entity ) { +// return _internalTimeline.getEntityEntityRelations( entity ); +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public Collection getEntities( final PointedTimeSpan timeSpan ) { +// return _internalTimeline.getEntities( timeSpan ); +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public Collection getEntities() { +// return _internalTimeline.getEntities(); +// } +// +// /** +// * {@inheritDoc} +// */ +// @Override +// public Collection 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 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 tlinks ) { + final Map timexTimeSpanMap + = getTimexTimeSpanMap( docTimex, Collections.singletonList( docTimex ) ); + return getUniqueIdTimeSpan( timexTimeSpanMap, tlinks ); + } + + static private PointedTimeSpan getUniqueIdTimeSpan( final Map timexTimeSpanMap, + final Iterable tlinks ) { + final Map> endPointMap + = getTimeEndPoints2( timexTimeSpanMap, tlinks ); + final Collection startTimes = endPointMap.get( EndPoint.START ); + final Collection 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 Map getUniqueIdTimeSpans( + final Map timexTimeSpanMap, + final Iterable tlinks, + final EntityIdStore entityIdCollection ) { + final Map>> endPointMap + = getTimeEndPoints( timexTimeSpanMap, tlinks, entityIdCollection ); + + final Map uniqueIdTimeSpanMap = new HashMap<>( endPointMap.size() ); + for ( Map.Entry>> uniqueIdEntry : endPointMap.entrySet() ) { + final Iterable startTimes = uniqueIdEntry.getValue().get( EndPoint.START ); + final Iterable 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>> getTimeEndPoints( + final Map timexTimeSpanMap, + final Iterable tlinks, + final EntityIdStore entityIdCollection ) { + final Map>> 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> times = allUniqueIdTimes.get( uniqueEntityId ); + if ( times == null ) { + times = new EnumMap<>( EndPoint.class ); + times.put( EndPoint.START, new HashSet() ); + times.put( EndPoint.STOP, new HashSet() ); + 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 startRemovals = new HashSet<>(); + final Collection stopRemovals = new HashSet<>(); + for ( Map> 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> getTimeEndPoints2( + final Map timexTimeSpanMap, + final Iterable tlinks ) { + final Map> times = new EnumMap<>( EndPoint.class ); + times.put( EndPoint.START, new HashSet() ); + times.put( EndPoint.STOP, new HashSet() ); + 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 startRemovals = new HashSet<>(); + final Collection 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 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 stopTimes, final boolean haveStartBefore ) { + TimeEndPoint bestTime = TimeEndPoint.NULL_END_POINT; + for ( TimeEndPoint time : stopTimes ) { + bestTime = getBetterStop( bestTime, time, haveStartBefore ); + } + return bestTime; + } + + + static public Map getTimexTimeSpanMap( final Entity docTimex, + final Collection 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 Map getTimexTimeSpanMap( final Calendar docTime, + final Collection timex3s ) { + // Get all Timex entities + final Collection 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 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 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 { + 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 ); + } +}