/*
* Copyright 2008-2016 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 com.nominanuda.zen.obj;
import static com.nominanuda.zen.common.Check.illegalargument;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import com.nominanuda.zen.stereotype.Factory;
class AnyNioDeserializer implements Subscriber<ByteBuffer>, Factory<Any> {
private static final byte EOF = 0;
private BinRange range;
private int len;
private int pos = 0;
private byte cur = 0;
private byte incr() {
if(pos < len -1) {
cur = range.at(++pos);
return cur;
} else if(pos < len) {
++pos;
cur = EOF;
return cur;
} else {
throw new IllegalStateException("trying to move beyond eof");
}
}
private byte stayOrIncrToNextNonWs() {
while(true) {
switch (cur) {
case ' ':
case '\n':
case '\t':
case '\r':
break;
default:
return cur;
}
incr();
}
}
private void eof() {
while(pos < len - 1) {
switch (cur) {
case ' ':
case '\n':
case '\t':
case '\r':
break;
default:
throw new IllegalArgumentException("trailing garbage at position "+pos);
}
incr();
}
}
public Any expr() {
stayOrIncrToNextNonWs();
Any res = any();
eof();
return res;
}
private Any any() {
stayOrIncrToNextNonWs();
switch (cur) {
case '{':
return object();
case '[':
return array();
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return num();
case '"':
return str();
case 't':
return _true();
case 'f':
return _false();
case 'n':
return _null();
default:
throw new IllegalArgumentException("unespected char "+((char)cur)+" at position "+pos);
}
}
private Val _true() {
if(incr() != 'r' || incr() != 'u' || incr() != 'e') {
illegalargument.fail("invalid literal at position "+pos);
}
Val v = new ValImpl(range.range(pos - 3, 4), JsonType.bool, true);
incr();
return v;
}
private Val _false() {
if(incr() != 'a' || incr() != 'l' || incr() != 's' || incr() != 'e') {
illegalargument.fail("invalid literal at position "+pos);
}
Val v = new ValImpl(range.range(pos - 4, 5), JsonType.bool, false);
incr();
return v;
}
private Val _null() {
if(incr() != 'u' || incr() != 'l' || incr() != 'l') {
illegalargument.fail("invalid literal at position "+pos);
}
Val v = new ValImpl(range.range(pos - 3, 4), JsonType.nil, null);
incr();
return v;
}
private Val str() {
int start = pos;
boolean backSlashSeen = false;
while(true) {
incr();
if(backSlashSeen) {
switch (cur) {
case '\\':
case '"':
case '/':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
backSlashSeen = false;
break;
case 'u':
for(int j = 0; j < 4; j++) {
illegalargument.assertTrue(incr() >= '/' && cur <= ':', "illegal unicode digit "+cur+" at position "+pos);
}
backSlashSeen = false;
break;
default:
throw new IllegalArgumentException("illegal escape code "+cur+" at position "+pos);
}
} else {
switch (cur) {
case '\\':
backSlashSeen = true;
break;
case '"':
incr();
Val v = new ValImpl(range.range(start, pos - start), JsonType.str);
return v;
default:
break;
}
}
}
}
private Val num() {
int start = pos;
boolean dotSeen = false;
while(true) {
incr();
switch (cur) {
case '.':
if(dotSeen) {
throw new IllegalArgumentException("malformed number at position"+pos);
} else {
dotSeen = true;
}
break;
case '-':
if(pos != start) {
throw new IllegalArgumentException("usespected not leading '-' in number at position"+pos);
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
case EOF:
default:
//decr();
return new ValImpl(range.range(start, pos - start), JsonType.num);
}
}
}
private Arr array() {
int start = pos;
boolean first = true;
LinkedList<Any> members = new LinkedList<>();
incr();
while(true) {
stayOrIncrToNextNonWs();
if(cur == ']') {
Arr a = new ArrImpl(range.range(start, pos - start), members);
incr();
return a;
}
if(! first) {
comma();
}
stayOrIncrToNextNonWs();
Any memberVal = any();
members.add(memberVal);
first = false;
}
}
private Obj object() {
int start = pos;
boolean first = true;
LinkedHashMap<Key,Any> members = new LinkedHashMap<>();
incr();
while(true) {
stayOrIncrToNextNonWs();
if(cur == '}') {
Obj o = new ObjImpl(range.range(start, pos - start), members);
incr();
return o;
}
if(! first) {
comma();
}
stayOrIncrToNextNonWs();
Key memberKey = keyAndDot();
stayOrIncrToNextNonWs();
Any memberVal = any();
members.put(memberKey, memberVal);
first = false;
}
}
private void comma() {
if(cur != ',') {
throw new IllegalArgumentException("not found expected comma at "+pos);
}
incr();
}
private Key keyAndDot() {
Val k = str();
stayOrIncrToNextNonWs();
if(cur != ':') {
throw new IllegalArgumentException("not found expected colon at "+pos);
}
incr();
//TODO
return new KeyImpl(((ValImpl)k).range());
}
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public final void onNext(ByteBuffer t) {
events.add(t);
}
@Override
public void onError(Throwable t) {
events.clear();
}
@Override
public void onComplete() {
ByteBuf bb = Unpooled.copiedBuffer(events.toArray(new ByteBuffer[events.size()]));
range = new BinRange(bb);
len = range.getLength();
cur = range.at(pos);
}
@Override
public Any get() {
return expr();
}
private final List<ByteBuffer> events = new LinkedList<ByteBuffer>();
}