/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.flink.api.common.operators; import org.apache.flink.annotation.Internal; import org.apache.flink.api.common.operators.util.FieldList; import org.apache.flink.api.common.operators.util.FieldSet; import java.util.ArrayList; /** * This class represents an ordering on a set of fields. It specifies the fields and order direction * (ascending, descending). */ @Internal public class Ordering implements Cloneable { protected FieldList indexes = new FieldList(); protected final ArrayList<Class<? extends Comparable<?>>> types = new ArrayList<Class<? extends Comparable<?>>>(); protected final ArrayList<Order> orders = new ArrayList<Order>(); // -------------------------------------------------------------------------------------------- /** * Creates an empty ordering. */ public Ordering() {} /** * @param index * @param type * @param order */ public Ordering(int index, Class<? extends Comparable<?>> type, Order order) { appendOrdering(index, type, order); } /** * Extends this ordering by appending an additional order requirement. * If the index has been previously appended then the unmodified Ordering * is returned. * * @param index Field index of the appended order requirement. * @param type Type of the appended order requirement. * @param order Order of the appended order requirement. * * @return This ordering with an additional appended order requirement. */ public Ordering appendOrdering(Integer index, Class<? extends Comparable<?>> type, Order order) { if (index < 0) { throw new IllegalArgumentException("The key index must not be negative."); } if (order == null) { throw new NullPointerException(); } if (order == Order.NONE) { throw new IllegalArgumentException("An ordering must not be created with a NONE order."); } if (!this.indexes.contains(index)) { this.indexes = this.indexes.addField(index); this.types.add(type); this.orders.add(order); } return this; } // -------------------------------------------------------------------------------------------- public int getNumberOfFields() { return this.indexes.size(); } public FieldList getInvolvedIndexes() { return this.indexes; } public Integer getFieldNumber(int index) { if (index < 0 || index >= this.indexes.size()) { throw new IndexOutOfBoundsException(String.valueOf(index)); } return this.indexes.get(index); } public Class<? extends Comparable<?>> getType(int index) { if (index < 0 || index >= this.types.size()) { throw new IndexOutOfBoundsException(String.valueOf(index)); } return this.types.get(index); } public Order getOrder(int index) { if (index < 0 || index >= this.types.size()) { throw new IndexOutOfBoundsException(String.valueOf(index)); } return orders.get(index); } // -------------------------------------------------------------------------------------------- @SuppressWarnings("unchecked") public Class<? extends Comparable<?>>[] getTypes() { return this.types.toArray(new Class[this.types.size()]); } public int[] getFieldPositions() { final int[] ia = new int[this.indexes.size()]; for (int i = 0; i < ia.length; i++) { ia[i] = this.indexes.get(i); } return ia; } public Order[] getFieldOrders() { return this.orders.toArray(new Order[this.orders.size()]); } public boolean[] getFieldSortDirections() { final boolean[] directions = new boolean[this.orders.size()]; for (int i = 0; i < directions.length; i++) { directions[i] = this.orders.get(i) != Order.DESCENDING; } return directions; } // -------------------------------------------------------------------------------------------- public boolean isMetBy(Ordering otherOrdering) { if (otherOrdering == null || this.indexes.size() > otherOrdering.indexes.size()) { return false; } for (int i = 0; i < this.indexes.size(); i++) { if (!this.indexes.get(i).equals(otherOrdering.indexes.get(i))) { return false; } // if this one request no order, everything is good if (this.orders.get(i) != Order.NONE) { if (this.orders.get(i) == Order.ANY) { // if any order is requested, any not NONE order is good if (otherOrdering.orders.get(i) == Order.NONE) { return false; } } else if (otherOrdering.orders.get(i) != this.orders.get(i)) { // the orders must be equal return false; } } } return true; } public boolean isOrderEqualOnFirstNFields(Ordering other, int n) { if (n > getNumberOfFields() || n > other.getNumberOfFields()) { throw new IndexOutOfBoundsException(); } for (int i = 0; i < n; i++) { final Order o = this.orders.get(i); if (o == Order.NONE || o == Order.ANY || o != other.orders.get(i)) { return false; } } return true; } /** * Creates a new ordering the represents an ordering on a prefix of the fields. If the * exclusive index up to which to create the ordering is <code>0</code>, then there is * no resulting ordering and this method return <code>null</code>. * * @param exclusiveIndex The index (exclusive) up to which to create the ordering. * @return The new ordering on the prefix of the fields, or <code>null</code>, if the prefix is empty. */ public Ordering createNewOrderingUpToIndex(int exclusiveIndex) { if (exclusiveIndex == 0) { return null; } final Ordering newOrdering = new Ordering(); for (int i = 0; i < exclusiveIndex; i++) { newOrdering.appendOrdering(this.indexes.get(i), this.types.get(i), this.orders.get(i)); } return newOrdering; } public boolean groupsFields(FieldSet fields) { if (fields.size() > this.indexes.size()) { return false; } for (int i = 0; i < fields.size(); i++) { if (!fields.contains(this.indexes.get(i))) { return false; } } return true; } // -------------------------------------------------------------------------------------------- public Ordering clone() { Ordering newOrdering = new Ordering(); newOrdering.indexes = this.indexes; newOrdering.types.addAll(this.types); newOrdering.orders.addAll(this.orders); return newOrdering; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((indexes == null) ? 0 : indexes.hashCode()); result = prime * result + ((orders == null) ? 0 : orders.hashCode()); result = prime * result + ((types == null) ? 0 : types.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Ordering other = (Ordering) obj; if (indexes == null) { if (other.indexes != null) { return false; } } else if (!indexes.equals(other.indexes)) { return false; } if (orders == null) { if (other.orders != null) { return false; } } else if (!orders.equals(other.orders)) { return false; } if (types == null) { if (other.types != null) { return false; } } else if (!types.equals(other.types)) { return false; } return true; } public String toString() { final StringBuilder buf = new StringBuilder("["); for (int i = 0; i < indexes.size(); i++) { if (i != 0) { buf.append(","); } buf.append(this.indexes.get(i)); if (this.types.get(i) != null) { buf.append(":"); buf.append(this.types.get(i).getName()); } buf.append(":"); buf.append(this.orders.get(i).getShortName()); } buf.append("]"); return buf.toString(); } }