All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sandius.rembulan.lib.impl.ArgumentIterator Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
/*
 * Copyright 2016 Miroslav Janíček
 *
 * 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 net.sandius.rembulan.lib.impl;

import net.sandius.rembulan.ByteString;
import net.sandius.rembulan.Conversions;
import net.sandius.rembulan.Table;
import net.sandius.rembulan.Userdata;
import net.sandius.rembulan.ValueTypeNamer;
import net.sandius.rembulan.lib.BadArgumentException;
import net.sandius.rembulan.lib.UnexpectedArgumentException;
import net.sandius.rembulan.runtime.Coroutine;
import net.sandius.rembulan.runtime.LuaFunction;
import net.sandius.rembulan.util.Check;

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

import static net.sandius.rembulan.LuaFormat.TYPENAME_FUNCTION;
import static net.sandius.rembulan.LuaFormat.TYPENAME_NUMBER;
import static net.sandius.rembulan.LuaFormat.TYPENAME_STRING;
import static net.sandius.rembulan.LuaFormat.TYPENAME_TABLE;
import static net.sandius.rembulan.LuaFormat.TYPENAME_THREAD;
import static net.sandius.rembulan.LuaFormat.TYPENAME_USERDATA;

public class ArgumentIterator implements Iterator {

	// TODO: clean up!

	private final ValueTypeNamer namer;

	private final String name;
	private final Object[] args;
	private int index;

	private ArgumentIterator(ValueTypeNamer namer, String name, Object[] args, int index) {
		this.namer = Check.notNull(namer);
		this.name = Check.notNull(name);
		this.args = Check.notNull(args);
		this.index = Check.nonNegative(index);
	}

	public ArgumentIterator(ValueTypeNamer namer, String name, Object[] args) {
		this(namer, name, args, 0);
	}

	@Override
	public boolean hasNext() {
		return index < args.length;
	}

	@Override
	public Object next() {
		Object o = peek();
		skip();
		return o;
	}

	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}

	public ValueTypeNamer namer() {
		return namer;
	}

	public int at() {
		// 0-based!
		return index;
	}

	public void skip() {
		index += 1;
	}

	public void goTo(int index) {
		this.index = index;
	}

	public void rewind() {
		goTo(0);
	}

	public int size() {
		return args.length;
	}

	public int tailSize() {
		return Math.max(args.length - index, 0);
	}

	public Object[] getAll() {
		return args;
	}

	public Object[] getTail() {
		return Arrays.copyOfRange(args, index, args.length);
	}

	protected BadArgumentException badArgument(Throwable cause) {
		return new BadArgumentException(index + 1, name, cause);
	}

	protected BadArgumentException badArgument(int argIndex, String message) {
		return new BadArgumentException(argIndex, name, message);
	}

	protected BadArgumentException badArgument(String message) {
		return new BadArgumentException(index + 1, name, message);
	}

	protected Object peek() {
		if (index < args.length) {
			return args[index];
		}
		else {
			throw new NoSuchElementException("value expected");
		}
	}

	protected Object peek(String expectedType) {
		try {
			return peek();
		}
		catch (NoSuchElementException ex) {
			throw new UnexpectedArgumentException(expectedType, "no value");
		}
	}

	// guaranteed not to return null
	protected Number peekNumber() {
		Object arg = peek(TYPENAME_NUMBER.toString());
		Number n = Conversions.numericalValueOf(arg);
		if (n != null) {
			return n;
		}
		else {
			throw new UnexpectedArgumentException(TYPENAME_NUMBER.toString(), namer.typeNameOf(arg).toString());
		}
	}

	public Object peekOrNil() {
		if (hasNext()) {
			return peek();
		}
		else {
			return null;
		}
	}

	public  T nextStrict(ByteString expectedTypeName, Class clazz) {
		final T result;
		try {
			Object arg = peek(expectedTypeName.toString());
			if (arg != null && clazz.isAssignableFrom(arg.getClass())) {
				@SuppressWarnings("unchecked")
				T typed = (T) arg;
				result = typed;
			}
			else {
				throw new UnexpectedArgumentException(expectedTypeName.toString(), namer.typeNameOf(arg).toString());
			}
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public  T nextStrictOrNil(String expectedTypeName, Class clazz) {
		final T result;
		try {
			Object arg = peek(expectedTypeName);
			if (arg == null || clazz.isAssignableFrom(arg.getClass())) {
				@SuppressWarnings("unchecked")
				T typed = (T) arg;
				result = typed;
			}
			else {
				throw new UnexpectedArgumentException(expectedTypeName, namer.typeNameOf(arg).toString());
			}
		}
		catch (RuntimeException ex) {
			String message = "nil or " + expectedTypeName + " expected";
			throw badArgument(message);
		}
		skip();
		return result;
	}

	public Object nextAny() {
		final Object result;
		try {
			result = peek();
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	// does not distinguish missing value vs nil
	// this should be the last argument to check, otherwise use peekOrNil() followed by skip()
	public Object optNextAny() {
		if (hasNext()) {
			return nextAny();
		}
		else {
			return null;
		}
	}

	public Number nextNumber() {
		final Number result;
		try {
			result = peekNumber();
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public long nextInteger() {
		final long result;
		try {
			result = Conversions.toIntegerValue(peekNumber());
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public double nextFloat() {
		return nextNumber().doubleValue();
	}

	public int nextInt() {
		return (int) nextInteger();
	}

	public int optNextInt(int defaultValue) {
		if (hasNext()) {
			Object o = peek();
			if (o == null) {
				skip();
				return defaultValue;
			}
			else {
				return nextInt();
			}
		}
		else {
			return defaultValue;
		}
	}

	public int nextIntRange(String rangeName, int min, int max) {
		final int result;
		try {
			long value = Conversions.toIntegerValue(peekNumber());
			if (value >= min && value <= max) {
				result = (int) value;
			}
			else {
				throw new IndexOutOfBoundsException(rangeName + " out of range");
			}
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public boolean optNextBoolean(boolean defaultValue) {
		if (hasNext()) {
			Object o = peek();
			boolean result = Conversions.booleanValueOf(o);
			skip();
			return result;
		}
		else {
			return defaultValue;
		}
	}

	public ByteString nextString() {
		final ByteString result;
		try {
			Object arg = peek(TYPENAME_STRING.toString());
			ByteString v = Conversions.stringValueOf(arg);
			if (v != null) {
				result = v;
			}
			else {
				throw new UnexpectedArgumentException(TYPENAME_STRING.toString(), namer.typeNameOf(arg).toString());
			}
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public ByteString nextStrictString() {
		final ByteString result;
		try {
			Object arg = peek(TYPENAME_STRING.toString());
			if (arg instanceof ByteString) {
				result = (ByteString) arg;
			}
			else if (arg instanceof String) {
				result = ByteString.of((String) arg);
			}
			else {
				throw new UnexpectedArgumentException(TYPENAME_STRING.toString(), namer.typeNameOf(arg).toString());
			}
		}
		catch (RuntimeException ex) {
			throw badArgument(ex);
		}
		skip();
		return result;
	}

	public LuaFunction nextFunction() {
		return nextStrict(TYPENAME_FUNCTION, LuaFunction.class);
	}

	public Table nextTable() {
		return nextStrict(TYPENAME_TABLE, Table.class);
	}

	public Table nextTableOrNil() {
		return nextStrictOrNil(TYPENAME_TABLE.toString(), Table.class);
	}

	public Coroutine nextCoroutine() {
		return nextStrict(TYPENAME_THREAD, Coroutine.class);
	}

	public  T nextUserdata(String typeName, Class clazz) {
		return nextStrict(ByteString.of(typeName), clazz);
	}

	public Userdata nextUserdata() {
		return nextUserdata(TYPENAME_USERDATA.toString(), Userdata.class);
	}

}