org.optaplanner.core.config.domain.ScanAnnotatedClassesConfig.java Source code

Java tutorial

Introduction

Here is the source code for org.optaplanner.core.config.domain.ScanAnnotatedClassesConfig.java

Source

/*
 * Copyright 2015 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.config.domain;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.config.AbstractConfig;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.solution.AbstractSolution;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

@XStreamAlias("scanAnnotatedClasses")
public class ScanAnnotatedClassesConfig extends AbstractConfig<ScanAnnotatedClassesConfig> {

    @XStreamImplicit(itemFieldName = "packageInclude")
    private List<String> packageIncludeList = null;

    public List<String> getPackageIncludeList() {
        return packageIncludeList;
    }

    public void setPackageIncludeList(List<String> packageIncludeList) {
        this.packageIncludeList = packageIncludeList;
    }

    // ************************************************************************
    // Builder methods
    // ************************************************************************

    public SolutionDescriptor buildSolutionDescriptor(SolverConfigContext configContext,
            ScoreDefinition deprecatedScoreDefinition) {
        ClassLoader[] classLoaders;
        if (configContext.getClassLoader() != null) {
            classLoaders = new ClassLoader[] { configContext.getClassLoader() };
        } else if (configContext.getKieContainer() != null) {
            classLoaders = new ClassLoader[] { configContext.getKieContainer().getClassLoader() };
            ReflectionsKieVfsUrlType.register(configContext.getKieContainer());
        } else {
            classLoaders = new ClassLoader[0];
        }
        ConfigurationBuilder builder = new ConfigurationBuilder();
        if (!ConfigUtils.isEmptyCollection(packageIncludeList)) {
            FilterBuilder filterBuilder = new FilterBuilder();
            for (String packageInclude : packageIncludeList) {
                if (StringUtils.isEmpty(packageInclude)) {
                    throw new IllegalArgumentException(
                            "The scanAnnotatedClasses (" + this + ") has a packageInclude (" + packageInclude
                                    + ") that is empty or null. Remove it or fill it in.");
                }
                builder.addUrls(ReflectionsWorkaroundClasspathHelper.forPackage(packageInclude, classLoaders));
                filterBuilder.includePackage(packageInclude);
            }
            builder.filterInputsBy(filterBuilder);
        } else {
            builder.addUrls(ReflectionsWorkaroundClasspathHelper.forPackage("", classLoaders));
        }
        builder.setClassLoaders(classLoaders);
        Reflections reflections = new Reflections(builder);
        Class<?> solutionClass = loadSolutionClass(reflections);
        List<Class<?>> entityClassList = loadEntityClassList(reflections);
        return SolutionDescriptor.buildSolutionDescriptor(solutionClass, entityClassList,
                deprecatedScoreDefinition);
    }

    protected Class<?> loadSolutionClass(Reflections reflections) {
        Set<Class<?>> solutionClassSet = reflections.getTypesAnnotatedWith(PlanningSolution.class);
        retainOnlyClassesWithDeclaredAnnotation(solutionClassSet, PlanningSolution.class);
        if (solutionClassSet.contains(AbstractSolution.class)) {
            // Remove that core class to avoid a pointless fail-fast.
            // (if users have a class like this, they need to use packageIncludeList)
            solutionClassSet.remove(AbstractSolution.class);
            // Note: Another abstract solution class might be fine, if extended by an unannotated solution class.
        }
        if (ConfigUtils.isEmptyCollection(solutionClassSet)) {
            throw new IllegalStateException("The scanAnnotatedClasses (" + this
                    + ") did not find any classes with a " + PlanningSolution.class.getSimpleName()
                    + " annotation.\n" + "Maybe you forgot to annotate a class with a "
                    + PlanningSolution.class.getSimpleName() + " annotation.\n"
                    + (ConfigUtils.isEmptyCollection(packageIncludeList) ? ""
                            : "Maybe the annotated class does match the packageIncludeList (" + packageIncludeList
                                    + ").\n")
                    + "Maybe you're using special classloading mechanisms (OSGi, ...) and this is a bug."
                    + " If you can confirm that, report it to our issue tracker"
                    + " and workaround it by defining the classes explicitly in the solver configuration.");
        } else if (solutionClassSet.size() > 1) {
            throw new IllegalStateException("The scanAnnotatedClasses (" + this + ") found multiple classes ("
                    + solutionClassSet + ") with a " + PlanningSolution.class.getSimpleName() + " annotation.");
        }
        Class<?> solutionClass = solutionClassSet.iterator().next();
        return solutionClass;
    }

    protected List<Class<?>> loadEntityClassList(Reflections reflections) {
        Set<Class<?>> entityClassSet = reflections.getTypesAnnotatedWith(PlanningEntity.class);
        retainOnlyClassesWithDeclaredAnnotation(entityClassSet, PlanningEntity.class);
        if (ConfigUtils.isEmptyCollection(entityClassSet)) {
            throw new IllegalStateException("The scanAnnotatedClasses (" + this
                    + ") did not find any classes with a " + PlanningEntity.class.getSimpleName() + " annotation.");
        }
        return new ArrayList<>(entityClassSet);
    }

    // TODO We need unit test for this: annotation scanning with TestdataUnannotatedExtendedEntity
    private void retainOnlyClassesWithDeclaredAnnotation(Set<Class<?>> classSet,
            Class<? extends Annotation> annotation) {
        classSet.removeIf(clazz -> !clazz.isAnnotationPresent(annotation));
    }

    @Override
    public void inherit(ScanAnnotatedClassesConfig inheritedConfig) {
        packageIncludeList = ConfigUtils.inheritMergeableListProperty(packageIncludeList,
                inheritedConfig.getPackageIncludeList());
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "(" + (packageIncludeList == null ? "" : packageIncludeList) + ")";
    }

}