/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.utils;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.Days;
import org.joda.time.Hours;
import org.joda.time.Interval;
import org.joda.time.Minutes;
import org.joda.time.Months;
import org.joda.time.Seconds;
import org.joda.time.Years;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
/**
* Bundles up the results of analyzing a time range for the appropriate
* TimeUnits to use to visualize it. Partly, this class exists so I
* don't have to have more member variables in other places , and partly because
* I can only return a single value from a function. This might only be a
* temporary design but is working well for now.
*/
@Immutable
public class RangeDivisionInfo {
/**
* the size of the periods we should divide the interval into
*/
private final TimeUnits blockSize;
/**
* The number of Blocks we are going to divide the interval into.
*/
private final int numberOfBlocks;
/**
* a DateTimeFormatter corresponding to the block size for the tick
* marks on the date axis of the graph
*/
private final DateTimeFormatter tickFormatter;
/**
* an adjusted lower bound for the range such that is lines up with a block
* boundary before or at the start of the timerange
*/
private final long lowerBound;
/**
* an adjusted upper bound for the range such that is lines up with a block
* boundary at or after the end of the timerange
*/
private final long upperBound;
/**
* the time range this RangeDivisionInfo describes
*/
private final Interval timeRange;
private ImmutableList<Interval> intervals;
public Interval getTimeRange() {
return timeRange;
}
private RangeDivisionInfo(Interval timeRange, int periodsInRange, TimeUnits periodSize, DateTimeFormatter tickformatter, long lowerBound, long upperBound) {
this.numberOfBlocks = periodsInRange;
this.blockSize = periodSize;
this.tickFormatter = tickformatter;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.timeRange = timeRange;
}
/**
* Static factory method.
*
* Determine the period size, number of periods, whole period bounds, and
* formatters to use to visualize the given timerange.
*
* @param timeRange
*
* @return
*/
public static RangeDivisionInfo getRangeDivisionInfo(Interval timeRange) {
//Check from largest to smallest unit
//TODO: make this more generic... reduce code duplication -jm
DateTimeFieldType timeUnit;
final DateTime startWithZone = timeRange.getStart().withZone(TimeLineController.getJodaTimeZone());
final DateTime endWithZone = timeRange.getEnd().withZone(TimeLineController.getJodaTimeZone());
if (Years.yearsIn(timeRange).isGreaterThan(Years.THREE)) {
timeUnit = DateTimeFieldType.year();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Years.yearsIn(timeRange).get(timeUnit.getDurationType()) + 1, TimeUnits.YEARS, ISODateTimeFormat.year(), lower, upper);
} else if (Months.monthsIn(timeRange).isGreaterThan(Months.THREE)) {
timeUnit = DateTimeFieldType.monthOfYear();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Months.monthsIn(timeRange).getMonths() + 1, TimeUnits.MONTHS, DateTimeFormat.forPattern("YYYY'-'MMMM"), lower, upper); // NON-NLS
} else if (Days.daysIn(timeRange).isGreaterThan(Days.THREE)) {
timeUnit = DateTimeFieldType.dayOfMonth();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Days.daysIn(timeRange).getDays() + 1, TimeUnits.DAYS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd"), lower, upper); // NON-NLS
} else if (Hours.hoursIn(timeRange).isGreaterThan(Hours.THREE)) {
timeUnit = DateTimeFieldType.hourOfDay();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Hours.hoursIn(timeRange).getHours() + 1, TimeUnits.HOURS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH"), lower, upper); // NON-NLS
} else if (Minutes.minutesIn(timeRange).isGreaterThan(Minutes.THREE)) {
timeUnit = DateTimeFieldType.minuteOfHour();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Minutes.minutesIn(timeRange).getMinutes() + 1, TimeUnits.MINUTES, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH':'mm"), lower, upper); // NON-NLS
} else {
timeUnit = DateTimeFieldType.secondOfMinute();
long lower = startWithZone.property(timeUnit).roundFloorCopy().getMillis();
long upper = endWithZone.property(timeUnit).roundCeilingCopy().getMillis();
return new RangeDivisionInfo(timeRange, Seconds.secondsIn(timeRange).getSeconds() + 1, TimeUnits.SECONDS, DateTimeFormat.forPattern("YYYY'-'MMMM'-'dd HH':'mm':'ss"), lower, upper); // NON-NLS
}
}
public DateTimeFormatter getTickFormatter() {
return tickFormatter;
}
public int getPeriodsInRange() {
return numberOfBlocks;
}
public TimeUnits getPeriodSize() {
return blockSize;
}
public long getUpperBound() {
return upperBound;
}
public long getLowerBound() {
return lowerBound;
}
@SuppressWarnings("ReturnOfCollectionOrArrayField")
synchronized public List<Interval> getIntervals() {
if (intervals == null) {
ArrayList<Interval> tempList = new ArrayList<>();
//extend range to block bounderies (ie day, month, year)
final Interval range = new Interval(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()), new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
DateTime start = range.getStart();
while (range.contains(start)) {
//increment for next iteration
DateTime end = start.plus(getPeriodSize().getPeriod());
final Interval interval = new Interval(start, end);
tempList.add(interval);
start = end;
}
intervals = ImmutableList.copyOf(tempList);
}
return intervals;
}
public String formatForTick(Interval interval) {
return interval.getStart().toString(tickFormatter);
}
}