All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.gov.gchq.gaffer.time.RBMBackedTimestampSet Maven / Gradle / Ivy

There is a newer version: 2.3.1
Show newest version
/*
 * Copyright 2017-2020 Crown Copyright
 *
 * 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 uk.gov.gchq.gaffer.time;


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.roaringbitmap.IntIterator;
import org.roaringbitmap.RoaringBitmap;

import uk.gov.gchq.gaffer.commonutil.CommonTimeUtil;
import uk.gov.gchq.gaffer.commonutil.ToStringBuilder;

import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Stream;

import static uk.gov.gchq.gaffer.commonutil.CommonTimeUtil.TimeBucket;

/**
 * An {@code RBMBackedTimestampSet} is an implementation of {@link TimestampSet} that stores timestamps
 * truncated to a certain {@link TimeBucket}, e.g. if a {@link TimeBucket} of a minute is specified then a timestamp
 * of 12:34:56 on January 1st 2015 would be truncated to the previous minute, namely 12:34:00 on January 1st 2015.
 * Timebuckets of second, minute, hour, day, week, month and year are supported.
 * 

* Internally this class stores the timestamps in a {@link RoaringBitmap}. *

*

* NB: This class does not accept {@link Instant}s that are before the Unix epoch or after the {@link Instant} * which is {@code Integer.MAX_VALUE * 1000L} milliseconds after the epoch (approximately 3:14 on January 19th * 2038). This is due to {@link RoaringBitmap} only accepting integers. As the smallest {@link TimeBucket} is * a second then the maximum supported {@link Instant} is the maximum integer multiplied by 1000L milliseconds after * the epoch. *

*/ @JsonPropertyOrder(alphabetic = true) @JsonDeserialize(builder = RBMBackedTimestampSet.Builder.class) public class RBMBackedTimestampSet implements TimestampSet { private static final Instant MIN_TIME = Instant.ofEpochMilli(0L); private static final Instant MAX_TIME = Instant.ofEpochMilli(Integer.MAX_VALUE * CommonTimeUtil.MILLISECONDS_IN_SECOND); private static final Set VALID_TIMEBUCKETS = new HashSet<>(Arrays.asList( TimeBucket.SECOND, TimeBucket.MINUTE, TimeBucket.HOUR, TimeBucket.DAY, TimeBucket.WEEK, TimeBucket.MONTH, TimeBucket.YEAR )); private final TimeBucket timeBucket; private RoaringBitmap rbm = new RoaringBitmap(); public RBMBackedTimestampSet(final TimeBucket timeBucket) { if (!VALID_TIMEBUCKETS.contains(timeBucket)) { throw new IllegalArgumentException("A TimeBucket of " + timeBucket + " is not supported"); } this.timeBucket = timeBucket; } public RBMBackedTimestampSet(final TimeBucket timeBucket, final Instant... instants) { this(timeBucket); Stream.of(instants).forEach(this::add); } @Override public void add(final Instant instant) { if (instant.isBefore(MIN_TIME) || instant.isAfter(MAX_TIME)) { throw new IllegalArgumentException("Invalid instant of " + instant); } rbm.add(toInt(instant.toEpochMilli())); } @Override public void add(final Collection instants) { instants.forEach(this::add); } @Override public SortedSet getTimestamps() { final SortedSet instants = new TreeSet<>(); rbm.forEach(i -> instants.add(getInstantFromInt(i))); return instants; } @Override public long getNumberOfTimestamps() { return rbm.getCardinality(); } @Override public Instant getEarliest() { final IntIterator it = rbm.getIntIterator(); if (!it.hasNext()) { return null; } return getInstantFromInt(it.next()); } @Override public Instant getLatest() { final IntIterator it = rbm.getReverseIntIterator(); if (!it.hasNext()) { return null; } return getInstantFromInt(it.next()); } /** * Applies a time range mask. Timestamps which fall outside the range are filtered. * * @param startMillis filter start time in milliseconds * @param endMillis filter end time in milliseconds */ public void applyTimeRangeMask(final Long startMillis, final Long endMillis) { final int startTime; final int endTime; if (startMillis != null && endMillis != null && startMillis > endMillis) { throw new IllegalArgumentException("The start time should not be chronologically later than the end time"); } if (startMillis == null) { startTime = toInt(getEarliest().toEpochMilli()); } else { startTime = toInt(startMillis); if (startMillis > 0 && startTime < 0) { throw new RuntimeException("Failed to convert start time to " + timeBucket.name() + " as the resulting value was outside the range of Integer"); } } if (endMillis == null) { endTime = toInt(getLatest().toEpochMilli()); } else { endTime = toInt(endMillis); if (endMillis > 0 && endTime < 0) { throw new RuntimeException("Failed to convert end time to " + timeBucket.name() + " as the resulting value was outside the range of Integer"); } } RoaringBitmap timeRange = new RoaringBitmap(); // end date is exclusive timeRange.add(startTime, endTime + 1); rbm.and(timeRange); } public TimeBucket getTimeBucket() { return timeBucket; } /** * This exposes the underlying {@link RoaringBitmap} so that serialisers can access it. * * @return the {@link RoaringBitmap} used by this class to store the timestamps. */ @JsonIgnore public RoaringBitmap getRbm() { return rbm; } /** * Allows the {@link RoaringBitmap} to be set. * * @param rbm the {@link RoaringBitmap} to set the {@link RoaringBitmap} of this class to. */ public void setRbm(final RoaringBitmap rbm) { this.rbm = rbm; } public void addAll(final RBMBackedTimestampSet other) { rbm.or(other.getRbm()); } @JsonIgnore public RBMBackedTimestampSet getShallowClone() { return new Builder() .timeBucket(timeBucket) .timestamps(getTimestamps()) .build(); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (null == obj || getClass() != obj.getClass()) { return false; } final RBMBackedTimestampSet rbmBackedTimestampSet = (RBMBackedTimestampSet) obj; return new EqualsBuilder() .append(timeBucket, rbmBackedTimestampSet.timeBucket) .append(rbm, rbmBackedTimestampSet.rbm) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(21, 83) .append(timeBucket) .append(rbm) .toHashCode(); } @Override public String toString() { return new ToStringBuilder(this) .append("timeBucket", timeBucket) .append("timestamps", StringUtils.join(getTimestamps(), ',')) .toString(); } private int toInt(final long time) { return toInt(timeBucket, time); } private static int toInt(final TimeBucket timeBucket, final long time) { final long timeTruncatedToBucket = CommonTimeUtil.timeToBucket(time, timeBucket); switch (timeBucket) { case SECOND: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_SECOND); case MINUTE: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_MINUTE); case HOUR: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_HOUR); case DAY: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_DAY); case WEEK: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_DAY); case MONTH: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_DAY); case YEAR: return (int) (timeTruncatedToBucket / CommonTimeUtil.MILLISECONDS_IN_DAY); default: throw new IllegalStateException("Unknown time bucket of " + timeBucket); } } private long fromInt(final int i) { return fromInt(timeBucket, i); } static long fromInt(final TimeBucket timeBucket, final int i) { switch (timeBucket) { case SECOND: return i * CommonTimeUtil.MILLISECONDS_IN_SECOND; case MINUTE: return i * CommonTimeUtil.MILLISECONDS_IN_MINUTE; case HOUR: return i * CommonTimeUtil.MILLISECONDS_IN_HOUR; case DAY: return i * CommonTimeUtil.MILLISECONDS_IN_DAY; case WEEK: return i * CommonTimeUtil.MILLISECONDS_IN_DAY; case MONTH: return i * CommonTimeUtil.MILLISECONDS_IN_DAY; case YEAR: return i * CommonTimeUtil.MILLISECONDS_IN_DAY; default: throw new IllegalStateException("Unknown time bucket of " + timeBucket); } } private Instant getInstantFromInt(final int i) { return Instant.ofEpochMilli(fromInt(i)); } @JsonIgnoreProperties(value = {"numberOfTimestamps", "earliest", "latest"}) @JsonPOJOBuilder(withPrefix = "") public static class Builder { private TimeBucket timeBucket; private Collection timestamps; public Builder timeBucket(final TimeBucket timeBucket) { this.timeBucket = timeBucket; return this; } public Builder timestamps(final Collection timestamps) { this.timestamps = timestamps; return this; } public RBMBackedTimestampSet build() { final RBMBackedTimestampSet set = new RBMBackedTimestampSet(timeBucket); if (null != timestamps) { set.add(timestamps); } return set; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy