// If not, we need to provide a "surrogate" for the beginning interval. The availabilities
// obtained from the db are sorted in ascending order of time. So we can insert one
// pseudo-availability in front of the list if needed. Note that due to avail purging
// we can end up with periods without avail data.
if (availabilities.size() > 0) {
Availability earliestAvailability = availabilities.get(0);
if (earliestAvailability.getStartTime() > fullRangeBeginDate.getTime()) {
Availability surrogateAvailability = new SurrogateAvailability(earliestAvailability.getResource(),
fullRangeBeginDate.getTime());
surrogateAvailability.setEndTime(earliestAvailability.getStartTime());
availabilities.add(0, surrogateAvailability); // add at the head of the list
}
} else {
Resource surrogateResource = context.type == EntityContext.Type.Resource ? entityManager.find(
Resource.class, context.resourceId) : new Resource(-1);
Availability surrogateAvailability = new SurrogateAvailability(surrogateResource,
fullRangeBeginDate.getTime());
surrogateAvailability.setEndTime(fullRangeEndDate.getTime());
availabilities.add(surrogateAvailability); // add as the only element
}
// Now check if the date range passed in by the user extends into the future. If so, finish the last
// availability at now and add a surrogate after it, as we know nothing about the future.
long now = System.currentTimeMillis();
if (fullRangeEndDate.getTime() > now) {
Availability latestAvailability = availabilities.get(availabilities.size() - 1);
latestAvailability.setEndTime(now);
Availability unknownFuture = new SurrogateAvailability(latestAvailability.getResource(), now);
availabilities.add(unknownFuture);
}
// Now calculate the individual data points. We start at the end time of the range
// and move a current time pointer backwards in time, stopping at each barrier along the way, where a barrier
// is either the start of a data point or the start of an availability record. We move backwards
// in time because the full range may not be neatly divisible by the number of points so we want
// any "leftover" data that we can't account for in the returned list to be the oldest data possible.
long totalMillis = fullRangeEndTime - fullRangeBeginTime;
long perPointMillis = totalMillis / numberOfPoints;
List<AvailabilityPoint> availabilityPoints = new ArrayList<AvailabilityPoint>(numberOfPoints);
long currentTime = fullRangeEndTime;
int currentAvailabilityIndex = availabilities.size() - 1;
long timeUpInDataPoint = 0;
long timeDisabledInDataPoint = 0;
boolean hasDownPeriods = false;
boolean hasDisabledPeriods = false;
boolean hasUnknownPeriods = false;
long dataPointStartBarrier = fullRangeEndTime - perPointMillis;
while (currentTime > fullRangeBeginTime) {
if (currentAvailabilityIndex <= -1) {
// no more availability data, the rest of the data points are unknown
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.UNKNOWN, currentTime));
currentTime -= perPointMillis;
continue;
}
Availability currentAvailability = availabilities.get(currentAvailabilityIndex);
long availabilityStartBarrier = currentAvailability.getStartTime();
// the start of the data point comes first or at same time as availability record (remember, we are going
// backwards in time)
if (dataPointStartBarrier >= availabilityStartBarrier) {
// end the data point
if (currentAvailability instanceof SurrogateAvailability) {
// we are on the edge of the range with a surrogate for this data point. Be pessimistic,
// if we have had any down time, set to down, then disabled, then up, and finally unknown.
if (hasDownPeriods) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.DOWN, currentTime));
} else if (hasDisabledPeriods) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.DISABLED, currentTime));
} else if (timeUpInDataPoint > 0) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.UP, currentTime));
} else {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.UNKNOWN, currentTime));
}
} else {
// bump up the proper counter or set the proper flag for the current time frame
switch (currentAvailability.getAvailabilityType()) {
case UP:
timeUpInDataPoint += currentTime - dataPointStartBarrier;
break;
case DOWN:
hasDownPeriods = true;
break;
case DISABLED:
hasDisabledPeriods = true;
break;
case UNKNOWN:
hasUnknownPeriods = true;
break;
default:
// Only stored avail types are relevant, MISSING, for example, is never stored
break;
}
// if the period has been all green, then set it to UP, otherwise, be pessimistic if there is any
// mix of avail types
if (timeUpInDataPoint == perPointMillis) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.UP, currentTime));
} else if (hasDownPeriods) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.DOWN, currentTime));
} else if (hasDisabledPeriods) {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.DISABLED, currentTime));
} else {
availabilityPoints.add(new AvailabilityPoint(AvailabilityType.UNKNOWN, currentTime));
}
}
timeUpInDataPoint = 0;
hasDownPeriods = false;
hasDisabledPeriods = false;
hasUnknownPeriods = false;
// if we reached the start of the current availability record, move to the previous one (going back in time, remember)
if (dataPointStartBarrier == availabilityStartBarrier) {
currentAvailabilityIndex--;
}
// move the current time pointer to the next data point and move back to the next data point start time
currentTime = dataPointStartBarrier;
dataPointStartBarrier -= perPointMillis;
// the division determing perPointMillis drops the remainder, which may leave us slightly short.
// if we go negative, we're done.
if (dataPointStartBarrier < 0) {
break;
}
} else { // the end of the availability record comes first, in the middle of a data point
switch (currentAvailability.getAvailabilityType()) {
case UP:
// if the resource has been up in the current time frame, bump up the counter
timeUpInDataPoint += currentTime - availabilityStartBarrier;
break;
case DOWN: