/* * Copyright 2014 Red Hat, Inc. and/or its affiliates. * * 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 org.optaplanner.core.impl.heuristic.selector.value.nearby; import java.util.Iterator; import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import org.optaplanner.core.impl.heuristic.selector.common.iterator.SelectionIterator; import org.optaplanner.core.impl.heuristic.selector.common.nearby.NearbyDistanceMatrix; import org.optaplanner.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter; import org.optaplanner.core.impl.heuristic.selector.common.nearby.NearbyRandom; import org.optaplanner.core.impl.heuristic.selector.entity.EntitySelector; import org.optaplanner.core.impl.heuristic.selector.value.AbstractValueSelector; import org.optaplanner.core.impl.heuristic.selector.value.ValueSelector; import org.optaplanner.core.impl.phase.scope.AbstractPhaseScope; public class NearEntityNearbyValueSelector extends AbstractValueSelector { protected final ValueSelector childValueSelector; protected final EntitySelector originEntitySelector; protected final NearbyDistanceMeter nearbyDistanceMeter; protected final NearbyRandom nearbyRandom; protected final boolean randomSelection; protected final boolean discardNearbyIndexZero; protected NearbyDistanceMatrix nearbyDistanceMatrix = null; public NearEntityNearbyValueSelector(ValueSelector childValueSelector, EntitySelector originEntitySelector, NearbyDistanceMeter nearbyDistanceMeter, NearbyRandom nearbyRandom, boolean randomSelection) { this.childValueSelector = childValueSelector; this.originEntitySelector = originEntitySelector; this.nearbyDistanceMeter = nearbyDistanceMeter; this.nearbyRandom = nearbyRandom; this.randomSelection = randomSelection; if (randomSelection && nearbyRandom == null) { throw new IllegalArgumentException("The valueSelector (" + this + ") with randomSelection (" + randomSelection + ") has no nearbyRandom (" + nearbyRandom + ")."); } discardNearbyIndexZero = childValueSelector.getVariableDescriptor().getVariablePropertyType().isAssignableFrom( originEntitySelector.getEntityDescriptor().getEntityClass()); phaseLifecycleSupport.addEventListener(childValueSelector); phaseLifecycleSupport.addEventListener(originEntitySelector); } @Override public GenuineVariableDescriptor getVariableDescriptor() { return childValueSelector.getVariableDescriptor(); } @Override public void phaseStarted(AbstractPhaseScope phaseScope) { // Cannot be done during solverStarted because super.phaseStarted(phaseScope); long originSize = originEntitySelector.getSize(); if (originSize > (long) Integer.MAX_VALUE) { throw new IllegalStateException("The originEntitySelector (" + originEntitySelector + ") has an entitySize (" + originSize + ") which is higher than Integer.MAX_VALUE."); } nearbyDistanceMatrix = new NearbyDistanceMatrix(nearbyDistanceMeter, (int) originSize); for (Iterator originIt = originEntitySelector.endingIterator(); originIt.hasNext(); ) { final Object origin = originIt.next(); long childSize = childValueSelector.getSize(origin); if (childSize > (long) Integer.MAX_VALUE) { throw new IllegalStateException("The childEntitySelector (" + childValueSelector + ") has an entitySize (" + childSize + ") which is higher than Integer.MAX_VALUE."); } int destinationSize = (int) childSize; if (randomSelection) { // Reduce RAM memory usage by reducing destinationSize if nearbyRandom will never select a higher value int overallSizeMaximum = nearbyRandom.getOverallSizeMaximum(); if (discardNearbyIndexZero && overallSizeMaximum < Integer.MAX_VALUE) { overallSizeMaximum++; } if (destinationSize > overallSizeMaximum) { destinationSize = overallSizeMaximum; } } nearbyDistanceMatrix.addAllDestinations(origin, childValueSelector.endingIterator(origin), destinationSize); } } @Override public void phaseEnded(AbstractPhaseScope phaseScope) { super.phaseEnded(phaseScope); nearbyDistanceMatrix = null; } // ************************************************************************ // Worker methods // ************************************************************************ @Override public boolean isCountable() { return childValueSelector.isCountable(); } @Override public boolean isNeverEnding() { return randomSelection || !isCountable(); } @Override public long getSize(Object entity) { return childValueSelector.getSize(entity) - (discardNearbyIndexZero ? 1 : 0); } @Override public Iterator<Object> iterator(Object entity) { if (!randomSelection) { return new OriginalEntityNearbyValueIterator( originEntitySelector.iterator(), childValueSelector.getSize(entity)); } else { return new RandomEntityNearbyValueIterator( originEntitySelector.iterator(), childValueSelector.getSize(entity)); } } @Override public Iterator<Object> endingIterator(Object entity) { // TODO It should probably use nearby order // It must include the origin entity too return childValueSelector.endingIterator(entity); } private class OriginalEntityNearbyValueIterator extends SelectionIterator<Object> { private final Iterator<Object> originEntityIterator; private final long childSize; private boolean originSelected = false; private boolean originIsNotEmpty; private Object origin; private int nextNearbyIndex; public OriginalEntityNearbyValueIterator(Iterator<Object> originEntityIterator, long childSize) { this.originEntityIterator = originEntityIterator; this.childSize = childSize; nextNearbyIndex = discardNearbyIndexZero ? 1 : 0; } private void selectOrigin() { if (originSelected) { return; } originIsNotEmpty = originEntityIterator.hasNext(); origin = originEntityIterator.next(); originSelected = true; } @Override public boolean hasNext() { selectOrigin(); return originIsNotEmpty && nextNearbyIndex < childSize; } @Override public Object next() { selectOrigin(); Object next = nearbyDistanceMatrix.getDestination(origin, nextNearbyIndex); nextNearbyIndex++; return next; } } private class RandomEntityNearbyValueIterator extends SelectionIterator<Object> { private final Iterator<Object> originEntityIterator; private final int nearbySize; public RandomEntityNearbyValueIterator(Iterator<Object> originEntityIterator, long childSize) { this.originEntityIterator = originEntityIterator; if (childSize > (long) Integer.MAX_VALUE) { throw new IllegalStateException("The valueSelector (" + this + ") has an entitySize (" + childSize + ") which is higher than Integer.MAX_VALUE."); } nearbySize = (int) childSize - (discardNearbyIndexZero ? 1 : 0); } @Override public boolean hasNext() { return originEntityIterator.hasNext() && nearbySize > 0; } @Override public Object next() { Object origin = originEntityIterator.next(); int nearbyIndex = nearbyRandom.nextInt(workingRandom, nearbySize); if (discardNearbyIndexZero) { nearbyIndex++; } return nearbyDistanceMatrix.getDestination(origin, nearbyIndex); } } @Override public String toString() { return getClass().getSimpleName() + "(" + originEntitySelector + ", " + childValueSelector + ")"; } }