Search code examples
optaplanner

Resolve an UnsupportedOperationException for an Immovable Entity in Optaplanner


I'm getting an UnsupportedOperationException when implementing an immovable entity as per the Optaplanner documentation. I'm using version 6.4.0.

My entity:

@PlanningEntity(difficultyComparatorClass=AssignmentCreditHoursComparator.class, movableEntitySelectionFilter=ImmovableChangeMoveFilter.class) public class Assignment{...}

My Filter

public class ImmovableChangeMoveFilter implements SelectionFilter<Assignment> {
@Override
public boolean accept(ScoreDirector scoreDirector, Assignment a) {
    if(a.isLocked())
        return false;
    else
        return true;    
}

}

The exception:

Exception in thread "main" java.lang.UnsupportedOperationException
at org.optaplanner.core.impl.heuristic.selector.entity.decorator.FilteringEntitySelector.listIterator(FilteringEntitySelector.java:125)
at org.optaplanner.core.impl.heuristic.selector.common.iterator.AbstractOriginalSwapIterator.<init>(AbstractOriginalSwapIterator.java:49)
at org.optaplanner.core.impl.heuristic.selector.move.generic.SwapMoveSelector$1.<init>(SwapMoveSelector.java:129)
at org.optaplanner.core.impl.heuristic.selector.move.generic.SwapMoveSelector.iterator(SwapMoveSelector.java:129)
at org.optaplanner.core.impl.heuristic.selector.move.decorator.AbstractCachingMoveSelector.constructCache(AbstractCachingMoveSelector.java:76)
at org.optaplanner.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge.phaseStarted(SelectionCacheLifecycleBridge.java:49)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.phase.event.PhaseLifecycleSupport.firePhaseStarted(PhaseLifecycleSupport.java:39)
at org.optaplanner.core.impl.heuristic.selector.AbstractSelector.phaseStarted(AbstractSelector.java:47)
at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.phaseStarted(LocalSearchDecider.java:97)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.phaseStarted(DefaultLocalSearchPhase.java:122)
at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:66)
at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:215)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)

This has been taken from the Optaplanner code...

public class FilteringEntitySelector extends AbstractEntitySelector {

protected final EntitySelector childEntitySelector;
protected final List<SelectionFilter> filterList;
protected final boolean bailOutEnabled;

protected ScoreDirector scoreDirector = null;

public FilteringEntitySelector(EntitySelector childEntitySelector, List<SelectionFilter> filterList) {
    this.childEntitySelector = childEntitySelector;
    this.filterList = filterList;
    bailOutEnabled = childEntitySelector.isNeverEnding();
    phaseLifecycleSupport.addEventListener(childEntitySelector);
}

// ************************************************************************
// Worker methods
// ************************************************************************

@Override
public void phaseStarted(AbstractPhaseScope phaseScope) {
    super.phaseStarted(phaseScope);
    scoreDirector = phaseScope.getScoreDirector();
}

@Override
public void phaseEnded(AbstractPhaseScope phaseScope) {
    super.phaseEnded(phaseScope);
    scoreDirector = null;
}

public EntityDescriptor getEntityDescriptor() {
    return childEntitySelector.getEntityDescriptor();
}

public boolean isCountable() {
    return childEntitySelector.isCountable();
}

public boolean isNeverEnding() {
    return childEntitySelector.isNeverEnding();
}

public long getSize() {
    return childEntitySelector.getSize();
}

public Iterator<Object> iterator() {
    return new JustInTimeFilteringEntityIterator(childEntitySelector.iterator());
}

protected class JustInTimeFilteringEntityIterator extends UpcomingSelectionIterator<Object> {

    private final Iterator<Object> childEntityIterator;
    private final long bailOutSize;

    public JustInTimeFilteringEntityIterator(Iterator<Object> childEntityIterator) {
        this.childEntityIterator = childEntityIterator;
        this.bailOutSize = determineBailOutSize();
    }

    @Override
    protected Object createUpcomingSelection() {
        Object next;
        long attemptsBeforeBailOut = bailOutSize;
        do {
            if (!childEntityIterator.hasNext()) {
                return noUpcomingSelection();
            }
            if (bailOutEnabled) {
                // if childEntityIterator is neverEnding and nothing is accepted, bail out of the infinite loop
                if (attemptsBeforeBailOut <= 0L) {
                    logger.warn("Bailing out of neverEnding selector ({}) to avoid infinite loop.",
                            FilteringEntitySelector.this);
                    return noUpcomingSelection();
                }
                attemptsBeforeBailOut--;
            }
            next = childEntityIterator.next();
        } while (!accept(scoreDirector, next));
        return next;
    }

}

protected long determineBailOutSize() {
    if (!bailOutEnabled) {
        return -1L;
    }
    return childEntitySelector.getSize() * 10L;
}

public ListIterator<Object> listIterator() {
    // TODO Not yet implemented
    throw new UnsupportedOperationException();
}

public ListIterator<Object> listIterator(int index) {
    // TODO Not yet implemented
    throw new UnsupportedOperationException();
}

public Iterator<Object> endingIterator() {
    return new JustInTimeFilteringEntityIterator(childEntitySelector.endingIterator());
}

private boolean accept(ScoreDirector scoreDirector, Object entity) {
    for (SelectionFilter filter : filterList) {
        if (!filter.accept(scoreDirector, entity)) {
            return false;
        }
    }
    return true;
}

@Override
public String toString() {
    return "Filtering(" + childEntitySelector + ")";
}

}

<solver>  
<scanAnnotatedClasses>
    <packageInclude>com.fast.planner.solution</packageInclude>
</scanAnnotatedClasses>

<scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>com/fast/planner/solution/rostering-rules.drl</scoreDrl>
    <initializingScoreTrend>ONLY_DOWN/ONLY_DOWN</initializingScoreTrend> 
</scoreDirectorFactory>



<localSearch>
    <unionMoveSelector>

        <cacheType>PHASE</cacheType>
        <selectionOrder>SHUFFLED</selectionOrder>

        <changeMoveSelector>
            <cacheType>PHASE</cacheType>

            <filterClass>com.fast.planner.solution.filter.ReserveRestrictionsChangeMoveFilter</filterClass>
            <fixedProbabilityWeight>1.0</fixedProbabilityWeight>
        </changeMoveSelector>
        <swapMoveSelector>
            <cacheType>PHASE</cacheType>
            <fixedProbabilityWeight>2.0</fixedProbabilityWeight>
        </swapMoveSelector>


    </unionMoveSelector>
    <acceptor>
        <simulatedAnnealingStartingTemperature>2hard/200soft</simulatedAnnealingStartingTemperature>
        <entityTabuSize>5</entityTabuSize>
    </acceptor>
    <forager>
        <acceptedCountLimit>4</acceptedCountLimit>
    </forager>
</localSearch>


Solution

  • This is a bug and is resolved in later versions.