/**
* Copyright (c) 2009 - 2010 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.utils.net.throttledconnection
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.utils.net.throttledconnection;
import java.io.IOException;
import java.io.InputStream;
/**
* @author daniel
*
*/
public class ThrottledInputStream extends InputStream implements ThrottledConnection {
private ThrottledConnectionManager manager;
private final InputStream in;
protected volatile long transferedCounter = 0;
protected volatile long transferedCounter2 = 0;
private volatile int limitCurrent = 0;
private int limitManaged = 0;
private int limitCustom = 0;
private int limitCounter = 0;
private int lastRead2;
private long ret;
private long slotTimeLeft = 0;
private long lastTimeRead = 0;
/**
* constructor for not managed ThrottledInputStream
*
* @param in
*/
public ThrottledInputStream(final InputStream in) {
this.in = in;
}
/**
* constructor for not managed ThrottledInputStream with given limit(see
* setCustomLimit)
*
* @param in
* @param kpsLimit
*/
public ThrottledInputStream(final InputStream in, final int kpsLimit) {
this(in);
this.setCustomLimit(kpsLimit);
}
/**
* constructor for managed ThrottledInputStream
*
* @param in
* @param manager
*/
protected ThrottledInputStream(final InputStream in, final ThrottledConnectionManager manager) {
this.manager = manager;
this.in = in;
}
@Override
public int available() throws IOException {
return this.in.available();
}
/**
* change current limit
*
* @param kpsLimit
*/
private void changeCurrentLimit(final int kpsLimit) {
if (kpsLimit == this.limitCurrent) { return; }
/* TODO: maybe allow little jitter here */
this.limitCurrent = Math.max(0, kpsLimit);
}
/**
* DO NOT FORGET TO CLOSE
*/
@Override
public void close() throws IOException {
/* remove this stream from manager */
if (this.manager != null) {
this.manager.removeManagedThrottledInputStream(this);
this.manager = null;
}
this.in.close();
}
/**
* get custom set limit
*
* @return
*/
public int getCustomLimit() {
return this.limitCustom;
}
@Override
public synchronized void mark(final int readlimit) {
this.in.mark(readlimit);
}
@Override
public boolean markSupported() {
return this.in.markSupported();
}
/**
* WARNING: this function has a huge overhead
*/
@Override
public int read() throws IOException {
this.lastRead2 = this.in.read();
if (this.lastRead2 == -1) {
/* end of line */
return -1;
}
this.transferedCounter++;
if (this.limitCurrent != 0) {
/* a Limit is set */
this.limitCounter--;
this.slotTimeLeft = 0;
if (this.limitCounter <= 0 && (this.slotTimeLeft = System.currentTimeMillis() - this.lastTimeRead) < 1000) {
/* Limit reached and slotTime not over yet */
synchronized (this) {
try {
this.wait(1000 - this.slotTimeLeft);
} catch (final InterruptedException e) {
throw new IOException("throttle interrupted");
}
}
/* refill Limit */
this.limitCounter = this.limitCurrent;
} else if (this.slotTimeLeft > 1000) {
/* slotTime is over, refill Limit too */
this.limitCounter = this.limitCurrent;
}
this.lastTimeRead = System.currentTimeMillis();
}
return this.lastRead2;
}
@Override
public int read(final byte b[], final int off, final int len) throws IOException {
if (this.limitCurrent == 0) {
this.lastRead2 = this.in.read(b, off, len);
if (this.lastRead2 == -1) {
/* end of line */
return -1;
}
this.transferedCounter += this.lastRead2;
} else {
/* a Limit is set */
this.slotTimeLeft = 0;
if (this.limitCounter <= 0 && (this.slotTimeLeft = System.currentTimeMillis() - this.lastTimeRead) < 1000) {
/* Limit reached and slotTime not over yet */
synchronized (this) {
try {
this.wait(1000 - this.slotTimeLeft);
} catch (final InterruptedException e) {
throw new IOException("throttle interrupted");
}
}
/* refill Limit */
this.limitCounter = this.limitCurrent;
if (this.limitCounter <= 0) this.limitCounter = len;
} else if (this.slotTimeLeft > 1000) {
/* slotTime is over, refill Limit too */
this.limitCounter = this.limitCurrent;
if (this.limitCounter <= 0) this.limitCounter = len;
}
this.lastRead2 = this.in.read(b, off, Math.min(this.limitCounter, len));
if (this.lastRead2 == -1) {
/* end of line */
return -1;
}
this.transferedCounter += this.lastRead2;
this.limitCounter -= this.lastRead2;
this.lastTimeRead = System.currentTimeMillis();
}
return this.lastRead2;
}
@Override
public synchronized void reset() throws IOException {
this.in.reset();
}
/**
* sets custom speed limit -1 : no limit 0 : use managed limit >0: use
* custom limit
*
* @param kpsLimit
*/
public void setCustomLimit(final int kpsLimit) {
if (this.limitCustom == kpsLimit) { return; }
if (kpsLimit < 0) {
this.limitCustom = -1;
this.changeCurrentLimit(0);
} else if (kpsLimit == 0) {
this.limitCustom = 0;
this.changeCurrentLimit(this.limitManaged);
} else {
this.limitCustom = kpsLimit;
this.changeCurrentLimit(kpsLimit);
}
}
/**
* sets managed limit 0: no limit >0: use managed limit
*
* @param kpsLimit
*/
public void setManagedLimit(final int kpsLimit) {
if (kpsLimit == this.limitManaged) { return; }
if (kpsLimit <= 0) {
this.limitManaged = 0;
if (this.limitCustom == 0) {
this.changeCurrentLimit(0);
}
} else {
this.limitManaged = kpsLimit;
if (this.limitCustom == 0) {
this.changeCurrentLimit(kpsLimit);
}
}
}
/**
* set a new ThrottledConnectionManager
*
* @param manager
*/
public void setManager(final ThrottledConnectionManager manager) {
if (this.manager != null && this.manager != manager) {
this.manager.removeManagedThrottledInputStream(this);
}
this.manager = manager;
if (this.manager != null) {
this.manager.addManagedThrottledInputStream(this);
}
}
@Override
public long skip(final long n) throws IOException {
return this.in.skip(n);
}
/**
* return how many bytes got transfered till now and reset counter
*
* @return
*/
public synchronized long transferedSinceLastCall() {
this.ret = this.transferedCounter - this.transferedCounter2;
this.transferedCounter2 = this.transferedCounter;
return this.ret;
}
}