== Contributing to Timefold Solver This is an open source project, and you are more than welcome to contribute! * Found an issue? https://github.com/TimefoldAI/timefold-solver/issues[Submit an issue.] * Want to fix an issue or contribute an improvement? https://github.com/TimefoldAI/timefold-solver/discussions[Talk to us about your ideas] or just start coding: . https://github.com/TimefoldAI/timefold-solver/fork[Fork it.] . Create a feature branch: `git checkout -b feature` . Commit your changes with a comment: `git commit -m "feat: add shiny new feature"` (See xref:commit-messages[Commit messages] for details.) . Push to the branch to GitHub: `git push origin feature` . https://github.com/TimefoldAI/timefold-solver/compare[Create a new Pull Request.] The CI checks against your PR to ensure that it doesn't introduce errors. If the CI identifies a potential problem, our friendly PR maintainers will help you resolve it. === Build the Timefold Solver project Use one of the following ways to build this project: * :rocket: *build-fast*: `./mvnw clean install -Dquickly` skips any checks and code analysis (~1 min) * :hammer: *build-normally*: `./mvnw clean install` runs tests, checks code style, skips documentation (~17 min) * :receipt: *build-doc*: `./mvnw clean install` in the `docs` directory creates asciidoctor documentation `docs/target/html_single/index.html` (~2 min) * :mechanical_arm: *build-all*: `./mvnw clean install -Dfull` runs all checks and creates documentation and distribution files (~20 min) === Set up your development environment . To develop with IntelliJ IDEA, Eclipse or VSCode, open the root `pom.xml` as a new project and configure a _Run/Debug configuration_ like this: + * Type: Application * Main class: `ai.timefold.solver.examples.app.TimefoldExamplesApp` * VM options: `-Xmx2G` (memory only needed when using the big datasets in the examples) * Program arguments: (none) * Working directory: `$MODULE_DIR$` (must resolve to `examples` directory) * Use classpath of module: `timefold-solver-examples` === Code style Your code is automatically formatted according to the _Import and Code Style_ conventions during every Maven build. CI checks enforce those conventions too, so be sure to build your project with maven before creating your PR: ---- ./mvnw clean install ---- For information about how to set up code style checks, see https://github.com/TimefoldAI/timefold-solver/blob/main/build/ide-config/ide-configuration.adoc[IDE Setup Instructions]. [#commit-messages] === Commit Messages * Use the present tense ("Add feature" not "Added feature") * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") * Reference issues and pull requests liberally after the first line We use link:https://www.conventionalcommits.org/en/v1.0.0/[Conventional Commits] for PR titles and commit messages. The following prefixes are allowed: - `feat` for changes that add new features, - `fix` for changes that fix bugs, - `docs` for changes that only affect documentation, - `perf` for changes that improve performance, - `test` for adding missing tests or correcting existing tests, - `build` for changes that affect the build system or external dependencies, - `ci` for changes to our CI configuration files and scripts, - `revert` for reverting previous changes, - `deps` for updates to dependencies (mostly used by Dependabot), - `chore` for any other changes. This convention is enforced by CI checks on pull requests. [#methodologyOverview] === Methodology overview ==== Development philosophy Timefold Solver is developed in accordance with the following general principles: * **Fail fast**: Invalid states are checked as early as possible. * **Understandable error message**: Errors describe what happened and if possible, suggest solutions. * **Consistent terminology**: Names of features and components are not ambiguous and are used consistently throughout the codebase. * **Real world usefulness**: Every feature is used in an example or a quickstart. A feature is only delivered when tested and fully documented. * **Automated testing**: There are unit tests, integration tests, performance regressions tests and stress tests. The test coverage is high. * **Good code hygiene**: The code is clean, readable and understandable. Readability of code is preferred over ease of writing. ==== Package structure The codebase of Timefold Solver is structured into three conceptual parts: * Public API, typically in a package with *api* somewhere in its name. * Configuration, typically in a package with *config* somewhere in its name. * Implementation, which is everything else. The public API and configuration are the parts that we keep 100 % backwards compatible. Compatibility-breaking changes are only allowed in major versions of Timefold Solver, such as 2.0.0. The implementation is out of bounds for users and it can and does change all the time. If users choose to depend on the implementation classes, they do so at their own risk. [#developmentGuidelines] === Development guidelines ==== Fail fast There are several levels of fail fast, from better to worse: . **Fail Fast at compile time**. For example: Don't accept an `Object` as a parameter if it needs to be a `String` or an ``Integer``. . **Fail Fast at startup time**. For example: if the configuration parameter needs to be a positive `int` and it's negative, fail fast . **Fail Fast at runtime**. For example: if the request needs to contain a double between `0.0` and `1.0` and it's bigger than ``1.0``, fail fast. . *Fail Fast at runtime in assertion mode* if the detection performance cost is high. For example: If, after every low level iteration, the variable A needs to be equal to the square root of B, check it if and only if an assert flag is set to true (usually controlled by the xref:using-timefold-solver/running-the-solver.adoc#environmentMode[EnvironmentMode]). ==== Exception messages . The `Exception` message must include the name and state of each relevant variable. For example: + [source,java,options="nowrap"] ---- if (fooSize < 0) { throw new IllegalArgumentException("The fooSize (" + fooSize + ") of bar (" + this + ") must be positive."); } ---- Notice that the output clearly explains what's wrong: + [source,java,options="nowrap"] ---- Exception in thread "main" java.lang.IllegalArgumentException: The fooSize (-5) of bar (myBar) must be positive. at ... ---- . Whenever possible, the `Exception` message must include context. . Whenever the fix is not obvious, the `Exception` message should include advice. Advice normally starts with the word _maybe_ on a new line: + [source,java,options="nowrap"] ---- Exception in thread "main" java.lang.IllegalStateException: The valueRangeDescriptor (fooRange) is nullable, but not countable (false). Maybe the member (getFooRange) should return CountableValueRange. at ... ---- + The word _maybe_ is to indicate that the advice is not guaranteed to be right in all cases. ==== Generics . The `@PlanningSolution` class is often passed as a generic type parameter to subsystems. . The `@PlanningEntity` class(es) are rarely passed as a generic type parameter because there could be multiple planning entities. ==== Lifecycle One of the biggest challenges in multi-algorithm implementations (such as Timefold Solver) is the lifecycle management of internal subsystems. These guidelines avoid lifecycle complexity: . The subsystems are called in the same order in `*Started()` and `*Ended` methods. .. This avoids cyclic subsystem dependencies. . The `*Scope` class's fields are filled in piecemeal by the subsystems as the algorithms discover more information about its current scope subject. .. Therefore, a `*Scope` has mutable fields. It's not an `Event`. .. A subsystem can only depend on scope information provided by an earlier subsystem. . Global variables are sorted: .. First by volatility .. Then by initialization time