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

com.google.cloud.storage.ByteRangeSpec Maven / Gradle / Ivy

There is a newer version: 2.45.0
Show newest version
/*
 * Copyright 2022 Google LLC
 *
 * 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 com.google.cloud.storage;

import static com.google.api.client.util.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;

import com.google.api.core.InternalApi;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.storage.v2.ReadObjectRequest;
import java.io.Serializable;
import java.util.Objects;
import java.util.function.BiFunction;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Typeful sealed class hierarchy for representing an HTTP
 * Range Header There are certain subtleties when building these header values depending on
 * whether explicit boundaries or relative lengths are used. This class encapsulates the edge cases
 * as well as the concept of an effective infinity value for end of range.
 *
 * 

This class does not currently support negative offsets, i.e. start from end of content. */ @InternalApi @ThreadSafe abstract class ByteRangeSpec implements Serializable { public static final long EFFECTIVE_INFINITY = Long.MAX_VALUE; @Nullable private volatile String httpRangeHeader; private ByteRangeSpec() {} abstract long beginOffset(); abstract long endOffset() throws ArithmeticException; abstract long endOffsetInclusive() throws ArithmeticException; abstract long length() throws ArithmeticException; // TODO: add validation to this if it ever becomes public abstract ByteRangeSpec withNewBeginOffset(long beginOffset); abstract ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException; abstract ByteRangeSpec withNewEndOffset(long endOffsetExclusive); abstract ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive); abstract ByteRangeSpec withNewRelativeLength(long relativeLength); /** * If a range has no effectively declared beginning and end the string returned here will be null. */ @Nullable final String getHttpRangeHeader() throws ArithmeticException { if (httpRangeHeader == null) { synchronized (this) { if (httpRangeHeader == null) { httpRangeHeader = fmtAsHttpRangeHeader(); } } } return httpRangeHeader; } abstract ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b); @Nullable protected abstract String fmtAsHttpRangeHeader() throws ArithmeticException; @Override public int hashCode() { return Objects.hash(getHttpRangeHeader()); } @Override public boolean equals(Object o) { if (o instanceof ByteRangeSpec) { ByteRangeSpec that = (ByteRangeSpec) o; return Objects.equals(this.getHttpRangeHeader(), that.getHttpRangeHeader()); } return false; } @Override public String toString() { return append(MoreObjects.toStringHelper("ByteRangeSpec")).toString(); } protected abstract MoreObjects.ToStringHelper append(MoreObjects.ToStringHelper tsh); static ByteRangeSpec nullRange() { return NullByteRangeSpec.INSTANCE; } static ByteRangeSpec relativeLength(@Nullable Long beginOffset, @Nullable Long length) { return create(beginOffset, length, RelativeByteRangeSpec::new); } static ByteRangeSpec explicit(@Nullable Long beginOffset, @Nullable Long endOffsetExclusive) { return create(beginOffset, endOffsetExclusive, LeftClosedRightOpenByteRangeSpec::new); } static ByteRangeSpec explicitClosed( @Nullable Long beginOffset, @Nullable Long endOffsetInclusive) { return create(beginOffset, endOffsetInclusive, LeftClosedRightClosedByteRangeSpec::new); } static ByteRangeSpec parse(String string) { checkNotNull(string, "Range header is null"); checkArgument(string.startsWith("bytes="), "malformed Range header value: %s", string); int i = string.indexOf('-'); String minS = string.substring(6, i); String maxS = string.substring(i + 1); long min = Long.parseLong(minS); long max = Long.parseLong(maxS); return explicitClosed(min, max); } private static ByteRangeSpec create( @Nullable Long beginOffset, @Nullable Long length, BiFunction<@NonNull Long, @NonNull Long, ByteRangeSpec> f) { if (beginOffset == null && length == null) { return nullRange(); } else if (beginOffset != null && length != null) { if (beginOffset == 0 && length == EFFECTIVE_INFINITY) { return nullRange(); } else if (length == EFFECTIVE_INFINITY) { return new LeftClosedByteRangeSpec(beginOffset); } return f.apply(beginOffset, length); } else if (beginOffset == null /* && length != null*/) { if (length == EFFECTIVE_INFINITY) { return nullRange(); } return f.apply(0L, length); } else { return new LeftClosedByteRangeSpec(beginOffset); } } @Immutable private static final class RelativeByteRangeSpec extends ByteRangeSpec { private static final long serialVersionUID = -3910856417374881377L; private final long beginOffset; private final long length; private RelativeByteRangeSpec(long beginOffset, long length) { this.beginOffset = beginOffset; this.length = length; } @Override long beginOffset() { return beginOffset; } @Override long endOffset() throws ArithmeticException { return Math.addExact(beginOffset, length); } @Override long endOffsetInclusive() throws ArithmeticException { return Math.addExact(beginOffset, length) - 1; } @Override long length() throws ArithmeticException { return length; } @Override ByteRangeSpec withNewBeginOffset(long beginOffset) { if (beginOffset != this.beginOffset) { return new RelativeByteRangeSpec(beginOffset, length); } else { return this; } } @Override ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException { if (beginOffsetIncrement != 0) { return new RelativeByteRangeSpec(Math.addExact(beginOffset, beginOffsetIncrement), length); } else { return this; } } @Override ByteRangeSpec withNewEndOffset(long endOffsetExclusive) { return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetExclusive); } @Override ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) { return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive); } @Override ByteRangeSpec withNewRelativeLength(long relativeLength) { if (relativeLength != this.length) { return new RelativeByteRangeSpec(beginOffset, relativeLength); } else { return this; } } @Override public ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b) { return b.setReadOffset(beginOffset()).setReadLimit(length()); } @Override protected String fmtAsHttpRangeHeader() throws ArithmeticException { return String.format("bytes=%d-%d", beginOffset, endOffsetInclusive()); } @Override protected ToStringHelper append(ToStringHelper tsh) { return tsh.addValue(String.format("%d + %d", beginOffset, length)); } } @Immutable private static final class LeftClosedRightOpenByteRangeSpec extends ByteRangeSpec { private static final long serialVersionUID = -2706235472358072141L; private final long beginOffset; private final long endOffsetExclusive; private LeftClosedRightOpenByteRangeSpec(long beginOffset, long endOffsetExclusive) { this.beginOffset = beginOffset; this.endOffsetExclusive = endOffsetExclusive; } @Override long beginOffset() { return beginOffset; } @Override long endOffset() throws ArithmeticException { return endOffsetExclusive; } @Override long endOffsetInclusive() throws ArithmeticException { return Math.subtractExact(endOffsetExclusive, 1); } @Override long length() throws ArithmeticException { return Math.subtractExact(endOffsetExclusive, beginOffset); } @Override ByteRangeSpec withNewBeginOffset(long beginOffset) { if (beginOffset != this.beginOffset) { return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetExclusive); } else { return this; } } @Override ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException { if (beginOffsetIncrement != 0) { return new LeftClosedRightOpenByteRangeSpec( Math.addExact(beginOffset, beginOffsetIncrement), endOffsetExclusive); } else { return this; } } @Override ByteRangeSpec withNewEndOffset(long endOffsetExclusive) { if (endOffsetExclusive != this.endOffsetExclusive) { return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetExclusive); } else { return this; } } @Override ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) { return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive); } @Override ByteRangeSpec withNewRelativeLength(long relativeLength) { return new RelativeByteRangeSpec(beginOffset, relativeLength); } @Override public ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b) { return b.setReadOffset(beginOffset()).setReadLimit(length()); } @Override protected String fmtAsHttpRangeHeader() throws ArithmeticException { return String.format("bytes=%d-%d", beginOffset, endOffsetInclusive()); } @Override protected ToStringHelper append(ToStringHelper tsh) { return tsh.addValue(String.format("[%d, %d)", beginOffset, endOffsetExclusive)); } } @Immutable private static final class LeftClosedRightClosedByteRangeSpec extends ByteRangeSpec { private static final long serialVersionUID = -2706235472358072141L; private final long beginOffset; private final long endOffsetInclusive; private LeftClosedRightClosedByteRangeSpec(long beginOffset, long endOffsetInclusive) { this.beginOffset = beginOffset; this.endOffsetInclusive = endOffsetInclusive; } @Override long beginOffset() { return beginOffset; } @Override long endOffset() throws ArithmeticException { return Math.addExact(endOffsetInclusive, 1L); } @Override long endOffsetInclusive() throws ArithmeticException { return endOffsetInclusive; } @Override long length() throws ArithmeticException { return Math.addExact(Math.subtractExact(endOffsetInclusive, beginOffset), 1); } @Override ByteRangeSpec withNewBeginOffset(long beginOffset) { if (beginOffset != this.beginOffset) { return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive); } else { return this; } } @Override ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException { if (beginOffsetIncrement != 0) { return new LeftClosedRightClosedByteRangeSpec( Math.addExact(beginOffset, beginOffsetIncrement), endOffsetInclusive); } else { return this; } } @Override ByteRangeSpec withNewEndOffset(long endOffsetExclusive) { return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetExclusive); } @Override ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) { if (endOffsetInclusive != this.endOffsetInclusive) { return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive); } else { return this; } } @Override ByteRangeSpec withNewRelativeLength(long relativeLength) { return new RelativeByteRangeSpec(beginOffset, relativeLength); } @Override public ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b) { return b.setReadOffset(beginOffset()).setReadLimit(length()); } @Override protected String fmtAsHttpRangeHeader() throws ArithmeticException { return String.format("bytes=%d-%d", beginOffset, endOffsetInclusive); } @Override protected ToStringHelper append(ToStringHelper tsh) { return tsh.addValue(String.format("[%d, %d]", beginOffset, endOffsetInclusive)); } } @Immutable private static final class LeftClosedByteRangeSpec extends ByteRangeSpec { private static final long serialVersionUID = 4732278479149027012L; private final long beginOffset; private LeftClosedByteRangeSpec(long beginOffset) { this.beginOffset = beginOffset; } @Override long beginOffset() { return beginOffset; } @Override long endOffset() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override long endOffsetInclusive() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override long length() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override ByteRangeSpec withNewBeginOffset(long beginOffset) { if (beginOffset != this.beginOffset) { return new LeftClosedByteRangeSpec(beginOffset); } else { return this; } } @Override ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException { if (beginOffsetIncrement != 0) { return new LeftClosedByteRangeSpec(Math.addExact(beginOffset, beginOffsetIncrement)); } else { return this; } } @Override ByteRangeSpec withNewEndOffset(long endOffsetExclusive) { return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetExclusive); } @Override ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) { return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive); } @Override ByteRangeSpec withNewRelativeLength(long relativeLength) { return new RelativeByteRangeSpec(beginOffset, relativeLength); } @Override public ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b) { return b.setReadOffset(beginOffset()); } @Override protected String fmtAsHttpRangeHeader() throws ArithmeticException { if (beginOffset > 0) { return String.format("bytes=%d-", beginOffset); } else if (beginOffset < 0) { return String.format("bytes=%d", beginOffset); } else { return null; } } @Override protected ToStringHelper append(ToStringHelper tsh) { return tsh.addValue(String.format("[%d, +INF)", beginOffset)); } } @Immutable private static final class NullByteRangeSpec extends ByteRangeSpec { private static final NullByteRangeSpec INSTANCE = new NullByteRangeSpec(); private static final long serialVersionUID = 9110512497431639881L; @Override long beginOffset() { return 0; } @Override long endOffset() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override long endOffsetInclusive() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override long length() throws ArithmeticException { return EFFECTIVE_INFINITY; } @Override ByteRangeSpec withNewBeginOffset(long beginOffset) { if (beginOffset != 0) { return new LeftClosedByteRangeSpec(beginOffset); } else { return this; } } @Override ByteRangeSpec withShiftBeginOffset(long beginOffsetIncrement) throws ArithmeticException { if (beginOffsetIncrement != 0) { return new LeftClosedByteRangeSpec(beginOffsetIncrement); } else { return this; } } @Override ByteRangeSpec withNewEndOffset(long endOffsetExclusive) { if (endOffsetExclusive != EFFECTIVE_INFINITY) { return new LeftClosedRightOpenByteRangeSpec(0, endOffsetExclusive); } else { return this; } } @Override ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) { if (endOffsetInclusive != EFFECTIVE_INFINITY) { return new LeftClosedRightClosedByteRangeSpec(0, endOffsetInclusive); } else { return this; } } @Override ByteRangeSpec withNewRelativeLength(long relativeLength) { if (relativeLength != EFFECTIVE_INFINITY) { return new RelativeByteRangeSpec(0, relativeLength); } else { return this; } } @Override public ReadObjectRequest.Builder seekReadObjectRequest(ReadObjectRequest.Builder b) { return b; } @Override protected String fmtAsHttpRangeHeader() throws ArithmeticException { return null; } @Override protected ToStringHelper append(ToStringHelper tsh) { return tsh.addValue("[0, INF]"); } /** prevent java serialization from using a new instance */ private Object readResolve() { return INSTANCE; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy