/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * 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 com.analog.lyric.dimple.model.domains; import static java.util.Objects.*; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import net.jcip.annotations.ThreadSafe; import org.eclipse.jdt.annotation.Nullable; /** * {@link JointDomainReindexer} implementation that permutes the domain order * including added and removed domains in the permutation. */ @ThreadSafe public final class JointDomainIndexPermuter extends JointDomainReindexer { /*------- * State */ private static final int APPEND = 1; private static final int PREPEND = 2; private static final int REMOVE_FROM_FRONT = 3; private static final int REMOVE_FROM_BACK = 4; final private int[] _oldToNewIndex; final private int _hashCode; final private JointDomainIndexPermuter _inverse; final private boolean _maintainsOrder; final private int _jointIndexConversionType; /*--------------- * Construction */ private JointDomainIndexPermuter( JointDomainIndexer fromDomains, @Nullable JointDomainIndexer addedDomains, JointDomainIndexer toDomains, @Nullable JointDomainIndexer removedDomains, int[] oldToNewIndex, @Nullable JointDomainIndexPermuter inverse) { super(fromDomains, addedDomains, toDomains, removedDomains); final int fromSize = fromDomains.size(); final int addedSize = addedDomains == null ? 0 : addedDomains.size(); final int toSize = toDomains.size(); final int removedSize = removedDomains == null ? 0 : removedDomains.size(); final int size = fromSize + addedSize; if (inverse == null) { if (size != toSize + removedSize) { throw new IllegalArgumentException( "Combined size of 'fromDomains' and 'addedDomains' does not equal " + "'toDomains' and 'removedDomains'"); } if (size != oldToNewIndex.length) { throw new IllegalArgumentException("Length of 'oldToNewIndex' does not match domain sizes."); } final int[] newToOldIndex = new int[size]; Arrays.fill(newToOldIndex, -1); for (int from = 0; from < size; ++from) { final int to = oldToNewIndex[from]; if (to < 0 || to >= size) { throw new IllegalArgumentException( String.format("'oldToNewIndex' contains out-of-range value %d", to)); } if (newToOldIndex[to] >= 0) { throw new IllegalArgumentException( String.format("'oldToNewIndex' contains two entries mapping to %d", to)); } int fromDomainSize = from < fromSize ? fromDomains.getDomainSize(from) : requireNonNull(addedDomains).getDomainSize(from - fromSize); int toDomainSize = to < toSize ? toDomains.getDomainSize(to) : requireNonNull(removedDomains).getDomainSize(to - toSize); if (fromDomainSize != toDomainSize) { throw new IllegalArgumentException( String.format("'oldToNewIndex' domain size mismatch at index %d", from)); } newToOldIndex[to] = from; } inverse = new JointDomainIndexPermuter(toDomains, removedDomains, fromDomains, addedDomains, newToOldIndex, this); } _hashCode = computeHashCode(); _oldToNewIndex = oldToNewIndex; _inverse = inverse; // In theory, we could perform these optimization on directed domains with non-canonical // domain order, but it is not worth the trouble at this time. boolean maintainsOrder = false; int jointIndexType = 0; if (_fromDomains.hasCanonicalDomainOrder() && _toDomains.hasCanonicalDomainOrder()) { maintainsOrderCheck: do { // Order is only maintained if domains are only removed from front of // list, added to the end of the list, and relative order of domains is maintained. for (int i = 0; i < removedSize; ++i) { if (oldToNewIndex[i] != (toSize + i)) { break maintainsOrderCheck; } } for (int i = removedSize; i < size; ++i) { if (oldToNewIndex[i] != i - removedSize) { break maintainsOrderCheck; } } maintainsOrder = true; } while (false); if (maintainsOrder) { if (_addedDomains != null && _removedDomains == null) { jointIndexType = APPEND; } else if (_addedDomains == null && _removedDomains != null) { jointIndexType = REMOVE_FROM_FRONT; } } else { if (_addedDomains != null && _removedDomains == null) { jointIndexType = PREPEND; for (int i = 0; i < fromSize; ++i) { if (oldToNewIndex[i] != addedSize + i) { jointIndexType = 0; break; } } for (int i = 0; i < addedSize; ++i) { if (oldToNewIndex[fromSize + i] != i) { jointIndexType = 0; break; } } } else if (_addedDomains == null && _removedDomains != null) { jointIndexType = REMOVE_FROM_BACK; for (int i = 0; i < size; ++i) { if (i != oldToNewIndex[i]) { jointIndexType = 0; break; } } } } } _maintainsOrder = maintainsOrder; _jointIndexConversionType = jointIndexType; } JointDomainIndexPermuter( JointDomainIndexer fromDomains, @Nullable JointDomainIndexer addedDomains, JointDomainIndexer toDomains, @Nullable JointDomainIndexer removedDomains, int[] oldToNewIndex) { this(fromDomains, addedDomains, toDomains, removedDomains, oldToNewIndex, null); } @Override protected int computeHashCode() { return super.computeHashCode() * 19 + Arrays.hashCode(_oldToNewIndex); } /*---------------- * Object methods */ @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (other instanceof JointDomainIndexPermuter && super.equals(other)) { return Arrays.equals(_oldToNewIndex, ((JointDomainIndexPermuter)other)._oldToNewIndex); } return false; } @Override public int hashCode() { return _hashCode; } /*------------------------------------- * JointDomainReindexer methods */ @Override public void convertIndices(Indices indices) { assert(indices.converter == this); final int[] oldToNew = _oldToNewIndex; final int fromSize = _fromDomains.size(); final int toSize = _toDomains.size(); final int[] fromIndices = indices.fromIndices; final int[] toIndices = indices.toIndices; final int[] addedIndices = indices.addedIndices; final int[] removedIndices = indices.removedIndices; for (int from = fromIndices.length; --from >=0; ) { final int index = fromIndices[from]; final int to = oldToNew[from]; if (to < toSize) { toIndices[to] = index; } else { removedIndices[to-toSize] = index; } } for (int added = addedIndices.length; --added >=0; ) { final int index = addedIndices[added]; final int to = oldToNew[added+fromSize]; if (to < toSize) { toIndices[to] = index; } else { // This case is unusual, since there is no point in moving // a domain from added list immediately to the removed list. removedIndices[to] = index; } } } @SuppressWarnings("null") @Override public int convertJointIndex(int jointIndex, int addedJointIndex) { switch (_jointIndexConversionType) { case PREPEND: return addedJointIndex + _addedDomains.getCardinality() * jointIndex; case APPEND: return jointIndex + _fromDomains.getCardinality() * addedJointIndex; case REMOVE_FROM_FRONT: return jointIndex / _removedDomains.getCardinality(); case REMOVE_FROM_BACK: return jointIndex % _toDomains.getCardinality(); // TODO: // insert domains at position k: // // oldJointIndex - oldJointIndex/kCardinality + // kCardinality * addedJointIndex + // kCardinality * addedCardinality * oldJointIndex/kCardinality // // remove domains at position k default: return super.convertJointIndex(jointIndex, addedJointIndex); } } @SuppressWarnings("null") @Override public int convertJointIndex(int jointIndex, int addedJointIndex, @Nullable AtomicInteger removedJointIndexRef) { switch (_jointIndexConversionType) { case PREPEND: return addedJointIndex + _addedDomains.getCardinality() * jointIndex; case APPEND: return jointIndex + _fromDomains.getCardinality() * addedJointIndex; case REMOVE_FROM_FRONT: if (removedJointIndexRef == null) { return jointIndex / _removedDomains.getCardinality(); } else { final int removedCardinality = _removedDomains.getCardinality(); final int newJointIndex = jointIndex / removedCardinality; removedJointIndexRef.set(jointIndex - newJointIndex * removedCardinality); return newJointIndex; } case REMOVE_FROM_BACK: if (removedJointIndexRef == null) { return jointIndex % _toDomains.getCardinality(); } else { final int toCardinality = _toDomains.getCardinality(); final int removedJointIndex = jointIndex / toCardinality; removedJointIndexRef.set(removedJointIndex); return jointIndex - removedJointIndex * toCardinality; } default: return super.convertJointIndex(jointIndex, addedJointIndex, removedJointIndexRef); } } @Override public JointDomainIndexPermuter getInverse() { return _inverse; } @Override public boolean hasFastJointIndexConversion() { return _jointIndexConversionType != 0; } @Override protected boolean maintainsJointIndexOrder() { return _maintainsOrder; } }