com.google.cloud.BaseWriteChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of google-cloud-core Show documentation
Show all versions of google-cloud-core Show documentation
Core module for the google-cloud.
/*
* Copyright 2015 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;
import com.google.api.core.InternalApi;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Base implementation for a {@link WriteChannel}.
*
* @param the service options used by the channel to issue RPC requests
* @param the entity this channel writes data to. Possibly with additional configuration
*/
public abstract class BaseWriteChannel<
ServiceOptionsT extends ServiceOptions, ServiceOptionsT>, EntityT extends Serializable>
implements WriteChannel {
private static final int MIN_CHUNK_SIZE = 256 * 1024; // 256 KiB
private static final int DEFAULT_CHUNK_SIZE = 60 * MIN_CHUNK_SIZE; // 15MiB
private final ServiceOptionsT options;
private final EntityT entity;
private final String uploadId;
private long position;
private byte[] buffer = new byte[0];
private int limit;
private boolean isOpen = true;
private int chunkSize = getDefaultChunkSize();
protected int getMinChunkSize() {
return MIN_CHUNK_SIZE;
}
protected int getDefaultChunkSize() {
return DEFAULT_CHUNK_SIZE;
}
/**
* Writes {@code length} bytes of {@link #getBuffer()} to the {@link #getUploadId()} URL.
*
* @param length the number of bytes to write from {@link #getBuffer()}
* @param last if {@code true} the resumable session is closed
*/
protected abstract void flushBuffer(int length, boolean last);
protected ServiceOptionsT getOptions() {
return options;
}
protected EntityT getEntity() {
return entity;
}
protected String getUploadId() {
return uploadId;
}
protected long getPosition() {
return position;
}
protected byte[] getBuffer() {
return buffer;
}
protected int getLimit() {
return limit;
}
protected int getChunkSize() {
return chunkSize;
}
@Override
public final void setChunkSize(int chunkSize) {
int minSize = getMinChunkSize();
this.chunkSize = Math.max(minSize, (chunkSize + minSize - 1) / minSize * minSize);
}
@InternalApi("This class should only be extended within google-cloud-java")
protected BaseWriteChannel(ServiceOptionsT options, EntityT entity, String uploadId) {
this.options = options;
this.entity = entity;
this.uploadId = uploadId;
}
private void flush() {
if (limit >= chunkSize) {
final int length = limit - limit % getMinChunkSize();
flushBuffer(length, false);
position += length;
limit -= length;
byte[] temp = new byte[chunkSize];
System.arraycopy(buffer, length, temp, 0, limit);
buffer = temp;
}
}
private void validateOpen() throws ClosedChannelException {
if (!isOpen) {
throw new ClosedChannelException();
}
}
@Override
public final int write(ByteBuffer byteBuffer) throws IOException {
validateOpen();
int toWrite = byteBuffer.remaining();
int spaceInBuffer = buffer.length - limit;
if (spaceInBuffer >= toWrite) {
byteBuffer.get(buffer, limit, toWrite);
} else {
buffer = Arrays.copyOf(buffer, Math.max(chunkSize, buffer.length + toWrite - spaceInBuffer));
byteBuffer.get(buffer, limit, toWrite);
}
limit += toWrite;
flush();
return toWrite;
}
@Override
public boolean isOpen() {
return isOpen;
}
@Override
public final void close() throws IOException {
if (isOpen) {
flushBuffer(limit, true);
position += buffer.length;
isOpen = false;
buffer = null;
}
}
/** Creates a {@link BaseState.Builder} for the current write channel. */
protected abstract BaseState.Builder stateBuilder();
@Override
public RestorableState capture() {
byte[] bufferToSave = null;
if (isOpen) {
bufferToSave = Arrays.copyOf(buffer, limit);
}
return stateBuilder()
.setPosition(position)
.setBuffer(bufferToSave)
.setIsOpen(isOpen)
.setChunkSize(chunkSize)
.build();
}
/** Restores the state of the current write channel given a {@link BaseState} object. */
protected void restore(BaseState state) {
if (state.buffer != null) {
this.buffer = state.buffer.clone();
this.limit = state.buffer.length;
}
this.position = state.position;
this.isOpen = state.isOpen;
this.chunkSize = state.chunkSize;
}
protected abstract static class BaseState<
ServiceOptionsT extends ServiceOptions, ServiceOptionsT>, EntityT extends Serializable>
implements RestorableState, Serializable {
private static final long serialVersionUID = 8541062465055125619L;
protected final ServiceOptionsT serviceOptions;
protected final EntityT entity;
protected final String uploadId;
protected final long position;
protected final byte[] buffer;
protected final boolean isOpen;
protected final int chunkSize;
@InternalApi("This class should only be extended within google-cloud-java")
protected BaseState(Builder builder) {
this.serviceOptions = builder.serviceOptions;
this.entity = builder.entity;
this.uploadId = builder.uploadId;
this.position = builder.position;
this.buffer = builder.buffer;
this.isOpen = builder.isOpen;
this.chunkSize = builder.chunkSize;
}
/**
* Base builder for a write channel's state. Users are not supposed to access this class
* directly.
*
* @param the service options used by the channel to issue RPC requests
* @param the entity this channel writes data to. Possibly with additional
* configuration
*/
public abstract static class Builder<
ServiceOptionsT extends ServiceOptions, ServiceOptionsT>, EntityT extends Serializable> {
private final ServiceOptionsT serviceOptions;
private final EntityT entity;
private final String uploadId;
private long position;
private byte[] buffer;
private boolean isOpen;
private int chunkSize;
@InternalApi("This class should only be extended within google-cloud-java")
protected Builder(ServiceOptionsT options, EntityT entity, String uploadId) {
this.serviceOptions = options;
this.entity = entity;
this.uploadId = uploadId;
}
public Builder setPosition(long position) {
this.position = position;
return this;
}
public Builder setBuffer(byte[] buffer) {
this.buffer = buffer;
return this;
}
public Builder setIsOpen(boolean isOpen) {
this.isOpen = isOpen;
return this;
}
public Builder setChunkSize(int chunkSize) {
this.chunkSize = chunkSize;
return this;
}
public abstract RestorableState build();
}
@Override
public int hashCode() {
return Objects.hash(
serviceOptions, entity, uploadId, position, isOpen, chunkSize, Arrays.hashCode(buffer));
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof BaseState)) {
return false;
}
final BaseState other = (BaseState) obj;
return Objects.equals(this.serviceOptions, other.serviceOptions)
&& Objects.equals(this.entity, other.entity)
&& Objects.equals(this.uploadId, other.uploadId)
&& Objects.deepEquals(this.buffer, other.buffer)
&& this.position == other.position
&& this.isOpen == other.isOpen
&& this.chunkSize == other.chunkSize;
}
protected static final class ValueHolder {
final String name;
final Object value;
private ValueHolder(String name, Object value) {
this.name = name;
this.value = value;
}
public static ValueHolder create(String name, Object value) {
return new ValueHolder(name, value);
}
@Override
public String toString() {
String result = name + "=";
if (value != null && value.getClass().isArray()) {
Object[] objectArray = new Object[] {value};
String arrayString = Arrays.deepToString(objectArray);
result += arrayString.substring(1, arrayString.length() - 1);
} else {
result += value;
}
return result;
}
}
protected List toStringHelper() {
List valueList = new ArrayList<>();
valueList.add(ValueHolder.create("entity", entity));
valueList.add(ValueHolder.create("uploadId", uploadId));
valueList.add(ValueHolder.create("position", String.valueOf(position)));
valueList.add(ValueHolder.create("isOpen", String.valueOf(isOpen)));
return valueList;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getClass().getSimpleName()).append('{');
String nextSeparator = "";
for (ValueHolder valueHolder : toStringHelper()) {
builder.append(nextSeparator).append(valueHolder);
nextSeparator = ", ";
}
builder.append('}');
return builder.toString();
}
}
}