Package org.optaplanner.core.impl.domain.entity.descriptor

Source Code of org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor

/*
* Copyright 2011 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.entity.descriptor;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.Solution;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.CustomShadowVariable;
import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.config.heuristic.selector.common.decorator.SelectionSorterOrder;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.policy.DescriptorPolicy;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.CustomShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
import org.optaplanner.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter;
import org.optaplanner.core.impl.score.director.ScoreDirector;

public class EntityDescriptor {

    private final SolutionDescriptor solutionDescriptor;

    private final Class<?> entityClass;
    private final BeanInfo entityBeanInfo;
    private SelectionFilter movableEntitySelectionFilter;
    private SelectionSorter decreasingDifficultySorter;

    private List<EntityDescriptor> inheritedEntityDescriptorList;

    // Only declared variable descriptors, excludes inherited variable descriptors
    private Map<String, GenuineVariableDescriptor> declaredGenuineVariableDescriptorMap;
    private Map<String, ShadowVariableDescriptor> declaredShadowVariableDescriptorMap;

    // Caches the inherited and declared variable descriptors
    private Map<String, GenuineVariableDescriptor> effectiveGenuineVariableDescriptorMap;
    private Map<String, ShadowVariableDescriptor> effectiveShadowVariableDescriptorMap;

    public EntityDescriptor(SolutionDescriptor solutionDescriptor, Class<?> entityClass) {
        this.solutionDescriptor = solutionDescriptor;
        this.entityClass = entityClass;
        try {
            entityBeanInfo = Introspector.getBeanInfo(entityClass);
        } catch (IntrospectionException e) {
            throw new IllegalStateException("The entityClass (" + entityClass
                    + ") is not a valid java bean.", e);
        }
    }

    public void processAnnotations(DescriptorPolicy descriptorPolicy) {
        processEntityAnnotations(descriptorPolicy);
        processMethodAnnotations(descriptorPolicy);
        processPropertyAnnotations(descriptorPolicy);
    }

    private void processEntityAnnotations(DescriptorPolicy descriptorPolicy) {
        PlanningEntity entityAnnotation = entityClass.getAnnotation(PlanningEntity.class);
        if (entityAnnotation == null) {
            throw new IllegalStateException("The entityClass (" + entityClass
                    + ") has been specified as a planning entity in the configuration," +
                    " but does not have a " + PlanningEntity.class.getSimpleName() + " annotation.");
        }
        processMovable(descriptorPolicy, entityAnnotation);
        processDifficulty(descriptorPolicy, entityAnnotation);
    }

    private void processMovable(DescriptorPolicy descriptorPolicy, PlanningEntity entityAnnotation) {
        Class<? extends SelectionFilter> movableEntitySelectionFilterClass = entityAnnotation.movableEntitySelectionFilter();
        if (movableEntitySelectionFilterClass == PlanningEntity.NullMovableEntitySelectionFilter.class) {
            movableEntitySelectionFilterClass = null;
        }
        if (movableEntitySelectionFilterClass != null) {
            movableEntitySelectionFilter = ConfigUtils.newInstance(this,
                    "movableEntitySelectionFilterClass", movableEntitySelectionFilterClass);
        }
    }

    private void processDifficulty(DescriptorPolicy descriptorPolicy, PlanningEntity entityAnnotation) {
        Class<? extends Comparator> difficultyComparatorClass = entityAnnotation.difficultyComparatorClass();
        if (difficultyComparatorClass == PlanningEntity.NullDifficultyComparator.class) {
            difficultyComparatorClass = null;
        }
        Class<? extends SelectionSorterWeightFactory> difficultyWeightFactoryClass
                = entityAnnotation.difficultyWeightFactoryClass();
        if (difficultyWeightFactoryClass == PlanningEntity.NullDifficultyWeightFactory.class) {
            difficultyWeightFactoryClass = null;
        }
        if (difficultyComparatorClass != null && difficultyWeightFactoryClass != null) {
            throw new IllegalStateException("The entityClass (" + entityClass
                    + ") cannot have a difficultyComparatorClass (" + difficultyComparatorClass.getName()
                    + ") and a difficultyWeightFactoryClass (" + difficultyWeightFactoryClass.getName()
                    + ") at the same time.");
        }
        if (difficultyComparatorClass != null) {
            Comparator<Object> difficultyComparator = ConfigUtils.newInstance(this,
                    "difficultyComparatorClass", difficultyComparatorClass);
            decreasingDifficultySorter = new ComparatorSelectionSorter(
                    difficultyComparator, SelectionSorterOrder.DESCENDING);
        }
        if (difficultyWeightFactoryClass != null) {
            SelectionSorterWeightFactory difficultyWeightFactory = ConfigUtils.newInstance(this,
                    "difficultyWeightFactoryClass", difficultyWeightFactoryClass);
            decreasingDifficultySorter = new WeightFactorySelectionSorter(
                    difficultyWeightFactory, SelectionSorterOrder.DESCENDING);
        }
    }

    private void processMethodAnnotations(DescriptorPolicy descriptorPolicy) {
        // Only iterate declared methods, not inherited methods, to avoid registering the same ValueRangeProvide twice
        for (Method method : entityClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(ValueRangeProvider.class)) {
                descriptorPolicy.addFromEntityValueRangeProvider(method);
            }
        }
    }

    private void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) {
        PropertyDescriptor[] propertyDescriptors = entityBeanInfo.getPropertyDescriptors();
        declaredGenuineVariableDescriptorMap = new LinkedHashMap<String, GenuineVariableDescriptor>(propertyDescriptors.length);
        declaredShadowVariableDescriptorMap = new LinkedHashMap<String, ShadowVariableDescriptor>(propertyDescriptors.length);
        boolean noPlanningVariableAnnotation = true;
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method propertyGetter = propertyDescriptor.getReadMethod();
            // Only process declared methods, not inherited methods, to avoid registering the same variable twice
            if (propertyGetter != null && propertyGetter.getDeclaringClass() == entityClass) {
                Class<? extends Annotation> variableAnnotationClass = null;
                for (Class<? extends Annotation> detectedAnnotationClass : Arrays.asList(
                        PlanningVariable.class, InverseRelationShadowVariable.class, CustomShadowVariable.class)) {
                    if (propertyGetter.isAnnotationPresent(detectedAnnotationClass)) {
                        if (variableAnnotationClass != null) {
                            throw new IllegalStateException("The entityClass (" + entityClass
                                    + ") has a property (" + propertyDescriptor.getName() + ") that has both a "
                                    + variableAnnotationClass.getSimpleName() + " annotation and a "
                                    + detectedAnnotationClass.getSimpleName() + " annotation.");
                        }
                        variableAnnotationClass = detectedAnnotationClass;
                    }
                }
                if (variableAnnotationClass != null) {
                    noPlanningVariableAnnotation = false;
                    if (propertyDescriptor.getWriteMethod() == null) {
                        throw new IllegalStateException("The entityClass (" + entityClass
                                + ") has a " + variableAnnotationClass.getSimpleName()
                                + " annotated property (" + propertyDescriptor.getName()
                                + ") that should have a setter.");
                    }
                    if (variableAnnotationClass.equals(PlanningVariable.class)) {
                        GenuineVariableDescriptor variableDescriptor = new GenuineVariableDescriptor(
                                this, propertyDescriptor);
                        declaredGenuineVariableDescriptorMap.put(propertyDescriptor.getName(), variableDescriptor);
                        variableDescriptor.processAnnotations(descriptorPolicy);
                    } else if (variableAnnotationClass.equals(InverseRelationShadowVariable.class)) {
                        ShadowVariableDescriptor variableDescriptor = new InverseRelationShadowVariableDescriptor(
                                this, propertyDescriptor);
                        declaredShadowVariableDescriptorMap.put(propertyDescriptor.getName(), variableDescriptor);
                        variableDescriptor.processAnnotations(descriptorPolicy);
                    } else if (variableAnnotationClass.equals(CustomShadowVariable.class)) {
                        ShadowVariableDescriptor variableDescriptor = new CustomShadowVariableDescriptor(
                                this, propertyDescriptor);
                        declaredShadowVariableDescriptorMap.put(propertyDescriptor.getName(), variableDescriptor);
                        variableDescriptor.processAnnotations(descriptorPolicy);
                    } else {
                        throw new IllegalStateException("The variableAnnotationClass ("
                                + variableAnnotationClass + ") is not implemented.");
                    }
                }
            }
        }
        if (noPlanningVariableAnnotation) {
            throw new IllegalStateException("The entityClass (" + entityClass
                    + ") should have at least 1 getter with a " + PlanningVariable.class.getSimpleName()
                    + " annotation or a shadow variable annotation.");
        }
    }

    public void linkInheritedEntityDescriptors(DescriptorPolicy descriptorPolicy) {
        inheritedEntityDescriptorList = new ArrayList<EntityDescriptor>(4);
        investigateParentsToLinkInherited(entityClass);
        createEffectiveVariableDescriptorMaps();
    }

    private void investigateParentsToLinkInherited(Class<?> investigateClass) {
        if (investigateClass == null || investigateClass.isArray()) {
            return;
        }
        linkInherited(investigateClass.getSuperclass());
        for (Class<?> superInterface : investigateClass.getInterfaces()) {
            linkInherited(superInterface);
        }
    }

    private void linkInherited(Class<?> investigateClass) {
        EntityDescriptor superEntityDescriptor = solutionDescriptor.getEntityDescriptorStrict(investigateClass);
        if (superEntityDescriptor != null) {
            inheritedEntityDescriptorList.add(superEntityDescriptor);
        } else {
            investigateParentsToLinkInherited(investigateClass);
        }
    }

    public void linkShadowSources(DescriptorPolicy descriptorPolicy) {
        for (ShadowVariableDescriptor shadowVariableDescriptor : declaredShadowVariableDescriptorMap.values()) {
            shadowVariableDescriptor.linkShadowSources(descriptorPolicy);
        }
    }

    private void createEffectiveVariableDescriptorMaps() {
        effectiveGenuineVariableDescriptorMap = new LinkedHashMap<String, GenuineVariableDescriptor>(
                declaredGenuineVariableDescriptorMap.size());
        effectiveShadowVariableDescriptorMap = new LinkedHashMap<String, ShadowVariableDescriptor>(
                declaredShadowVariableDescriptorMap.size());
        for (EntityDescriptor inheritedEntityDescriptor : inheritedEntityDescriptorList) {
            effectiveGenuineVariableDescriptorMap.putAll(inheritedEntityDescriptor.getGenuineVariableDescriptorMap());
            effectiveShadowVariableDescriptorMap.putAll(inheritedEntityDescriptor.getShadowVariableDescriptorMap());
        }
        effectiveGenuineVariableDescriptorMap.putAll(declaredGenuineVariableDescriptorMap);
        effectiveShadowVariableDescriptorMap.putAll(declaredShadowVariableDescriptorMap);
    }

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

    public SolutionDescriptor getSolutionDescriptor() {
        return solutionDescriptor;
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }
   
    public boolean matchesEntity(Object entity) {
        return entityClass.isAssignableFrom(entity.getClass());
    }

    public boolean hasMovableEntitySelectionFilter() {
        return movableEntitySelectionFilter != null;
    }

    public SelectionFilter getMovableEntitySelectionFilter() {
        return movableEntitySelectionFilter;
    }

    public SelectionSorter getDecreasingDifficultySorter() {
        return decreasingDifficultySorter;
    }

    public boolean hasProperty(String propertyName) {
        for (PropertyDescriptor propertyDescriptor : entityBeanInfo.getPropertyDescriptors()) {
            if (propertyDescriptor.getName().equals(propertyName)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasAnyDeclaredGenuineVariableDescriptor() {
        return !declaredGenuineVariableDescriptorMap.isEmpty();
    }

    public Collection<String> getGenuineVariableNameSet() {
        return effectiveGenuineVariableDescriptorMap.keySet();
    }

    public Map<String, GenuineVariableDescriptor> getGenuineVariableDescriptorMap() {
        return effectiveGenuineVariableDescriptorMap;
    }

    public Map<String, ShadowVariableDescriptor> getShadowVariableDescriptorMap() {
        return effectiveShadowVariableDescriptorMap;
    }

    public Collection<GenuineVariableDescriptor> getGenuineVariableDescriptors() {
        return effectiveGenuineVariableDescriptorMap.values();
    }

    public boolean hasGenuineVariableDescriptor(String variableName) {
        return effectiveGenuineVariableDescriptorMap.containsKey(variableName);
    }
   
    public GenuineVariableDescriptor getGenuineVariableDescriptor(String variableName) {
        return effectiveGenuineVariableDescriptorMap.get(variableName);
    }

    public boolean hasShadowVariableDescriptor(String variableName) {
        return effectiveShadowVariableDescriptorMap.containsKey(variableName);
    }

    public ShadowVariableDescriptor getShadowVariableDescriptor(String variableName) {
        return effectiveShadowVariableDescriptorMap.get(variableName);
    }

    public void addDeclaredVariableListenersToMap(
            Map<VariableDescriptor, List<VariableListener>> variableListenerMap) {
        for (GenuineVariableDescriptor variableDescriptor : declaredGenuineVariableDescriptorMap.values()) {
            variableListenerMap.put(variableDescriptor, variableDescriptor.buildVariableListenerList());
        }
        for (ShadowVariableDescriptor variableDescriptor : declaredShadowVariableDescriptorMap.values()) {
            variableListenerMap.put(variableDescriptor, variableDescriptor.buildVariableListenerList());
        }
    }

    public Collection<VariableDescriptor> getDeclaredVariableDescriptors() {
        Collection<VariableDescriptor> variableDescriptors = new ArrayList<VariableDescriptor>(
                declaredGenuineVariableDescriptorMap.size() + declaredShadowVariableDescriptorMap.size());
        variableDescriptors.addAll(declaredGenuineVariableDescriptorMap.values());
        variableDescriptors.addAll(declaredShadowVariableDescriptorMap.values());
        return variableDescriptors;
    }

    public VariableDescriptor getVariableDescriptor(String variableName) {
        VariableDescriptor variableDescriptor = effectiveGenuineVariableDescriptorMap.get(variableName);
        if (variableDescriptor == null) {
            variableDescriptor = effectiveShadowVariableDescriptorMap.get(variableName);
        }
        return variableDescriptor;
    }

    public String buildInvalidVariableNameExceptionMessage(String variableName) {
        if (!hasProperty(variableName)) {
            String exceptionMessage = "The variableName (" + variableName
                    + ") for entityClass (" + entityClass
                    + ") does not exists as a property (getter/setter) on that class.\n"
                    + "Check the spelling of the variableName (" + variableName + ").";
            if (variableName.length() >= 2
                    && !Character.isUpperCase(variableName.charAt(0))
                    && Character.isUpperCase(variableName.charAt(1))) {
                String correctedVariableName = variableName.substring(0, 1).toUpperCase()
                        + variableName.substring(1);
                exceptionMessage += " It probably needs to be correctedVariableName ("
                        + correctedVariableName + ") instead because the JavaBeans spec states" +
                        " the first letter should be a upper case if the second is upper case.";
            }
            return exceptionMessage;
        }
        return "The variableName (" + variableName
                + ") for entityClass (" + entityClass
                + ") exists as a property (getter/setter) on that class,"
                + " but not as an annotated as a planning variable.\n"
                + "Check if your planning entity's getter has the annotation "
                + PlanningVariable.class.getSimpleName() + " (or a shadow variable annotation).";

    }

    // ************************************************************************
    // Extraction methods
    // ************************************************************************

    public List<Object> extractEntities(Solution solution) {
        return solutionDescriptor.getEntityListByEntityClass(solution, entityClass);
    }

    public long getVariableCount() {
        return effectiveGenuineVariableDescriptorMap.size();
    }

    public long getProblemScale(Solution solution, Object entity) {
        long problemScale = 1L;
        for (GenuineVariableDescriptor variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) {
            problemScale *= variableDescriptor.getValueCount(solution, entity);
        }
        return problemScale;
    }

    public int countUninitializedVariables(Object entity) {
        int count = 0;
        for (GenuineVariableDescriptor variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) {
            if (!variableDescriptor.isInitialized(entity)) {
                count++;
            }
        }
        return count;
    }

    public boolean isInitialized(Object entity) {
        for (GenuineVariableDescriptor variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) {
            if (!variableDescriptor.isInitialized(entity)) {
                return false;
            }
        }
        return true;
    }

    public int countReinitializableVariables(ScoreDirector scoreDirector, Object entity) {
        int count = 0;
        for (GenuineVariableDescriptor variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) {
            if (variableDescriptor.isReinitializable(scoreDirector, entity)) {
                count++;
            }
        }
        return count;
    }

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

}
TOP

Related Classes of org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.