package eu.stratosphere.sopremo.expressions; import eu.stratosphere.sopremo.EvaluationContext; import eu.stratosphere.sopremo.pact.SopremoUtil; import eu.stratosphere.sopremo.type.ArrayNode; import eu.stratosphere.sopremo.type.IArrayNode; import eu.stratosphere.sopremo.type.IJsonNode; import eu.stratosphere.sopremo.type.MissingNode; import eu.stratosphere.sopremo.type.NullNode; /** * Returns one or more elements of an array.<br> * There are two special cases supported when specifying the indices. * <ul> * <li>When one or both indices are negatives, the position is counted from the rear of the list. More specifically, the * index will be added to the size of the array. * <li>If the first index is higher than the second index, the returned list will still contain elements within the * range but in reversed order. * </ul> * * @author Arvid Heise */ @OptimizerHints(scope = Scope.ARRAY, iterating = true) public class ArrayAccess extends EvaluationExpression { /** * */ private static final long serialVersionUID = -2326222517008315722L; private final int startIndex, endIndex; /** * Initializes ArrayAccess that reproduces any input array. */ public ArrayAccess() { this(0, -1); } /** * Initializes ArrayAccess that selects one element at a given location. If the location is negative, it will be * added to the size of the array to allow selection of rear elements of arrays with unknown size. * * @param index * the index of the element */ public ArrayAccess(final int index) { this(index, index); } /** * Initializes ArrayAccess to return a subarray ranging from the start to the end location. If a location is * negative, it will be added to the size of the array to allow selection of rear elements of arrays with unknown * size. * * @param startIndex * the start index * @param endIndex * the end index (inclusive) */ public ArrayAccess(final int startIndex, final int endIndex) { // if (0 <= startIndex && 0 <= endIndex && endIndex < startIndex) // throw new IllegalArgumentException("startIndex < endIndex"); // if (startIndex < 0 && endIndex < 0 && startIndex < endIndex) // throw new IllegalArgumentException("negative endIndex < negative startIndex"); this.startIndex = startIndex; this.endIndex = endIndex; } @Override public boolean equals(final Object obj) { if (!super.equals(obj)) return false; final ArrayAccess other = (ArrayAccess) obj; return this.startIndex == other.startIndex && this.endIndex == other.endIndex; } @Override public IJsonNode evaluate(final IJsonNode node, final IJsonNode target, final EvaluationContext context) { if (!node.isArray()) return MissingNode.getInstance(); final IArrayNode arrayNode = (IArrayNode) node; if (this.isSelectingAll()) { final IArrayNode targetArray = SopremoUtil.reinitializeTarget(target, ArrayNode.class); targetArray.addAll(arrayNode); return targetArray; } final int size = arrayNode.size(); if (this.isSelectingRange()) { final IArrayNode targetArray = SopremoUtil.reinitializeTarget(target, ArrayNode.class); int index = this.resolveIndex(this.startIndex, size); final int endIndex = this.resolveIndex(this.endIndex, size); final int increment = index < endIndex ? 1 : -1; for (boolean moreElements = true; moreElements; index += increment) { targetArray.add(arrayNode.get(index)); moreElements = index != endIndex; } return targetArray; } final IJsonNode value = arrayNode.get(this.resolveIndex(this.startIndex, size)); return value == null ? NullNode.getInstance() : value; } /** * Returns the endIndex. * * @return the endIndex */ public int getEndIndex() { return this.endIndex; } /** * Returns the startIndex. * * @return the startIndex */ public int getStartIndex() { return this.startIndex; } @Override public int hashCode() { return (47 * super.hashCode() + this.startIndex) * 47 + this.endIndex; } /** * Returns true if any incoming array would be wholly reproduced. * * @return true if any incoming array would be wholly reproduced */ public boolean isSelectingAll() { return this.startIndex == 0 && this.endIndex == -1; } /** * Returns true if more than one element is selected. * * @return true if more than one element is selected */ public boolean isSelectingRange() { return this.startIndex != this.endIndex; } @Override public IJsonNode set(final IJsonNode node, final IJsonNode value, final EvaluationContext context) { if (this.isSelectingAll()) return value; final int size = ((IArrayNode) node).size(); if (this.isSelectingRange()) { final IArrayNode arrayNode = (IArrayNode) node; int index = this.resolveIndex(this.startIndex, size), replaceIndex = 0; final int endIndex = this.resolveIndex(this.endIndex, size); final int increment = index < endIndex ? 1 : -1; for (boolean moreElements = true; moreElements; index += increment, replaceIndex++) { arrayNode.set(index, ((IArrayNode) node).get(replaceIndex)); moreElements = index != endIndex; } } else ((IArrayNode) node).set(this.resolveIndex(this.startIndex, size), value); return node; } @Override public void toString(final StringBuilder builder) { builder.append('['); if (this.isSelectingAll()) builder.append('*'); else { builder.append(this.startIndex); if (this.startIndex != this.endIndex) { builder.append(':'); builder.append(this.endIndex); } } builder.append(']'); } private int resolveIndex(final int index, final int size) { if (index < 0) return size + index; return index; } /** * Returns an optimal expression that returns an array that aggregates the given indices.<br> * Please note that the result of this expression is always an array in contrast to ArrayAccess with only one * index. * * @param indices * the indices in the original array that should be concatenated to a new array * @return an optimal expression that evaluates to an array */ public static EvaluationExpression arrayWithIndices(final int... indices) { switch (indices.length) { case 0: return new ArrayCreation(); case 1: return new ArrayCreation(new ArrayAccess(indices[0])); default: boolean monoton = true; final int step = indices[1] - indices[0]; if (Math.abs(step) != 1) monoton = false; for (int index = 2; monoton && index < indices.length; index += step) monoton = indices[index] - indices[index - 1] == step; if (monoton) return new ArrayAccess(indices[0], indices[indices.length - 1]); final ArrayAccess[] accesses = new ArrayAccess[indices.length]; for (int index = 0; index < indices.length; index++) accesses[index] = new ArrayAccess(indices[index]); return new ArrayCreation(); } } }