package php.runtime.ext.core.classes.util;
import php.runtime.Memory;
import php.runtime.common.HintType;
import php.runtime.env.Environment;
import php.runtime.ext.core.classes.lib.ItemsUtils;
import php.runtime.ext.core.classes.stream.Stream;
import php.runtime.invoke.Invoker;
import php.runtime.lang.BaseObject;
import php.runtime.lang.ForeachIterator;
import php.runtime.lang.IObject;
import php.runtime.lang.spl.iterator.Iterator;
import php.runtime.memory.*;
import php.runtime.reflection.ClassEntity;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import static php.runtime.annotation.Reflection.*;
import static php.runtime.annotation.Runtime.FastMethod;
@Name("php\\util\\Flow")
public class WrapFlow extends BaseObject implements Iterator {
protected ForeachIterator selfIterator;
protected ForeachIterator iterator;
protected Worker worker;
protected boolean init;
protected boolean valid = true;
protected boolean withKeys = false;
public WrapFlow(Environment env, Iterable iterable) {
this(env, ForeachIterator.of(env, iterable));
}
public WrapFlow(Environment env, ForeachIterator iterator) {
super(env);
this.iterator = iterator;
this.worker = new Worker() {
@Override
public boolean next(Environment env) {
return WrapFlow.this.iterator.next();
}
};
this.worker.setIterator(iterator);
}
public WrapFlow(Environment env, ForeachIterator iterator, Worker worker) {
super(env);
this.iterator = iterator;
this.worker = worker;
this.worker.setIterator(iterator);
}
public WrapFlow(Environment env, ClassEntity clazz) {
super(env, clazz);
}
protected ForeachIterator getSelfIterator(Environment env) {
if (this.selfIterator == null)
this.selfIterator = new ObjectMemory(this).getNewIterator(env);
if (!this.valid)
env.exception("Unable to iterate the flow repeatedly");
return this.selfIterator;
}
protected static Memory call(ForeachIterator iterator, Invoker invoker) {
if (invoker == null)
return iterator.getValue();
if (invoker.getArgumentCount() == 1)
return invoker.callNoThrow(iterator.getValue());
else
return invoker.callNoThrow(iterator.getValue(), iterator.getMemoryKey());
}
@Signature({
@Arg(value = "collection", type = HintType.TRAVERSABLE)
})
public Memory __construct(Environment env, Memory... args) {
ForeachIterator iterator = args[0].toImmutable().getNewIterator(env);
this.iterator = iterator;
this.worker = new Worker() {
@Override
public boolean next(Environment env) {
return WrapFlow.this.iterator.next();
}
};
this.worker.setIterator(iterator);
return Memory.NULL;
}
@Signature
public Memory withKeys(Environment env, Memory... args) {
withKeys = true;
return new ObjectMemory(this);
}
@FastMethod
@Signature
public static Memory ofEmpty(Environment env, Memory... args) {
return new ObjectMemory(new WrapFlow(env, new ArrayMemory().getNewIterator(env)));
}
@FastMethod
@Signature({
@Arg(value = "collection", type = HintType.TRAVERSABLE)
})
public static Memory of(Environment env, Memory... args) {
return new ObjectMemory(new WrapFlow(env, args[0].toImmutable().getNewIterator(env)));
}
@FastMethod
@Signature({
@Arg("from"),
@Arg("to"),
@Arg(value = "step", optional = @Optional("1"))
})
public static Memory ofRange(Environment env, Memory... args) {
final int from = args[0].toInteger();
final int to = args[1].toInteger();
final int step = args[2].toInteger() < 1 ? 1 : args[2].toInteger();
return new ObjectMemory(new WrapFlow(env, new ForeachIterator(false, false, false) {
protected int i;
@Override
public void reset() {
this.i = from;
currentKeyMemory = LongMemory.valueOf(-1);
currentKey = currentKeyMemory;
}
@Override
protected boolean init() {
reset();
return true;
}
@Override
protected boolean nextValue() {
if (i <= to) {
currentValue = LongMemory.valueOf(i);
if (currentKey == null)
currentKey = LongMemory.valueOf(0);
currentKey = ((LongMemory)currentKey).inc();
currentKeyMemory = null;
i += step;
return true;
}
return false;
}
@Override
protected boolean prevValue() {
return false;
}
}));
}
@FastMethod
@Signature({
@Arg(value = "stream", typeClass = Stream.CLASS_NAME),
@Arg(value = "chunkSize", optional = @Optional("1"))
})
public static Memory ofStream(final Environment env, Memory... args) {
final Stream stream = args[0].toObject(Stream.class);
final int chunkSize = args[1].toInteger() < 1 ? 1 : args[1].toInteger();
return new ObjectMemory(new WrapFlow(env, new ForeachIterator(false, false, false) {
protected boolean eof() {
env.pushCall(stream, "eof");
try {
return stream.eof(env).toBoolean();
} finally {
env.popCall();
}
}
@Override
protected boolean init() {
currentKey = Memory.CONST_INT_M1;
return !eof();
}
@Override
protected boolean nextValue() {
if (eof())
return false;
env.pushCall(stream, "read", LongMemory.valueOf(chunkSize));
try {
currentValue = stream.read(env, LongMemory.valueOf(chunkSize));
currentKey = ((LongMemory)currentKey).inc();
} catch (IOException e) {
env.catchUncaught(e);
} finally {
env.popCall();
}
return true;
}
@Override
protected boolean prevValue() {
return false;
}
@Override
public void reset() {
currentKey = Memory.CONST_INT_M1;
env.pushCall(stream, "seek", Memory.CONST_INT_0);
try {
stream.seek(env, Memory.CONST_INT_0);
} catch (IOException e) {
env.catchUncaught(e);
} finally {
env.popCall();
}
}
}));
}
@FastMethod
@Signature({
@Arg("string"),
@Arg(value = "chunkSize", optional = @Optional("1"))
})
public static Memory ofString(Environment env, Memory... args) {
final String string = args[0].toString();
final int chunkSize = args[1].toInteger() < 1 ? 1 : args[1].toInteger();
return new ObjectMemory(new WrapFlow(env, new ForeachIterator(false, false, false) {
protected int i = 0;
protected int length = string.length();
@Override
public void reset() {
this.i = 0;
}
@Override
protected boolean init() {
this.reset();
return length > 0;
}
@Override
protected boolean nextValue() {
if (i < length) {
int endIndex = i + chunkSize;
if (endIndex > length)
endIndex = length;
currentValue = chunkSize == 1
? StringMemory.valueOf(string.charAt(i))
: StringMemory.valueOf(string.substring(i, endIndex));
if (currentKeyMemory == null)
currentKeyMemory = LongMemory.valueOf(0);
currentKeyMemory = currentKeyMemory.inc();
currentKey = currentKeyMemory;
i += chunkSize;
return true;
}
return false;
}
@Override
protected boolean prevValue() {
return false;
}
}));
}
@Signature(@Arg(value = "collection", type = HintType.TRAVERSABLE))
public Memory append(Environment env, Memory... args) {
final ForeachIterator appendIterator = args[0].toImmutable().getNewIterator(env);
final ForeachIterator iterator = getSelfIterator(env);
return new ObjectMemory(new WrapFlow(env, new ForeachIterator(false, false, false) {
protected boolean applyAppended = false;
@Override
public void reset() {
iterator.reset();
appendIterator.reset();
applyAppended = false;
}
@Override
protected boolean init() {
return true;
}
@Override
protected boolean nextValue() {
ForeachIterator it = appendIterator;
boolean r = false;
if (!applyAppended) {
it = iterator;
r = it.next();
if (!r) {
applyAppended = true;
it = appendIterator;
}
} else
applyAppended = true;
if (applyAppended)
r = it.next();
return r;
}
@Override
protected boolean prevValue() {
return false;
}
@Override
public Object getKey() {
return applyAppended ? appendIterator.getKey() : iterator.getKey();
}
@Override
public Memory getMemoryKey() {
return applyAppended ? appendIterator.getMemoryKey() : iterator.getMemoryKey();
}
@Override
public Memory getValue() {
return applyAppended ? appendIterator.getValue() : iterator.getValue();
}
}));
}
@Override
@Signature
public Memory current(Environment env, Memory... args) {
if (!valid)
return Memory.NULL;
if (!init)
rewind(env, args);
return worker.current(env);
}
@Override
@Signature
public Memory key(Environment env, Memory... args) {
return worker.key(env);
}
@Override
@Signature
public Memory next(Environment env, Memory... args) {
if (!valid)
return Memory.NULL;
if (!worker.next(env))
valid = false;
return Memory.NULL;
}
@Override
@Signature
public Memory rewind(Environment env, Memory... args) {
if (init) {
worker.reset();
return Memory.NULL;
//throw new IllegalStateException("Cannot rewind() collection object");
}
init = true;
if (!worker.next(env)){
valid = false;
return Memory.NULL;
}
valid = true;
return Memory.NULL;
}
@Override
@Signature
public Memory valid(Environment env, Memory... args) {
return valid ? Memory.TRUE : Memory.FALSE;
}
@Signature
public Memory count(Environment env, Memory... args) {
int cnt = 0;
ForeachIterator iterator = getSelfIterator(env);
while (iterator.next()) cnt++;
return LongMemory.valueOf(cnt);
}
@Signature(@Arg(value = "comparator", type = HintType.CALLABLE, optional = @Optional("null")))
public Memory sort(Environment env, Memory... args) {
return ItemsUtils.sort(env, new ObjectMemory(this), args[0], this.withKeys ? Memory.TRUE : Memory.FALSE);
}
@Signature(@Arg(value = "comparator", type = HintType.CALLABLE, optional = @Optional("null")))
public Memory sortByKeys(Environment env, Memory... args) {
return ItemsUtils.sortByKeys(env, new ObjectMemory(this), args[0], this.withKeys ? Memory.TRUE : Memory.FALSE);
}
@Signature
public Memory toArray(Environment env, Memory... args) {
ForeachIterator iterator = getSelfIterator(env);
ArrayMemory r = new ArrayMemory();
while (iterator.next()) {
if (withKeys)
r.put(iterator.getKey(), iterator.getValue());
else
r.add(iterator.getValue());
}
return r.toConstant();
}
@Signature(@Arg(value = "separator"))
public Memory toString(Environment env, Memory... args) {
String sep = args[0].toString();
ForeachIterator iterator = getSelfIterator(env);
StringBuilderMemory sb = new StringBuilderMemory();
int i = 0;
while (iterator.next()) {
if (i != 0)
sb.append(sep);
sb.append(iterator.getValue());
i++;
}
return sb;
}
@Signature(@Arg(value = "callback", type = HintType.CALLABLE))
public Memory reduce(Environment env, Memory... args) {
Invoker invoker = Invoker.valueOf(env, null, args[0]);
ForeachIterator iterator = getSelfIterator(env);
Memory r = Memory.NULL;
int argCount = invoker.getArgumentCount();
while (iterator.next()) {
if (argCount < 3)
r = invoker.callNoThrow(r, iterator.getValue());
else
r = invoker.callNoThrow(r, iterator.getValue(), iterator.getMemoryKey());
}
return r;
}
@Signature(@Arg(value = "callback", type = HintType.CALLABLE))
public Memory each(Environment env, Memory... args) {
ForeachIterator iterator = getSelfIterator(env);
Invoker invoker = Invoker.valueOf(env, null, args[0]);
int cnt = 0;
while (iterator.next()) {
cnt++;
if (call(iterator, invoker).toValue() == Memory.FALSE)
break;
}
return LongMemory.valueOf(cnt);
}
@Signature({
@Arg(value = "sliceSize"),
@Arg(value = "callback", type = HintType.CALLABLE)
})
public Memory eachSlice(Environment env, Memory... args) {
Invoker invoker = Invoker.valueOf(env, null, args[1]);
ArrayMemory tmp = new ArrayMemory();
int cnt = 0, r = 0, n = args[0].toInteger();
while (iterator.next()) {
cnt++;
if (withKeys)
tmp.refOfIndex(iterator.getMemoryKey()).assign(iterator.getValue());
else
tmp.add(iterator.getValue());
if (cnt >= n) {
r++;
Memory ret = invoker.callNoThrow(tmp);
tmp = new ArrayMemory();
if (ret.toValue() == Memory.FALSE)
break;
cnt = 0;
}
}
if (tmp.size() > 0) {
r++;
invoker.callNoThrow(tmp);
}
return LongMemory.valueOf(r);
}
@Signature({
@Arg(value = "callback", type = HintType.CALLABLE)
})
public Memory group(Environment env, Memory... args) {
final Invoker invoker = Invoker.valueOf(env, null, args[0]);
final ForeachIterator iterator = getSelfIterator(env);
return new ObjectMemory(new WrapFlow(env, new ForeachIterator(false, false, false) {
protected Memory key;
@Override
protected boolean init() {
key = Memory.CONST_INT_M1;
return true;
}
@Override
protected boolean nextValue() {
Memory r = new ArrayMemory();
boolean done = false;
while (iterator.next()) {
done = true;
if (withKeys)
r.refOfIndex(iterator.getMemoryKey()).assign(iterator.getValue());
else
r.refOfPush().assign(iterator.getValue());
if (call(iterator, invoker).toBoolean())
break;
}
if (done) {
currentKeyMemory = key.inc();
currentKey = currentKeyMemory;
currentValue = r;
}
return done;
}
@Override
protected boolean prevValue() {
return false;
}
@Override
public void reset() {
iterator.reset();
key = Memory.CONST_INT_M1;
}
}));
}
@Signature
public IObject onlyKeys(Environment env, ForeachIterator iterator) {
return onlyKeys(env, iterator, false);
}
@Signature
public IObject onlyKeys(Environment env, ForeachIterator iterator, final boolean ignoreCase) {
final Set<String> keys = new HashSet<String>();
for (Memory el : iterator) {
if (ignoreCase) {
keys.add(el.toString());
} else {
keys.add(el.toString().toLowerCase());
}
}
return new WrapFlow(env, getSelfIterator(env), new Worker() {
@Override
public boolean next(Environment env) {
while (iterator.next()) {
String key = iterator.getKey().toString();
if (ignoreCase) {
key = key.toLowerCase();
}
if (keys.contains(key)) {
return true;
}
}
return false;
}
});
}
@Signature(@Arg(value = "filter", type = HintType.CALLABLE, optional = @Optional("NULL")))
public Memory find(Environment env, Memory... args) {
final Invoker invoker = Invoker.valueOf(env, null, args[0]);
return new ObjectMemory(new WrapFlow(env, getSelfIterator(env), new Worker() {
@Override
public boolean next(final Environment env) {
while (iterator.next()) {
if (call(iterator, invoker).toBoolean())
return true;
}
return false;
}
}));
}
@Signature(@Arg(value = "filter", type = HintType.CALLABLE, optional = @Optional("NULL")))
public Memory findOne(Environment env, Memory... args) {
final Invoker invoker = Invoker.valueOf(env, null, args[0]);
ForeachIterator iterator = getSelfIterator(env);
while (iterator.next()) {
if (call(iterator, invoker).toBoolean())
return iterator.getValue();
}
return Memory.NULL;
}
@Signature({
@Arg(value = "value"),
@Arg(value = "strict", optional = @Optional("false"))
})
public Memory findValue(Environment env, Memory... args) {
ForeachIterator iterator = getSelfIterator(env);
boolean strict = args[1].toBoolean();
while (iterator.next()) {
if (strict && iterator.getValue().identical(args[0])) {
return iterator.getMemoryKey();
} else if (iterator.getValue().equal(args[0])) {
return iterator.getMemoryKey();
}
}
return Memory.NULL;
}
@Signature
public Memory keys(Environment env, Memory... args) {
return new ObjectMemory(new WrapFlow(env, getSelfIterator(env), new Worker() {
Memory current;
Memory key;
@Override
public boolean next(final Environment env) {
if (iterator.next()) {
current = iterator.getMemoryKey();
key = key == null ? Memory.CONST_INT_0 : key.inc();
return true;
}
return false;
}
@Override
public Memory current(Environment env) {
return current == null ? Memory.NULL : current;
}
@Override
public Memory key(Environment env) {
return key == null ? Memory.NULL : key;
}
@Override
public void reset() {
key = null;
current = null;
}
}));
}
@Signature(@Arg(value = "callback", type = HintType.CALLABLE))
public Memory map(Environment env, Memory... args) {
final Invoker invoker = Invoker.valueOf(env, null, args[0]);
return new ObjectMemory(new WrapFlow(env, getSelfIterator(env), new Worker() {
Memory current;
@Override
public boolean next(final Environment env) {
if (iterator.next()) {
current = call(iterator, invoker);
return true;
}
return false;
}
@Override
public Memory current(Environment env) {
return current == null ? Memory.NULL : current;
}
}));
}
@Signature(@Arg(value = "n"))
public Memory skip(Environment env, Memory... args) {
final int skip = args[0].toInteger();
if (skip <= 0)
return new ObjectMemory(this);
return new ObjectMemory(new WrapFlow(env, getSelfIterator(env), new Worker() {
protected int i = 0;
@Override
public boolean next(final Environment env) {
while (iterator.next()) {
i++;
if (i > skip)
return true;
}
return false;
}
}));
}
@Signature(@Arg("max"))
public Memory limit(Environment env, Memory... args) {
final int limit = args[0].toInteger();
return new ObjectMemory(new WrapFlow(env, getSelfIterator(env), new Worker() {
protected int i = 0;
@Override
public boolean next(final Environment env) {
if (i >= limit)
return false;
if (iterator.next()) {
i++;
return true;
}
return false;
}
}));
}
@Override
public ForeachIterator getNewIterator(Environment env, boolean getReferences, boolean getKeyReferences) {
return ObjectMemory.valueOf(this).getNewIterator(env, getReferences, getKeyReferences);
}
@Override
public ForeachIterator getNewIterator(Environment env) {
return ObjectMemory.valueOf(this).getNewIterator(env);
}
abstract static protected class Worker {
protected ForeachIterator iterator;
public void setIterator(ForeachIterator iterator) {
this.iterator = iterator;
}
abstract public boolean next(Environment env);
public Memory current(Environment env) {
return iterator.getValue();
}
public Memory key(Environment env) {
return iterator.getMemoryKey();
}
public void reset() {
iterator.reset();
}
}
}