/*********************************************************************************************************************** * * Copyright (C) 2010 by the Stratosphere project (http://stratosphere.eu) * * 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 eu.stratosphere.sopremo.expressions; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javolution.text.TypeFormat; 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> */ @OptimizerHints(scope = Scope.ARRAY, iterating = true) public class ArrayAccess extends PathSegmentExpression { private final int startIndex, endIndex; private final transient IArrayNode<IJsonNode> result = new ArrayNode<IJsonNode>(); /** * 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 void appendAsString(final Appendable appendable) throws IOException { this.getInputExpression().appendAsString(appendable); appendable.append('['); if (this.isSelectingAll()) appendable.append('*'); else { TypeFormat.format(this.startIndex, appendable); if (this.startIndex != this.endIndex) { appendable.append(':'); TypeFormat.format(this.endIndex, appendable); } } appendable.append(']'); } public Collection<ArrayAccess> decompose() { if (!this.isFixedSize()) throw new IllegalStateException("Not decomposable"); if (!this.isSelectingRange()) return Arrays.asList(this); final ArrayList<ArrayAccess> accesses = new ArrayList<ArrayAccess>(); for (int index = this.startIndex; index <= this.endIndex; index++) accesses.add(new ArrayAccess(index)); return accesses; } /* * (non-Javadoc) * @see * eu.stratosphere.sopremo.expressions.PathSegmentExpression#equalsSameClass(eu.stratosphere.sopremo.expressions * .PathSegmentExpression) */ @Override public boolean equalsSameClass(final PathSegmentExpression obj) { final ArrayAccess other = (ArrayAccess) obj; return this.startIndex == other.startIndex && this.endIndex == other.endIndex; } /** * Returns the endIndex. * * @return the endIndex */ public int getEndIndex() { return this.endIndex; } public int[] getIndices() { if (!this.isFixedSize()) return null; if (this.startIndex >= 0) { // normal access final int[] indices = new int[this.endIndex - this.startIndex + 1]; for (int index = this.startIndex; index <= this.endIndex; index++) indices[index - this.startIndex] = index; return indices; } // backward array final int[] indices = new int[-this.startIndex - this.endIndex + 1]; for (int index = this.startIndex; index <= this.endIndex; index++) indices[this.startIndex - index] = index; return indices; } /** * Returns the startIndex. * * @return the startIndex */ public int getStartIndex() { return this.startIndex; } public boolean isFixedSize() { return this.startIndex >= 0 == this.endIndex >= 0; } /** * 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 protected IJsonNode evaluateSegment(final IJsonNode node) { if (!(node instanceof IArrayNode<?>)) return MissingNode.getInstance(); final IArrayNode<?> arrayNode = (IArrayNode<?>) node; if (this.isSelectingAll()) { this.result.clear(); this.result.addAll(arrayNode); return this.result; } final int size = arrayNode.size(); if (this.isSelectingRange()) { this.result.clear(); 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) { this.result.add(arrayNode.get(index)); moreElements = index != endIndex; } return this.result; } final IJsonNode value = arrayNode.get(this.resolveIndex(this.startIndex, size)); return value == null ? NullNode.getInstance() : value; } /* * (non-Javadoc) * @see eu.stratosphere.sopremo.expressions.PathSegmentExpression#segmentHashCode() */ @Override protected int segmentHashCode() { return this.startIndex * 47 + this.endIndex; } /* * (non-Javadoc) * @see eu.stratosphere.sopremo.expressions.PathSegmentExpression#setSegment(eu.stratosphere.sopremo.type.IJsonNode, * eu.stratosphere.sopremo.type.IJsonNode) */ @SuppressWarnings("unchecked") @Override protected IJsonNode setSegment(final IJsonNode node, final IJsonNode value) { if (this.isSelectingAll()) return value; final IArrayNode<IJsonNode> arrayNode = (IArrayNode<IJsonNode>) node; final int size = arrayNode.size(); if (this.isSelectingRange()) { int index = this.resolveIndex(this.startIndex, size), replaceIndex = 0; final int endIndex = this.resolveIndex(this.endIndex, size); final int increment = index < endIndex ? 1 : -1; final IArrayNode<?> otherArray = (IArrayNode<?>) node; for (boolean moreElements = true; moreElements; index += increment, replaceIndex++) { SopremoUtil.replaceWithCopy(arrayNode, index, otherArray.get(replaceIndex)); moreElements = index != endIndex; } } else SopremoUtil.replaceWithCopy(arrayNode, this.resolveIndex(this.startIndex, size), value); return node; } 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 an 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(); } } }