/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2009 by Trifork
*
* 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 erjang.driver;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;
import kilim.Pausable;
import kilim.ReentrantLock;
import erjang.EAtom;
import erjang.EBinList;
import erjang.EBinary;
import erjang.EHandle;
import erjang.EInternalPort;
import erjang.EObject;
import erjang.EPID;
import erjang.EPeer;
import erjang.ERT;
import erjang.ERef;
import erjang.EString;
import erjang.ETask;
import erjang.ETuple;
import erjang.ErlangException;
import erjang.NotImplemented;
import erjang.driver.efile.Posix;
import erjang.m.erlang.ErlProc;
/**
*
*/
public abstract class EDriverInstance extends EDriverControl {
protected static Logger log = Logger.getLogger("erjang.driver");
EDriver driver;
protected EDriverTask task;
Lock pdl;
public EDriverInstance(EDriver driver) {
this.driver = driver;
}
public EDriver getDriver() {
return driver;
}
public EInternalPort port() {
return task.self_handle();
}
@Override
void setTask(EDriverTask task) {
this.task = task;
}
protected static final int ERL_DRV_READ = SelectionKey.OP_READ;
protected static final int ERL_DRV_WRITE = SelectionKey.OP_WRITE;
protected static final int ERL_DRV_ACCEPT = SelectionKey.OP_ACCEPT;
protected static final int ERL_DRV_CONNECT = SelectionKey.OP_CONNECT;
protected static final int ERL_DRV_USE = 1 << 5;
static private final int ALL_OPS = ERL_DRV_READ | ERL_DRV_WRITE
| ERL_DRV_ACCEPT | ERL_DRV_CONNECT;
/**
* Register selector
*
* @param ch
* {@link SelectableChannel} for which to perform select
* @param mode
* bit-or of ERL_DRV_{READ,WRITE,ACCEPT,CONNECT}
* @param onOff
* one of {@link SelectMode}.SET or {@link SelectMode}.CLEAR
*/
public void select(SelectableChannel ch, int mode, SelectMode onOff) {
int selectOps = mode & ALL_OPS;
if (onOff == SelectMode.SET) {
NIOSelector.setInterest(ch, selectOps, task);
} else if (onOff == SelectMode.CLEAR) {
boolean releaseNotify = (mode & ERL_DRV_USE) == ERL_DRV_USE;
NIOSelector.clearInterest(ch, selectOps, releaseNotify, task);
}
}
protected void driver_exit(int i) {
this.task.exit(i==0?ETask.am_normal:EAtom.intern(Posix.errno_id(i)));
}
protected void driver_async(EAsync job) {
ERT.run_async(job, task);
}
protected void driver_outputv(ByteBuffer hdr, ByteBuffer[] ev) throws Pausable {
EObject resp = ERT.NIL;
if (task.send_binary_data) {
if (ev.length > 0) {
ev[ev.length-1].flip();
resp = EBinary.make(ev[ev.length-1]);
for (int i = ev.length-2; i >= 0; i--) {
ev[i].flip();
resp = resp.cons( EBinary.make(ev[i]) );
}
}
} else {
throw new NotImplemented();
}
hdr.flip();
EBinList res = new EBinList(hdr, resp);
task.output_from_driver(res);
}
public void driver_output2(ByteBuffer header, ByteBuffer buf) throws Pausable {
int status = task.status;
if ((status & EDriverTask.ERTS_PORT_SFLG_CLOSING) != 0) {
return;
}
header.flip();
if (buf != null)
buf.flip();
if ((status & EDriverTask.ERTS_PORT_SFLG_DISTRIBUTION) != 0) {
task.node().net_message(task.self_handle(), null, buf);
return;
}
if ((status & EDriverTask.ERTS_PORT_SFLG_LINEBUF_IO) != 0) {
throw new NotImplemented();
}
EObject tail = null;
if (buf == null || !buf.hasRemaining()) {
tail = ERT.NIL;
} else if (task.send_binary_data) {
tail = EBinary.make(buf);
} else {
tail = EString.make(buf);
}
EBinList out = new EBinList(header, tail);
task.output_from_driver(out);
}
protected void driver_output(ByteBuffer buf) throws Pausable {
int status = task.status;
if ((status & EDriverTask.ERTS_PORT_SFLG_CLOSING) != 0) {
return;
}
buf.flip();
if ((status & EDriverTask.ERTS_PORT_SFLG_DISTRIBUTION) != 0) {
task.node().net_message(task.self_handle(), null, buf);
return;
}
if ((status & EDriverTask.ERTS_PORT_SFLG_LINEBUF_IO) != 0) {
throw new NotImplemented();
}
EObject out;
if (buf == null || !buf.hasRemaining()) {
out = ERT.NIL;
} else if (task.send_binary_data) {
out = EBinary.make(buf);
} else {
out = EString.make(buf);
}
task.output_from_driver(out);
}
public void driver_output_term(EObject term) throws Pausable {
task.output_term_from_driver(term);
}
public void driver_send_term(EHandle caller, ETuple msg) throws Pausable {
if (caller != null) {
caller.send(task.self_handle(), msg);
}
}
/**
* @param fileRespOkHeader
* @param binp
* @throws Pausable
*/
protected void driver_output_binary(byte[] header, ByteBuffer binp) throws Pausable {
EObject out = EBinary.make(binp);
if (header.length > 0) {
out = new EBinList(header, out);
}
task.output_from_driver(out);
}
/**
*/
protected void driver_cancel_timer() {
task.cancel_timer(port());
}
protected void driver_set_timer(long howlong) {
task.set_timer(howlong);
}
protected long driver_read_timer() {
return task.read_timer();
}
/**
* @return
*/
protected Lock driver_pdl_create() {
if (pdl == null) {
pdl = new ReentrantLock();
}
return pdl;
}
private ByteBuffer[] queue = null;
private EPID caller;
/**
* @return
*/
protected ByteBuffer[] driver_peekq() {
return queue;
}
protected EPID driver_caller() {
return this.caller;
}
protected boolean driver_demonitor_process(ERef monitor) throws Pausable {
try {
return ErlProc.demonitor(task, monitor, ERT.NIL) == ERT.TRUE;
} catch (ErlangException e) {
EDriverTask.log.log(Level.FINE, "demonitor", e);
return false;
}
}
protected EHandle driver_get_monitored_process(ERef monitor) {
return task.get_monitored_process(monitor);
}
protected ERef driver_monitor_process(EPID pid) throws Pausable {
ERef ref = ERT.getLocalNode().createRef();
if (!task.monitor(pid, pid, ref)) {
port().send(port(), ETuple.make(ERT.am_DOWN, ref, ErlProc.am_process, pid, ERT.am_noproc));
}
return ref;
}
protected int driver_sizeq() {
if (queue == null) return 0;
int size = 0;
int p = 0;
for (p = 0; p < queue.length; p++) {
if (queue[p] != null)
size += queue[p].remaining();
}
return size;
}
protected long driver_deq(long size) {
ByteBuffer[] queue = this.queue;
if (queue == null)
return 0;
int p = 0;
for (p = 0; p < queue.length && queue[p] != null && !queue[p].hasRemaining(); p++) {
/* skip */
}
if (p == queue.length)
return 0;
long res = 0;
for (int i = 0; (p+i) < queue.length; i++) {
queue[i] = queue[p + i];
if (queue[i] != null) {
res += queue[i].remaining();
}
}
for (int i = p; i < queue.length; i++) {
queue[i] = null;
}
return res;
}
protected void driver_enqv(ByteBuffer[] q) {
if (queue == null || (queue.length>0 && queue[0] == null))
queue = q;
else {
ArrayList<ByteBuffer> bbs = new ArrayList<ByteBuffer>();
for (int i = 0; i < queue.length; i++) {
if (queue[i] != null && queue[i].hasRemaining())
bbs.add(queue[i]);
}
for (int i = 0; i < q.length; i++) {
if (q[i] != null && q[i].hasRemaining())
bbs.add(q[i]);
}
queue = bbs.toArray(new ByteBuffer[bbs.size()]);
}
}
/*
* Called on behalf of driver_select when it is safe to release 'event'. A
* typical unix driver would call close(event)
*/
protected void stopSelect(SelectableChannel event) throws Pausable {
}
/**
* @param out
* @return
*/
public static ByteBuffer flatten(ByteBuffer[] out) {
return EDriverTask.flatten(out);
}
/*
* called when port is closed, and when the emulator is halted. Default
* behavior is to do nothing.
*/
protected void stop(EObject reason) throws Pausable {
if ((task.status & EDriverTask.ERTS_PORT_SFLG_DISTRIBUTION) != 0) {
EPeer node = task.node();
if (node != null) {
node.node_going_down(port(), reason);
}
task.status &= ~EDriverTask.ERTS_PORT_SFLG_DISTRIBUTION;
}
}
/*
* called when we have output from erlang to the port
*/
protected abstract void output(EHandle caller, ByteBuffer buf) throws IOException, Pausable;
/*
* called when we have output from erlang to the port, and the iodata()
* passed in contains multiple fragments. Default behavior is to flatten the
* input vector, and call EDriverInstance#output(ByteBuffer).
*/
protected void outputv(EHandle caller, ByteBuffer[] ev) throws IOException, Pausable {
output(caller, flatten(ev));
}
/*
* called when we have input from one of the driver's handles)
*/
protected void readyInput(SelectableChannel ch) throws Pausable {};
/*
* called when output is possible to one of the driver's handles
*/
protected void readyOutput(SelectableChannel evt) throws Pausable {};
/* called when "action" is possible, async job done */
protected void readyAsync(EAsync data) throws Pausable {}
/*
* "ioctl" for drivers - invoked by port_control/3)
*/
protected ByteBuffer control(EPID pid, int command, ByteBuffer cmd) throws Pausable {
throw ERT.badarg();
}
/* Handling of timeout in driver */
protected void timeout() throws Pausable {};
/*
* called when the port is about to be closed, and there is data in the
* driver queue that needs to be flushed before 'stop' can be called
*/
protected void flush() throws Pausable {};
/*
* Works mostly like 'control', a syncronous call into the driver.
*/
protected EObject call(EPID caller, int command, EObject data) throws Pausable {
throw ERT.badarg();
}
/**
* @param ch
* @throws Pausable
*/
public void readyConnect(SelectableChannel evt) throws Pausable {
// TODO Auto-generated method stub
}
/**
* @param ch
* @throws Pausable
*/
public void readyAccept(SelectableChannel ch) throws Pausable {
}
protected void set_busy_port(boolean b) {
throw new erjang.NotImplemented();
}
public static void dump_buffer(ByteBuffer[] buffer) {
dump_buffer(log, null, buffer);
}
public static void dump_buffer(Logger log, String heading, ByteBuffer[] buffer) {
dump_buffer(log, Level.FINEST, heading, buffer);
}
public static void dump_buffer(Logger log, Level level, String heading, ByteBuffer[] buffer) {
if (!log.isLoggable(level)) return;
StringBuilder sb = new StringBuilder();
if (heading != null) {
sb.append(heading);
sb.append("\n");
}
sb.append(" vec[" + buffer.length + "]:: \n");
for (int i = 0; i < buffer.length; i++) {
ByteBuffer evp = buffer[i];
int off = 0;
boolean did_print = false;
for (int p = evp.position(); p < evp.limit(); p++) {
if ((off % 0x10) == 0 && off != 0)
sb.append("\n");
if ((off % 0x10) == 0)
sb.append("["+i+"] 0x" + hex4(off) + " :");
did_print = true;
sb.append(" ");
byte ch = evp.get(p);
sb.append(hex2(ch&0xff));
off += 1;
}
if (i < buffer.length-1 && did_print) sb.append("\n");
}
sb.append("---\n");
for (int i = 0; i < buffer.length; i++) {
ByteBuffer evp = buffer[i];
int off = 0;
boolean did_print = false;
for (int p = evp.position(); p < evp.limit(); p++) {
if ((off % 0x10) == 0 && off != 0)
sb.append("\n");
if ((off % 0x10) == 0)
sb.append("["+i+"] 0x" + hex4(off) + " : ");
did_print = true;
byte ch = evp.get(p);
if (ch >= 32 && ch <= 127) {
sb.append((char)ch);
} else {
sb.append('.');
}
off += 1;
}
if (i < buffer.length-1 && did_print) sb.append("\n");
}
String msg = sb.toString();
log.log(level, msg);
}
public static String hex2(int i) {
if (i < 0x10) return "0" + Integer.toHexString(i);
return Integer.toHexString(i);
}
public static String hex4(int i) {
if (i < 0x10) return "000" + Integer.toHexString(i);
if (i < 0x100) return "00" + Integer.toHexString(i);
if (i < 0x1000) return "0" + Integer.toHexString(i);
return Integer.toHexString(i);
}
}