package php.runtime.ext.core.classes.stream;
import php.runtime.Memory;
import php.runtime.common.HintType;
import php.runtime.common.Messages;
import php.runtime.common.Modifier;
import php.runtime.env.Environment;
import php.runtime.invoke.Invoker;
import php.runtime.lang.BaseObject;
import php.runtime.lang.Resource;
import php.runtime.memory.BinaryMemory;
import php.runtime.memory.LongMemory;
import php.runtime.memory.ObjectMemory;
import php.runtime.memory.StringMemory;
import php.runtime.reflection.ClassEntity;
import php.runtime.reflection.support.ReflectionUtils;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Arrays;
import java.util.Scanner;
import static java.lang.annotation.ElementType.TYPE;
import static php.runtime.annotation.Reflection.*;
@Name(Stream.CLASS_NAME)
@Signature({
@Arg(value = "path", modifier = Modifier.PRIVATE, readOnly = true, type = HintType.STRING),
@Arg(value = "mode", modifier = Modifier.PRIVATE, readOnly = true)
})
abstract public class Stream extends BaseObject implements Resource {
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE})
public @interface UsePathWithProtocols {}
@Ignore
public final static String CLASS_NAME = "php\\io\\Stream";
private String path;
private String mode;
private Memory context = Memory.NULL;
public Stream(Environment env) {
super(env);
}
public Stream(Environment env, ClassEntity clazz) {
super(env, clazz);
}
@Signature({@Arg("path"), @Arg(value = "mode", optional = @Optional("NULL"))})
public Memory __construct(Environment env, Memory... args) throws IOException {
setPath(args[0].toString());
setMode(args[1].isNull() ? null : args[1].toString());
return Memory.NULL;
}
@Signature({@Arg("value")})
public Memory setContext(Environment env, Memory... args){
context = args[0];
return Memory.NULL;
}
@Signature
public Memory getContext(Environment env, Memory... args){
return context;
}
@Signature
public String getPath() {
return path;
}
public void setPath(String path) {
__class__.setProperty(this, "path", new StringMemory(path));
this.path = path;
}
@Signature
public String getMode() {
return mode;
}
public void setMode(String mode) {
__class__.setProperty(this, "mode", mode == null ? null : new StringMemory(mode));
this.mode = mode;
}
@Signature({@Arg("value"), @Arg(value = "length", optional = @Optional("NULL"))})
abstract public Memory write(Environment env, Memory... args) throws IOException;
@Signature({@Arg(value = "length")})
abstract public Memory read(Environment env, Memory... args) throws IOException;
@Signature
abstract public Memory readFully(Environment env, Memory... args) throws IOException;
@Signature
abstract public Memory eof(Environment env, Memory... args);
@Signature(@Arg("position"))
abstract public Memory seek(Environment env, Memory... args) throws IOException;
@Signature
abstract public Memory getPosition(Environment env, Memory... args);
@Signature
abstract public Memory close(Environment env, Memory... args) throws IOException;
@Signature
public Memory __toString(Environment env, Memory... args) throws Throwable {
Memory memory = env.invokeMethod(this, "readFully");
if (memory.isString()) {
return memory;
} else {
return StringMemory.valueOf(memory.toString());
}
}
public static Stream create(Environment env, String path, String mode) throws Throwable {
return of(env, StringMemory.valueOf(path), StringMemory.valueOf(mode)).toObject(Stream.class);
}
@Signature({@Arg("path"), @Arg(value = "mode", optional = @Optional("r"))})
public static Memory getContents(Environment env, Memory... args) throws Throwable {
Stream stream = create(env, args[0].toString(), args[1].toString());
try {
return env.invokeMethod(stream, "readFully");
} finally {
env.invokeMethod(stream, "close");
}
}
@Signature({@Arg("path"), @Arg("data"), @Arg(value = "mode", optional = @Optional("w+"))})
public static Memory putContents(Environment env, Memory... args) throws Throwable {
Stream stream = create(env, args[0].toString(), args[2].toString());
FileLock lock = null;
try {
if (stream instanceof FileStream) {
lock = ((FileStream) stream).getAccessFile().getChannel().lock();
}
try {
return env.invokeMethod(stream, "write", args[1]);
} finally {
if (lock != null) {
lock.release();
}
}
} finally {
env.invokeMethod(stream, "close");
}
}
@Signature({
@Arg("path")
})
public static Memory exists(Environment env, Memory... args) throws Throwable {
Stream stream = null;
try {
stream = create(env, args[0].toString(), "r");
if (stream._isExternalResourceStream()) {
env.exception("Unable to check external stream");
}
return Memory.TRUE;
} catch (WrapIOException e) {
return Memory.FALSE;
} finally {
if (stream != null) {
env.invokeMethod(stream, "close");
}
}
}
@Signature({
@Arg("path"),
@Arg(value = "callback", type = HintType.CALLABLE),
@Arg(value = "mode", optional = @Optional("r"))}
)
public static Memory tryAccess(Environment env, Memory... args) throws Throwable {
Stream stream = create(env, args[0].toString(), args[2].toString());
try {
Invoker invoker = Invoker.create(env, args[1]);
return invoker.call(ObjectMemory.valueOf(stream));
} finally {
env.invokeMethod(stream, "close");
}
}
@Signature({@Arg("path"), @Arg(value = "mode", optional = @Optional("r"))})
public static Memory of(Environment env, Memory... args) throws Throwable {
String path = args[0].toString();
String protocol = "file";
int pos = path.indexOf("://");
if (pos > -1) {
protocol = path.substring(0, pos);
path = path.substring(pos + 3);
}
String className = env.getUserValue(Stream.class.getName() + "#" + protocol, String.class);
if (className == null){
throw new IOException("Unregistered protocol - " + protocol + "://");
}
ClassEntity classEntity = env.fetchClass(className);
if (classEntity.getNativeClass().getAnnotation(UsePathWithProtocols.class) != null) {
path = protocol + "://" + path;
}
return new ObjectMemory(
classEntity.newObject(env, env.trace(), true, new StringMemory(path), args[1])
);
}
@Signature({@Arg("protocol"), @Arg("className")})
public static Memory register(Environment env, Memory... args) {
String protocol = args[0].toString();
String className = args[1].toString();
if (protocol.isEmpty()) {
throw new IllegalArgumentException("Argument 1 (protocol) must be not empty");
}
if (!protocol.matches("^[A-Za-z0-9]+$")) {
throw new IllegalArgumentException("Invalid Argument 1 (protocol)");
}
ClassEntity classEntity = env.fetchClass(className, true);
if (classEntity == null){
throw new IllegalArgumentException(Messages.ERR_CLASS_NOT_FOUND.fetch(className));
}
env.setUserValue(Stream.class.getName() + "#" + protocol, classEntity.getName());
return Memory.TRUE;
}
@Signature({@Arg("protocol")})
public static Memory unregister(Environment env, Memory... args) {
String protocol = args[0].toString();
if (protocol.isEmpty())
return Memory.FALSE;
return env.removeUserValue(Stream.class.getName() + "#" + protocol) ? Memory.TRUE : Memory.FALSE;
}
public static void initEnvironment(Environment env) {
registerProtocol(env, "file", FileStream.class);
registerProtocol(env, "php", MiscStream.class);
registerProtocol(env, "res", ResourceStream.class);
}
@Override
public String getResourceType() {
return "stream";
}
@Signature
public Memory __destruct(Environment env, Memory... args) throws IOException {
close(env, args);
return Memory.NULL;
}
@Signature({
@Arg(value = "callback", type = HintType.CALLABLE),
@Arg(value = "encoding", optional = @Optional("null"))
})
public Memory eachLine(Environment env, Memory... args) throws Throwable {
Invoker invoker = Invoker.create(env, args[0]);
InputStream is = getInputStream(env, this);
Scanner scanner = new Scanner(is, args[1].isNull() ? env.getDefaultCharset().name() : args[1].toString());
int count = 0;
while (scanner.hasNextLine()) {
Memory call = invoker.call(StringMemory.valueOf(scanner.nextLine()));
if (call.toBoolean()) {
break;
}
count++;
}
return LongMemory.valueOf(count);
}
/**
* Internal method.
* @return bool
*/
public boolean _isExternalResourceStream() {
return false;
}
public static void registerProtocol(Environment env, String protocol, Class<? extends Stream> clazz) {
env.setUserValue(Stream.class.getName() + "#" + protocol, ReflectionUtils.getClassName(clazz));
}
public static OutputStream getOutputStream(Environment env, Memory arg){
try {
if (arg.instanceOf(FileObject.class)){
return new FileOutputStream(arg.toObject(FileObject.class).file);
} else if (arg.instanceOf(Stream.class)){
return new StreamOutputStream(env, arg.toObject(Stream.class));
} else {
StreamOutputStream outputStream = new StreamOutputStream(env, Stream.create(env, arg.toString(), "w+"));
outputStream.autoClose = true;
return outputStream;
}
} catch (IOException e){
env.exception(WrapIOException.class, e.getMessage());
} catch (Throwable throwable) {
env.forwardThrow(throwable);
}
return null;
}
/**
* @param arg
* @return
*/
public static String getPath(Memory arg) {
if (arg.instanceOf(FileObject.class)){
return arg.toObject(FileObject.class).file.getPath();
} else if (arg.instanceOf(Stream.class)){
return arg.toObject(Stream.class).getPath();
} else
return arg.toString();
}
public static InputStream getInputStream(Environment env, Stream stream) {
if (stream instanceof ResourceStream) {
return ((ResourceStream) stream).getInputStream();
}
return new StreamInputStream(env, stream);
}
public static OutputStream getOutputStream(Environment env, Stream stream) {
return new StreamOutputStream(env, stream);
}
/**
* @param arg - File path or Stream object
* @return
*/
public static InputStream getInputStream(Environment env, Memory arg){
try {
if (arg.instanceOf(FileObject.class)){
return new FileInputStream(arg.toObject(FileObject.class).file);
} else if (arg.instanceOf(Stream.class)){
if (arg.instanceOf(ResourceStream.class)) {
return arg.toObject(ResourceStream.class).getInputStream();
}
return new StreamInputStream(env, arg.toObject(Stream.class));
} else {
StreamInputStream inputStream = new StreamInputStream(env, Stream.create(env, arg.toString(), "r"));
inputStream.autoClose = true;
return inputStream;
}
} catch (IOException e){
env.exception(WrapIOException.class, e.getMessage());
} catch (Throwable throwable) {
env.forwardThrow(throwable);
}
return null;
}
public static void closeStream(Environment env, InputStream inputStream){
if (inputStream instanceof FileInputStream
|| (inputStream instanceof StreamInputStream && ((StreamInputStream) inputStream).autoClose))
try {
inputStream.close();
} catch (IOException e) {
env.exception(WrapIOException.class, e.getMessage());
}
}
public static void closeStream(Environment env, OutputStream outputStream){
if (outputStream instanceof FileOutputStream
|| (outputStream instanceof StreamOutputStream && ((StreamOutputStream) outputStream).autoClose))
try {
outputStream.close();
} catch (IOException e) {
env.exception(WrapIOException.class, e.getMessage());
}
}
public static class StreamOutputStream extends OutputStream {
protected final Stream stream;
protected final Environment env;
protected boolean autoClose = false;
public StreamOutputStream(Environment env, Stream stream) {
this.stream = stream;
this.env = env;
}
@Override
public void write(int b) throws IOException {
stream.write(env, new BinaryMemory((byte)b), Memory.NULL);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if (off == 0 && len == b.length) {
stream.write(env, new BinaryMemory(b), Memory.NULL);
} else if (off == 0 && len != b.length) {
stream.write(env, new BinaryMemory(Arrays.copyOf(b, len)), Memory.NULL);
} else {
stream.write(env, new BinaryMemory(Arrays.copyOfRange(b, off, off + len)), Memory.NULL);
}
}
@Override
public void close() throws IOException {
super.close();
stream.close(env);
}
}
public static class StreamInputStream extends InputStream {
protected final Stream stream;
protected final Environment env;
protected boolean autoClose = false;
public StreamInputStream(Environment env, Stream stream) {
this.stream = stream;
this.env = env;
}
@Override
public int read() throws IOException {
Memory result = stream.read(env, Memory.CONST_INT_1);
return result.isString() ? result.getBinaryBytes(env.getDefaultCharset())[0] & 0xFF : -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
Memory result = stream.read(env, LongMemory.valueOf(len));
if (!result.isString())
return -1;
byte[] copy = result.getBinaryBytes(env.getDefaultCharset());
System.arraycopy(copy, 0, b, off, copy.length);
return copy.length;
}
@Override
public void close() throws IOException {
super.close();
stream.close(env);
}
}
}