blob: 8de6b02a04ce56597864b4e67ccb838d2488cea4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.jclouds.io.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.inject.Singleton;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.io.PayloadSlicer;
import org.jclouds.io.payloads.BaseMutableContentMetadata;
import org.jclouds.io.payloads.ByteSourcePayload;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.hash.HashCode;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
@Singleton
public class BasePayloadSlicer implements PayloadSlicer {
private static final class InputStreamPayloadIterable implements Iterable<Payload> {
private final InputStream input;
private final ContentMetadata metaData;
InputStreamPayloadIterable(InputStream input, ContentMetadata metaData) {
this.input = checkNotNull(input, "input");
this.metaData = checkNotNull(metaData, "metaData");
}
@Override
public Iterator<Payload> iterator() {
return new InputStreamPayloadIterator(input, metaData);
}
}
private static final class InputStreamPayloadIterator implements Iterator<Payload> {
private final InputStream input;
private final ContentMetadata metaData;
private Payload nextPayload;
private final int readLen;
InputStreamPayloadIterator(InputStream input, ContentMetadata metaData) {
this.input = checkNotNull(input, "input");
this.metaData = checkNotNull(metaData, "metaData");
this.readLen = checkNotNull(this.metaData.getContentLength(), "content-length").intValue();
this.nextPayload = getNextPayload();
}
@Override
public boolean hasNext() {
return nextPayload != null;
}
@Override
public Payload next() {
Payload payload;
if (!hasNext())
throw new NoSuchElementException();
payload = nextPayload;
nextPayload = getNextPayload();
return payload;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Payload iterator does not support removal");
}
private Payload getNextPayload() {
byte[] content = new byte[readLen];
int offset = 0;
try {
while (true) {
int read = input.read(content, offset, readLen - offset);
if (read <= 0) {
if (offset == 0) {
return null;
} else {
break;
}
}
offset += read;
}
} catch (IOException e) {
throw Throwables.propagate(e);
}
return createPayload((content.length == offset) ? content : Arrays.copyOf(content, offset));
}
private Payload createPayload(byte[] content) {
Payload payload = null;
if (content.length > 0) {
payload = Payloads.newByteArrayPayload(content);
ContentMetadata cm = metaData.toBuilder().contentLength((long)content.length).contentMD5((HashCode) null).build();
payload.setContentMetadata(BaseMutableContentMetadata.fromContentMetadata(cm));
}
return payload;
}
}
private static final class ByteSourcePayloadIterable implements Iterable<Payload> {
private final ByteSource input;
private final ContentMetadata metaData;
ByteSourcePayloadIterable(ByteSource input, ContentMetadata metaData) {
this.input = checkNotNull(input, "input");
this.metaData = checkNotNull(metaData, "metaData");
}
@Override
public Iterator<Payload> iterator() {
return new ByteSourcePayloadIterator(input, metaData);
}
}
private static final class ByteSourcePayloadIterator implements Iterator<Payload> {
private final ByteSource input;
private final ContentMetadata metaData;
private Payload nextPayload;
private long offset = 0;
private final long readLen;
ByteSourcePayloadIterator(ByteSource input, ContentMetadata metaData) {
this.input = checkNotNull(input, "input");
this.metaData = checkNotNull(metaData, "metaData");
this.readLen = checkNotNull(this.metaData.getContentLength(), "content-length").longValue();
this.nextPayload = getNextPayload();
}
@Override
public boolean hasNext() {
return nextPayload != null;
}
@Override
public Payload next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Payload payload = nextPayload;
nextPayload = getNextPayload();
return payload;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Payload iterator does not support removal");
}
private Payload getNextPayload() {
ByteSource byteSource;
long byteSourceSize;
try {
if (offset >= input.size()) {
return null;
}
byteSource = input.slice(offset, readLen);
byteSourceSize = byteSource.size();
} catch (IOException e) {
throw Throwables.propagate(e);
}
Payload nextPayload = new ByteSourcePayload(byteSource);
ContentMetadata cm = metaData.toBuilder()
.contentLength(byteSourceSize)
.contentMD5((HashCode) null)
.build();
nextPayload.setContentMetadata(BaseMutableContentMetadata.fromContentMetadata(cm));
offset += byteSourceSize;
return nextPayload;
}
}
/**
* {@inheritDoc}
*/
@Override
public Payload slice(Payload input, long offset, long length) {
checkNotNull(input);
checkArgument(offset >= 0, "offset is negative");
checkArgument(length >= 0, "length is negative");
Payload returnVal;
if (input.getRawContent() instanceof File) {
returnVal = doSlice((File) input.getRawContent(), offset, length);
} else if (input.getRawContent() instanceof String) {
returnVal = doSlice((String) input.getRawContent(), offset, length);
} else if (input.getRawContent() instanceof byte[]) {
returnVal = doSlice((byte[]) input.getRawContent(), offset, length);
} else if (input.getRawContent() instanceof InputStream) {
returnVal = doSlice((InputStream) input.getRawContent(), offset, length);
} else if (input.getRawContent() instanceof ByteSource) {
returnVal = doSlice((ByteSource) input.getRawContent(), offset, length);
} else {
returnVal = doSlice(input, offset, length);
}
return copyMetadataAndSetLength(input, returnVal, length);
}
protected Payload doSlice(Payload content, long offset, long length) {
return doSlice(content.getInput(), offset, length);
}
protected Payload doSlice(String content, long offset, long length) {
return doSlice(content.getBytes(), offset, length);
}
protected Payload doSlice(File content, long offset, long length) {
return doSlice(Files.asByteSource(content), offset, length);
}
protected Payload doSlice(InputStream content, long offset, long length) {
try {
ByteStreams.skipFully(content, offset);
} catch (IOException ioe) {
throw Throwables.propagate(ioe);
}
return Payloads.newInputStreamPayload(ByteStreams.limit(content, length));
}
protected Payload doSlice(ByteSource content, long offset, long length) {
return Payloads.newByteSourcePayload(content.slice(offset, length));
}
protected Payload doSlice(byte[] content, long offset, long length) {
checkArgument(offset <= Integer.MAX_VALUE, "offset is too big for an array");
checkArgument(length <= Integer.MAX_VALUE, "length is too big for an array");
// TODO(adriancole): Make ByteArrayPayload carry offset, length as opposed to wrapping here.
return Payloads.newByteSourcePayload(ByteSource.wrap(content).slice(offset, length));
}
protected Payload copyMetadataAndSetLength(Payload input, Payload returnVal, long length) {
returnVal.setContentMetadata(BaseMutableContentMetadata.fromContentMetadata(input.getContentMetadata()
.toBuilder().contentLength(length).contentMD5((HashCode) null).build()));
return returnVal;
}
@Override
public Iterable<Payload> slice(Payload input, long size) {
checkNotNull(input, "input");
checkArgument(size >= 0, "size must be non-negative but was: %s", size);
ContentMetadata meta = BaseMutableContentMetadata.fromContentMetadata(input.getContentMetadata())
.toBuilder()
.contentLength(size)
.contentMD5((HashCode) null)
.build();
Object rawContent = input.getRawContent();
if (rawContent instanceof File) {
return doSlice((File) rawContent, meta);
} else if (rawContent instanceof String) {
return doSlice((String) rawContent, meta);
} else if (rawContent instanceof byte[]) {
return doSlice((byte[]) rawContent, meta);
} else if (rawContent instanceof InputStream) {
return doSlice((InputStream) rawContent, meta);
} else if (rawContent instanceof ByteSource) {
return doSlice((ByteSource) rawContent, meta);
} else {
return doSlice(input, meta);
}
}
protected Iterable<Payload> doSlice(Payload input, ContentMetadata meta) {
return doSlice(input.getInput(), meta);
}
protected Iterable<Payload> doSlice(String rawContent, ContentMetadata meta) {
return doSlice(ByteSource.wrap(rawContent.getBytes(Charsets.UTF_8)), meta);
}
protected Iterable<Payload> doSlice(byte[] rawContent, ContentMetadata meta) {
return doSlice(ByteSource.wrap(rawContent), meta);
}
protected Iterable<Payload> doSlice(File rawContent, ContentMetadata meta) {
return doSlice(Files.asByteSource(rawContent), meta);
}
protected Iterable<Payload> doSlice(InputStream rawContent, ContentMetadata meta) {
return new InputStreamPayloadIterable(rawContent, meta);
}
protected Iterable<Payload> doSlice(ByteSource rawContent, ContentMetadata meta) {
return new ByteSourcePayloadIterable(rawContent, meta);
}
}