Skip to content

Commit

Permalink
CollectionInverseVariable support: prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
ge0ffrey committed Mar 30, 2015
1 parent ea1a106 commit d1996cf
Show file tree
Hide file tree
Showing 6 changed files with 453 additions and 12 deletions.
@@ -0,0 +1,76 @@
/*
* Copyright 2015 JBoss 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 org.optaplanner.core.impl.domain.variable.inverserelation;

import java.io.Serializable;

import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.supply.Demand;
import org.optaplanner.core.impl.score.director.InnerScoreDirector;

public class CollectionInverseVariableDemand implements Demand<CollectionInverseVariableSupply>, Serializable {

private static final int CLASS_NAME_HASH_CODE = CollectionInverseVariableDemand.class.getName().hashCode() * 37;

protected final VariableDescriptor sourceVariableDescriptor;

public CollectionInverseVariableDemand(VariableDescriptor sourceVariableDescriptor) {
this.sourceVariableDescriptor = sourceVariableDescriptor;
}

public VariableDescriptor getSourceVariableDescriptor() {
return sourceVariableDescriptor;
}

// ************************************************************************
// Creation method
// ************************************************************************

public CollectionInverseVariableSupply createExternalizedSupply(InnerScoreDirector scoreDirector) {
return new ExternalizedCollectionInverseVariableSupply(sourceVariableDescriptor);
}

// ************************************************************************
// Equals/hashCode method
// ************************************************************************

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CollectionInverseVariableDemand)) {
return false;
}
CollectionInverseVariableDemand other = (CollectionInverseVariableDemand) o;
if (!sourceVariableDescriptor.equals(other.sourceVariableDescriptor)) {
return false;
}
return true;
}

@Override
public int hashCode() {
return CLASS_NAME_HASH_CODE + sourceVariableDescriptor.hashCode();
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + sourceVariableDescriptor.getSimpleEntityAndVariableName() + ")";
}

}
@@ -0,0 +1,122 @@
/*
* Copyright 2015 JBoss 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 org.optaplanner.core.impl.domain.variable.inverserelation;

import java.util.Collection;

import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
import org.optaplanner.core.impl.score.director.ScoreDirector;

public class CollectionInverseVariableListener implements VariableListener<Object>, CollectionInverseVariableSupply {

protected final InverseRelationShadowVariableDescriptor shadowVariableDescriptor;
protected final VariableDescriptor sourceVariableDescriptor;

public CollectionInverseVariableListener(InverseRelationShadowVariableDescriptor shadowVariableDescriptor,
VariableDescriptor sourceVariableDescriptor) {
this.shadowVariableDescriptor = shadowVariableDescriptor;
this.sourceVariableDescriptor = sourceVariableDescriptor;
}

public void beforeEntityAdded(ScoreDirector scoreDirector, Object entity) {
// Do nothing
}

public void afterEntityAdded(ScoreDirector scoreDirector, Object entity) {
insert(scoreDirector, entity);
}

public void beforeVariableChanged(ScoreDirector scoreDirector, Object entity) {
retract(scoreDirector, entity);
}

public void afterVariableChanged(ScoreDirector scoreDirector, Object entity) {
insert(scoreDirector, entity);
}

public void beforeEntityRemoved(ScoreDirector scoreDirector, Object entity) {
retract(scoreDirector, entity);
}

public void afterEntityRemoved(ScoreDirector scoreDirector, Object entity) {
// Do nothing
}

protected void insert(ScoreDirector scoreDirector, Object entity) {
Object shadowEntity = sourceVariableDescriptor.getValue(entity);
if (shadowEntity != null) {
Collection shadowCollection = (Collection) shadowVariableDescriptor.getValue(shadowEntity);
if (shadowCollection == null) {
throw new IllegalStateException("The entity (" + entity
+ ") has a variable (" + sourceVariableDescriptor.getVariableName()
+ ") with value (" + shadowEntity
+ ") which has a sourceVariableName variable (" + shadowVariableDescriptor.getVariableName()
+ ") with a value (" + shadowCollection + ") which is null.\n"
+ "Verify the consistency of your input problem for that bi-directional relationship.\n"
+ "Every non-singleton inverse variable can never be null. It should at least be an empty "
+ Collection.class.getSimpleName() + " instead.");
}
scoreDirector.beforeVariableChanged(shadowVariableDescriptor, shadowEntity);
boolean added = shadowCollection.add(entity);
if (!added) {
throw new IllegalStateException("The entity (" + entity
+ ") has a variable (" + sourceVariableDescriptor.getVariableName()
+ ") with value (" + shadowEntity
+ ") which has a sourceVariableName variable (" + shadowVariableDescriptor.getVariableName()
+ ") with a value (" + shadowCollection
+ ") which already contained the entity (" + entity + ").\n"
+ "Verify the consistency of your input problem for that bi-directional relationship.");
}
scoreDirector.afterVariableChanged(shadowVariableDescriptor, shadowEntity);
}
}

protected void retract(ScoreDirector scoreDirector, Object entity) {
Object shadowEntity = sourceVariableDescriptor.getValue(entity);
if (shadowEntity != null) {
Collection shadowCollection = (Collection) shadowVariableDescriptor.getValue(shadowEntity);
if (shadowCollection == null) {
throw new IllegalStateException("The entity (" + entity
+ ") has a variable (" + sourceVariableDescriptor.getVariableName()
+ ") with value (" + shadowEntity
+ ") which has a sourceVariableName variable (" + shadowVariableDescriptor.getVariableName()
+ ") with a value (" + shadowCollection + ") which is null.\n"
+ "Verify the consistency of your input problem for that bi-directional relationship.\n"
+ "Every non-singleton inverse variable can never be null. It should at least be an empty "
+ Collection.class.getSimpleName() + " instead.");
}
scoreDirector.beforeVariableChanged(shadowVariableDescriptor, shadowEntity);
boolean removed = shadowCollection.remove(entity);
if (!removed) {
throw new IllegalStateException("The entity (" + entity
+ ") has a variable (" + sourceVariableDescriptor.getVariableName()
+ ") with value (" + shadowEntity
+ ") which has a sourceVariableName variable (" + shadowVariableDescriptor.getVariableName()
+ ") with a value (" + shadowCollection
+ ") which did not contain the entity (" + entity + ").\n"
+ "Verify the consistency of your input problem for that bi-directional relationship.");
}
scoreDirector.afterVariableChanged(shadowVariableDescriptor, shadowEntity);
}
}

public Collection<?> getInverseCollection(Object planningValue) {
return (Collection<?>) shadowVariableDescriptor.getValue(planningValue);
}

}
@@ -0,0 +1,32 @@
/*
* Copyright 2015 JBoss 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 org.optaplanner.core.impl.domain.variable.inverserelation;

import java.util.Collection;

import org.optaplanner.core.impl.domain.variable.supply.Supply;

public interface CollectionInverseVariableSupply extends Supply {

/**
* If entity1.varA = x then an inverse of x is entity1.
* @param planningValue never null
* @return never null, a {@link Collection} of entities for which the planning variable is the planningValue.
*/
Collection<?> getInverseCollection(Object planningValue);

}
@@ -0,0 +1,135 @@
/*
* Copyright 2015 JBoss 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 org.optaplanner.core.impl.domain.variable.inverserelation;

import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.StatefulVariableListener;
import org.optaplanner.core.impl.score.director.ScoreDirector;

/**
* Alternative to {@link CollectionInverseVariableListener}.
*/
public class ExternalizedCollectionInverseVariableSupply implements StatefulVariableListener<Object>, CollectionInverseVariableSupply {

protected final VariableDescriptor sourceVariableDescriptor;

protected Map<Object, Set<Object>> inverseEntitySetMap = null;

public ExternalizedCollectionInverseVariableSupply(VariableDescriptor sourceVariableDescriptor) {
this.sourceVariableDescriptor = sourceVariableDescriptor;
}

public VariableDescriptor getSourceVariableDescriptor() {
return sourceVariableDescriptor;
}

public void resetWorkingSolution(ScoreDirector scoreDirector) {
EntityDescriptor entityDescriptor = sourceVariableDescriptor.getEntityDescriptor();
List<Object> entityList = entityDescriptor.extractEntities(scoreDirector.getWorkingSolution());
inverseEntitySetMap = new IdentityHashMap<Object, Set<Object>>(entityList.size());
for (Object entity : entityList) {
insert(scoreDirector, entity);
}
}

public void clearWorkingSolution(ScoreDirector scoreDirector) {
inverseEntitySetMap = null;
}

public void beforeEntityAdded(ScoreDirector scoreDirector, Object entity) {
// Do nothing
}

public void afterEntityAdded(ScoreDirector scoreDirector, Object entity) {
insert(scoreDirector, entity);
}

public void beforeVariableChanged(ScoreDirector scoreDirector, Object entity) {
retract(scoreDirector, entity);
}

public void afterVariableChanged(ScoreDirector scoreDirector, Object entity) {
insert(scoreDirector, entity);
}

public void beforeEntityRemoved(ScoreDirector scoreDirector, Object entity) {
retract(scoreDirector, entity);
}

public void afterEntityRemoved(ScoreDirector scoreDirector, Object entity) {
// Do nothing
}

protected void insert(ScoreDirector scoreDirector, Object entity) {
Object value = sourceVariableDescriptor.getValue(entity);
if (value == null) {
return;
}
Set<Object> inverseEntitySet = inverseEntitySetMap.get(value);
if (inverseEntitySet == null) {
inverseEntitySet = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
inverseEntitySetMap.put(value, inverseEntitySet);
}
boolean addSucceeded = inverseEntitySet.add(entity);
if (!addSucceeded) {
throw new IllegalStateException("The supply (" + this + ") is corrupted,"
+ " because the entity (" + entity
+ ") for sourceVariable (" + sourceVariableDescriptor.getVariableName()
+ ") cannot be inserted: it was already inserted.");
}
}

protected void retract(ScoreDirector scoreDirector, Object entity) {
Object value = sourceVariableDescriptor.getValue(entity);
if (value == null) {
return;
}
Set<Object> inverseEntitySet = inverseEntitySetMap.get(value);
boolean removeSucceeded = inverseEntitySet.remove(entity);
if (!removeSucceeded) {
throw new IllegalStateException("The supply (" + this + ") is corrupted,"
+ " because the entity (" + entity
+ ") for sourceVariable (" + sourceVariableDescriptor.getVariableName()
+ ") cannot be retracted: it was never inserted.");
}
if (inverseEntitySet.isEmpty()) {
inverseEntitySetMap.put(value, null);
}
}

public Collection<?> getInverseCollection(Object value) {
Set<Object> inverseEntitySet = inverseEntitySetMap.get(value);
if (inverseEntitySet == null) {
return Collections.emptySet();
}
return inverseEntitySet;
}

@Override
public String toString() {
return getClass().getSimpleName() + "(" + sourceVariableDescriptor.getVariableName() + ")";
}

}

0 comments on commit d1996cf

Please sign in to comment.