/*****************************************************************************
* Copyright (C) Codehaus.org *
* ------------------------------------------------------------------------- *
* 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.ion.rosetta.misc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import net.ion.rosetta.Parser;
import net.ion.rosetta.Parsers;
import net.ion.rosetta.functors.Binary;
import net.ion.rosetta.functors.Map;
import net.ion.rosetta.functors.Unary;
import net.ion.rosetta.util.Checks;
import net.ion.rosetta.util.Lists;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
/**
* Allows mapping arbitrary number of {@link Parser} results to an object of
* {@code T} type-safely using the {@code map} method defined in subclass, or
* the curried constructor defined in the target class.
*
* <p>
* The {@link #sequence(Parser[])} method creates a parser that runs a series of
* parser objects and maps the return values via the {@code map} method defined
* in the subclass (or the curried constructor in target class).
*
* <p>
* For example:
*
* <pre>
* Parser<Foo> fooParser = new Mapper<Foo>() {
* Foo map(String s, Integer n, Bar bar, Baz baz) {
* return new Foo(s, n, bar, baz);
* }
* }.sequence(stringParser, integerParser, barParser, bazParser);
* </pre>
*
* <p>
* Alternatively, instead of sequencing the operands and operators directly, a
* parser of {@link Unary} or {@link Binary} can be returned to cooperate with
* {@link net.ion.rosetta.OperatorTable}, {@link Parser#prefix(Parser)},
* {@link Parser#postfix(Parser)}, {@link Parser#infixl(Parser)},
* {@link Parser#infixn(Parser)} or {@link Parser#infixr(Parser)}.
*
* <p>
* Another useful utility provide by this class, is the
* {@link #curry(Class, Object[])} method. It allows currying constructor
* defined in the target class so that no explicit mapping code is needed. For
* the above example, it can be more concisely written as:
*
* <pre>
* Parser<Foo> fooParser = Mapper.curry(Foo.class).sequence(stringParser, integerParser, barParser, bazParser);
* </pre>
*
* <p>
* NOTE: cglib is required on the classpath.
*
* @author Ben Yu
*/
public abstract class Mapper<T> {
private static final ConcurrentHashMap<Class<?>, FastMethod> mapMethods = new ConcurrentHashMap<Class<?>, FastMethod>();
final Object source;
final Invokable invokable;
/**
* Default constructor that uses the {@code map} method defined in subclass
* for mapping.
*/
protected Mapper() {
FastMethod method = mapMethod(getClass());
this.source = method;
this.invokable = Invokables.method(this, method);
}
Mapper(Object source, Invokable invokable) {
this.source = source;
this.invokable = invokable;
}
/**
* A {@link Mapper} that curries the only public constructor defined in
* {@code clazz} and invokes it with parameters returned by the sequentially
* executed {@link Parser} objects. For example, to parse an expression with
* binary operator and create an instance of the following object model:
*
* <pre>
* class BinaryExpression implements Expression {
* public BinaryExpression(Expression left, Operator op, Expression right) {...}
* ...
* }
* </pre>
*
* The parser that parses this expression with binary operator can be
* written as:
*
* <pre>
* Parser<Expression> binary(Parser<Expression> operand, Parser<Operator> operator) {
* return Sequencer.<Expression> curry(BinaryExpression.class).sequence(operand, operator, operand);
* }
* </pre>
*
* Which is equivalent to the more verbose but reflection-free version:
*
* <pre>
* Parser<Expression> binary(Parser<Expression> expr, Parser<Operator> op) {
* return Parsers.sequence(expr, op, expr, new Map3<Expression, Operator, Expression, Expression>() {
* public Expression map(Expression left, Operator op, Expression right) {
* return new BinaryExpression(left, op, right);
* }
* });
* }
* </pre>
*/
public static <T> Mapper<T> curry(Class<? extends T> clazz, Object... curryArgs) {
return Curry.of(clazz, curryArgs);
}
/**
* A {@link Parser} that sequentially runs {@code parsers} and invokes the
* underlying {@code map} method or curried constructor using the returned
* values.
*/
public final Parser<T> sequence(Parser<?>... parsers) {
parsers = toArray(mergeSkipped(parsers));
int providedParameters = parsers.length;
int expectedParameters = expectedParams();
Checks.checkArgument(providedParameters == expectedParameters, "%s parameters expected for sequencing, %s provided.", expectedParameters, providedParameters);
return Parsers.array(parsers).map(asMap());
}
/**
* A {@link Parser} that returns a {@link Unary} instance that invokes the
* underlying {@code map} method or curried constructor with the only
* parameter of the {@link Unary#map(Object)} method.
*/
public final Parser<Unary<T>> unary() {
return Parsers.constant(asUnary());
}
/**
* A {@link Parser} that returns a {@link Binary} instance that invokes the
* underlying {@code map} method or curried constructor with the two
* parameters of the {@link Binary#map(Object, Object)} method.
*/
public final Parser<Binary<T>> binary() {
return Parsers.constant(asBinary());
}
/**
* A {@link Parser} that runs {@code operator} and returns a {@link Unary}
* instance, which will pass along the return value of {@code operator}
* followed by its only parameter to the underlying {@code map} method or
* curried constructor.
*
* <p>
* For example:
*
* <pre>
* Parser<Unary<Expression>> prefixOperator(Parser<Operator> op) {
* return new Mapper<Expression>() {
* Expression map(Operator operator, Expression operand) {
* return new PrefixExpression(operator, operand);
* }
* }.prefix(op);
* }
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Unary<Expression>> prefixOperator(Parser<Operator> op) {
* return Mapper.<Expression> curry(PrefixExpression.class).prefix(op);
* }
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#prefix(Parser)}
* or {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Unary<T>> prefix(Parser<?> operator) {
checkNotSkipped(operator);
checkFutureParameters(Unary.class, 2);
return operator.map(new Map<Object, Unary<T>>() {
public Unary<T> map(final Object pre) {
return new Unary<T>() {
public T map(T v) {
return apply(pre, v);
}
};
}
});
}
/**
* A {@link Parser} that runs {@code operator} sequentially and returns a
* {@link Unary} instance, which will pass along the return values of
* {@code operator} followed by its only parameter to the underlying
* {@code map} method or curried constructor.
*
* <p>
* Use this version instead of {@link #prefix(Parser)} if the operator is
* composed of more than one components. For example, the Java label
* statement (like {@code here:}) can be modeled as a prefix operator
* applied to statements:
*
* <pre>
* Parser<Unary<Statement>> label = new Mapper<Statement>() {
* Statement map(String label, Statement statement) {
* return new LabelStatement(label, statement);
* }
* }.prefix(Terminals.STRING, _(terminal(":")));
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Unary<Statement>> label = Mapper.<Statement> curry(LabelStatement.class).prefix(Terminals.STRING, _(terminal(":")));
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#prefix(Parser)}
* or {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Unary<T>> prefix(Parser<?>... operator) {
List<Parser<?>> operatorList = mergeSkipped(operator);
if (operatorList.size() == 1)
return prefix(operatorList.get(0));
checkFutureParameters(Unary.class, operatorList.size() + 1);
return Parsers.list(operatorList).map(new Map<List<Object>, Unary<T>>() {
public Unary<T> map(final List<Object> list) {
return new Unary<T>() {
public T map(T v) {
list.add(v);
return apply(list.toArray());
}
};
}
});
}
/**
* A {@link Parser} that runs {@code operator} and returns a {@link Unary}
* instance, which will pass along its only parameter followed by the return
* value of {@code operator} to the underlying {@code map} method or curried
* constructor.
*
* <p>
* For example:
*
* <pre>
* Parser<Binary<Expression>> postfixOperator(Parser<Operator> op) {
* return new Mapper<Expression>() {
* Expression map(Expression operand, Operator operator) {
* return new PostfixExpression(operand, operator);
* }
* }.postfix(op);
* }
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Unary<Expression>> postfixOperator(Parser<Operator> op) {
* return Mapper.<Expression> curry(PostfixExpression.class).postfix(op);
* }
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#postfix(Parser)}
* or {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Unary<T>> postfix(Parser<?> operator) {
checkNotSkipped(operator);
checkFutureParameters(Unary.class, 2);
return operator.map(new Map<Object, Unary<T>>() {
public Unary<T> map(final Object post) {
return new Unary<T>() {
public T map(T v) {
return apply(v, post);
}
};
}
});
}
/**
* A {@link Parser} that runs {@code operator} sequentially and returns a
* {@link Unary} instance, which will pass along its only parameter followed
* by the return values of {@code operator} to the underlying {@code map}
* method or curried constructor.
*
* <p>
* Use this version instead of {@link #postfix(Parser)} if the operator is
* composed of more than one components. For example, in order to parse an
* array slice syntax such as {@code array[from, to]}, where {@code array},
* {@code from} and {@code to} are all themselves expressions, the
* {@code [from, to]} part can be modeled as a postfix operator that turns
* the {@code array} expression to an array slice expression. The parser can
* be written as:
*
* <pre>
* Parser<Unary<Expression>> slice(Parser<Expression bound) {
* return new Mapper<Expression>() {
* Expression map(Expression array, Expression from, Expression to) {
* return new ArraySliceExpression(array, from, to);
* }
* }.postfix(_(terminal("[")), bound, _(terminal(",")), bound, _(terminal("]")));
* }
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Unary<Expression>> slice(Parser<Expression bound) {
* return Mapper.<Expression>curry(ArraySliceExpression.class)
* .postfix(_(terminal("[")), bound, _(terminal(",")), bound, _(terminal("]")));
* }
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#postfix(Parser)}
* or {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Unary<T>> postfix(Parser<?>... operator) {
operator = toArray(mergeSkipped(operator));
if (operator.length == 1)
return postfix(operator[0]);
checkFutureParameters(Unary.class, operator.length + 1);
return Parsers.array(operator).map(new Map<Object[], Unary<T>>() {
public Unary<T> map(final Object[] array) {
return new Unary<T>() {
public T map(T v) {
Object[] args = new Object[array.length + 1];
args[0] = v;
System.arraycopy(array, 0, args, 1, array.length);
return apply(args);
}
};
}
});
}
/**
* A {@link Parser} that runs {@code operator} and returns a {@link Binary}
* instance, which will pass along its first parameter, followed by the
* return value of {@code operator}, followed by its second parameter to the
* underlying {@code map} method or curried constructor.
*
* <p>
* For example:
*
* <pre>
* Parser<Binary<Expression>> infixOperator(Parser<Operator> op) {
* return new Mapper<Expression>() {
* Expression map(Expression left, Operator operator, Expression right) {
* return new InfixExpression(left, operand, right);
* }
* }.infix(op);
* }
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Binary<Expression>> infixOperator(Parser<Operator> op) {
* return Mapper.<Expression> curry(InfixExpression.class).infix(op);
* }
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#infixl(Parser)},
* {@link Parser#infixn(Parser)}, {@link Parser#infixr(Parser)} or
* {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Binary<T>> infix(Parser<?> operator) {
checkNotSkipped(operator);
checkFutureParameters(Binary.class, 3);
return operator.map(new Map<Object, Binary<T>>() {
public Binary<T> map(final Object op) {
return new Binary<T>() {
public T map(T left, T right) {
return apply(left, op, right);
}
};
}
});
}
/**
* A {@link Parser} that runs {@code operator} sequentially and returns a
* {@link Binary} instance, which will pass along its first parameter,
* followed by the return values of {@code operator}, followed by its second
* parameter to the underlying {@code map} method or curried constructor.
*
* <p>
* Use this version instead of {@link #infix(Parser)} if the operator is
* composed of more than one components. For example, in order to parse the
* Java ternary {@code ?:} operator, we can model the
* {@code ? consequence :} part as a right associative infix operator that
* binds the condition and the alternative expression together as a
* composite expression. The parser can be written as:
*
* <pre>
* Parser<Binary<Expression>> conditional(Parser<Expression> expr) {
* return new Mapper<Expression>() {
* Expression map(Expression condition, Expression consequence, Expression alternative) {
* return new ConditionalExpression(condition, consequence, alternative);
* }
* }.postfix(_(terminal("?")), expr, _(terminal(":")));
* }
* </pre>
*
* Or alternatively, by using the {@link #curry(Class, Object[])} method:
*
* <pre>
* Parser<Binary<Expression>> conditional(Parser<Expression> expr) {
* return Mapper.<Expression>.curry(ConditionalExpression.class)
* .postfix(_(terminal("?")), expr, _(terminal(":")));
* }
* </pre>
*
* <p>
* Useful when the returned parser is used in {@link Parser#infixl(Parser)},
* {@link Parser#infixn(Parser)}, {@link Parser#infixr(Parser)} or
* {@link net.ion.rosetta.OperatorTable}.
*/
public final Parser<Binary<T>> infix(Parser<?>... operator) {
operator = toArray(mergeSkipped(operator));
if (operator.length == 1)
return infix(operator[0]);
checkFutureParameters(Binary.class, operator.length + 2);
return Parsers.array(operator).map(new Map<Object[], Binary<T>>() {
public Binary<T> map(final Object[] array) {
return new Binary<T>() {
public T map(T left, T right) {
Object[] args = new Object[array.length + 2];
args[0] = left;
System.arraycopy(array, 0, args, 1, array.length);
args[args.length - 1] = right;
return apply(args);
}
};
}
});
}
/**
* Wraps {@code parser} so that it will be skipped when applied in
* {@link #sequence(Parser[])}, {@link #prefix(Parser[])} or
* {@link #postfix(Parser[])}. For example, the following code maps the two
* expressions after "if" and "else" to the constructor of
* {@code IfElseExpression} and skips the return values of the keyword "if"
* and "else".
*
* <pre>
* Parser<IfElseExpression> expression = curry(IfElseExpression.class).sequence(_(word("if")), expr, _(word("else")), expr);
* </pre>
*/
public static final Parser<?> _(Parser<?> parser) {
return parser.map(SKIP);
}
/** Returns the string representation of this object. */
@Override
public String toString() {
return source.toString();
}
void checkFutureParameters(Class<?> targetType, int providedParameters) {
checkFutureParameters(expectedParams(), targetType, providedParameters);
}
final void checkFutureParameters(int expectedParameters, Class<?> targetType, int providedParameters) {
Checks.checkArgument(providedParameters == expectedParameters, "Invalid curry: %s parameters expected by %s," + " %s will be provided by curried and explicit parameters of %s",
expectedParameters, invokable, providedParameters, targetType.getName());
}
int expectedParams() {
return invokable.parameterTypes().length;
}
final String name() {
return invokable.returnType().getName();
}
/**
* Returns a {@link Unary} instance that invokes the underlying {@code map}
* method or curried constructor with the only parameter of the
* {@link Unary#map(Object)} method.
*/
final Unary<T> asUnary() {
checkFutureParameters(Unary.class, 1);
return new Unary<T>() {
public T map(T v) {
return apply(v);
}
@Override
public String toString() {
return name();
}
};
}
/**
* Returns a {@link Binary} instance that invokes the underlying {@code map}
* method or curried constructor with the two parameters of the
* {@link Binary#map(Object, Object)} method.
*/
final Binary<T> asBinary() {
checkFutureParameters(Binary.class, 2);
return new Binary<T>() {
public T map(T left, T right) {
return apply(left, right);
}
@Override
public String toString() {
return name();
}
};
}
/**
* Returns a {@link Map} instance that invokes the underlying {@code map}
* method or curried constructor with the only parameter of the
* {@link Unary#map(Object)} method.
*/
final Map<Object[], T> asMap() {
return new Map<Object[], T>() {
public T map(Object[] args) {
return apply(args);
}
@Override
public String toString() {
return name();
}
};
}
@SuppressWarnings("unchecked")
final T apply(Object... args) {
try {
return (T) invoke(args);
} catch (Throwable e) {
throw propagate(e);
}
}
static RuntimeException propagate(Throwable e) {
if (e instanceof InvocationTargetException) {
return propagate(((InvocationTargetException) e).getCause());
}
if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
if (e instanceof Error) {
throw (Error) e;
}
return new RuntimeException(e);
}
Object invoke(Object[] args) throws Throwable {
checkArgumentTypes(args);
return invokable.invoke(args);
}
private static FastMethod mapMethod(Class<?> type) {
FastMethod method = mapMethods.get(type);
if (method == null) {
method = introspectMapperMethod(type);
mapMethods.put(type, method);
}
return method;
}
private static FastMethod introspectMapperMethod(Class<?> type) {
Method method = findMapMethod(type);
Checks.checkNotNullState(method, "A method named as 'map' should be defined in %s", type.getName());
Class<?> targetType = getTargetType(type);
if (targetType != null) {
Checks.checkState(targetType.isAssignableFrom(Reflection.wrapperClass(method.getReturnType())), "%s should return a subtype of %s", method, targetType.getName());
}
return FastClass.create(type).getMethod(method);
}
private static Class<?> getTargetType(Class<?> type) {
Type genericSuperclass = type.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
if (parameterizedType.getRawType() == Mapper.class) {
return getRawClass(parameterizedType.getActualTypeArguments()[0]);
}
}
Class<?> superclass = type.getSuperclass();
return (superclass == null) ? Object.class : getTargetType(superclass);
}
private static Class<?> getRawClass(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
}
if (type instanceof ParameterizedType) {
return getRawClass(((ParameterizedType) type).getRawType());
}
return null;
}
private static Method findMapMethod(Class<?> type) {
Method mapMethod = null;
for (Method method : type.getDeclaredMethods()) {
if (method.getName().equals("map")) {
Checks.checkState(mapMethod == null, "only one map method can be defined: %s", type.getName());
mapMethod = method;
}
}
if (mapMethod != null) {
return mapMethod;
}
Class<?> superclass = type.getSuperclass();
return (superclass == null) ? null : findMapMethod(superclass);
}
private void checkArgumentTypes(Object... vals) {
Class<?>[] parameterTypes = invokable.parameterTypes();
if (vals.length != parameterTypes.length) {
throw new IllegalArgumentException(vals.length + " arguments received, " + parameterTypes.length + " expected: " + invokable);
}
for (int i = 0; i < vals.length; i++) {
checkArgumentType(i, parameterTypes[i], vals[i]);
}
}
final void checkArgumentType(int i, Class<?> parameterType, Object arg) {
if (!Reflection.isAssignable(parameterType, arg)) {
throw new IllegalArgumentException(parameterType.getName() + " expected for parameter " + i + " of " + invokable + ", " + Reflection.getClassName(arg) + " provided.");
}
}
// Use new to ensure uniqueness of the string.
private static final String SKIPPED = new String("skipped");
private static final Unary<Object> SKIP = new Unary<Object>() {
public Object map(Object v) {
return v;
}
@Override
public String toString() {
return SKIPPED;
}
};
private static boolean isSkipped(Parser<?> parser) {
return parser.toString() == SKIPPED;
}
static Parser<?>[] toArray(Collection<? extends Parser<?>> parsers) {
return parsers.toArray(new Parser<?>[parsers.size()]);
}
private static void checkNotSkipped(Parser<?> operator) {
Checks.checkArgument(!isSkipped(operator), "Cannot skip the only parser parameter.");
}
private static List<Parser<?>> mergeSkipped(Parser<?>... parsers) {
ArrayList<Parser<?>> result = Lists.arrayList(parsers.length);
List<Parser<?>> all = Arrays.asList(parsers);
for (int i = 0; i < parsers.length; i++) {
Parser<?> parser = parsers[i];
if (isSkipped(parser)) {
// scan forward until a non-skipped parser is found or the end
// of the array.
int from = i;
for (i++; i < parsers.length && isSkipped(parsers[i]); i++)
;
if (i == parsers.length) {
// we are at the end of the array
Checks.checkArgument(!result.isEmpty(), "Cannot skip all parser parameters.");
Parser<?> skippedSequence = Parsers.sequence(all.subList(from, i));
int lastIndex = result.size() - 1;
result.set(lastIndex, result.get(lastIndex).followedBy(skippedSequence));
return result;
}
// parsers[i] is not skipped.
result.add(Parsers.sequence(all.subList(from, i + 1)));
} else {
result.add(parser);
}
}
return result;
}
}