// TODO: when no rewind - stop thread
package kg.apc.jmeter.modifiers;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import kg.apc.jmeter.EndOfFileException;
import kg.apc.jmeter.RuntimeEOFException;
import org.apache.commons.io.IOExceptionWithCause;
import org.apache.jmeter.engine.util.NoThreadClone;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 *
 * @author undera
 */
public class RawRequestSourcePreProcessor
        extends AbstractTestElement
        implements PreProcessor, NoThreadClone {
    public static final String regexp = "\\s";

    private static final Logger log = LoggingManager.getLoggerForClass();
    public static final String VARIABLE_NAME = "variable_name";
    public static final String FILENAME = "filename";
    public static final String REWIND = "rewind";
    private FileChannel file;
    private final ByteBuffer metaBuf = ByteBuffer.allocateDirect(1024);
    private final ByteBuffer oneByte = ByteBuffer.allocateDirect(1);

    public RawRequestSourcePreProcessor() {
        super();
    }

    public synchronized void process() {
        if (file == null) {
            log.info("Creating file object: " + getFileName());
            try {
                file = new FileInputStream(getFileName()).getChannel();
            } catch (FileNotFoundException ex) {
                log.error(getFileName(), ex);
                return;
            }
        }
        String rawData;

        try {
            rawData = readNextChunk(getNextChunkSize());
        } catch (EndOfFileException ex) {
            if (getRewindOnEOF()) {
                if (log.isDebugEnabled()) log.debug("Rewind file");
                try {
                    file.position(0);
                } catch (IOException ex1) {
                    log.error("Cannot rewind", ex1);
                }
                process();
                return;
            } else {
                log.info("End of file reached: " + getFileName());
                JMeterContextService.getContext().getThread().stop();
                throw new RuntimeEOFException("End of file reached", ex);
            }
        } catch (IOException ex) {
            log.error("Error reading next chunk", ex);
            throw new RuntimeException("Error reading next chunk", ex);
        }
        final JMeterVariables vars = JMeterContextService.getContext().getVariables();
        if (vars != null) {
            vars.put(getVarName(), rawData);
        }
    }

    private synchronized String readNextChunk(int capacity)
            throws IOException {
        if (capacity == 0) {
            throw new EndOfFileException("Zero chunk size, possibly end of file reached.");
        }

        ByteBuffer buf = ByteBuffer.allocateDirect(capacity);
        byte[] dst = new byte[capacity];


        int cnt = file.read(buf);
        //log.debug("Read " + cnt);
        if (cnt != capacity) {
            throw new IOException("Expected chunk size (" + capacity + ") differs from read bytes count (" + cnt + ")");
        }

        buf.flip();
        buf.get(dst);
        if (log.isDebugEnabled()) log.debug("Chunk : "+new String(dst));

        return new String(dst);
    }

    private int getNextChunkSize() throws IOException {
        metaBuf.clear();
        while (true) {
            byte b = getOneByte();
            if (b == 10 || b == 13) {
                // if we have \r\n then skip \n
                byte b2 = getOneByte();
                if (b2 != 10) {
                    file.position(file.position() - 1);
                }

                // ignore newlines before length marker
                if (metaBuf.position() > 0) {
                    break;
                }
            } else {
                //if (log.isDebugEnabled()) log.debug("Read byte: "+b);
                metaBuf.put(b);
            }
        }
        //if (log.isDebugEnabled()) log.debug("Meta line: "+JMeterPluginsUtils.byteBufferToString(metaBuf));

        byte[] bLine = new byte[metaBuf.position()];
        metaBuf.rewind();
        metaBuf.get(bLine);
        String sLine = new String(bLine).trim();
        String[] ar = sLine.split(regexp);
        if (log.isDebugEnabled()) log.debug("Chunk size: "+ar[0]);

        int res = 0;
        try {
            res = Integer.parseInt(ar[0]);
        } catch (NumberFormatException ex) {
            log.error("Error reading chunk size near: " + sLine, ex);
            throw new IOExceptionWithCause(ex);
        }
        return res;
    }

    private byte getOneByte() throws IOException {
        oneByte.rewind();
        if (file.read(oneByte) < 1) {
            throw new EndOfFileException(getFileName());
        }
        return oneByte.get(0);
    }

    public String getVarName() {
        return getPropertyAsString(VARIABLE_NAME);
    }

    public void setVarName(String name) {
        setProperty(VARIABLE_NAME, name);
    }

    public String getFileName() {
        return getPropertyAsString(FILENAME);
    }

    public void setFileName(String filename) {
        setProperty(FILENAME, filename);
        file = null;
    }

    public void setRewindOnEOF(boolean isRew) {
        setProperty(new BooleanProperty(REWIND, isRew));
    }

    public boolean getRewindOnEOF() {
        return getPropertyAsBoolean(REWIND);
    }
}

@@ -923,24 +923,25 @@
 readClone %7B%0A
+%0A
     public s
@@ -975,17 +975,16 @@
  %22%5C%5Cs%22;%0A
-%0A
     priv
@@ -1449,32 +1449,46 @@
 super();%0A    %7D%0A%0A
+    @Override%0A
     public synch
@@ -2044,32 +2044,54 @@
 sDebugEnabled())
+ %7B%0A                   
  log.debug(%22Rewi
@@ -2097,24 +2097,42 @@
 ind file%22);%0A
+                %7D%0A
             
@@ -2425,32 +2425,113 @@
 getFileName());%0A
+                if (JMeterContextService.getContext().getThread() != null) %7B%0A    
                 
@@ -2580,24 +2580,42 @@
 d().stop();%0A
+                %7D%0A
             
@@ -3669,32 +3669,46 @@
 sDebugEnabled())
+ %7B%0A           
  log.debug(%22Chun
@@ -3712,17 +3712,19 @@
 hunk : %22
-+
+ + 
 new Stri
@@ -3732,16 +3732,26 @@
 g(dst));
+%0A        %7D
 %0A%0A      
@@ -4785,32 +4785,46 @@
 sDebugEnabled())
+ %7B%0A           
  log.debug(%22Chun
@@ -4836,16 +4836,28 @@
 e: %22
-+
+ + 
 ar%5B0%5D);
+%0A        %7D
 %0A%0A  

fix npe on raw file check