/*
* ArrayConsumer.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* 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 org.simpleframework.http.message;
import java.io.IOException;
import org.simpleframework.transport.Cursor;
/**
* The <code>ArrayConsumer</code> object is a consumer that consumes bytes in to
* an internal array before processing. This consumes all bytes read in to an
* internal array. Each read is met with an invocation of the <code>scan</code>
* method, which searches for the terminal token within the read chunk. Once the
* terminal token has been read the excess bytes are reset and the data can be
* processed by the subclass implementation. The internal array is expanded if
* the number of consumed bytes exceeds its capacity.
*
* @author Niall Gallagher
*/
public abstract class ArrayConsumer implements Consumer {
/**
* This is the array that is used to contain the read bytes.
*/
protected byte[] array;
/**
* This is the number of bytes that have been consumed so far.
*/
protected int count;
/**
* This is the size of the chunk of bytes to read each time.
*/
protected int chunk;
/**
* This determines whether the terminal token has been read.
*/
protected boolean done;
/**
* Constructor for the <code>ArrayConsumer</code> object. This is used to
* create a consumer that will consume all bytes in to an internal array
* until a terminal token has been read. If excess bytes are read by this
* consumer they are reset in the cursor.
*/
public ArrayConsumer() {
this(1024);
}
/**
* Constructor for the <code>ArrayConsumer</code> object. This is used to
* create a consumer that will consume all bytes in to an internal array
* until a terminal token has been read. If excess bytes are read by this
* consumer they are reset in the cursor.
*
* @param size
* this is the initial array and chunk size to use
*/
public ArrayConsumer(int size) {
this(size, 512);
}
/**
* Constructor for the <code>ArrayConsumer</code> object. This is used to
* create a consumer that will consume all bytes in to an internal array
* until a terminal token has been read. If excess bytes are read by this
* consumer they are reset in the cursor.
*
* @param size
* this is the initial array size that is to be used
* @param chunk
* this is the chunk size to read bytes as
*/
public ArrayConsumer(int size, int chunk) {
this.array = new byte[size];
this.chunk = chunk;
}
/**
* This method is used to consume bytes from the provided cursor. Each read
* performed is done in a specific chunk size to ensure that a sufficiently
* large or small amount of data is read from the <code>Cursor</code>
* object. After each read the byte array is scanned for the terminal token.
* When the terminal token is found the bytes are processed by the
* implementation.
*
* @param cursor
* this is the cursor to consume the bytes from
*/
@Override
public void consume(Cursor cursor) throws IOException {
if (!this.done) {
int ready = cursor.ready();
while (ready > 0) {
int size = Math.min(ready, this.chunk);
if ((this.count + size) > this.array.length) {
this.resize(this.count + size);
}
size = cursor.read(this.array, this.count, size);
this.count += size;
if (size > 0) {
int reset = this.scan();
if (reset > 0) {
cursor.reset(reset);
}
if (this.done) {
this.process();
break;
}
}
ready = cursor.ready();
}
}
}
/**
* This method is used to add an additional chunk size to the internal
* array. Resizing of the internal array is required as the consumed bytes
* may exceed the initial size of the array. In such a scenario the array is
* expanded the chunk size.
*
* @param size
* this is the minimum size to expand the array to
*/
protected void resize(int size) throws IOException {
if (this.array.length < size) {
int expand = this.array.length + this.chunk;
int max = Math.max(expand, size);
byte[] temp = new byte[max];
System.arraycopy(this.array, 0, temp, 0, this.count);
this.array = temp;
}
}
/**
* When the terminal token is read from the cursor this will be true. The
* <code>scan</code> method is used to determine the terminal token. It is
* invoked after each read, when the scan method returns a non-zero value
* then excess bytes are reset and the consumer has finished.
*
* @return this returns true when the terminal token is read
*/
@Override
public boolean isFinished() {
return this.done;
}
/**
* This method is invoked after the terminal token has been read. It is used
* to process the consumed data and is typically used to parse the input
* such that it can be used by the subclass for some useful purpose. This is
* called only once by the consumer.
*/
protected abstract void process() throws IOException;
/**
* This method is used to scan for the terminal token. It searches for the
* token and returns the number of bytes in the buffer after the terminal
* token. Returning the excess bytes allows the consumer to reset the bytes
* within the consumer object.
*
* @return this returns the number of excess bytes consumed
*/
protected abstract int scan() throws IOException;
}