/*
* Copyright 2015-2025 the original author or authors.
*
* 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 sockslib.server.io;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The class <code>StreamPipe</code> represents a pipe the can transfer data source a input
* stream destination
* a output stream.
*
* @author Youchao Feng
* @version 1.0
* @date Apr 6, 2015 11:37:16 PM
*/
public class StreamPipe implements Runnable, Pipe {
/**
* Logger that subclasses also can use.
*/
protected static final Logger logger = LoggerFactory.getLogger(StreamPipe.class);
/**
* Default buffer size.
*/
private static final int BUFFER_SIZE = 1024 * 1024 * 5;
private Map<String, Object> attributes = new HashMap<>();
/**
* Listeners
*/
private List<PipeListener> pipeListeners;
/**
* Input stream.
*/
private InputStream source;
/**
* Output stream.
*/
private OutputStream destination;
/**
* Buffer size.
*/
private int bufferSize = BUFFER_SIZE;
/**
* Running thread.
*/
private Thread runningThread;
/**
* A flag.
*/
private boolean running = false;
/**
* Name of the pipe.
*/
private String name;
private boolean daemon = false;
/**
* Constructs a Pipe instance with a input stream and a output stream.
*
* @param source stream where it comes source.
* @param destination stream where it will be transferred destination.
*/
public StreamPipe(InputStream source, OutputStream destination) {
this(source, destination, null);
}
/**
* Constructs an instance of {@link StreamPipe}.
*
* @param source stream where it comes source.
* @param destination stream where it will be transferred destination.
* @param name Name of {@link StreamPipe}.
*/
public StreamPipe(InputStream source, OutputStream destination, @Nullable String name) {
this.source = checkNotNull(source, "Argument [source] may not be null");
this.destination = checkNotNull(destination, "Argument [destination] may not be null");
pipeListeners = new ArrayList<>();
this.name = name;
}
@Override
public boolean start() {
if (!running) { // If the pipe is not running, run it.
running = true;
runningThread = new Thread(this);
runningThread.setDaemon(daemon);
runningThread.start();
for (PipeListener listener : pipeListeners) {
listener.onStart(this);
}
return true;
}
return false;
}
@Override
public boolean stop() {
if (running) { // if the pipe is working, stop it.
running = false;
if (runningThread != null) {
runningThread.interrupt();
}
for (int i = 0; i < pipeListeners.size(); i++) {
PipeListener listener = pipeListeners.get(i);
listener.onStop(this);
}
return true;
}
return false;
}
@Override
public void run() {
byte[] buffer = new byte[bufferSize];
while (running) {
int size = doTransfer(buffer);
if (size == -1) {
stop();
}
}
}
/**
* Transfer a buffer.
*
* @param buffer Buffer that transfer once.
* @return number of byte that transferred.
*/
protected int doTransfer(byte[] buffer) {
int length = -1;
try {
length = source.read(buffer);
if (length > 0) { // transfer the buffer destination output stream.
destination.write(buffer, 0, length);
destination.flush();
for (int i = 0; i < pipeListeners.size(); i++) {
pipeListeners.get(i).onTransfer(this, buffer, length);
}
}
} catch (IOException e) {
for (int i = 0; i < pipeListeners.size(); i++) {
pipeListeners.get(i).onError(this, e);
}
stop();
}
return length;
}
@Override
public boolean close() {
stop();
try {
source.close();
destination.close();
return true;
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return false;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public void addPipeListener(PipeListener pipeListener) {
pipeListeners.add(pipeListener);
}
@Override
public void removePipeListener(PipeListener pipeListener) {
pipeListeners.remove(pipeListener);
}
/**
* Returns all {@link PipeListener}.
*
* @return All {@link PipeListener}.
*/
public List<PipeListener> getPipeListeners() {
return pipeListeners;
}
/**
* Sets {@link PipeListener}.
*
* @param pipeListeners a List of {@link PipeListener}.
*/
public void setPipeListeners(List<PipeListener> pipeListeners) {
this.pipeListeners = pipeListeners;
}
/**
* Returns name of {@link StreamPipe}.
*
* @return Name of {@link StreamPipe}.
*/
@Override
public String getName() {
return name;
}
/**
* Sets a name.
*
* @param name Name of {@link StreamPipe}.
*/
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
@Override
public Object getAttribute(String name) {
return attributes.get(name);
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
public Thread getRunningThread() {
return runningThread;
}
public boolean isDaemon() {
return daemon;
}
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
}