[
  {
    "path": ".gitattributes",
    "content": "# Default to linux endings\n* text eol=lf\n\n# OS specific files\n###################\n\n*.sh eol=lf\n*.bat eol=crlf\n\n# Binary files\n##############\n\n# Image files\n*.png binary\n*.jpg binary\n*.gif binary\n*.bmp binary\n*.ico binary\n\n# Audio files\n*.wav binary\n*.mp3 binary\n*.ogg binary\n\n# Other binary files\n*.jar binary\n*.pdf binary\n*.xls binary\n*.xlsx binary\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: maven\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: '03:00'\n  open-pull-requests-limit: 0\n  target-branch: \"main\"\n  commit-message:\n    prefix: \"[bot][main]\"\n- package-ecosystem: maven\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: '03:00'\n  open-pull-requests-limit: 0\n  target-branch: \"8.13.x\"\n  commit-message:\n    prefix: \"[bot][8.13.x]\"\n- package-ecosystem: \"github-actions\"\n  directory: \"/\"\n  schedule:\n    interval: \"daily\"\n"
  },
  {
    "path": ".gitignore",
    "content": "/.DATA_DIR_LAST\n\n/target\n/local\n\n# Maven Profiler reports\n.profiler\n\n# IntelliJ\n.idea\n*.ipr\n*.iws\n*.iml\n\n# NetBeans\nnbproject\n\n# STS (Spring Tools Suite)\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n# Visual Studio Code\n.vscode\n\n# macOS .DS_Store files\n.DS_Store\n"
  },
  {
    "path": ".mvn/extensions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<extensions>\n  <extension>\n    <groupId>fr.jcgay.maven</groupId>\n    <artifactId>maven-profiler</artifactId>\n    <version>3.0</version>\n  </extension>\n  <extension>\n    <groupId>io.takari.aether</groupId>\n    <artifactId>takari-local-repository</artifactId>\n    <version>0.11.3</version>\n  </extension>\n  <extension>\n    <groupId>io.takari</groupId>\n    <artifactId>takari-filemanager</artifactId>\n    <version>0.8.3</version>\n  </extension>\n  <extension>\n    <groupId>io.takari.maven</groupId>\n    <artifactId>takari-smart-builder</artifactId>\n     <version>0.6.1</version>\n  </extension>\n</extensions>\n"
  },
  {
    "path": ".mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\n * Copyright 2007-present the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    private static final String WRAPPER_VERSION = \"0.5.6\";\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL = \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/\"\n        + WRAPPER_VERSION + \"/maven-wrapper-\" + WRAPPER_VERSION + \".jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output directory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        if (System.getenv(\"MVNW_USERNAME\") != null && System.getenv(\"MVNW_PASSWORD\") != null) {\n            String username = System.getenv(\"MVNW_USERNAME\");\n            char[] password = System.getenv(\"MVNW_PASSWORD\").toCharArray();\n            Authenticator.setDefault(new Authenticator() {\n                @Override\n                protected PasswordAuthentication getPasswordAuthentication() {\n                    return new PasswordAuthentication(username, password);\n                }\n            });\n        }\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\n"
  },
  {
    "path": "CONTRIBUTING.adoc",
    "content": "= Developing Drools, OptaPlanner and jBPM\n\n*If you want to build or contribute to a kiegroup project,\nhttps://github.com/kiegroup/droolsjbpm-build-bootstrap/blob/main/README.md[read this document].*\n\n*It will save you and us a lot of time by setting up your development environment correctly.*\nIt solves all known pitfalls that can disrupt your development.\nIt also describes all guidelines, tips and tricks.\nIf you want your pull requests (or patches) to be merged into main, please respect those guidelines.\n"
  },
  {
    "path": "CREDITS.adoc",
    "content": "\"link:https://www.iconfinder.com/icons/2222740/big_building_construction_home_house_icon[Big, building, construction, home,\nhouse icon]\" by https://www.iconfinder.com/strokeicon[strongicon] is licensed under\n https://creativecommons.org/licenses/by/3.0/[CC BY 3.0]\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.adoc",
    "content": ":projectKey: org.optaweb.vehiclerouting:optaweb-vehicle-routing\n\n*This project is no longer maintained.*\nVisit https://github.com/kiegroup/optaplanner-quickstarts/tree/stable/use-cases/vehicle-routing[OptaPlanner Vehicle Routing Quickstart] to see how to integrate https://www.optaplanner.org/[OptaPlanner] in your application.\n\n= OptaWeb Vehicle Routing\n\nimage:https://img.shields.io/badge/stackoverflow-ask_question-orange.svg?logo=stackoverflow[\n\"Ask question on Stack Overflow\",link=\"https://stackoverflow.com/questions/tagged/optaplanner\"]\nimage:https://img.shields.io/badge/zulip-join_chat-brightgreen.svg?logo=zulip[\n\"Join Zulip Chat\",link=\"https://kie.zulipchat.com/#narrow/stream/232679-optaplanner\"]\n\nWeb application for solving the https://www.optaplanner.org/learn/useCases/vehicleRoutingProblem.html[Vehicle Routing Problem]\nusing https://www.optaplanner.org/[OptaPlanner].\n\n== Run the application using a Bash script\n\nIf you're on Linux or macOS, you can use `runLocally.sh` to start the application on your computer.\n\n* Use `runLocally.sh` with no arguments to run with the defaults.\nThis will download an OSM file needed to work with the built-in data set.\n\n* Use `runLocally.sh -i` for the interactive mode.\nIn this mode you can choose from downloaded OSM files or download more OSM files.\n\n* Use `runLocally.sh <REGION>` to run with the selected region.\n\nSee the\nxref:optaweb-vehicle-routing-docs/src/main/asciidoc/run-locally.adoc[documentation]\nto learn more about the `runLocally.sh` script.\n\n=== Getting started video\n\nThe following https://youtu.be/rEeAML74oWo?t=107[video] shows how to download the OptaPlanner Vehicle Routing distribution and run it using the `runLocally.sh` script.\n\n== Development\n\nRead the <<optaweb-vehicle-routing-docs/src/main/asciidoc/development-guide#development-guide,Development>> chapter in the documentation.\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      jarUrl=\"$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    else\n      jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    fi\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n    if $cygwin; then\n      wrapperJarPath=`cygpath --path --windows \"$wrapperJarPath\"`\n    fi\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget \"$jarUrl\" -O \"$wrapperJarPath\"\n        else\n            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD \"$jarUrl\" -O \"$wrapperJarPath\"\n        fi\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        else\n            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        fi\n\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaClass=`cygpath --path --windows \"$javaClass\"`\n        fi\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    http://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n\nFOR /F \"tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\n    IF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B\n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Found %WRAPPER_JAR%\n    )\n) else (\n    if not \"%MVNW_REPOURL%\" == \"\" (\n        SET DOWNLOAD_URL=\"%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    )\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\n        echo Downloading from: %DOWNLOAD_URL%\n    )\n\n    powershell -Command \"&{\"^\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\n\t\t\"}\"^\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"^\n\t\t\"}\"\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Finished downloading %WRAPPER_JAR%\n    )\n)\n@REM End of extension\n\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\n@REM work with both Windows and non-Windows executions.\nset MAVEN_CMD_LINE_ARGS=%*\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/.dockerignore",
    "content": "*\n!target/*-runner\n!target/*-runner.jar\n!target/lib/*\n!target/quarkus-app*/*\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/.env-example",
    "content": "APP_DEMO_DATA_SET_DIR=${user.home}/.optaweb-vehicle-routing/dataset\nAPP_PERSISTENCE_H2_DIR=${user.home}/.optaweb-vehicle-routing/db\nAPP_PERSISTENCE_H2_FILENAME=dev\nAPP_ROUTING_GH_DIR=${user.home}/.optaweb-vehicle-routing/graphhopper\nAPP_ROUTING_OSM_DIR=${user.home}/.optaweb-vehicle-routing/openstreetmap\n#APP_ROUTING_ENGINE=AIR\n\n#QUARKUS_HTTP_PORT=8180\n\n#LOG_LEVEL_APP=DEBUG\n#LOG_LEVEL_OPTAPLANNER=DEBUG\n#LOG_LEVEL_HIBERNATE=DEBUG\n#LOG_LEVEL_DROOLS=DEBUG\n#LOG_LEVEL_RESTEASY=DEBUG\n\nQUARKUS_OPTAPLANNER_SOLVER_TERMINATION_SPENT_LIMIT=20s\n\n#APP_ROUTING_OSM_FILE=massachusetts-latest.osm.pbf\n#APP_REGION_COUNTRY_CODES=US\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/.gitignore",
    "content": "!.mvn/wrapper/maven-wrapper.jar\n\n/.env\n\n/panache-archive.marker\n\n/target\n/local\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/Dockerfile",
    "content": "FROM docker.io/adoptopenjdk/openjdk15:ubi-minimal-jre\nENV APP_ROUTING_ENGINE air\nCOPY target/*-exec.jar /opt/app/optaweb-vehicle-routing.jar\nWORKDIR /opt/app\nVOLUME /opt/app/local\nCMD [\"java\", \"-jar\", \"optaweb-vehicle-routing.jar\"]\nEXPOSE 8080\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/README.adoc",
    "content": "= OptaWeb Vehicle Routing back end\n\nSee the <<../optaweb-vehicle-routing-docs/src/main/asciidoc/development-guide#backend,back end development chapter>>\nin the documentation.\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaweb.vehiclerouting</groupId>\n    <artifactId>optaweb-vehicle-routing</artifactId>\n    <version>8.35.0.Final</version>\n  </parent>\n\n  <artifactId>optaweb-vehicle-routing-backend</artifactId>\n  <packaging>jar</packaging>\n\n  <name>OptaWeb Vehicle Routing Backend</name>\n\n  <properties>\n    <java.module.name>org.optaweb.vehiclerouting.backend</java.module.name>\n    <maven.compiler.parameters>true</maven.compiler.parameters>\n    <maven.compiler.source>11</maven.compiler.source>\n    <maven.compiler.target>11</maven.compiler.target>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n  </properties>\n\n  <dependencies>\n    <!-- Production dependencies -->\n    <!-- Dataset YAML -->\n    <dependency>\n      <groupId>com.fasterxml.jackson.dataformat</groupId>\n      <artifactId>jackson-dataformat-yaml</artifactId>\n    </dependency>\n    <!-- Persistence -->\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-hibernate-orm-panache</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-jdbc-h2</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.h2database</groupId>\n      <artifactId>h2</artifactId>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-jdbc-postgresql</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.postgresql</groupId>\n      <artifactId>postgresql</artifactId>\n      <optional>true</optional>\n    </dependency>\n    <!-- Planner -->\n    <dependency>\n      <groupId>org.optaplanner</groupId>\n      <artifactId>optaplanner-quarkus</artifactId>\n    </dependency>\n    <!-- TODO avoid guava if possible. -->\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n    </dependency>\n    <!-- Region -->\n    <dependency>\n      <groupId>com.neovisionaries</groupId>\n      <artifactId>nv-i18n</artifactId>\n    </dependency>\n    <!-- REST -->\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-resteasy-jackson</artifactId>\n    </dependency>\n    <!-- Routing -->\n    <dependency>\n      <groupId>com.graphhopper</groupId>\n      <artifactId>graphhopper-core</artifactId>\n    </dependency>\n    <!--\n      Add JAXB API dependency that's been deprecated in Java 9 and dropped in Java 11. See\n      https://stackoverflow.com/questions/43574426/how-to-resolve-java-lang-noclassdeffounderror-javax-xml-bind-jaxbexception-in-j.\n    -->\n    <dependency>\n      <groupId>jakarta.xml.bind</groupId>\n      <artifactId>jakarta.xml.bind-api</artifactId>\n      <scope>runtime</scope>\n    </dependency>\n    <!-- Testing dependencies -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-api</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-junit-jupiter</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.optaplanner</groupId>\n      <artifactId>optaplanner-test</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-junit5-mockito</artifactId>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.assertj</groupId>\n      <artifactId>assertj-core</artifactId>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <compilerArgs>\n            <!--\n              Visit https://docs.oracle.com/en/java/javase/11/tools/javac.html to learn more about javac warnings.\n            -->\n            <arg>-Xlint:all</arg>\n            <arg>-Xlint:-processing</arg>\n            <arg>-Xlint:-serial</arg>\n          </compilerArgs>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>io.quarkus</groupId>\n        <artifactId>quarkus-maven-plugin</artifactId>\n        <version>${version.io.quarkus}</version>\n        <extensions>true</extensions>\n        <executions>\n          <execution>\n            <goals>\n              <goal>build</goal>\n              <goal>generate-code</goal>\n              <goal>generate-code-tests</goal>\n            </goals>\n            <configuration>\n              <properties>\n                <quarkus.package.output-directory>quarkus-app-h2</quarkus.package.output-directory>\n                <quarkus.package.filter-optional-dependencies>true</quarkus.package.filter-optional-dependencies>\n                <quarkus.package.included-optional-dependencies>com.h2database:h2::jar</quarkus.package.included-optional-dependencies>\n              </properties>\n            </configuration>\n          </execution>\n          <execution>\n            <id>postgresql</id>\n            <goals>\n              <goal>build</goal>\n            </goals>\n            <configuration>\n              <properties>\n                <quarkus.profile>postgresql</quarkus.profile>\n                <quarkus.package.output-directory>quarkus-app-postgresql</quarkus.package.output-directory>\n                <quarkus.package.filter-optional-dependencies>true</quarkus.package.filter-optional-dependencies>\n                <quarkus.package.included-optional-dependencies>org.postgresql:postgresql::jar</quarkus.package.included-optional-dependencies>\n              </properties>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <configuration>\n          <systemPropertyVariables>\n            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>\n            <maven.home>${maven.home}</maven.home>\n          </systemPropertyVariables>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-javadoc-plugin</artifactId>\n        <configuration>\n          <skip>true</skip>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <profile>\n      <id>mutationCoverage</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.pitest</groupId>\n            <artifactId>pitest-maven</artifactId>\n            <version>1.11.4</version>\n            <dependencies>\n              <dependency>\n                <groupId>org.pitest</groupId>\n                <artifactId>pitest-junit5-plugin</artifactId>\n                <version>1.1.2</version>\n              </dependency>\n            </dependencies>\n            <!--\n              optaplanner-build-parent contains Pitest configuration that doesn't work well for this project\n              and it's not possible to change it without a complete override (combine.self=\"override\"),\n              for example excludedClasses, timeoutConstant.\n            -->\n            <configuration>\n              <reportsDirectory>local/pit-reports</reportsDirectory>\n              <timestampedReports>true</timestampedReports>\n              <!--\n                Experimental. Using more than 2 threads doesn't reduce execution time further\n                and leads to minion timeouts.\n              -->\n              <threads>2</threads>\n              <mutators>\n                <!-- See http://pitest.org/quickstart/mutators/ -->\n                <mutator>DEFAULTS</mutator>\n                <mutator>NON_VOID_METHOD_CALLS</mutator>\n                <mutator>REMOVE_CONDITIONALS</mutator>\n              </mutators>\n              <avoidCallsTo>\n                <!--\n                  String concatenation (\"a\" + \"b\") is implemented using StringBuilder.append() in bytecode.\n                  We're not interested in mutations of these calls - it's mostly toString() and exception messages.\n                  Reducing number of mutations also improves execution time.\n                -->\n                <avoidCallsTo>java.lang.StringBuilder</avoidCallsTo>\n                <avoidCallsTo>org.slf4j</avoidCallsTo>\n              </avoidCallsTo>\n              <excludedClasses>\n                <excludedClass>*Config</excludedClass>\n                <excludedClass>*Properties</excludedClass>\n              </excludedClasses>\n              <excludedMethods>hashCode</excludedMethods>\n              <excludedTestClasses>\n                <param>*IntegrationTest</param>\n              </excludedTestClasses>\n            </configuration>\n            <executions>\n              <execution>\n                <id>default-pitest</id>\n                <phase>verify</phase>\n                <goals>\n                  <goal>mutationCoverage</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n\n    <profile>\n      <id>native</id>\n      <activation>\n        <property>\n          <name>native</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>integration-test</goal>\n                  <goal>verify</goal>\n                </goals>\n                <configuration>\n                  <systemPropertyVariables>\n                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>\n                    <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>\n                    <maven.home>${maven.home}</maven.home>\n                  </systemPropertyVariables>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n      <properties>\n        <quarkus.package.type>native</quarkus.package.type>\n      </properties>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/docker/Dockerfile.jvm",
    "content": "####\n# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode\n#\n# Before building the container image run:\n#\n# ./mvnw package\n#\n# Then, build the image with:\n#\n# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/optaweb-vehicle-routing-backend-jvm .\n#\n# Then run the container using:\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend-jvm\n#\n# If you want to include the debug port into your docker image\n# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005\n#\n# Then run the container using :\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend-jvm\n#\n# This image uses the `run-java.sh` script to run the application.\n# This scripts computes the command line to execute your Java application, and\n# includes memory/GC tuning.\n# You can configure the behavior using the following environment properties:\n# - JAVA_OPTS: JVM options passed to the `java` command (example: \"-verbose:class\")\n# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options\n#   in JAVA_OPTS (example: \"-Dsome.property=foo\")\n# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is\n#   used to calculate a default maximal heap memory based on a containers restriction.\n#   If used in a container without any memory constraints for the container then this\n#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio\n#   of the container available memory as set here. The default is `50` which means 50%\n#   of the available memory is used as an upper boundary. You can skip this mechanism by\n#   setting this value to `0` in which case no `-Xmx` option is added.\n# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This\n#   is used to calculate a default initial heap memory based on the maximum heap memory.\n#   If used in a container without any memory constraints for the container then this\n#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio\n#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`\n#   is used as the initial heap size. You can skip this mechanism by setting this value\n#   to `0` in which case no `-Xms` option is added (example: \"25\")\n# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.\n#   This is used to calculate the maximum value of the initial heap memory. If used in\n#   a container without any memory constraints for the container then this option has\n#   no effect. If there is a memory constraint then `-Xms` is limited to the value set\n#   here. The default is 4096MB which means the calculated value of `-Xms` never will\n#   be greater than 4096MB. The value of this variable is expressed in MB (example: \"4096\")\n# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output\n#   when things are happening. This option, if set to true, will set\n#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: \"true\").\n# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:\n#    true\").\n# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: \"8787\").\n# - CONTAINER_CORE_LIMIT: A calculated core limit as described in\n#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: \"2\")\n# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: \"1024\").\n# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.\n#   (example: \"20\")\n# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.\n#   (example: \"40\")\n# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.\n#   (example: \"4\")\n# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus\n#   previous GC times. (example: \"90\")\n# - GC_METASPACE_SIZE: The initial metaspace size. (example: \"20\")\n# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: \"100\")\n# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should\n#   contain the necessary JRE command-line options to specify the required GC, which\n#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).\n# - HTTPS_PROXY: The location of the https proxy. (example: \"myuser@127.0.0.1:8080\")\n# - HTTP_PROXY: The location of the http proxy. (example: \"myuser@127.0.0.1:8080\")\n# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be\n#   accessed directly. (example: \"foo.example.com,bar.example.com\")\n#\n###\nFROM registry.access.redhat.com/ubi8/openjdk-11:1.14\n\nENV LANGUAGE='en_US:en'\n\n\nARG QUARKUS_APP_BUILD_QUALIFIER=h2\n# We make four distinct layers so if there are application changes the library layers can be re-used\nCOPY --chown=185 target/quarkus-app-${QUARKUS_APP_BUILD_QUALIFIER}/lib/ /deployments/lib/\nCOPY --chown=185 target/quarkus-app-${QUARKUS_APP_BUILD_QUALIFIER}/*.jar /deployments/\nCOPY --chown=185 target/quarkus-app-${QUARKUS_APP_BUILD_QUALIFIER}/app/ /deployments/app/\nCOPY --chown=185 target/quarkus-app-${QUARKUS_APP_BUILD_QUALIFIER}/quarkus/ /deployments/quarkus/\n\nEXPOSE 8080\nUSER 185\nENV AB_JOLOKIA_OFF=\"\"\nENV JAVA_OPTS=\"-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager\"\nENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"\nENV APP_PERSISTENCE_H2_DIR=/deployments/local/db\n\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/docker/Dockerfile.legacy-jar",
    "content": "####\n# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode\n#\n# Before building the container image run:\n#\n# ./mvnw package -Dquarkus.package.type=legacy-jar\n#\n# Then, build the image with:\n#\n# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/optaweb-vehicle-routing-backend-legacy-jar .\n#\n# Then run the container using:\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend-legacy-jar\n#\n# If you want to include the debug port into your docker image\n# you will have to expose the debug port (default 5005) like this :  EXPOSE 8080 5005\n#\n# Then run the container using :\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend-legacy-jar\n#\n# This image uses the `run-java.sh` script to run the application.\n# This scripts computes the command line to execute your Java application, and\n# includes memory/GC tuning.\n# You can configure the behavior using the following environment properties:\n# - JAVA_OPTS: JVM options passed to the `java` command (example: \"-verbose:class\")\n# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options\n#   in JAVA_OPTS (example: \"-Dsome.property=foo\")\n# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is\n#   used to calculate a default maximal heap memory based on a containers restriction.\n#   If used in a container without any memory constraints for the container then this\n#   option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio\n#   of the container available memory as set here. The default is `50` which means 50%\n#   of the available memory is used as an upper boundary. You can skip this mechanism by\n#   setting this value to `0` in which case no `-Xmx` option is added.\n# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This\n#   is used to calculate a default initial heap memory based on the maximum heap memory.\n#   If used in a container without any memory constraints for the container then this\n#   option has no effect. If there is a memory constraint then `-Xms` is set to a ratio\n#   of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`\n#   is used as the initial heap size. You can skip this mechanism by setting this value\n#   to `0` in which case no `-Xms` option is added (example: \"25\")\n# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.\n#   This is used to calculate the maximum value of the initial heap memory. If used in\n#   a container without any memory constraints for the container then this option has\n#   no effect. If there is a memory constraint then `-Xms` is limited to the value set\n#   here. The default is 4096MB which means the calculated value of `-Xms` never will\n#   be greater than 4096MB. The value of this variable is expressed in MB (example: \"4096\")\n# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output\n#   when things are happening. This option, if set to true, will set\n#  `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: \"true\").\n# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:\n#    true\").\n# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: \"8787\").\n# - CONTAINER_CORE_LIMIT: A calculated core limit as described in\n#   https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: \"2\")\n# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: \"1024\").\n# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.\n#   (example: \"20\")\n# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.\n#   (example: \"40\")\n# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.\n#   (example: \"4\")\n# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus\n#   previous GC times. (example: \"90\")\n# - GC_METASPACE_SIZE: The initial metaspace size. (example: \"20\")\n# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: \"100\")\n# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should\n#   contain the necessary JRE command-line options to specify the required GC, which\n#   will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).\n# - HTTPS_PROXY: The location of the https proxy. (example: \"myuser@127.0.0.1:8080\")\n# - HTTP_PROXY: The location of the http proxy. (example: \"myuser@127.0.0.1:8080\")\n# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be\n#   accessed directly. (example: \"foo.example.com,bar.example.com\")\n#\n###\nFROM registry.access.redhat.com/ubi8/openjdk-11:1.14\n\nENV LANGUAGE='en_US:en'\n\n\nCOPY target/lib/* /deployments/lib/\nCOPY target/*-runner.jar /deployments/quarkus-run.jar\n\nEXPOSE 8080\nUSER 185\nENV AB_JOLOKIA_OFF=\"\"\nENV JAVA_OPTS=\"-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager\"\nENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/docker/Dockerfile.native",
    "content": "####\n# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.\n#\n# Before building the container image run:\n#\n# ./mvnw package -Pnative\n#\n# Then, build the image with:\n#\n# docker build -f src/main/docker/Dockerfile.native -t quarkus/optaweb-vehicle-routing-backend .\n#\n# Then run the container using:\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend\n#\n###\nFROM registry.access.redhat.com/ubi8/ubi-minimal:8.6\nWORKDIR /work/\nRUN chown 1001 /work \\\n    && chmod \"g+rwX\" /work \\\n    && chown 1001:root /work\nCOPY --chown=1001:root target/*-runner /work/application\n\nEXPOSE 8080\nUSER 1001\n\nCMD [\"./application\", \"-Dquarkus.http.host=0.0.0.0\"]\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/docker/Dockerfile.native-micro",
    "content": "####\n# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.\n# It uses a micro base image, tuned for Quarkus native executables.\n# It reduces the size of the resulting container image.\n# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.\n#\n# Before building the container image run:\n#\n# ./mvnw package -Pnative\n#\n# Then, build the image with:\n#\n# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/optaweb-vehicle-routing-backend .\n#\n# Then run the container using:\n#\n# docker run -i --rm -p 8080:8080 quarkus/optaweb-vehicle-routing-backend\n#\n###\nFROM quay.io/quarkus/quarkus-micro-image:1.0\nWORKDIR /work/\nRUN chown 1001 /work \\\n    && chmod \"g+rwX\" /work \\\n    && chown 1001:root /work\nCOPY --chown=1001:root target/*-runner /work/application\n\nEXPOSE 8080\nUSER 1001\n\nCMD [\"./application\", \"-Dquarkus.http.host=0.0.0.0\"]\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/Profiles.java",
    "content": "package org.optaweb.vehiclerouting;\n\npublic class Profiles {\n\n    public static final String TEST = \"test\";\n\n    private Profiles() {\n        throw new AssertionError(\"Constants class\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/Coordinates.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport java.math.BigDecimal;\nimport java.util.Objects;\n\n/**\n * Horizontal geographical coordinates consisting of latitude and longitude.\n */\npublic class Coordinates {\n\n    private final BigDecimal latitude;\n    private final BigDecimal longitude;\n\n    public Coordinates(BigDecimal latitude, BigDecimal longitude) {\n        this.latitude = Objects.requireNonNull(latitude);\n        this.longitude = Objects.requireNonNull(longitude);\n    }\n\n    /**\n     * Create coordinates with the given latitude and longitude.\n     *\n     * @param latitude latitude\n     * @param longitude longitude\n     * @return coordinates with the given latitude and longitude\n     */\n    public static Coordinates of(double latitude, double longitude) {\n        return new Coordinates(BigDecimal.valueOf(latitude), BigDecimal.valueOf(longitude));\n    }\n\n    /**\n     * Latitude.\n     *\n     * @return latitude (never {@code null})\n     */\n    public BigDecimal latitude() {\n        return latitude;\n    }\n\n    /**\n     * Longitude.\n     *\n     * @return longitude (never {@code null})\n     */\n    public BigDecimal longitude() {\n        return longitude;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Coordinates coordinates = (Coordinates) o;\n        return latitude.compareTo(coordinates.latitude) == 0 &&\n                longitude.compareTo(coordinates.longitude) == 0;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(latitude.doubleValue(), longitude.doubleValue());\n    }\n\n    @Override\n    public String toString() {\n        return \"[\" + latitude.toPlainString() +\n                \", \" + longitude.toPlainString() +\n                ']';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/CountryCodeValidator.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.neovisionaries.i18n.CountryCode;\n\n/**\n * Validates ISO 3166-1 alpha-2 country codes.\n */\npublic class CountryCodeValidator {\n\n    private static final Logger logger = LoggerFactory.getLogger(CountryCodeValidator.class);\n\n    private CountryCodeValidator() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Validates the list of country codes and returns a normalized copy.\n     *\n     * @param countryCodes input list\n     * @return normalized copy of the input list converted to upper case and without duplicates\n     * @throws NullPointerException if the list is {@code null} or if any of its elements is {@code null}\n     * @throws IllegalArgumentException if any of the elements is not an ISO 3166-1 alpha-2 country code\n     */\n    public static List<String> validate(List<String> countryCodes) {\n        List<String> upperCaseCountries = Objects.requireNonNull(countryCodes).stream()\n                .map(String::toUpperCase)\n                .collect(toList());\n        List<String> invalidCodes = upperCaseCountries.stream()\n                .filter(s -> CountryCode.getByAlpha2Code(s) == null)\n                .collect(toList());\n        if (!invalidCodes.isEmpty()) {\n            throw new IllegalArgumentException(\n                    \"Following elements (\" + invalidCodes + \") are not valid ISO 3166-1 alpha-2 country codes\");\n        }\n        List<String> uniqueCountries = upperCaseCountries.stream().distinct().collect(toList());\n        if (uniqueCountries.size() < countryCodes.size()) {\n            logger.warn(\"Duplicate items were removed from {}\", countryCodes);\n        }\n        return uniqueCountries;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/Distance.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\n/**\n * Travel cost (distance between two {@link Location locations} or the length of a {@link Route route}).\n */\npublic class Distance {\n\n    /**\n     * Zero distance, for example the distance from a location to itself.\n     */\n    public static final Distance ZERO = Distance.ofMillis(0);\n\n    private final long millis;\n\n    /**\n     * Create a distance of the given milliseconds.\n     *\n     * @param millis must be positive or zero\n     * @return distance\n     */\n    public static Distance ofMillis(long millis) {\n        return new Distance(millis);\n    }\n\n    private Distance(long millis) {\n        if (millis < 0) {\n            throw new IllegalArgumentException(\"Milliseconds (\" + millis + \") must not be negative.\");\n        }\n        this.millis = millis;\n    }\n\n    /**\n     * Distance in milliseconds.\n     *\n     * @return positive number or zero\n     */\n    public long millis() {\n        return millis;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Distance distance = (Distance) o;\n        return millis == distance.millis;\n    }\n\n    @Override\n    public int hashCode() {\n        return Long.hashCode(millis);\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\n                \"%dh %dm %ds %dms\",\n                millis / 3600_000,\n                millis / 60_000 % 60,\n                millis / 1000 % 60,\n                millis % 1000);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/Location.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\n/**\n * A unique location significant to the user.\n */\npublic class Location extends LocationData {\n\n    private final long id;\n\n    public Location(long id, Coordinates coordinates) {\n        // TODO remove this?\n        this(id, coordinates, \"\");\n    }\n\n    public Location(long id, Coordinates coordinates, String description) {\n        super(coordinates, description);\n        this.id = id;\n    }\n\n    /**\n     * Location's ID.\n     *\n     * @return unique ID\n     */\n    public long id() {\n        return id;\n    }\n\n    /**\n     * Full description of the location including its ID, description and coordinates.\n     *\n     * @return full description\n     */\n    public String fullDescription() {\n        return \"[\" + id + \"]: \" + super.toString();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Location location = (Location) o;\n        return id == location.id;\n    }\n\n    @Override\n    public int hashCode() {\n        return Long.hashCode(id);\n    }\n\n    @Override\n    public String toString() {\n        return description().isEmpty() ? Long.toString(id) : (id + \": '\" + description() + \"'\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/LocationData.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport java.util.Objects;\n\n/**\n * Location properties. It's not an entity yet (it doesn't have an identity, it's a value object).\n * It might be the data about a location sent from a client or data stored in a file,\n * ready to be loaded but not yet tied to a specific location entity.\n */\npublic class LocationData {\n\n    private final Coordinates coordinates;\n    private final String description;\n\n    /**\n     * Create location data.\n     *\n     * @param coordinates never {@code null}\n     * @param description never {@code null}\n     */\n    public LocationData(Coordinates coordinates, String description) {\n        this.coordinates = Objects.requireNonNull(coordinates);\n        this.description = Objects.requireNonNull(description);\n    }\n\n    /**\n     * Location coordinates.\n     *\n     * @return coordinates (never {@code null})\n     */\n    public Coordinates coordinates() {\n        return coordinates;\n    }\n\n    /**\n     * Location description.\n     *\n     * @return description (never {@code null})\n     */\n    public String description() {\n        return description;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        LocationData that = (LocationData) o;\n        return coordinates.equals(that.coordinates) &&\n                description.equals(that.description);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(coordinates, description);\n    }\n\n    @Override\n    public String toString() {\n        return (description.isEmpty() ? \"<noname>\" : \"'\" + description + \"'\") + \" \" + coordinates;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/Route.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Vehicle's itinerary (sequence of visits) and its depot. This entity cannot exist without the vehicle and the depot\n * but it's allowed to have no visits when the vehicle hasn't been assigned any (it's idle).\n * <p>\n * This entity describes part of a {@link RoutingPlan solution} of the vehicle routing problem\n * (assignment of a subset of visits to one of the vehicles).\n * It doesn't carry the data about physical tracks between adjacent visits.\n * Geographical data is held by {@link RouteWithTrack}.\n */\npublic class Route {\n\n    private final Vehicle vehicle;\n    private final Location depot;\n    private final List<Location> visits;\n\n    /**\n     * Create a vehicle route.\n     *\n     * @param vehicle the vehicle assigned to this route (not {@code null})\n     * @param depot vehicle's depot (not {@code null})\n     * @param visits list of visits (not {@code null})\n     */\n    public Route(Vehicle vehicle, Location depot, List<Location> visits) {\n        this.vehicle = Objects.requireNonNull(vehicle);\n        this.depot = Objects.requireNonNull(depot);\n        this.visits = new ArrayList<>(Objects.requireNonNull(visits));\n        // TODO Probably remove this check when we have more types: new Route(Depot depot, List<Visit> visits).\n        //      Then visits obviously cannot contain the depot. But will we still require that no visit has the same\n        //      location as the depot? (I don't think so).\n        if (visits.contains(depot)) {\n            throw new IllegalArgumentException(\"Depot (\" + depot + \") must not be one of the visits (\" + visits + \")\");\n        }\n        long uniqueVisits = visits.stream().distinct().count();\n        if (uniqueVisits < visits.size()) {\n            long duplicates = visits.size() - uniqueVisits;\n            throw new IllegalArgumentException(\"Some visits have been visited multiple times (\" + duplicates + \")\");\n        }\n    }\n\n    /**\n     * The vehicle assigned to this route.\n     *\n     * @return route's vehicle (never {@code null})\n     */\n    public Vehicle vehicle() {\n        return vehicle;\n    }\n\n    /**\n     * Depot in which the route starts and ends.\n     *\n     * @return route's depot (never {@code null})\n     */\n    public Location depot() {\n        return depot;\n    }\n\n    /**\n     * List of vehicle's visits (not including the depot).\n     *\n     * @return list of visits\n     */\n    public List<Location> visits() {\n        return Collections.unmodifiableList(visits);\n    }\n\n    @Override\n    public String toString() {\n        return \"Route{\" +\n                \"vehicle=\" + vehicle +\n                \", depot=\" + depot.id() +\n                \", visits=\" + visits.stream().map(Location::id).collect(toList()) +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/RouteWithTrack.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Vehicle's {@link Route itinerary} enriched with detailed geographical description of the route.\n * This object contains data needed to visualize vehicle's route on a map.\n */\npublic class RouteWithTrack extends Route {\n\n    private final List<List<Coordinates>> track;\n\n    /**\n     * Create a route with track. When route is empty (no visits), track must be empty too and vice-versa\n     * (non-empty route must have a non-empty track).\n     *\n     * @param route vehicle's route (not {@code null})\n     * @param track track going through all visits (not {@code null})\n     */\n    public RouteWithTrack(Route route, List<List<Coordinates>> track) {\n        super(route.vehicle(), route.depot(), route.visits());\n        this.track = new ArrayList<>(Objects.requireNonNull(track));\n        if (route.visits().isEmpty() && !track.isEmpty() || !route.visits().isEmpty() && track.isEmpty()) {\n            throw new IllegalArgumentException(\"Route and track must be either both empty or both non-empty\");\n        }\n    }\n\n    /**\n     * Vehicle's track that goes from vehicle's depot through all visits and returns to the depot.\n     *\n     * @return vehicle's track (not {@code null})\n     */\n    public List<List<Coordinates>> track() {\n        return Collections.unmodifiableList(track);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/RoutingPlan.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Route plan for the whole vehicle fleet.\n */\npublic class RoutingPlan {\n\n    private static final Logger logger = LoggerFactory.getLogger(RoutingPlan.class);\n\n    private final Distance distance;\n    private final List<Vehicle> vehicles;\n    private final Location depot;\n    private final List<Location> visits;\n    private final List<RouteWithTrack> routes;\n\n    /**\n     * Create a routing plan.\n     *\n     * @param distance the overall travel distance\n     * @param vehicles all available vehicles\n     * @param depot the depot (may be {@code null})\n     * @param visits all visits\n     * @param routes routes of all vehicles\n     */\n    public RoutingPlan(\n            Distance distance,\n            List<Vehicle> vehicles,\n            Location depot,\n            List<Location> visits,\n            List<RouteWithTrack> routes) {\n        this.distance = Objects.requireNonNull(distance);\n        this.vehicles = new ArrayList<>(Objects.requireNonNull(vehicles));\n        this.depot = depot;\n        this.visits = new ArrayList<>(Objects.requireNonNull(visits));\n        this.routes = new ArrayList<>(Objects.requireNonNull(routes));\n        if (depot == null) {\n            if (!routes.isEmpty()) {\n                throw new IllegalArgumentException(\"Routes must be empty when depot is null\");\n            }\n        } else if (routes.size() != vehicles.size()) {\n            throw new IllegalArgumentException(describeVehiclesRoutesInconsistency(\n                    \"There must be exactly one route per vehicle\", vehicles, routes));\n        } else if (haveDifferentVehicles(vehicles, routes)) {\n            throw new IllegalArgumentException(describeVehiclesRoutesInconsistency(\n                    \"Some routes are assigned to non-existent vehicles\", vehicles, routes));\n        } else if (!routes.isEmpty()) {\n            List<Location> visited = routes.stream()\n                    .map(Route::visits)\n                    .flatMap(Collection::stream)\n                    .collect(toList());\n            ArrayList<Location> unvisited = new ArrayList<>(visits);\n            unvisited.removeAll(visited);\n            if (!unvisited.isEmpty()) {\n                // This happens because we're also publishing solutions that are not fully initialized.\n                // TODO decide whether this allowed or not\n                logger.warn(\"Some visits are unvisited: {}\", unvisited);\n            }\n            visited.removeAll(visits);\n            if (!visited.isEmpty()) {\n                throw new IllegalArgumentException(\n                        \"Some routes are going through visits that haven't been defined: \" + visited);\n            }\n        }\n    }\n\n    private static boolean haveDifferentVehicles(List<Vehicle> vehicles, List<RouteWithTrack> routes) {\n        return routes.stream()\n                .map(Route::vehicle)\n                .anyMatch(vehicle -> !vehicles.contains(vehicle));\n    }\n\n    private static String describeVehiclesRoutesInconsistency(\n            String cause,\n            List<Vehicle> vehicles,\n            List<? extends Route> routes) {\n        List<Long> vehicleIdsFromRoutes = routes.stream()\n                .map(route -> route.vehicle().id())\n                .collect(toList());\n        return cause\n                + \":\\n- Vehicles (\" + vehicles.size() + \"): \" + vehicles\n                + \"\\n- Routes' vehicleIds (\" + routes.size() + \"): \" + vehicleIdsFromRoutes;\n    }\n\n    /**\n     * Create an empty routing plan.\n     *\n     * @return empty routing plan\n     */\n    public static RoutingPlan empty() {\n        return new RoutingPlan(Distance.ZERO, emptyList(), null, emptyList(), emptyList());\n    }\n\n    /**\n     * Total distance traveled (sum of distances of all routes).\n     *\n     * @return travel distance\n     */\n    public Distance distance() {\n        return distance;\n    }\n\n    /**\n     * All available vehicles.\n     *\n     * @return all vehicles\n     */\n    public List<Vehicle> vehicles() {\n        return Collections.unmodifiableList(vehicles);\n    }\n\n    /**\n     * Routes of all vehicles in the depot. Includes empty routes of vehicles that stay in the depot.\n     *\n     * @return all routes (may be empty when there is no depot or no vehicles)\n     */\n    public List<RouteWithTrack> routes() {\n        return Collections.unmodifiableList(routes);\n    }\n\n    /**\n     * All visits that are part of the routing problem.\n     *\n     * @return all visits\n     */\n    public List<Location> visits() {\n        return Collections.unmodifiableList(visits);\n    }\n\n    /**\n     * The depot.\n     *\n     * @return depot (may be missing)\n     */\n    public Optional<Location> depot() {\n        return Optional.ofNullable(depot);\n    }\n\n    /**\n     * Routing plan is empty when there is no depot, no vehicles and no routes.\n     *\n     * @return {@code true} if the plan is empty\n     */\n    public boolean isEmpty() {\n        // No need to check routes. No depot => no routes.\n        return depot == null && vehicles.isEmpty();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/RoutingProblem.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * Definition of the vehicle routing problem instance.\n */\npublic class RoutingProblem {\n\n    private final String name;\n    private final List<VehicleData> vehicles;\n    private final LocationData depot;\n    private final List<LocationData> visits;\n\n    /**\n     * Create routing problem instance.\n     *\n     * @param name the instance name\n     * @param vehicles list of vehicles (not {@code null})\n     * @param depot the depot (may be {@code null} if there is no depot)\n     * @param visits list of visits (not {@code null})\n     */\n    public RoutingProblem(\n            String name,\n            List<? extends VehicleData> vehicles,\n            LocationData depot,\n            List<? extends LocationData> visits) {\n        this.name = Objects.requireNonNull(name);\n        this.vehicles = new ArrayList<>(Objects.requireNonNull(vehicles));\n        this.depot = depot;\n        this.visits = new ArrayList<>(Objects.requireNonNull(visits));\n    }\n\n    /**\n     * Get routing problem instance name.\n     *\n     * @return routing problem instance name\n     */\n    public String name() {\n        return name;\n    }\n\n    /**\n     * Get the depot.\n     *\n     * @return depot (never {@code null})\n     */\n    public Optional<LocationData> depot() {\n        return Optional.ofNullable(depot);\n    }\n\n    /**\n     * Get locations that should be visited.\n     *\n     * @return visits\n     */\n    public List<LocationData> visits() {\n        return visits;\n    }\n\n    /**\n     * Vehicles that are part of the problem definition.\n     *\n     * @return vehicles\n     */\n    public List<VehicleData> vehicles() {\n        return vehicles;\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/Vehicle.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\n/**\n * Vehicle that can be used to deliver cargo to visits.\n */\npublic class Vehicle extends VehicleData {\n\n    private final long id;\n\n    Vehicle(long id, String name, int capacity) {\n        super(name, capacity);\n        this.id = id;\n    }\n\n    /**\n     * Vehicle's ID.\n     *\n     * @return unique ID\n     */\n    public long id() {\n        return id;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Vehicle vehicle = (Vehicle) o;\n        return id == vehicle.id;\n    }\n\n    @Override\n    public int hashCode() {\n        return Long.hashCode(id);\n    }\n\n    @Override\n    public String toString() {\n        return name().isEmpty() ? Long.toString(id) : (id + \": '\" + name() + \"'\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/VehicleData.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport java.util.Objects;\n\n/**\n * Data about a vehicle.\n */\npublic class VehicleData {\n\n    private final String name;\n    private final int capacity;\n\n    VehicleData(String name, int capacity) {\n        this.name = Objects.requireNonNull(name);\n        this.capacity = capacity;\n    }\n\n    /**\n     * Vehicle's name (unique description).\n     *\n     * @return vehicle's name\n     */\n    public String name() {\n        return name;\n    }\n\n    /**\n     * Vehicle's capacity.\n     *\n     * @return vehicle's capacity\n     */\n    public int capacity() {\n        return capacity;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        VehicleData that = (VehicleData) o;\n        return capacity == that.capacity &&\n                name.equals(that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(name, capacity);\n    }\n\n    @Override\n    public String toString() {\n        return name.isEmpty() ? \"<noname>\" : \"'\" + name + \"'\";\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/VehicleFactory.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\n/**\n * Creates {@link Vehicle} instances.\n */\npublic class VehicleFactory {\n\n    private VehicleFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Create vehicle data.\n     *\n     * @param name vehicle's name\n     * @param capacity vehicle's capacity\n     * @return vehicle data\n     */\n    public static VehicleData vehicleData(String name, int capacity) {\n        return new VehicleData(name, capacity);\n    }\n\n    /**\n     * Create a new vehicle with the given ID, name and capacity.\n     *\n     * @param id vehicle's ID\n     * @param name vehicle's name\n     * @param capacity vehicle's capacity\n     * @return new vehicle\n     */\n    public static Vehicle createVehicle(long id, String name, int capacity) {\n        return new Vehicle(id, name, capacity);\n    }\n\n    /**\n     * Create a vehicle with given ID and capacity of zero. The vehicle will have a non-empty name.\n     *\n     * @param id vehicle's ID\n     * @return new testing vehicle instance\n     */\n    public static Vehicle testVehicle(long id) {\n        return new Vehicle(id, \"Vehicle \" + id, 0);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/domain/package-info.java",
    "content": "/**\n * Domain model. Contains vehicles, depots, visits and so on.\n * The code in this package only depends on {@link java.lang} and is not affected\n * by any framework used in this project.\n */\npackage org.optaweb.vehiclerouting.domain;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceCrudRepository.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport javax.enterprise.context.ApplicationScoped;\n\nimport io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;\nimport io.quarkus.panache.common.Parameters;\n\n/**\n * Distance repository.\n */\n@ApplicationScoped\npublic class DistanceCrudRepository implements PanacheRepositoryBase<DistanceEntity, DistanceKey> {\n\n    void deleteByFromIdOrToId(long deletedLocationId) {\n        delete(\n                \"fromId = :deletedLocationId or toId = :deletedLocationId\",\n                Parameters.with(\"deletedLocationId\", deletedLocationId));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceEntity.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport java.util.Objects;\n\nimport javax.persistence.EmbeddedId;\nimport javax.persistence.Entity;\n\n/**\n * Distance between two locations that can be persisted.\n */\n@Entity\nclass DistanceEntity {\n\n    @EmbeddedId\n    private DistanceKey key;\n\n    private Long distance;\n\n    protected DistanceEntity() {\n        // for JPA\n    }\n\n    DistanceEntity(DistanceKey key, Long distance) {\n        this.key = Objects.requireNonNull(key);\n        this.distance = Objects.requireNonNull(distance);\n    }\n\n    DistanceKey getKey() {\n        return key;\n    }\n\n    Long getDistance() {\n        return distance;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        DistanceEntity that = (DistanceEntity) o;\n        return key.equals(that.key) &&\n                distance.equals(that.distance);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(key, distance);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceKey.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport java.io.Serializable;\nimport java.util.Objects;\n\nimport javax.persistence.Embeddable;\n\n/**\n * Composite key for {@link DistanceEntity}.\n */\n@Embeddable\nclass DistanceKey implements Serializable {\n\n    // TODO make it a foreign key to LocationEntity\n    private Long fromId;\n    private Long toId;\n\n    protected DistanceKey() {\n        // for JPA\n    }\n\n    DistanceKey(long fromId, long toId) {\n        this.fromId = fromId;\n        this.toId = toId;\n    }\n\n    Long getFromId() {\n        return fromId;\n    }\n\n    void setFromId(Long fromId) {\n        this.fromId = fromId;\n    }\n\n    Long getToId() {\n        return toId;\n    }\n\n    void setToId(Long toId) {\n        this.toId = toId;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        DistanceKey that = (DistanceKey) o;\n        return fromId.equals(that.fromId) &&\n                toId.equals(that.toId);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(fromId, toId);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceRepositoryImpl.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.distance.DistanceRepository;\n\n@ApplicationScoped\nclass DistanceRepositoryImpl implements DistanceRepository {\n\n    private final DistanceCrudRepository distanceRepository;\n\n    @Inject\n    DistanceRepositoryImpl(DistanceCrudRepository distanceRepository) {\n        this.distanceRepository = distanceRepository;\n    }\n\n    @Override\n    public void saveDistance(Location from, Location to, Distance distance) {\n        DistanceEntity distanceEntity = new DistanceEntity(new DistanceKey(from.id(), to.id()), distance.millis());\n        distanceRepository.persist(distanceEntity);\n    }\n\n    @Override\n    public Optional<Distance> getDistance(Location from, Location to) {\n        return distanceRepository.findByIdOptional(new DistanceKey(from.id(), to.id()))\n                .map(DistanceEntity::getDistance)\n                .map(Distance::ofMillis);\n    }\n\n    @Override\n    public void deleteDistances(Location location) {\n        distanceRepository.deleteByFromIdOrToId(location.id());\n    }\n\n    @Override\n    public void deleteAll() {\n        distanceRepository.deleteAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/LocationCrudRepository.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport javax.enterprise.context.ApplicationScoped;\n\nimport io.quarkus.hibernate.orm.panache.PanacheRepository;\n\n/**\n * Location repository.\n */\n@ApplicationScoped\npublic class LocationCrudRepository implements PanacheRepository<LocationEntity> {\n\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/LocationEntity.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport java.math.BigDecimal;\nimport java.util.Objects;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.GenerationType;\nimport javax.persistence.Id;\n\n/**\n * Persistable location.\n */\n@Entity\nclass LocationEntity {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private long id;\n\n    // https://wiki.openstreetmap.org/wiki/Node#Structure\n    @Column(precision = 9, scale = 7)\n    private BigDecimal latitude;\n    @Column(precision = 10, scale = 7)\n    private BigDecimal longitude;\n\n    private String description;\n\n    protected LocationEntity() {\n        // for JPA\n    }\n\n    LocationEntity(long id, BigDecimal latitude, BigDecimal longitude, String description) {\n        this.id = id;\n        this.latitude = Objects.requireNonNull(latitude);\n        this.longitude = Objects.requireNonNull(longitude);\n        this.description = Objects.requireNonNull(description);\n    }\n\n    long getId() {\n        return id;\n    }\n\n    BigDecimal getLatitude() {\n        return latitude;\n    }\n\n    BigDecimal getLongitude() {\n        return longitude;\n    }\n\n    String getDescription() {\n        return description;\n    }\n\n    @Override\n    public String toString() {\n        return \"LocationEntity{\" +\n                \"id=\" + id +\n                \", latitude=\" + latitude +\n                \", longitude=\" + longitude +\n                \", description='\" + description + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/LocationRepositoryImpl.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@ApplicationScoped\nclass LocationRepositoryImpl implements LocationRepository {\n\n    private static final Logger logger = LoggerFactory.getLogger(LocationRepositoryImpl.class);\n    private final LocationCrudRepository repository;\n\n    @Inject\n    LocationRepositoryImpl(LocationCrudRepository repository) {\n        this.repository = repository;\n    }\n\n    @Override\n    public Location createLocation(Coordinates coordinates, String description) {\n        LocationEntity locationEntity = new LocationEntity(0, coordinates.latitude(), coordinates.longitude(), description);\n        repository.persist(locationEntity);\n        Location location = toDomain(locationEntity);\n        logger.info(\"Created location {}.\", location.fullDescription());\n        return location;\n    }\n\n    @Override\n    public List<Location> locations() {\n        return repository.streamAll()\n                .map(LocationRepositoryImpl::toDomain)\n                .collect(toList());\n    }\n\n    @Override\n    public Location removeLocation(long id) {\n        Optional<LocationEntity> maybeLocation = repository.findByIdOptional(id);\n        maybeLocation.ifPresent(locationEntity -> repository.deleteById(id));\n        LocationEntity locationEntity = maybeLocation.orElseThrow(\n                () -> new IllegalArgumentException(\"Location{id=\" + id + \"} doesn't exist\"));\n        Location location = toDomain(locationEntity);\n        logger.info(\"Deleted location {}.\", location.fullDescription());\n        return location;\n    }\n\n    @Override\n    public void removeAll() {\n        repository.deleteAll();\n    }\n\n    @Override\n    public Optional<Location> find(long locationId) {\n        return repository.findByIdOptional(locationId).map(LocationRepositoryImpl::toDomain);\n    }\n\n    private static Location toDomain(LocationEntity locationEntity) {\n        return new Location(\n                locationEntity.getId(),\n                new Coordinates(locationEntity.getLatitude(), locationEntity.getLongitude()),\n                locationEntity.getDescription());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/VehicleCrudRepository.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport javax.enterprise.context.ApplicationScoped;\n\nimport io.quarkus.hibernate.orm.panache.PanacheRepository;\n\n/**\n * Vehicle repository.\n */\n@ApplicationScoped\npublic class VehicleCrudRepository implements PanacheRepository<VehicleEntity> {\n\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/VehicleEntity.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.GenerationType;\nimport javax.persistence.Id;\n\n/**\n * Persistable vehicle.\n */\n@Entity\npublic class VehicleEntity {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private long id;\n    private String name;\n    private int capacity;\n\n    protected VehicleEntity() {\n        // for JPA\n    }\n\n    public VehicleEntity(long id, String name, int capacity) {\n        this.id = id;\n        this.name = name;\n        this.capacity = capacity;\n    }\n\n    public long getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getCapacity() {\n        return capacity;\n    }\n\n    public void setCapacity(int capacity) {\n        this.capacity = capacity;\n    }\n\n    @Override\n    public String toString() {\n        return \"VehicleEntity{\" +\n                \"id=\" + id +\n                \", name='\" + name + '\\'' +\n                \", capacity=\" + capacity +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/VehicleRepositoryImpl.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@ApplicationScoped\npublic class VehicleRepositoryImpl implements VehicleRepository {\n\n    private static final Logger logger = LoggerFactory.getLogger(VehicleRepositoryImpl.class);\n    private final VehicleCrudRepository repository;\n\n    @Inject\n    public VehicleRepositoryImpl(VehicleCrudRepository repository) {\n        this.repository = repository;\n    }\n\n    @Override\n    public Vehicle createVehicle(int capacity) {\n        VehicleEntity vehicleEntity = new VehicleEntity(0, null, capacity);\n        repository.persist(vehicleEntity);\n        vehicleEntity.setName(\"Vehicle \" + vehicleEntity.getId());\n        Vehicle vehicle = toDomain(vehicleEntity);\n        logger.info(\"Created vehicle {}.\", vehicle);\n        return vehicle;\n    }\n\n    @Override\n    public Vehicle createVehicle(VehicleData vehicleData) {\n        VehicleEntity vehicleEntity = new VehicleEntity(0, vehicleData.name(), vehicleData.capacity());\n        repository.persist(vehicleEntity);\n        Vehicle vehicle = toDomain(vehicleEntity);\n        logger.info(\"Created vehicle {}.\", vehicle);\n        return vehicle;\n    }\n\n    @Override\n    public List<Vehicle> vehicles() {\n        return repository.streamAll()\n                .map(VehicleRepositoryImpl::toDomain)\n                .collect(toList());\n    }\n\n    @Override\n    public Vehicle removeVehicle(long id) {\n        Optional<VehicleEntity> optionalVehicleEntity = repository.findByIdOptional(id);\n        VehicleEntity vehicleEntity = optionalVehicleEntity.orElseThrow(\n                () -> new IllegalArgumentException(\"Vehicle{id=\" + id + \"} doesn't exist\"));\n        repository.deleteById(id);\n        Vehicle vehicle = toDomain(vehicleEntity);\n        logger.info(\"Deleted vehicle {}.\", vehicle);\n        return vehicle;\n    }\n\n    @Override\n    public void removeAll() {\n        repository.deleteAll();\n    }\n\n    @Override\n    public Optional<Vehicle> find(long vehicleId) {\n        return repository.findByIdOptional(vehicleId).map(VehicleRepositoryImpl::toDomain);\n    }\n\n    @Override\n    public Vehicle changeCapacity(long vehicleId, int capacity) {\n        VehicleEntity vehicleEntity = repository.findByIdOptional(vehicleId).orElseThrow(() -> new IllegalArgumentException(\n                \"Can't change Vehicle{id=\" + vehicleId + \"} because it doesn't exist\"));\n        vehicleEntity.setCapacity(capacity);\n        repository.flush();\n        return VehicleFactory.createVehicle(vehicleEntity.getId(), vehicleEntity.getName(), vehicleEntity.getCapacity());\n    }\n\n    private static Vehicle toDomain(VehicleEntity vehicleEntity) {\n        return VehicleFactory.createVehicle(\n                vehicleEntity.getId(),\n                vehicleEntity.getName(),\n                vehicleEntity.getCapacity());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/persistence/package-info.java",
    "content": "/**\n * Persistence infrastructure.\n */\npackage org.optaweb.vehiclerouting.plugin.persistence;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/Constants.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\npublic class Constants {\n\n    public static final String SOLVER_CONFIG = \"solverConfig.xml\";\n\n    private Constants() {\n        throw new AssertionError(\"Constants class\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/DistanceMapImpl.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.plugin.planner.domain.DistanceMap;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\n\n/**\n * Provides distances to {@link PlanningLocation}s by reading from a {@link DistanceMatrixRow}.\n */\npublic class DistanceMapImpl implements DistanceMap {\n\n    private final DistanceMatrixRow distanceMatrixRow;\n\n    public DistanceMapImpl(DistanceMatrixRow distanceMatrixRow) {\n        this.distanceMatrixRow = Objects.requireNonNull(distanceMatrixRow);\n    }\n\n    @Override\n    public long distanceTo(PlanningLocation location) {\n        return distanceMatrixRow.distanceTo(location.getId()).millis();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/RouteChangedEventPublisher.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Event;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.route.RouteChangedEvent;\nimport org.optaweb.vehiclerouting.service.route.ShallowRoute;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Converts planning solution to a {@link RouteChangedEvent} and publishes it so that it can be processed by other\n * components that listen for this type of event.\n */\n@ApplicationScoped\nclass RouteChangedEventPublisher {\n\n    private static final Logger logger = LoggerFactory.getLogger(RouteChangedEventPublisher.class);\n\n    private final Event<RouteChangedEvent> eventPublisher;\n\n    @Inject\n    RouteChangedEventPublisher(Event<RouteChangedEvent> eventPublisher) {\n        this.eventPublisher = eventPublisher;\n    }\n\n    /**\n     * Publish solution as a {@link RouteChangedEvent}.\n     *\n     * @param solution solution\n     */\n    void publishSolution(VehicleRoutingSolution solution) {\n        RouteChangedEvent event = solutionToEvent(solution, this);\n        logger.info(\n                \"New solution with {} depots, {} vehicles, {} visits, distance: {}, score: {}\",\n                solution.getDepotList().size(),\n                solution.getVehicleList().size(),\n                solution.getVisitList().size(),\n                event.distance(),\n                solution.getScore());\n        logger.debug(\"Routes: {}\", event.routes());\n        eventPublisher.fire(event);\n    }\n\n    /**\n     * Convert a planning domain solution to an event that can be published.\n     *\n     * @param solution solution\n     * @param source source of the event\n     * @return new event describing the solution\n     */\n    static RouteChangedEvent solutionToEvent(VehicleRoutingSolution solution, Object source) {\n        List<ShallowRoute> routes = routes(solution);\n        return new RouteChangedEvent(\n                source,\n                // Turn negative soft score into a positive amount of time.\n                Distance.ofMillis(-solution.getScore().softScore()),\n                vehicleIds(solution),\n                depotId(solution),\n                visitIds(solution),\n                routes);\n    }\n\n    private static List<Long> visitIds(VehicleRoutingSolution solution) {\n        return solution.getVisitList().stream()\n                .map(visit -> visit.getLocation().getId())\n                .collect(toList());\n    }\n\n    /**\n     * Extract routes from the solution. Includes empty routes of vehicles that stay in the depot.\n     *\n     * @param solution solution\n     * @return one route per vehicle\n     */\n    private static List<ShallowRoute> routes(VehicleRoutingSolution solution) {\n        // TODO include unconnected customers in the result\n        if (solution.getDepotList().isEmpty()) {\n            return Collections.emptyList();\n        }\n        ArrayList<ShallowRoute> routes = new ArrayList<>();\n        for (PlanningVehicle vehicle : solution.getVehicleList()) {\n            PlanningDepot depot = vehicle.getDepot();\n            if (depot == null) {\n                throw new IllegalArgumentException(\n                        \"Vehicle (id=\" + vehicle.getId() + \") is not in the depot. That's not allowed\");\n            }\n            List<Long> visits = new ArrayList<>();\n            for (PlanningVisit visit : vehicle.getFutureVisits()) {\n                if (!solution.getVisitList().contains(visit)) {\n                    throw new IllegalArgumentException(\"Visit (\" + visit + \") doesn't exist\");\n                }\n                visits.add(visit.getLocation().getId());\n            }\n            routes.add(new ShallowRoute(vehicle.getId(), depot.getId(), visits));\n        }\n        return routes;\n    }\n\n    /**\n     * Get IDs of vehicles in the solution.\n     *\n     * @param solution the solution\n     * @return vehicle IDs\n     */\n    private static List<Long> vehicleIds(VehicleRoutingSolution solution) {\n        return solution.getVehicleList().stream()\n                .map(PlanningVehicle::getId)\n                .collect(toList());\n    }\n\n    /**\n     * Get solution's depot ID.\n     *\n     * @param solution the solution in which to look for the depot\n     * @return first depot ID from the solution or {@code null} if there are no depots\n     */\n    private static Long depotId(VehicleRoutingSolution solution) {\n        return solution.getDepotList().isEmpty() ? null : solution.getDepotList().get(0).getId();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/RouteOptimizerConfig.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport javax.enterprise.context.Dependent;\nimport javax.enterprise.inject.Produces;\n\nimport org.optaplanner.core.api.solver.Solver;\nimport org.optaplanner.core.api.solver.SolverFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\n\n/**\n * Configuration bean that creates {@link RouteOptimizerImpl route optimizer}'s dependencies.\n */\n@Dependent\nclass RouteOptimizerConfig {\n\n    private final SolverFactory<VehicleRoutingSolution> solverFactory;\n\n    RouteOptimizerConfig(SolverFactory<VehicleRoutingSolution> solverFactory) {\n        this.solverFactory = solverFactory;\n    }\n\n    @Produces\n    Solver<VehicleRoutingSolution> solver() {\n        return solverFactory.buildSolver();\n    }\n\n    @Produces\n    ListeningExecutorService executor() {\n        ExecutorService executorService = Executors.newFixedThreadPool(1);\n        return MoreExecutors.listeningDecorator(executorService);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/RouteOptimizerImpl.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\nimport org.optaweb.vehiclerouting.service.location.LocationPlanner;\nimport org.optaweb.vehiclerouting.service.vehicle.VehiclePlanner;\n\n/**\n * Accumulates vehicles, depots and visits until there's enough data to start the optimization.\n * Solutions are published even if solving hasn't started yet due to missing facts (e.g. no vehicles or no visits).\n * Stops solver when vehicles or visits are reduced to zero.\n */\n@ApplicationScoped\nclass RouteOptimizerImpl implements LocationPlanner, VehiclePlanner {\n\n    private final SolverManager solverManager;\n    private final RouteChangedEventPublisher routeChangedEventPublisher;\n\n    private final List<PlanningVehicle> vehicles = new ArrayList<>();\n    private final List<PlanningVisit> visits = new ArrayList<>();\n    private PlanningDepot depot;\n\n    @Inject\n    RouteOptimizerImpl(SolverManager solverManager, RouteChangedEventPublisher routeChangedEventPublisher) {\n        this.solverManager = solverManager;\n        this.routeChangedEventPublisher = routeChangedEventPublisher;\n    }\n\n    @Override\n    public void addLocation(Location domainLocation, DistanceMatrixRow distanceMatrixRow) {\n        PlanningLocation location = PlanningLocationFactory.fromDomain(\n                domainLocation,\n                new DistanceMapImpl(distanceMatrixRow));\n        // Unfortunately can't start solver with an empty solution (see https://issues.redhat.com/browse/PLANNER-776)\n        if (depot == null) {\n            depot = new PlanningDepot(location);\n            publishSolution();\n        } else {\n            PlanningVisit visit = PlanningVisitFactory.fromLocation(location);\n            visits.add(visit);\n            if (vehicles.isEmpty()) {\n                publishSolution();\n            } else if (visits.size() == 1) {\n                solverManager.startSolver(SolutionFactory.solutionFromVisits(vehicles, depot, visits));\n            } else {\n                solverManager.addVisit(visit);\n            }\n        }\n    }\n\n    @Override\n    public void removeLocation(Location domainLocation) {\n        if (visits.isEmpty()) {\n            if (depot == null) {\n                throw new IllegalArgumentException(\n                        \"Cannot remove \" + domainLocation + \" because there are no locations\");\n            }\n            if (depot.getId() != domainLocation.id()) {\n                throw new IllegalArgumentException(\"Cannot remove \" + domainLocation + \" because it doesn't exist\");\n            }\n            depot = null;\n            publishSolution();\n        } else {\n            if (depot.getId() == domainLocation.id()) {\n                throw new IllegalStateException(\"You can only remove depot if there are no visits\");\n            }\n            if (!visits.removeIf(item -> item.getId() == domainLocation.id())) {\n                throw new IllegalArgumentException(\"Cannot remove \" + domainLocation + \" because it doesn't exist\");\n            }\n            if (vehicles.isEmpty()) { // solver is not running\n                publishSolution();\n            } else if (visits.isEmpty()) { // solver is running\n                solverManager.stopSolver();\n                publishSolution();\n            } else {\n                // TODO maybe allow removing location by ID (only require the necessary information)\n                solverManager.removeVisit(\n                        PlanningVisitFactory.fromLocation(PlanningLocationFactory.fromDomain(domainLocation)));\n            }\n        }\n    }\n\n    @Override\n    public void addVehicle(Vehicle domainVehicle) {\n        PlanningVehicle vehicle = PlanningVehicleFactory.fromDomain(domainVehicle);\n        vehicle.setDepot(depot);\n        vehicles.add(vehicle);\n        if (visits.isEmpty()) {\n            publishSolution();\n        } else if (vehicles.size() == 1) {\n            solverManager.startSolver(SolutionFactory.solutionFromVisits(vehicles, depot, visits));\n        } else {\n            solverManager.addVehicle(vehicle);\n        }\n    }\n\n    @Override\n    public void removeVehicle(Vehicle domainVehicle) {\n        if (!vehicles.removeIf(vehicle -> vehicle.getId() == domainVehicle.id())) {\n            throw new IllegalArgumentException(\"Cannot remove \" + domainVehicle + \" because it doesn't exist\");\n        }\n        if (visits.isEmpty()) { // solver is not running\n            publishSolution();\n        } else if (vehicles.isEmpty()) { // solver is running\n            solverManager.stopSolver();\n            publishSolution();\n        } else {\n            solverManager.removeVehicle(PlanningVehicleFactory.fromDomain(domainVehicle));\n        }\n    }\n\n    @Override\n    public void changeCapacity(Vehicle domainVehicle) {\n        PlanningVehicle vehicle = vehicles.stream()\n                .filter(item -> item.getId() == domainVehicle.id())\n                .findFirst()\n                .orElseThrow(() -> new IllegalArgumentException(\n                        \"Cannot change capacity of \" + domainVehicle + \" because it doesn't exist\"));\n        vehicle.setCapacity(domainVehicle.capacity());\n        if (!visits.isEmpty()) {\n            solverManager.changeCapacity(vehicle);\n        } else {\n            publishSolution();\n        }\n    }\n\n    @Override\n    public void removeAllLocations() {\n        solverManager.stopSolver();\n        depot = null;\n        visits.clear();\n        publishSolution();\n    }\n\n    @Override\n    public void removeAllVehicles() {\n        solverManager.stopSolver();\n        vehicles.clear();\n        publishSolution();\n    }\n\n    private void publishSolution() {\n        routeChangedEventPublisher.publishSolution(SolutionFactory.solutionFromVisits(vehicles, depot, visits));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/SolverManager.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Event;\nimport javax.enterprise.inject.Default;\nimport javax.inject.Inject;\n\nimport org.optaplanner.core.api.solver.Solver;\nimport org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;\nimport org.optaplanner.core.api.solver.event.SolverEventListener;\nimport org.optaweb.vehiclerouting.plugin.planner.change.AddVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.change.AddVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.change.ChangeVehicleCapacity;\nimport org.optaweb.vehiclerouting.plugin.planner.change.RemoveVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.change.RemoveVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.error.ErrorEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\n\n/**\n * Manages a solver running in a different thread.\n * <p>\n * Does following:\n * <ul>\n * <li>Starts solver by running {@link Solver#solve(Object problem)} in a thread that's not the caller's thread.</li>\n * <li>Stops the solver (synchronously).</li>\n * <li>Adds problem fact changes to the solver.</li>\n * <li>Propagates any exception that happens in {@code Solver.solver()} (in a different thread) to the thread that\n * interacts with {@code SolverManager}.</li>\n * <li>Listens for best solution changes and publishes new best solutions via {@link RouteChangedEventPublisher}.</li>\n * </ul>\n */\n@ApplicationScoped\n@Default\nclass SolverManager implements SolverEventListener<VehicleRoutingSolution> {\n\n    private static final Logger logger = LoggerFactory.getLogger(SolverManager.class);\n\n    private final Solver<VehicleRoutingSolution> solver;\n    private final ListeningExecutorService executor;\n    private final RouteChangedEventPublisher routeChangedEventPublisher;\n    private final Event<ErrorEvent> errorEvent;\n\n    private ListenableFuture<VehicleRoutingSolution> solverFuture;\n\n    @Inject\n    SolverManager(\n            Solver<VehicleRoutingSolution> solver,\n            ListeningExecutorService executor,\n            RouteChangedEventPublisher routeChangedEventPublisher,\n            Event<ErrorEvent> errorEvent) {\n        this.solver = solver;\n        this.executor = executor;\n        this.routeChangedEventPublisher = routeChangedEventPublisher;\n        this.errorEvent = errorEvent;\n        this.solver.addEventListener(this);\n    }\n\n    @Override\n    public void bestSolutionChanged(BestSolutionChangedEvent<VehicleRoutingSolution> bestSolutionChangedEvent) {\n        // CAUTION! This runs on the solver thread. Implications:\n        // 1. The method should be as quick as possible to avoid blocking solver unnecessarily.\n        // 2. This place is a potential source of race conditions.\n        if (!bestSolutionChangedEvent.isEveryProblemChangeProcessed()) {\n            logger.info(\"Ignoring a new best solution that has some problem facts missing\");\n            return;\n        }\n        // TODO Race condition, if a servlet thread deletes that location in the middle of this method happening\n        //      on the solver thread. Make sure that location is still in the repository.\n        //      Maybe repair the solution OR ignore if it's inconsistent (log a WARNING).\n        routeChangedEventPublisher.publishSolution(bestSolutionChangedEvent.getNewBestSolution()); // TODO @Async\n    }\n\n    void startSolver(VehicleRoutingSolution solution) {\n        if (solverFuture != null) {\n            throw new IllegalStateException(\"Solver start has already been requested\");\n        }\n        solverFuture = executor.submit((SolvingTask) () -> solver.solve(solution));\n        solverFuture.addListener(\n                // IMPORTANT: This is happening on the solver thread.\n                // TODO maybe restart or somehow recover?\n                () -> {\n                    if (!solver.isTerminateEarly()) {\n                        // Solver in daemon mode can't return from solve() unless it has been terminated early\n                        // (see #stopSolver()).\n                        // So this case is only possible when an exception is thrown during solver.solve().\n                        try {\n                            solverFuture.get();\n                            logger.error(\"The solver has stopped without being terminated early so at this point\"\n                                    + \" it is expected to have crashed but there was no exception.\\n\"\n                                    + \"If you see this other than during test execution it is probably a bug.\");\n                            errorEvent.fire(new ErrorEvent(\n                                    this,\n                                    \"Solver stopped without being terminated early and without throwing an exception.\"\n                                            + \" This is a bug.\"));\n                        } catch (InterruptedException e) {\n                            Thread.currentThread().interrupt();\n                            throw new RuntimeException(\"Interrupted while retrieving the cause of solver failure\", e);\n                        } catch (ExecutionException e) {\n                            logger.error(\"Solver failed\", e);\n                            errorEvent.fire(new ErrorEvent(this, e.toString()));\n                        }\n                    }\n                },\n                MoreExecutors.directExecutor());\n    }\n\n    void stopSolver() {\n        if (solverFuture != null) {\n            // TODO what happens if solver hasn't started yet (solve() is called asynchronously)\n            solver.terminateEarly();\n            // make sure solver has terminated and propagate exceptions\n            try {\n                solverFuture.get();\n                solverFuture = null;\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new RuntimeException(\"Failed to stop solver\", e);\n            } catch (ExecutionException e) {\n                // Skipping the wrapper ExecutionException because it only tells that the problem occurred\n                // in solverFuture.get() but that's obvious.\n                throw new RuntimeException(\"Failed to stop solver\", e.getCause());\n            }\n        }\n    }\n\n    private void assertSolverIsAlive() {\n        if (solverFuture == null) {\n            throw new IllegalStateException(\"Solver has not started yet\");\n        }\n        if (solverFuture.isDone()) {\n            try {\n                solverFuture.get();\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                throw new RuntimeException(\"Solver has died\", e);\n            } catch (ExecutionException e) {\n                // Skipping the wrapper ExecutionException because it only tells that the problem occurred\n                // in solverFuture.get() but that's obvious.\n                throw new RuntimeException(\"Solver has died\", e.getCause());\n            }\n            throw new IllegalStateException(\"Solver has finished solving even though it operates in daemon mode\");\n        }\n    }\n\n    void addVisit(PlanningVisit visit) {\n        assertSolverIsAlive();\n        solver.addProblemChange(new AddVisit(visit));\n    }\n\n    void removeVisit(PlanningVisit visit) {\n        assertSolverIsAlive();\n        solver.addProblemChange(new RemoveVisit(visit));\n    }\n\n    void addVehicle(PlanningVehicle vehicle) {\n        assertSolverIsAlive();\n        solver.addProblemChange(new AddVehicle(vehicle));\n    }\n\n    void removeVehicle(PlanningVehicle vehicle) {\n        assertSolverIsAlive();\n        solver.addProblemChange(new RemoveVehicle(vehicle));\n    }\n\n    void changeCapacity(PlanningVehicle vehicle) {\n        assertSolverIsAlive();\n        solver.addProblemChange(new ChangeVehicleCapacity(vehicle));\n    }\n\n    /**\n     * An alias interface that fixates the Callable's type parameter. This avoids unchecked warnings in tests.\n     */\n    interface SolvingTask extends Callable<VehicleRoutingSolution> {\n\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/VehicleRoutingConstraintProvider.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.optaplanner.core.api.score.stream.ConstraintCollectors.sum;\n\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\nimport org.optaplanner.core.api.score.stream.Constraint;\nimport org.optaplanner.core.api.score.stream.ConstraintFactory;\nimport org.optaplanner.core.api.score.stream.ConstraintProvider;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\n\npublic class VehicleRoutingConstraintProvider implements ConstraintProvider {\n\n    @Override\n    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {\n        return new Constraint[] {\n                vehicleCapacity(constraintFactory),\n                distanceFromPreviousStandstill(constraintFactory),\n                distanceFromLastVisitToDepot(constraintFactory)\n        };\n    }\n\n    Constraint vehicleCapacity(ConstraintFactory constraintFactory) {\n        return constraintFactory.forEach(PlanningVisit.class)\n                .groupBy(PlanningVisit::getVehicle, sum(PlanningVisit::getDemand))\n                .filter((vehicle, demand) -> demand > vehicle.getCapacity())\n                .penalizeLong(HardSoftLongScore.ONE_HARD,\n                        (vehicle, demand) -> demand - vehicle.getCapacity())\n                .asConstraint(\"vehicle capacity\");\n    }\n\n    Constraint distanceFromPreviousStandstill(ConstraintFactory constraintFactory) {\n        return constraintFactory.forEach(PlanningVisit.class)\n                .penalizeLong(HardSoftLongScore.ONE_SOFT,\n                        PlanningVisit::distanceFromPreviousStandstill)\n                .asConstraint(\"distance from previous standstill\");\n    }\n\n    Constraint distanceFromLastVisitToDepot(ConstraintFactory constraintFactory) {\n        return constraintFactory.forEach(PlanningVisit.class)\n                .filter(PlanningVisit::isLast)\n                .penalizeLong(HardSoftLongScore.ONE_SOFT,\n                        PlanningVisit::distanceToDepot)\n                .asConstraint(\"distance from last visit to depot\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/AddVehicle.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport java.util.Objects;\n\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.core.api.solver.change.ProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\npublic class AddVehicle implements ProblemChange<VehicleRoutingSolution> {\n\n    private final PlanningVehicle vehicle;\n\n    public AddVehicle(PlanningVehicle vehicle) {\n        this.vehicle = Objects.requireNonNull(vehicle);\n    }\n\n    @Override\n    public void doChange(VehicleRoutingSolution workingSolution, ProblemChangeDirector problemChangeDirector) {\n        problemChangeDirector.addProblemFact(vehicle, workingSolution.getVehicleList()::add);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/AddVisit.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport java.util.Objects;\n\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.core.api.solver.change.ProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\npublic class AddVisit implements ProblemChange<VehicleRoutingSolution> {\n\n    private final PlanningVisit visit;\n\n    public AddVisit(PlanningVisit visit) {\n        this.visit = Objects.requireNonNull(visit);\n    }\n\n    @Override\n    public void doChange(VehicleRoutingSolution workingSolution, ProblemChangeDirector problemChangeDirector) {\n        problemChangeDirector.addEntity(visit, workingSolution.getVisitList()::add);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/ChangeVehicleCapacity.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport java.util.Objects;\n\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.core.api.solver.change.ProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\npublic class ChangeVehicleCapacity implements ProblemChange<VehicleRoutingSolution> {\n\n    private final PlanningVehicle vehicle;\n\n    public ChangeVehicleCapacity(PlanningVehicle vehicle) {\n        this.vehicle = Objects.requireNonNull(vehicle);\n    }\n\n    @Override\n    public void doChange(VehicleRoutingSolution workingSolution, ProblemChangeDirector problemChangeDirector) {\n        // No need to clone the workingVehicle because it is a planning entity, so it is already planning-cloned.\n        // To learn more about problem fact changes, see:\n        // https://www.optaplanner.org/docs/optaplanner/latest/repeated-planning/repeated-planning.html#problemChangeExample\n        problemChangeDirector.changeProblemProperty(vehicle,\n                workingVehicle -> workingVehicle.setCapacity(vehicle.getCapacity()));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/RemoveVehicle.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport java.util.Objects;\n\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.core.api.solver.change.ProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\npublic class RemoveVehicle implements ProblemChange<VehicleRoutingSolution> {\n\n    private final PlanningVehicle removedVehicle;\n\n    public RemoveVehicle(PlanningVehicle removedVehicle) {\n        this.removedVehicle = Objects.requireNonNull(removedVehicle);\n    }\n\n    @Override\n    public void doChange(VehicleRoutingSolution workingSolution, ProblemChangeDirector problemChangeDirector) {\n        // Look up a working copy of the vehicle\n        PlanningVehicle workingVehicle = problemChangeDirector.lookUpWorkingObjectOrFail(removedVehicle);\n\n        // Un-initialize all visits of this vehicle\n        for (PlanningVisit visit : workingVehicle.getFutureVisits()) {\n            problemChangeDirector.changeVariable(visit, \"previousStandstill\",\n                    planningVisit -> planningVisit.setPreviousStandstill(null));\n        }\n\n        // No need to clone the vehicleList because it is a planning entity collection, so it is already\n        // planning-cloned.\n        // To learn more about problem fact changes, see:\n        // https://www.optaplanner.org/docs/optaplanner/latest/repeated-planning/repeated-planning.html#problemChangeExample\n\n        // Remove the vehicle\n        problemChangeDirector.removeProblemFact(workingVehicle, planningVehicle -> {\n            if (!workingSolution.getVehicleList().remove(planningVehicle)) {\n                throw new IllegalStateException(\n                        \"Working solution's vehicleList \"\n                                + workingSolution.getVehicleList()\n                                + \" doesn't contain the workingVehicle (\"\n                                + planningVehicle\n                                + \"). This is a bug!\");\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/RemoveVisit.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport java.util.Objects;\n\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.core.api.solver.change.ProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\npublic class RemoveVisit implements ProblemChange<VehicleRoutingSolution> {\n\n    private final PlanningVisit planningVisit;\n\n    public RemoveVisit(PlanningVisit planningVisit) {\n        this.planningVisit = Objects.requireNonNull(planningVisit);\n    }\n\n    @Override\n    public void doChange(VehicleRoutingSolution workingSolution, ProblemChangeDirector problemChangeDirector) {\n        // Look up a working copy of the visit\n        PlanningVisit workingVisit = problemChangeDirector.lookUpWorkingObjectOrFail(planningVisit);\n\n        // Fix the next visit and set its previousStandstill to the removed visit's previousStandstill\n        PlanningVisit nextVisit = workingVisit.getNextVisit();\n        if (nextVisit != null) { // otherwise it's the last visit\n            problemChangeDirector.changeVariable(nextVisit, \"previousStandstill\",\n                    workingNextVisit -> workingNextVisit.setPreviousStandstill(workingVisit.getPreviousStandstill()));\n        }\n\n        // No need to clone the visitList because it is a planning entity collection, so it is already planning-cloned.\n        // To learn more about problem fact changes, see:\n        // https://www.optaplanner.org/docs/optaplanner/latest/repeated-planning/repeated-planning.html#problemChangeExample\n\n        // Remove the visit\n        problemChangeDirector.removeEntity(planningVisit, visit -> {\n            if (!workingSolution.getVisitList().remove(visit)) {\n                throw new IllegalStateException(\n                        \"Working solution's visitList \"\n                                + workingSolution.getVisitList()\n                                + \" doesn't contain the workingVisit (\"\n                                + visit\n                                + \"). This is a bug!\");\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/change/package-info.java",
    "content": "/**\n * {@link org.optaplanner.core.api.solver.change.ProblemChange} implementations.\n * <p>\n * Problem fact changes are difficult to write correctly. To understand the code and when implementing new fact changes,\n * read <a href=\"https://www.optaplanner.org/docs/optaplanner/latest/repeated-planning/repeated-planning.html#problemChange\">\n * ProblemChange documentation</a>.\n */\npackage org.optaweb.vehiclerouting.plugin.planner.change;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/DistanceMap.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\n/**\n * Contains travel distances from a reference location to other locations.\n */\n@FunctionalInterface\npublic interface DistanceMap {\n\n    /**\n     * Get distance from a reference location to the given location. The actual physical quantity (distance or time)\n     * and its units depend on the configuration of the routing engine and is not important for optimization.\n     *\n     * @param location location the distance of which will be returned\n     * @return location's distance\n     */\n    long distanceTo(PlanningLocation location);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningDepot.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport java.util.Objects;\n\npublic class PlanningDepot {\n\n    private final PlanningLocation location;\n\n    public PlanningDepot(PlanningLocation location) {\n        this.location = Objects.requireNonNull(location);\n    }\n\n    public long getId() {\n        return location.getId();\n    }\n\n    public PlanningLocation getLocation() {\n        return location;\n    }\n\n    @Override\n    public String toString() {\n        return \"PlanningDepot{\" +\n                \"location=\" + location.getId() +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningLocation.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport java.util.Objects;\n\npublic class PlanningLocation {\n\n    private final long id;\n    // Only used to calculate angle.\n    private final double latitude;\n    private final double longitude;\n    private final DistanceMap travelDistanceMap;\n\n    PlanningLocation(long id, double latitude, double longitude, DistanceMap travelDistanceMap) {\n        this.id = id;\n        this.latitude = latitude;\n        this.longitude = longitude;\n        this.travelDistanceMap = Objects.requireNonNull(travelDistanceMap);\n    }\n\n    /**\n     * ID of the corresponding domain location.\n     *\n     * @return domain location ID\n     */\n    public long getId() {\n        return id;\n    }\n\n    /**\n     * Distance to the given location.\n     *\n     * @param location other location\n     * @return distance to the other location\n     */\n    public long distanceTo(PlanningLocation location) {\n        if (this == location) {\n            return 0L;\n        }\n        return travelDistanceMap.distanceTo(location);\n    }\n\n    /**\n     * Angle between the given location and the direction EAST with {@code this} location being the vertex.\n     *\n     * @param location location that forms one side of the angle (not {@code null})\n     * @return angle in radians in the range of -π to π\n     */\n    public double angleTo(PlanningLocation location) {\n        // Euclidean distance (Pythagorean theorem) - not correct when the surface is a sphere\n        double latitudeDifference = location.latitude - latitude;\n        double longitudeDifference = location.longitude - longitude;\n        return Math.atan2(latitudeDifference, longitudeDifference);\n    }\n\n    @Override\n    public String toString() {\n        return \"PlanningLocation{\" +\n                \"latitude=\" + latitude +\n                \",longitude=\" + longitude +\n                \",travelDistanceMap=\" + travelDistanceMap +\n                \",id=\" + id +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningLocationFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport org.optaweb.vehiclerouting.domain.Location;\n\n/**\n * Creates {@link PlanningLocation}s.\n */\npublic class PlanningLocationFactory {\n\n    private PlanningLocationFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Create planning location without a distance map. This location cannot be used for planning but can be used for\n     * a problem fact change to remove a visit.\n     *\n     * @param location domain location\n     * @return planning location without distance map\n     */\n    public static PlanningLocation fromDomain(Location location) {\n        return fromDomain(location, PlanningLocationFactory::failFast);\n    }\n\n    /**\n     * Create planning location from a domain location and a distance map.\n     *\n     * @param location domain location\n     * @param distanceMap distance map of this planning location\n     * @return planning location\n     */\n    public static PlanningLocation fromDomain(Location location, DistanceMap distanceMap) {\n        return new PlanningLocation(\n                location.id(),\n                location.coordinates().latitude().doubleValue(),\n                location.coordinates().longitude().doubleValue(),\n                distanceMap);\n    }\n\n    /**\n     * Create test location without distance map and coordinates. Coordinates will be initialized to zero.\n     *\n     * @param id location ID\n     * @return planning location without distance map and coordinates\n     */\n    public static PlanningLocation testLocation(long id) {\n        return testLocation(id, PlanningLocationFactory::failFast);\n    }\n\n    /**\n     * Create test location with distance map and without coordinates. Coordinates will be initialized to zero.\n     *\n     * @param id location ID\n     * @param distanceMap distance map\n     * @return planning location with distance map and without coordinates\n     */\n    public static PlanningLocation testLocation(long id, DistanceMap distanceMap) {\n        return new PlanningLocation(id, 0, 0, distanceMap);\n    }\n\n    private static long failFast(PlanningLocation location) {\n        throw new IllegalStateException();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVehicle.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\nimport org.optaplanner.core.api.domain.lookup.PlanningId;\n\npublic class PlanningVehicle implements Standstill {\n\n    @PlanningId\n    private long id;\n    private int capacity;\n    private PlanningDepot depot;\n\n    // Shadow variables\n    private PlanningVisit nextVisit;\n\n    PlanningVehicle() {\n        // Hide public constructor in favor of the factory.\n    }\n\n    public long getId() {\n        return id;\n    }\n\n    public void setId(long id) {\n        this.id = id;\n    }\n\n    public int getCapacity() {\n        return capacity;\n    }\n\n    public void setCapacity(int capacity) {\n        this.capacity = capacity;\n    }\n\n    public PlanningDepot getDepot() {\n        return depot;\n    }\n\n    public void setDepot(PlanningDepot depot) {\n        this.depot = depot;\n    }\n\n    @Override\n    public PlanningVisit getNextVisit() {\n        return nextVisit;\n    }\n\n    @Override\n    public void setNextVisit(PlanningVisit nextVisit) {\n        this.nextVisit = nextVisit;\n    }\n\n    public Iterable<PlanningVisit> getFutureVisits() {\n        return () -> new Iterator<PlanningVisit>() {\n            PlanningVisit nextVisit = getNextVisit();\n\n            @Override\n            public boolean hasNext() {\n                return nextVisit != null;\n            }\n\n            @Override\n            public PlanningVisit next() {\n                if (nextVisit == null) {\n                    throw new NoSuchElementException();\n                }\n                PlanningVisit out = nextVisit;\n                nextVisit = nextVisit.getNextVisit();\n                return out;\n            }\n        };\n    }\n\n    @Override\n    public PlanningLocation getLocation() {\n        return depot.getLocation();\n    }\n\n    @Override\n    public String toString() {\n        return \"PlanningVehicle{\" +\n                \"capacity=\" + capacity +\n                (depot == null ? \"\" : \",depot=\" + depot.getId()) +\n                (nextVisit == null ? \"\" : \",nextVisit=\" + nextVisit.getId()) +\n                \",id=\" + id +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVehicleFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\n\n/**\n * Creates {@link PlanningVehicle} instances.\n */\npublic class PlanningVehicleFactory {\n\n    private PlanningVehicleFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Create planning vehicle from domain vehicle.\n     *\n     * @param domainVehicle domain vehicle\n     * @return planning vehicle\n     */\n    public static PlanningVehicle fromDomain(Vehicle domainVehicle) {\n        return vehicle(domainVehicle.id(), domainVehicle.capacity());\n    }\n\n    /**\n     * Create a testing vehicle with zero capacity.\n     *\n     * @param id vehicle's ID\n     * @return new vehicle with zero capacity\n     */\n    public static PlanningVehicle testVehicle(long id) {\n        return vehicle(id, 0);\n    }\n\n    /**\n     * Create a testing vehicle with capacity.\n     *\n     * @param id vehicle's ID\n     * @return new vehicle with the given capacity\n     */\n    public static PlanningVehicle testVehicle(long id, int capacity) {\n        return vehicle(id, capacity);\n    }\n\n    private static PlanningVehicle vehicle(long id, int capacity) {\n        PlanningVehicle vehicle = new PlanningVehicle();\n        vehicle.setId(id);\n        vehicle.setCapacity(capacity);\n        return vehicle;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVisit.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport org.optaplanner.core.api.domain.entity.PlanningEntity;\nimport org.optaplanner.core.api.domain.lookup.PlanningId;\nimport org.optaplanner.core.api.domain.variable.AnchorShadowVariable;\nimport org.optaplanner.core.api.domain.variable.PlanningVariable;\nimport org.optaplanner.core.api.domain.variable.PlanningVariableGraphType;\nimport org.optaweb.vehiclerouting.plugin.planner.weight.DepotAngleVisitDifficultyWeightFactory;\n\n@PlanningEntity(difficultyWeightFactoryClass = DepotAngleVisitDifficultyWeightFactory.class)\npublic class PlanningVisit implements Standstill {\n\n    @PlanningId\n    private long id;\n    private PlanningLocation location;\n    private int demand;\n\n    // Planning variable: changes during planning, between score calculations.\n    @PlanningVariable(valueRangeProviderRefs = { \"vehicleRange\", \"visitRange\" },\n            graphType = PlanningVariableGraphType.CHAINED)\n    private Standstill previousStandstill;\n\n    // Shadow variables\n    private PlanningVisit nextVisit;\n    @AnchorShadowVariable(sourceVariableName = \"previousStandstill\")\n    private PlanningVehicle vehicle;\n\n    PlanningVisit() {\n        // Hide public constructor in favor of the factory.\n    }\n\n    public long getId() {\n        return id;\n    }\n\n    public void setId(long id) {\n        this.id = id;\n    }\n\n    @Override\n    public PlanningLocation getLocation() {\n        return location;\n    }\n\n    public void setLocation(PlanningLocation location) {\n        this.location = location;\n    }\n\n    public int getDemand() {\n        return demand;\n    }\n\n    public void setDemand(int demand) {\n        this.demand = demand;\n    }\n\n    public Standstill getPreviousStandstill() {\n        return previousStandstill;\n    }\n\n    public void setPreviousStandstill(Standstill previousStandstill) {\n        this.previousStandstill = previousStandstill;\n    }\n\n    @Override\n    public PlanningVisit getNextVisit() {\n        return nextVisit;\n    }\n\n    @Override\n    public void setNextVisit(PlanningVisit nextVisit) {\n        this.nextVisit = nextVisit;\n    }\n\n    public PlanningVehicle getVehicle() {\n        return vehicle;\n    }\n\n    public void setVehicle(PlanningVehicle vehicle) {\n        this.vehicle = vehicle;\n    }\n\n    // ************************************************************************\n    // Complex methods\n    // ************************************************************************\n\n    /**\n     * Distance from the previous standstill to this visit. This is used to calculate the travel cost of a chain\n     * beginning with a vehicle (at a depot) and ending with the {@link #isLast() last} visit.\n     * The chain ends with a visit, not a depot so the cost of returning from the last visit back to the depot\n     * has to be added in a separate step using {@link #distanceToDepot()}.\n     *\n     * @return distance from previous standstill to this visit\n     */\n    public long distanceFromPreviousStandstill() {\n        if (previousStandstill == null) {\n            throw new IllegalStateException(\n                    \"This method must not be called when the previousStandstill (null) is not initialized yet.\");\n        }\n        return previousStandstill.getLocation().distanceTo(location);\n    }\n\n    /**\n     * Distance from this visit back to the depot.\n     *\n     * @return distance from this visit back its vehicle's depot\n     */\n    public long distanceToDepot() {\n        return location.distanceTo(vehicle.getLocation());\n    }\n\n    /**\n     * Whether this visit is the last in a chain.\n     *\n     * @return true, if this visit has no {@link #getNextVisit() next} visit\n     */\n    public boolean isLast() {\n        return nextVisit == null;\n    }\n\n    @Override\n    public String toString() {\n        return \"PlanningVisit{\" +\n                (location == null ? \"\" : \"location=\" + location.getId()) +\n                \",demand=\" + demand +\n                (previousStandstill == null ? \"\" : \",previousStandstill='\" + previousStandstill.getLocation().getId()) +\n                (nextVisit == null ? \"\" : \",nextVisit=\" + nextVisit.getId()) +\n                (vehicle == null ? \"\" : \",vehicle=\" + vehicle.getId()) +\n                \",id=\" + id +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVisitFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\n/**\n * Creates {@link PlanningVisit} instances.\n */\npublic class PlanningVisitFactory {\n\n    static final int DEFAULT_VISIT_DEMAND = 1;\n\n    private PlanningVisitFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Create visit with {@link #DEFAULT_VISIT_DEMAND}.\n     *\n     * @param location visit's location\n     * @return new visit with the default demand\n     */\n    public static PlanningVisit fromLocation(PlanningLocation location) {\n        return fromLocation(location, DEFAULT_VISIT_DEMAND);\n    }\n\n    /**\n     * Create visit of a location with the given demand.\n     *\n     * @param location visit's location\n     * @param demand visit's demand\n     * @return visit with demand at the given location\n     */\n    public static PlanningVisit fromLocation(PlanningLocation location, int demand) {\n        PlanningVisit visit = new PlanningVisit();\n        visit.setId(location.getId());\n        visit.setLocation(location);\n        visit.setDemand(demand);\n        return visit;\n    }\n\n    /**\n     * Create a test visit with the given ID.\n     *\n     * @param id ID of the visit and its location\n     * @return visit with an ID only\n     */\n    public static PlanningVisit testVisit(long id) {\n        return fromLocation(PlanningLocationFactory.testLocation(id));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/SolutionFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\n\n/**\n * Creates {@link VehicleRoutingSolution} instances.\n */\npublic class SolutionFactory {\n\n    private SolutionFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    /**\n     * Create an empty solution. Empty solution has zero locations, depots, visits and vehicles and a zero score.\n     *\n     * @return empty solution\n     */\n    public static VehicleRoutingSolution emptySolution() {\n        VehicleRoutingSolution solution = new VehicleRoutingSolution();\n        solution.setVisitList(new ArrayList<>());\n        solution.setDepotList(new ArrayList<>());\n        solution.setVehicleList(new ArrayList<>());\n        solution.setScore(HardSoftLongScore.ZERO);\n        return solution;\n    }\n\n    /**\n     * Create a new solution from given vehicles, depot and visits.\n     * All vehicles will be placed in the depot.\n     * <p>\n     * The returned solution's vehicles and locations are new collections so modifying the solution\n     * won't affect the collections given as arguments.\n     * <p>\n     * <strong><em>Elements of the argument collections are NOT cloned.</em></strong>\n     *\n     * @param vehicles vehicles\n     * @param depot depot\n     * @param visits visits\n     * @return solution containing the given vehicles, depot, visits and their locations\n     */\n    public static VehicleRoutingSolution solutionFromVisits(\n            List<PlanningVehicle> vehicles,\n            PlanningDepot depot,\n            List<PlanningVisit> visits) {\n        VehicleRoutingSolution solution = new VehicleRoutingSolution();\n        solution.setVehicleList(new ArrayList<>(vehicles));\n        solution.setDepotList(new ArrayList<>(1));\n        if (depot != null) {\n            solution.getDepotList().add(depot);\n            moveAllVehiclesToDepot(vehicles, depot);\n        }\n        solution.setVisitList(new ArrayList<>(visits));\n        solution.setScore(HardSoftLongScore.ZERO);\n        return solution;\n    }\n\n    private static void moveAllVehiclesToDepot(List<PlanningVehicle> vehicles, PlanningDepot depot) {\n        vehicles.forEach(vehicle -> vehicle.setDepot(depot));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/Standstill.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport org.optaplanner.core.api.domain.entity.PlanningEntity;\nimport org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;\n\n@PlanningEntity\npublic interface Standstill {\n\n    /**\n     * The standstill's location.\n     *\n     * @return never {@code null}\n     */\n    PlanningLocation getLocation();\n\n    /**\n     * The next visit after this standstill.\n     *\n     * @return sometimes {@code null}\n     */\n    @InverseRelationShadowVariable(sourceVariableName = \"previousStandstill\")\n    PlanningVisit getNextVisit();\n\n    void setNextVisit(PlanningVisit nextVisit);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/VehicleRoutingSolution.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport java.util.List;\n\nimport org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;\nimport org.optaplanner.core.api.domain.solution.PlanningScore;\nimport org.optaplanner.core.api.domain.solution.PlanningSolution;\nimport org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;\nimport org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\n\n@PlanningSolution\npublic class VehicleRoutingSolution {\n\n    @ProblemFactCollectionProperty\n    private List<PlanningDepot> depotList;\n    @PlanningEntityCollectionProperty\n    @ValueRangeProvider(id = \"vehicleRange\")\n    private List<PlanningVehicle> vehicleList;\n    @PlanningEntityCollectionProperty\n    @ValueRangeProvider(id = \"visitRange\")\n    private List<PlanningVisit> visitList;\n    @PlanningScore\n    private HardSoftLongScore score;\n\n    VehicleRoutingSolution() {\n        // Hide public constructor in favor of the factory.\n    }\n\n    public List<PlanningDepot> getDepotList() {\n        return this.depotList;\n    }\n\n    public void setDepotList(List<PlanningDepot> depotList) {\n        this.depotList = depotList;\n    }\n\n    public List<PlanningVehicle> getVehicleList() {\n        return this.vehicleList;\n    }\n\n    public void setVehicleList(List<PlanningVehicle> vehicleList) {\n        this.vehicleList = vehicleList;\n    }\n\n    public List<PlanningVisit> getVisitList() {\n        return this.visitList;\n    }\n\n    public void setVisitList(List<PlanningVisit> visitList) {\n        this.visitList = visitList;\n    }\n\n    public HardSoftLongScore getScore() {\n        return this.score;\n    }\n\n    public void setScore(HardSoftLongScore score) {\n        this.score = score;\n    }\n\n    @Override\n    public String toString() {\n        return \"VehicleRoutingSolution{\" +\n                \"depotList=\" + depotList +\n                \", vehicleList=\" + vehicleList +\n                \", visitList=\" + visitList +\n                \", score=\" + score +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/domain/package-info.java",
    "content": "/**\n * Domain model adjusted for OptaPlanner.\n */\npackage org.optaweb.vehiclerouting.plugin.planner.domain;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/package-info.java",
    "content": "/**\n * Route optimization.\n */\npackage org.optaweb.vehiclerouting.plugin.planner;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/weight/DepotAngleVisitDifficultyWeightFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.weight;\n\nimport static java.util.Comparator.comparingDouble;\nimport static java.util.Comparator.comparingLong;\n\nimport java.util.Comparator;\nimport java.util.Objects;\n\nimport org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\n/**\n * On large data sets, the constructed solution looks like pizza slices.\n * The order of the slices depends on the {@link PlanningLocation#angleTo} implementation.\n */\npublic class DepotAngleVisitDifficultyWeightFactory\n        implements SelectionSorterWeightFactory<VehicleRoutingSolution, PlanningVisit> {\n\n    @Override\n    public DepotAngleVisitDifficultyWeight createSorterWeight(VehicleRoutingSolution solution, PlanningVisit visit) {\n        PlanningDepot depot = solution.getDepotList().get(0);\n        return new DepotAngleVisitDifficultyWeight(\n                visit,\n                // angle of the line from visit to depot relative to visit→east\n                visit.getLocation().angleTo(depot.getLocation()),\n                visit.getLocation().distanceTo(depot.getLocation())\n                        + depot.getLocation().distanceTo(visit.getLocation()));\n    }\n\n    static class DepotAngleVisitDifficultyWeight implements Comparable<DepotAngleVisitDifficultyWeight> {\n\n        private static final Comparator<DepotAngleVisitDifficultyWeight> COMPARATOR =\n                comparingDouble((DepotAngleVisitDifficultyWeight weight) -> weight.depotAngle)\n                        // Ascending (further from the depot are more difficult)\n                        .thenComparingLong(weight -> weight.depotRoundTripDistance)\n                        .thenComparing(weight -> weight.visit, comparingLong(PlanningVisit::getId));\n\n        private final PlanningVisit visit;\n        private final double depotAngle;\n        private final long depotRoundTripDistance;\n\n        DepotAngleVisitDifficultyWeight(PlanningVisit visit, double depotAngle, long depotRoundTripDistance) {\n            this.visit = visit;\n            this.depotAngle = depotAngle;\n            this.depotRoundTripDistance = depotRoundTripDistance;\n        }\n\n        @Override\n        public int compareTo(DepotAngleVisitDifficultyWeight other) {\n            return COMPARATOR.compare(this, other);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (!(o instanceof DepotAngleVisitDifficultyWeight)) {\n                return false;\n            }\n            return compareTo((DepotAngleVisitDifficultyWeight) o) == 0;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(visit, depotAngle, depotRoundTripDistance);\n        }\n\n        @Override\n        public String toString() {\n            return \"DepotAngleVisitDifficultyWeight{\" +\n                    \"visit=\" + visit +\n                    \", depotAngle=\" + depotAngle +\n                    \", depotRoundTripDistance=\" + depotRoundTripDistance +\n                    '}';\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/planner/weight/package-info.java",
    "content": "/**\n * Implements\n * <a href=\"https://docs.optaplanner.org/latest/optaplanner-docs/html_single/#planningEntityDifficulty\">\n * planning entity difficulty comparison\n * </a>\n * or\n * <a href=\"https://docs.optaplanner.org/latest/optaplanner-docs/html_single/#planningValueStrength\">\n * planning variable strength comparison\n * </a>\n * to enable advanced\n * <a href=\"https://docs.optaplanner.org/latest/optaplanner-docs/html_single/#constructionHeuristics\">\n * Construction Heuristic\n * </a>\n * algorithms or\n * <a href=\"https://docs.optaplanner.org/latest/optaplanner-docs/html_single/#sortedSelection\">\n * sorted selection\n * </a>.\n */\npackage org.optaweb.vehiclerouting.plugin.planner.weight;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/ClearResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport javax.inject.Inject;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\n\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n@Path(\"api/clear\")\npublic class ClearResource {\n\n    private final LocationService locationService;\n    private final VehicleService vehicleService;\n\n    @Inject\n    public ClearResource(LocationService locationService, VehicleService vehicleService) {\n        this.locationService = locationService;\n        this.vehicleService = vehicleService;\n    }\n\n    @POST\n    public void clear() {\n        // TODO do this in one step (=> new RoutingPlanService)\n        vehicleService.removeAll();\n        locationService.removeAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/DataSetDownloadResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\n\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.core.HttpHeaders;\nimport javax.ws.rs.core.MediaType;\nimport javax.ws.rs.core.Response;\n\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\n\n/**\n * Serves the current data set as a downloadable YAML file.\n */\n@Path(\"api/dataset/export\")\n@Produces(\"text/x-yaml\")\npublic class DataSetDownloadResource {\n\n    private final DemoService demoService;\n\n    DataSetDownloadResource(DemoService demoService) {\n        this.demoService = demoService;\n    }\n\n    @GET\n    public Response exportDataSet() throws IOException {\n        String dataSet = demoService.exportDataSet();\n        byte[] dataSetBytes = dataSet.getBytes(StandardCharsets.UTF_8);\n        try (InputStream is = new ByteArrayInputStream(dataSetBytes)) {\n            return Response.ok()\n                    .header(HttpHeaders.CONTENT_DISPOSITION, \"attachment; filename=\\\"vrp_data_set.yaml\\\"\")\n                    .header(HttpHeaders.CONTENT_LENGTH, dataSetBytes.length)\n                    .type(new MediaType(\"text\", \"x-yaml\", StandardCharsets.UTF_8.name()))\n                    .entity(is)\n                    .build();\n        }\n    }\n\n    // TODO exception handler\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/DemoResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport javax.inject.Inject;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.PathParam;\n\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\n\n@Path(\"api/demo/{name}\")\npublic class DemoResource {\n\n    private final DemoService demoService;\n\n    @Inject\n    public DemoResource(DemoService demoService) {\n        this.demoService = demoService;\n    }\n\n    /**\n     * Load a demo data set.\n     *\n     * @param name data set name\n     */\n    @POST\n    public void loadDemo(@PathParam(\"name\") String name) {\n        demoService.loadDemo(name);\n    }\n\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/LocationResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport javax.inject.Inject;\nimport javax.transaction.Transactional;\nimport javax.ws.rs.DELETE;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.PathParam;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableLocation;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\n\n@Path(\"api/location\")\npublic class LocationResource {\n\n    private final LocationService locationService;\n\n    @Inject\n    public LocationResource(LocationService locationService) {\n        this.locationService = locationService;\n    }\n\n    /**\n     * Create new location.\n     *\n     * @param request new location description\n     */\n    @Transactional\n    @POST\n    public void addLocation(PortableLocation request) {\n        locationService.createLocation(\n                new Coordinates(request.getLatitude(), request.getLongitude()),\n                request.getDescription());\n    }\n\n    /**\n     * Delete location.\n     *\n     * @param id ID of the location to be deleted\n     */\n    @Transactional\n    @DELETE\n    @Path(\"{id}\")\n    public void deleteLocation(@PathParam(\"id\") long id) {\n        locationService.removeLocation(id);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/RouteEventResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport javax.annotation.PreDestroy;\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Observes;\nimport javax.inject.Inject;\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.core.Context;\nimport javax.ws.rs.core.MediaType;\nimport javax.ws.rs.sse.OutboundSseEvent;\nimport javax.ws.rs.sse.Sse;\nimport javax.ws.rs.sse.SseBroadcaster;\nimport javax.ws.rs.sse.SseEventSink;\n\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableErrorMessage;\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableRoutingPlanFactory;\nimport org.optaweb.vehiclerouting.service.error.ErrorMessage;\nimport org.optaweb.vehiclerouting.service.route.RouteListener;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@ApplicationScoped\n@Path(\"api/events\")\npublic class RouteEventResource {\n\n    private static final Logger logger = LoggerFactory.getLogger(RouteEventResource.class);\n\n    // TODO repository, not listener (service)\n    private final RouteListener routeListener;\n\n    private SseBroadcaster sseBroadcaster;\n    private OutboundSseEvent.Builder eventBuilder;\n\n    @Inject\n    public RouteEventResource(RouteListener routeListener) {\n        this.routeListener = routeListener;\n    }\n\n    // Handy during development.\n    @PreDestroy\n    public void closeBroadcaster() {\n        if (sseBroadcaster != null) {\n            logger.debug(\"Closing Server-Sent Events broadcaster.\");\n            sseBroadcaster.close();\n        }\n    }\n\n    public void observeRoute(@Observes RoutingPlan event) {\n        if (sseBroadcaster != null) {\n            sseBroadcaster.broadcast(eventBuilder\n                    .data(PortableRoutingPlanFactory.fromRoutingPlan(event))\n                    .name(\"route\")\n                    .comment(\"route update\")\n                    .build());\n        }\n    }\n\n    public void observeError(@Observes ErrorMessage event) {\n        if (sseBroadcaster != null) {\n            sseBroadcaster.broadcast(eventBuilder\n                    .data(PortableErrorMessage.fromMessage(event))\n                    .name(\"errorMessage\")\n                    .comment(\"error message\")\n                    .build());\n        }\n    }\n\n    @GET\n    @Produces(MediaType.SERVER_SENT_EVENTS)\n    public void sse(@Context Sse sse, @Context SseEventSink eventSink) {\n        if (sseBroadcaster == null) {\n            sseBroadcaster = sse.newBroadcaster();\n            eventBuilder = sse.newEventBuilder()\n                    .mediaType(MediaType.APPLICATION_JSON_TYPE)\n                    .reconnectDelay(3000);\n        }\n        OutboundSseEvent sseEvent = eventBuilder\n                .data(PortableRoutingPlanFactory.fromRoutingPlan(routeListener.getBestRoutingPlan()))\n                .comment(\"best route\")\n                .build();\n        eventSink.send(sseEvent);\n        sseBroadcaster.register(eventSink);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/ServerInfoResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport javax.inject.Inject;\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.core.MediaType;\n\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableCoordinates;\nimport org.optaweb.vehiclerouting.plugin.rest.model.RoutingProblemInfo;\nimport org.optaweb.vehiclerouting.plugin.rest.model.ServerInfo;\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\nimport org.optaweb.vehiclerouting.service.region.RegionService;\n\n@Path(\"api/serverInfo\")\npublic class ServerInfoResource {\n\n    private final DemoService demoService;\n    private final RegionService regionService;\n\n    @Inject\n    public ServerInfoResource(DemoService demoService, RegionService regionService) {\n        this.demoService = demoService;\n        this.regionService = regionService;\n    }\n\n    @GET\n    @Produces(MediaType.APPLICATION_JSON)\n    public ServerInfo serverInfo() {\n        BoundingBox boundingBox = regionService.boundingBox();\n        List<PortableCoordinates> portableBoundingBox = Arrays.asList(\n                PortableCoordinates.fromCoordinates(boundingBox.getSouthWest()),\n                PortableCoordinates.fromCoordinates(boundingBox.getNorthEast()));\n        List<RoutingProblemInfo> demos = demoService.demos().stream()\n                .map(routingProblem -> new RoutingProblemInfo(\n                        routingProblem.name(),\n                        routingProblem.visits().size()))\n                .collect(toList());\n        return new ServerInfo(portableBoundingBox, regionService.countryCodes(), demos);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/VehicleResource.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport javax.inject.Inject;\nimport javax.ws.rs.DELETE;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.PathParam;\n\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n@Path(\"api/vehicle\")\npublic class VehicleResource {\n\n    private final VehicleService vehicleService;\n\n    @Inject\n    public VehicleResource(VehicleService vehicleService) {\n        this.vehicleService = vehicleService;\n    }\n\n    @POST\n    public void addVehicle() {\n        vehicleService.createVehicle();\n    }\n\n    /**\n     * Delete vehicle.\n     *\n     * @param id ID of the vehicle to be deleted\n     */\n    @DELETE\n    @Path(\"{id}\")\n    public void removeVehicle(@PathParam(\"id\") long id) {\n        vehicleService.removeVehicle(id);\n    }\n\n    @POST\n    @Path(\"deleteAny\")\n    public void removeAnyVehicle() {\n        vehicleService.removeAnyVehicle();\n    }\n\n    @POST\n    @Path(\"{id}/capacity\")\n    public void changeCapacity(@PathParam(\"id\") long id, int capacity) {\n        vehicleService.changeCapacity(id, capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableCoordinates.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * {@link Coordinates} representation optimized for network transport.\n */\npublic class PortableCoordinates {\n\n    /*\n     * Five decimal places gives \"metric\" precision (±55 cm on equator). That's enough for visualising the track.\n     * https://wiki.openstreetmap.org/wiki/Node#Structure\n     */\n    private static final int LATLNG_SCALE = 5;\n\n    @JsonProperty(value = \"lat\")\n    private final BigDecimal latitude;\n    @JsonProperty(value = \"lng\")\n    private final BigDecimal longitude;\n\n    public static PortableCoordinates fromCoordinates(Coordinates coordinates) {\n        Objects.requireNonNull(coordinates, \"coordinates must not be null\");\n        return new PortableCoordinates(\n                coordinates.latitude(),\n                coordinates.longitude());\n    }\n\n    private static BigDecimal scale(BigDecimal number) {\n        return number.setScale(Math.min(number.scale(), LATLNG_SCALE), RoundingMode.HALF_EVEN).stripTrailingZeros();\n    }\n\n    PortableCoordinates(BigDecimal latitude, BigDecimal longitude) {\n        this.latitude = scale(latitude);\n        this.longitude = scale(longitude);\n    }\n\n    public BigDecimal getLatitude() {\n        return latitude;\n    }\n\n    public BigDecimal getLongitude() {\n        return longitude;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PortableCoordinates that = (PortableCoordinates) o;\n        return Objects.equals(latitude, that.latitude) &&\n                Objects.equals(longitude, that.longitude);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(latitude, longitude);\n    }\n\n    @Override\n    public String toString() {\n        return \"PortableCoordinates{\" +\n                \"latitude=\" + latitude +\n                \", longitude=\" + longitude +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableDistance.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\n\nimport com.fasterxml.jackson.annotation.JsonValue;\n\n/**\n * Portable representation of a {@link Distance distance}.\n */\npublic class PortableDistance {\n\n    @JsonValue\n    private final String distance;\n\n    static PortableDistance fromDistance(Distance distance) {\n        long seconds = (Objects.requireNonNull(distance).millis() + 500) / 1000;\n        return new PortableDistance(String.format(\"%dh %dm %ds\", seconds / 3600, seconds / 60 % 60, seconds % 60));\n    }\n\n    private PortableDistance(String distance) {\n        this.distance = distance;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PortableDistance that = (PortableDistance) o;\n        return distance.equals(that.distance);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(distance);\n    }\n\n    @Override\n    public String toString() {\n        return \"PortableDistance{\" +\n                \"distance='\" + distance + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableErrorMessage.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.service.error.ErrorMessage;\n\n/**\n * Portable error message.\n */\npublic class PortableErrorMessage {\n\n    private final String id;\n    private final String text;\n\n    public static PortableErrorMessage fromMessage(ErrorMessage message) {\n        return new PortableErrorMessage(message.id, message.text);\n    }\n\n    PortableErrorMessage(String id, String text) {\n        this.id = id;\n        this.text = text;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PortableErrorMessage that = (PortableErrorMessage) o;\n        return id.equals(that.id) &&\n                text.equals(that.text);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, text);\n    }\n\n    @Override\n    public String toString() {\n        return \"PortableErrorMessage{\" +\n                \"id='\" + id + '\\'' +\n                \", text='\" + text + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableLocation.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.math.BigDecimal;\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Location;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * {@link Location} representation convenient for marshalling.\n */\npublic class PortableLocation {\n\n    private final long id;\n\n    @JsonProperty(value = \"lat\", required = true)\n    private final BigDecimal latitude;\n    @JsonProperty(value = \"lng\", required = true)\n    private final BigDecimal longitude;\n\n    private final String description;\n\n    static PortableLocation fromLocation(Location location) {\n        Objects.requireNonNull(location, \"location must not be null\");\n        return new PortableLocation(\n                location.id(),\n                location.coordinates().latitude(),\n                location.coordinates().longitude(),\n                location.description());\n    }\n\n    @JsonCreator\n    public PortableLocation(\n            @JsonProperty(value = \"id\") long id,\n            @JsonProperty(value = \"lat\") BigDecimal latitude,\n            @JsonProperty(value = \"lng\") BigDecimal longitude,\n            @JsonProperty(value = \"description\") String description) {\n        this.id = id;\n        this.latitude = Objects.requireNonNull(latitude);\n        this.longitude = Objects.requireNonNull(longitude);\n        this.description = Objects.requireNonNull(description);\n    }\n\n    public long getId() {\n        return id;\n    }\n\n    public BigDecimal getLatitude() {\n        return latitude;\n    }\n\n    public BigDecimal getLongitude() {\n        return longitude;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PortableLocation that = (PortableLocation) o;\n        return id == that.id &&\n                description.equals(that.description) &&\n                latitude.compareTo(that.latitude) == 0 &&\n                longitude.compareTo(that.longitude) == 0;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, description, latitude, longitude);\n    }\n\n    @Override\n    public String toString() {\n        return \"PortableLocation{\" +\n                \"id=\" + id +\n                \", description='\" + description + '\\'' +\n                \", latitude=\" + latitude +\n                \", longitude=\" + longitude +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableRoute.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Route;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\n\n/**\n * Vehicle {@link Route route} representation convenient for marshalling.\n */\nclass PortableRoute {\n\n    private final PortableVehicle vehicle;\n    private final PortableLocation depot;\n    private final List<PortableLocation> visits;\n    @JsonFormat(shape = JsonFormat.Shape.ARRAY)\n    private final List<List<PortableCoordinates>> track;\n\n    PortableRoute(\n            PortableVehicle vehicle,\n            PortableLocation depot,\n            List<PortableLocation> visits,\n            List<List<PortableCoordinates>> track) {\n        this.vehicle = Objects.requireNonNull(vehicle);\n        this.depot = Objects.requireNonNull(depot);\n        this.visits = Objects.requireNonNull(visits);\n        this.track = Objects.requireNonNull(track);\n    }\n\n    public PortableVehicle getVehicle() {\n        return vehicle;\n    }\n\n    public PortableLocation getDepot() {\n        return depot;\n    }\n\n    public List<PortableLocation> getVisits() {\n        return visits;\n    }\n\n    public List<List<PortableCoordinates>> getTrack() {\n        return track;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableRoutingPlan.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.List;\n\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\n\n/**\n * {@link RoutingPlan} representation convenient for marshalling.\n */\npublic class PortableRoutingPlan {\n\n    private final PortableDistance distance;\n    private final List<PortableVehicle> vehicles;\n    private final PortableLocation depot;\n    private final List<PortableLocation> visits;\n    private final List<PortableRoute> routes;\n\n    PortableRoutingPlan(\n            PortableDistance distance,\n            List<PortableVehicle> vehicles,\n            PortableLocation depot,\n            List<PortableLocation> visits,\n            List<PortableRoute> routes) {\n        // TODO require non-null\n        this.distance = distance;\n        this.vehicles = vehicles;\n        this.depot = depot;\n        this.visits = visits;\n        this.routes = routes;\n    }\n\n    public PortableDistance getDistance() {\n        return distance;\n    }\n\n    public List<PortableVehicle> getVehicles() {\n        return vehicles;\n    }\n\n    public PortableLocation getDepot() {\n        return depot;\n    }\n\n    public List<PortableLocation> getVisits() {\n        return visits;\n    }\n\n    public List<PortableRoute> getRoutes() {\n        return routes;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableRoutingPlanFactory.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\n\n/**\n * Creates instances of {@link PortableRoutingPlan}.\n */\npublic class PortableRoutingPlanFactory {\n\n    private PortableRoutingPlanFactory() {\n        throw new AssertionError(\"Utility class\");\n    }\n\n    public static PortableRoutingPlan fromRoutingPlan(RoutingPlan routingPlan) {\n        PortableDistance distance = PortableDistance.fromDistance(routingPlan.distance());\n        List<PortableVehicle> vehicles = portableVehicles(routingPlan.vehicles());\n        PortableLocation depot = routingPlan.depot().map(PortableLocation::fromLocation).orElse(null);\n        List<PortableLocation> visits = portableVisits(routingPlan.visits());\n        List<PortableRoute> routes = routingPlan.routes().stream()\n                .map(routeWithTrack -> new PortableRoute(\n                        PortableVehicle.fromVehicle(routeWithTrack.vehicle()),\n                        depot,\n                        portableVisits(routeWithTrack.visits()),\n                        portableTrack(routeWithTrack.track())))\n                .collect(toList());\n        return new PortableRoutingPlan(distance, vehicles, depot, visits, routes);\n    }\n\n    private static List<List<PortableCoordinates>> portableTrack(List<List<Coordinates>> track) {\n        ArrayList<List<PortableCoordinates>> portableTrack = new ArrayList<>();\n        for (List<Coordinates> segment : track) {\n            List<PortableCoordinates> portableSegment = segment.stream()\n                    .map(PortableCoordinates::fromCoordinates)\n                    .collect(toList());\n            portableTrack.add(portableSegment);\n        }\n        return portableTrack;\n    }\n\n    private static List<PortableLocation> portableVisits(List<Location> visits) {\n        return visits.stream()\n                .map(PortableLocation::fromLocation)\n                .collect(toList());\n    }\n\n    private static List<PortableVehicle> portableVehicles(List<Vehicle> vehicles) {\n        return vehicles.stream()\n                .map(PortableVehicle::fromVehicle)\n                .collect(toList());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableVehicle.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\n\n/**\n * {@link Vehicle} representation suitable for network transport.\n */\npublic class PortableVehicle {\n\n    private final long id;\n    private final String name;\n    private final int capacity;\n\n    static PortableVehicle fromVehicle(Vehicle vehicle) {\n        Objects.requireNonNull(vehicle, \"vehicle must not be null\");\n        return new PortableVehicle(vehicle.id(), vehicle.name(), vehicle.capacity());\n    }\n\n    PortableVehicle(long id, String name, int capacity) {\n        this.id = id;\n        this.name = Objects.requireNonNull(name);\n        this.capacity = capacity;\n    }\n\n    public long getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public int getCapacity() {\n        return capacity;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        PortableVehicle vehicle = (PortableVehicle) o;\n        return id == vehicle.id &&\n                capacity == vehicle.capacity &&\n                name.equals(vehicle.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(id, name, capacity);\n    }\n\n    @Override\n    public String toString() {\n        return \"PortableVehicle{\" +\n                \"id=\" + id +\n                \", name='\" + name + '\\'' +\n                \", capacity=\" + capacity +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/RoutingProblemInfo.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\n\n/**\n * Information about a {@link RoutingProblem routing problem instance}.\n */\npublic class RoutingProblemInfo {\n\n    private final String name;\n    private final int visits;\n\n    public RoutingProblemInfo(String name, int visits) {\n        this.name = Objects.requireNonNull(name);\n        this.visits = visits;\n    }\n\n    /**\n     * Routing problem instance name.\n     *\n     * @return name\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * Number of visits in the routing problem instance.\n     *\n     * @return number of visits\n     */\n    public int getVisits() {\n        return visits;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/rest/model/ServerInfo.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport java.util.List;\n\n/**\n * Server info suitable for network transport.\n */\npublic class ServerInfo {\n\n    private final List<PortableCoordinates> boundingBox;\n    private final List<String> countryCodes;\n    private final List<RoutingProblemInfo> demos;\n\n    public ServerInfo(List<PortableCoordinates> boundingBox, List<String> countryCodes, List<RoutingProblemInfo> demos) {\n        this.boundingBox = boundingBox;\n        this.countryCodes = countryCodes;\n        this.demos = demos;\n    }\n\n    public List<PortableCoordinates> getBoundingBox() {\n        return boundingBox;\n    }\n\n    public List<String> getCountryCodes() {\n        return countryCodes;\n    }\n\n    public List<RoutingProblemInfo> getDemos() {\n        return demos;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/AirDistanceRouter.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport java.math.BigDecimal;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.enterprise.context.ApplicationScoped;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.service.distance.DistanceCalculator;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\nimport org.optaweb.vehiclerouting.service.region.Region;\nimport org.optaweb.vehiclerouting.service.route.Router;\n\nimport io.quarkus.arc.properties.IfBuildProperty;\n\n@ApplicationScoped\n@IfBuildProperty(name = \"app.routing.engine\", stringValue = \"AIR\")\npublic class AirDistanceRouter implements Router, DistanceCalculator, Region {\n\n    protected static final int TRAVEL_SPEED_KPH = 60;\n    // Approximate Metric Equivalents for Degrees. At the equator for longitude and for latitude anywhere,\n    // the following approximations are valid: 1° = 111 km (or 60 nautical miles) 0.1° = 11.1 km.\n    protected static final double KILOMETERS_PER_DEGREE = 111;\n    protected static final long MILLIS_IN_ONE_HOUR = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);\n\n    @Override\n    public long travelTimeMillis(Coordinates from, Coordinates to) {\n        BigDecimal latDiff = to.latitude().subtract(from.latitude());\n        BigDecimal lngDiff = to.longitude().subtract(from.longitude());\n        double distanceKilometers = Math.sqrt(latDiff.pow(2).add(lngDiff.pow(2)).doubleValue()) * KILOMETERS_PER_DEGREE;\n        return (long) Math.floor(distanceKilometers / TRAVEL_SPEED_KPH * MILLIS_IN_ONE_HOUR);\n    }\n\n    @Override\n    public List<Coordinates> getPath(Coordinates from, Coordinates to) {\n        return Arrays.asList(from, to);\n    }\n\n    @Override\n    public BoundingBox getBounds() {\n        return new BoundingBox(Coordinates.of(-90, -180), Coordinates.of(90, 180));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/Constants.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\npublic class Constants {\n\n    public static final String GRAPHHOPPER_PROFILE = \"optaweb_car\";\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/GraphHopperRouter.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.util.List;\nimport java.util.stream.StreamSupport;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.service.distance.DistanceCalculator;\nimport org.optaweb.vehiclerouting.service.distance.RoutingException;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\nimport org.optaweb.vehiclerouting.service.region.Region;\nimport org.optaweb.vehiclerouting.service.route.Router;\n\nimport com.graphhopper.GHRequest;\nimport com.graphhopper.GHResponse;\nimport com.graphhopper.GraphHopper;\nimport com.graphhopper.ResponsePath;\nimport com.graphhopper.util.PointList;\nimport com.graphhopper.util.shapes.BBox;\n\nimport io.quarkus.arc.properties.IfBuildProperty;\n\n/**\n * Provides geographical information needed for route optimization.\n */\n@ApplicationScoped\n@IfBuildProperty(name = \"app.routing.engine\", stringValue = \"GRAPHHOPPER\", enableIfMissing = true)\nclass GraphHopperRouter implements Router, DistanceCalculator, Region {\n\n    private final GraphHopper graphHopper;\n\n    @Inject\n    GraphHopperRouter(GraphHopper graphHopper) {\n        this.graphHopper = graphHopper;\n    }\n\n    @Override\n    public List<Coordinates> getPath(Coordinates from, Coordinates to) {\n        PointList points = getBestRoute(from, to).getPoints();\n        return StreamSupport.stream(points.spliterator(), false)\n                .map(ghPoint3D -> Coordinates.of(ghPoint3D.lat, ghPoint3D.lon))\n                .collect(toList());\n    }\n\n    @Override\n    public long travelTimeMillis(Coordinates from, Coordinates to) {\n        return getBestRoute(from, to).getTime();\n    }\n\n    private ResponsePath getBestRoute(Coordinates from, Coordinates to) {\n        GHRequest request = new GHRequest(\n                from.latitude().doubleValue(),\n                from.longitude().doubleValue(),\n                to.latitude().doubleValue(),\n                to.longitude().doubleValue()).setProfile(Constants.GRAPHHOPPER_PROFILE);\n        GHResponse response = graphHopper.route(request);\n        // TODO return wrapper that can hold both the result and error explanation instead of throwing exception\n        if (response.hasErrors()) {\n            throw new RoutingException(\"No route from (\" + from + \") to (\" + to + \")\", response.getErrors().get(0));\n        }\n        return response.getBest();\n    }\n\n    @Override\n    public BoundingBox getBounds() {\n        BBox bounds = graphHopper.getBaseGraph().getBounds();\n        return new BoundingBox(\n                Coordinates.of(bounds.minLat, bounds.minLon),\n                Coordinates.of(bounds.maxLat, bounds.maxLon));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/RoutingConfig.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.ProtocolException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport javax.enterprise.context.Dependent;\nimport javax.enterprise.inject.Produces;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.Profiles;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport com.graphhopper.GraphHopper;\nimport com.graphhopper.config.CHProfile;\nimport com.graphhopper.config.Profile;\n\nimport io.quarkus.arc.DefaultBean;\nimport io.quarkus.arc.profile.UnlessBuildProfile;\nimport io.quarkus.arc.properties.IfBuildProperty;\n\n/**\n * Configuration bean that creates a GraphHopper instance and allows to configure the path to OSM file\n * through environment.\n */\n@Dependent\nclass RoutingConfig {\n\n    private static final Logger logger = LoggerFactory.getLogger(RoutingConfig.class);\n\n    private final Path osmDir;\n    private final Path osmFile;\n    private final Optional<String> osmDownloadUrl;\n    private final Path graphHopperDir;\n    private final Path graphDir;\n\n    @Inject\n    RoutingConfig(RoutingProperties routingProperties) {\n        osmDir = Paths.get(routingProperties.osmDir()).toAbsolutePath();\n        osmFile = osmDir.resolve(routingProperties.osmFile()).toAbsolutePath();\n        osmDownloadUrl = routingProperties.osmDownloadUrl();\n        graphHopperDir = Paths.get(routingProperties.ghDir());\n        String regionName = routingProperties.osmFile().replaceFirst(\"\\\\.osm\\\\.pbf$\", \"\");\n        graphDir = graphHopperDir.resolve(regionName).toAbsolutePath();\n    }\n\n    /**\n     * Avoids creating a real GraphHopper instance when running a @QuarkusTest.\n     *\n     * @return real GraphHopper\n     */\n    @UnlessBuildProfile(Profiles.TEST)\n    @IfBuildProperty(name = \"app.routing.engine\", stringValue = \"GRAPHHOPPER\", enableIfMissing = true)\n    @Produces\n    @DefaultBean\n    GraphHopper graphHopper() {\n        GraphHopper graphHopper = new GraphHopper();\n        graphHopper.setGraphHopperLocation(graphDir.toString());\n\n        if (graphDirIsNotEmpty()) {\n            logger.info(\"Loading existing GraphHopper graph from: {}\", graphDir);\n        } else {\n            if (Files.notExists(osmFile)) {\n                initDirs();\n\n                if (!osmDownloadUrl.isPresent() || osmDownloadUrl.get().trim().isEmpty()) {\n                    throw new IllegalStateException(\n                            \"The osmFile (\" + osmFile + \") does not exist\"\n                                    + \" and no download URL was provided.\\n\"\n                                    + \"Download the OSM file from https://download.geofabrik.de/ first\"\n                                    + \" or provide an OSM file URL\"\n                                    + \" using the app.routing.osm-download-url property.\");\n                }\n                downloadOsmFile(osmDownloadUrl.get(), osmFile);\n            }\n            logger.info(\"Importing OSM file: {}\", osmFile);\n            graphHopper.setOSMFile(osmFile.toString());\n        }\n\n        /*\n         * Define a profile for each type of request that's going to be made at runtime. We're only going to ask for the fastest\n         * route for a car, so we only need one profile.\n         *\n         * Change the weighting to \"shortest\" (and delete the graph directory to re-import it) to optimize for shortest routes.\n         *\n         * Add a second profile with \"shortest\" weighting (and delete the graph directory) to be able to change travel cost\n         * optimization goal at runtime.\n         */\n        graphHopper.setProfiles(new Profile(Constants.GRAPHHOPPER_PROFILE).setVehicle(\"car\").setWeighting(\"fastest\"));\n        /*\n         * Quick overview of routing modes:\n         *\n         * Flexible mode:\n         * - Dijkstra or A*\n         * - able to change requirements per request\n         * Speed mode:\n         * - \"Contraction Hierarchies\" algorithm (CH)\n         * - still Dijkstra but on a \"shortcut graph\"\n         * Hybrid mode\n         * - landmark algorithm\n         * - flexible and fast\n         *\n         * See https://www.graphhopper.com/blog/2017/08/14/flexible-routing-15-times-faster/.\n         */\n        // Use CH for the only profile we have.\n        graphHopper.getCHPreparationHandler().setCHProfiles(new CHProfile(Constants.GRAPHHOPPER_PROFILE));\n        graphHopper.importOrLoad();\n        logger.info(\"GraphHopper graph loaded\");\n        return graphHopper;\n    }\n\n    /**\n     * Decide whether the graph can be loaded.\n     *\n     * @return true if the graph directory exists and is not empty\n     */\n    private boolean graphDirIsNotEmpty() {\n        if (Files.notExists(graphDir)) {\n            return false;\n        }\n        try (Stream<Path> graphDirFiles = Files.list(graphDir)) {\n            // Defensive programming. Check if the graph dir is empty. That happens if the import fails\n            // for example due to OutOfMemoryError.\n            return graphDirFiles.findAny().isPresent();\n        } catch (IOException e) {\n            throw new RoutingEngineException(\"Cannot read contents of the graph directory (\" + graphDir + \")\", e);\n        }\n    }\n\n    private void initDirs() {\n        try {\n            Files.createDirectories(osmDir);\n            Files.createDirectories(graphHopperDir);\n        } catch (IOException e) {\n            throw new RoutingEngineException(\"Can't create directory for storing OSM download\", e);\n        }\n    }\n\n    static void downloadOsmFile(String urlString, Path osmFile) {\n        HttpURLConnection con;\n        URL url;\n        try {\n            url = new URL(urlString);\n            con = (HttpURLConnection) url.openConnection();\n        } catch (MalformedURLException e) {\n            throw new RoutingEngineException(\"The OSM file URL is malformed\", e);\n        } catch (IOException e) {\n            throw new RoutingEngineException(\"The OSM file cannot be downloaded\", e);\n        }\n        try {\n            con.setRequestMethod(\"GET\");\n        } catch (ProtocolException e) {\n            throw new IllegalStateException(\"Can't set request method\", e);\n        }\n\n        con.setConnectTimeout(10000);\n        con.setReadTimeout(10000);\n\n        logger.info(\"Downloading OSM file from {}\", urlString);\n        try {\n            Files.copy(con.getInputStream(), osmFile);\n        } catch (IOException e) {\n            throw new RoutingEngineException(\"OSM file download failed\", e);\n        }\n        logger.info(\"File saved to {}\", osmFile);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/RoutingEngineException.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\npublic class RoutingEngineException extends RuntimeException {\n\n    RoutingEngineException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/RoutingProperties.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport java.util.Optional;\n\nimport io.smallrye.config.ConfigMapping;\n\n@ConfigMapping(prefix = \"app.routing\")\npublic interface RoutingProperties {\n\n    /**\n     * Directory to read OSM files from.\n     */\n    String osmDir();\n\n    /**\n     * Directory where GraphHopper graphs are stored.\n     */\n    String ghDir();\n\n    /**\n     * OpenStreetMap file name.\n     */\n    String osmFile();\n\n    /**\n     * URL of an .osm.pbf file that will be downloaded in case the file doesn't exist on the file system.\n     */\n    Optional<String> osmDownloadUrl();\n\n    /**\n     * Routing engine providing distances and paths.\n     */\n    RoutingEngine engine();\n\n    enum RoutingEngine {\n        AIR,\n        GRAPHHOPPER\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/plugin/routing/package-info.java",
    "content": "/**\n * Provides information based on geographical data, for example fastest and shortest distances between locations.\n */\npackage org.optaweb.vehiclerouting.plugin.routing;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/DemoProperties.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport java.util.Optional;\n\nimport io.smallrye.config.ConfigMapping;\n\n@ConfigMapping(prefix = \"app.demo\")\npublic interface DemoProperties {\n\n    /**\n     * Directory with demo data sets.\n     */\n    Optional<String> dataSetDir();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/DemoService.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.service.demo.dataset.DataSetMarshaller;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n/**\n * Performs demo-related use cases.\n */\n@ApplicationScoped\npublic class DemoService {\n\n    static final int MAX_TRIES = 10;\n\n    private final RoutingProblemList routingProblems;\n    private final LocationService locationService;\n    private final LocationRepository locationRepository;\n    private final VehicleService vehicleService;\n    private final VehicleRepository vehicleRepository;\n    private final DataSetMarshaller dataSetMarshaller;\n\n    @Inject\n    public DemoService(\n            RoutingProblemList routingProblems,\n            LocationService locationService,\n            LocationRepository locationRepository,\n            VehicleService vehicleService,\n            VehicleRepository vehicleRepository,\n            DataSetMarshaller dataSetMarshaller) {\n        this.routingProblems = routingProblems;\n        this.locationService = locationService;\n        this.locationRepository = locationRepository;\n        this.vehicleService = vehicleService;\n        this.vehicleRepository = vehicleRepository;\n        this.dataSetMarshaller = dataSetMarshaller;\n    }\n\n    public Collection<RoutingProblem> demos() {\n        return routingProblems.all();\n    }\n\n    public void loadDemo(String name) {\n        RoutingProblem routingProblem = routingProblems.byName(name);\n        // Add depot\n        routingProblem.depot().ifPresent(depot -> addWithRetry(depot.coordinates(), depot.description()));\n\n        // TODO start randomizing only after using all available cities (=> reproducibility for small demos)\n        routingProblem.visits().forEach(visit -> addWithRetry(visit.coordinates(), visit.description()));\n        routingProblem.vehicles().forEach(vehicleService::createVehicle);\n    }\n\n    private void addWithRetry(Coordinates coordinates, String description) {\n        int tries = 0;\n        while (tries < MAX_TRIES && !locationService.createLocation(coordinates, description).isPresent()) {\n            tries++;\n        }\n        if (tries == MAX_TRIES) {\n            throw new RuntimeException(\n                    \"Impossible to create a new location near \" + coordinates + \" after \" + tries + \" attempts\");\n        }\n    }\n\n    public String exportDataSet() {\n        // FIXME still relying on the fact that the first location in the repository is the depot\n        List<Location> visits = new ArrayList<>(locationRepository.locations());\n        Location depot = visits.isEmpty() ? null : visits.remove(0);\n        List<Vehicle> vehicles = vehicleRepository.vehicles();\n        return dataSetMarshaller.marshal(new RoutingProblem(\n                \"Custom Vehicle Routing instance\", vehicles, depot, visits));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/RoutingProblemConfig.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport javax.enterprise.context.Dependent;\nimport javax.enterprise.inject.Produces;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.service.demo.dataset.DataSetMarshaller;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Configuration bean that produces the list of available routing problem data sets.\n */\n@Dependent\nclass RoutingProblemConfig {\n\n    private static final Logger logger = LoggerFactory.getLogger(RoutingProblemConfig.class);\n    private final DemoProperties demoProperties;\n    private final DataSetMarshaller dataSetMarshaller;\n\n    @Inject\n    RoutingProblemConfig(DemoProperties demoProperties, DataSetMarshaller dataSetMarshaller) {\n        this.demoProperties = demoProperties;\n        this.dataSetMarshaller = dataSetMarshaller;\n    }\n\n    @Produces\n    RoutingProblemList routingProblems() {\n        Stream<RoutingProblem> allProblems = Stream.concat(classPathProblems(), dataSetDirProblems());\n        return new RoutingProblemList(allProblems);\n    }\n\n    private Stream<RoutingProblem> classPathProblems() {\n        return Stream.of(belgiumReader()).map(dataSetMarshaller::unmarshal);\n    }\n\n    private Stream<RoutingProblem> dataSetDirProblems() {\n        return dataSetDir().map(dir -> collectProblems(dir).stream()).orElse(Stream.empty());\n    }\n\n    private List<RoutingProblem> collectProblems(Path dataSetDirPath) {\n        try (Stream<Path> dataSetPaths = Files.list(dataSetDirPath)) {\n            return dataSetPaths\n                    .map(Path::toFile)\n                    .filter(file -> file.getName().endsWith(\".yaml\") && file.exists() && file.canRead())\n                    .map(file -> {\n                        try {\n                            return new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);\n                        } catch (FileNotFoundException e) {\n                            logger.error(\"Problem with dataset file {}\", file, e);\n                            return null;\n                        }\n                    })\n                    .filter(Objects::nonNull)\n                    // TODO make unmarshalling exception checked, catch it and ignore broken files\n                    .map(dataSetMarshaller::unmarshal)\n                    // Returning the stream here has no point because the stream is always closed by the try-with-resources.\n                    .collect(toList());\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Cannot list directory \" + dataSetDirPath, e);\n        }\n    }\n\n    private Optional<Path> dataSetDir() {\n        // TODO watch the dir (and make this a service that has local/data resource as a dependency -> is testable)\n        Optional<String> dataSetDirProperty = demoProperties.dataSetDir();\n        if (!dataSetDirProperty.isPresent()) {\n            logger.info(\"Data set directory (app.demo.data-set-dir) is not set.\");\n            return Optional.empty();\n        }\n        Path dataSetDirPath = Paths.get(dataSetDirProperty.get());\n        if (!isReadableDir(dataSetDirPath)) {\n            logger.warn(\n                    \"Data set directory '{}' doesn't exist or cannot be read. No external data sets will be loaded.\",\n                    dataSetDirPath.toAbsolutePath());\n            return Optional.empty();\n        }\n        return Optional.of(dataSetDirPath);\n    }\n\n    private static Reader belgiumReader() {\n        return new InputStreamReader(\n                DemoService.class.getResourceAsStream(\"belgium-cities.yaml\"),\n                StandardCharsets.UTF_8);\n    }\n\n    private static boolean isReadableDir(Path path) {\n        File file = path.toFile();\n        return file.exists() && file.canRead() && file.isDirectory();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/RoutingProblemList.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport static java.util.function.Function.identity;\nimport static java.util.stream.Collectors.toMap;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\n\n/**\n * Utility class that holds a map of routing problem instances and allows to look them up by name.\n */\nclass RoutingProblemList {\n\n    private final Map<String, RoutingProblem> routingProblems;\n\n    RoutingProblemList(Stream<RoutingProblem> routingProblems) {\n        this.routingProblems = Objects.requireNonNull(routingProblems)\n                // TODO use file name as the key (that's more likely to be unique than data set name)\n                .collect(toMap(RoutingProblem::name, identity()));\n    }\n\n    Collection<RoutingProblem> all() {\n        return routingProblems.values();\n    }\n\n    RoutingProblem byName(String name) {\n        RoutingProblem routingProblem = routingProblems.get(name);\n        if (routingProblem == null) {\n            throw new IllegalArgumentException(\"Data set with name '\" + name + \"' doesn't exist\");\n        }\n        return routingProblem;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/dataset/DataSet.java",
    "content": "package org.optaweb.vehiclerouting.service.demo.dataset;\n\nimport java.util.List;\n\n/**\n * Data set representation used for marshalling and unmarshalling.\n */\nclass DataSet {\n\n    private String name;\n    private List<DataSetVehicle> vehicles;\n    private DataSetLocation depot;\n    private List<DataSetLocation> visits;\n\n    /**\n     * Data set name (a short description).\n     *\n     * @return data set name (may be {@code null})\n     */\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    /**\n     * Vehicles.\n     *\n     * @return vehicles (may be {@code null})\n     */\n    public List<DataSetVehicle> getVehicles() {\n        return vehicles;\n    }\n\n    public void setVehicles(List<DataSetVehicle> vehicles) {\n        this.vehicles = vehicles;\n    }\n\n    /**\n     * The depot.\n     *\n     * @return the depot (may be {@code null})\n     */\n    public DataSetLocation getDepot() {\n        return depot;\n    }\n\n    public void setDepot(DataSetLocation depot) {\n        this.depot = depot;\n    }\n\n    /**\n     * Visits.\n     *\n     * @return visits (may be {@code null})\n     */\n    public List<DataSetLocation> getVisits() {\n        return visits;\n    }\n\n    public void setVisits(List<DataSetLocation> visits) {\n        this.visits = visits;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/dataset/DataSetLocation.java",
    "content": "package org.optaweb.vehiclerouting.service.demo.dataset;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * Data set location.\n */\nclass DataSetLocation {\n\n    private String label;\n    @JsonProperty(value = \"lat\")\n    private double latitude;\n    @JsonProperty(value = \"lng\")\n    private double longitude;\n\n    private DataSetLocation() {\n        // for unmarshalling\n    }\n\n    DataSetLocation(String label, double latitude, double longitude) {\n        this.latitude = latitude;\n        this.longitude = longitude;\n        this.label = label;\n    }\n\n    /**\n     * Location label.\n     *\n     * @return label\n     */\n    public String getLabel() {\n        return label;\n    }\n\n    public void setLabel(String label) {\n        this.label = label;\n    }\n\n    /**\n     * Latitude.\n     *\n     * @return latitude\n     */\n    public double getLatitude() {\n        return latitude;\n    }\n\n    public void setLatitude(double latitude) {\n        this.latitude = latitude;\n    }\n\n    /**\n     * Longitude.\n     *\n     * @return longitude\n     */\n    public double getLongitude() {\n        return longitude;\n    }\n\n    public void setLongitude(double longitude) {\n        this.longitude = longitude;\n    }\n\n    @Override\n    public String toString() {\n        return \"DataSetLocation{\" +\n                \"label='\" + label + '\\'' +\n                \", latitude=\" + latitude +\n                \", longitude=\" + longitude +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/dataset/DataSetMarshaller.java",
    "content": "package org.optaweb.vehiclerouting.service.demo.dataset;\n\nimport static java.util.stream.Collectors.toList;\n\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.LocationData;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\n\n/**\n * Data set marshaller using the YAML format.\n */\n@ApplicationScoped\npublic class DataSetMarshaller {\n\n    private final ObjectMapper mapper;\n\n    /**\n     * Create marshaller using the default object mapper, which is set up to use YAML format.\n     */\n    DataSetMarshaller() {\n        mapper = new ObjectMapper(new YAMLFactory());\n    }\n\n    /**\n     * Constructor for testing purposes.\n     *\n     * @param mapper usually a mock object mapper\n     */\n    DataSetMarshaller(ObjectMapper mapper) {\n        this.mapper = mapper;\n    }\n\n    /**\n     * Unmarshal routing problem from a reader.\n     *\n     * @param reader a reader\n     * @return routing problem\n     */\n    public RoutingProblem unmarshal(Reader reader) {\n        // TODO throw a checked exception that will force the caller to handle the reading problem\n        //      (e.g. a bad format) and report it to the user or log an error\n        return toDomain(unmarshalToDataSet(reader));\n    }\n\n    /**\n     * Marshal routing problem to string.\n     *\n     * @param routingProblem routing problem\n     * @return string containing the marshaled routing problem\n     */\n    public String marshal(RoutingProblem routingProblem) {\n        return marshal(toDataSet(routingProblem));\n    }\n\n    DataSet unmarshalToDataSet(Reader reader) {\n        try {\n            return mapper.readValue(reader, DataSet.class);\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Can't read demo data set\", e);\n        }\n    }\n\n    String marshal(DataSet dataSet) {\n        try {\n            return mapper.writeValueAsString(dataSet);\n        } catch (JsonProcessingException e) {\n            throw new IllegalStateException(\"Failed to marshal data set (\" + dataSet.getName() + \")\", e);\n        }\n    }\n\n    static DataSet toDataSet(RoutingProblem routingProblem) {\n        DataSet dataSet = new DataSet();\n        dataSet.setName(routingProblem.name());\n        dataSet.setDepot(routingProblem.depot().map(DataSetMarshaller::toDataSet).orElse(null));\n        dataSet.setVehicles(routingProblem.vehicles().stream()\n                .map(DataSetMarshaller::toDataSet)\n                .collect(toList()));\n        dataSet.setVisits(routingProblem.visits().stream()\n                .map(DataSetMarshaller::toDataSet)\n                .collect(toList()));\n        return dataSet;\n    }\n\n    static DataSetLocation toDataSet(LocationData locationData) {\n        return new DataSetLocation(\n                locationData.description(),\n                locationData.coordinates().latitude().doubleValue(),\n                locationData.coordinates().longitude().doubleValue());\n    }\n\n    static DataSetVehicle toDataSet(VehicleData vehicleData) {\n        return new DataSetVehicle(vehicleData.name(), vehicleData.capacity());\n    }\n\n    static RoutingProblem toDomain(DataSet dataSet) {\n        return new RoutingProblem(\n                Optional.ofNullable(dataSet.getName()).orElse(\"\"),\n                Optional.ofNullable(dataSet.getVehicles()).orElse(Collections.emptyList())\n                        .stream()\n                        .map(DataSetMarshaller::toDomain)\n                        .collect(toList()),\n                Optional.ofNullable(dataSet.getDepot()).map(DataSetMarshaller::toDomain).orElse(null),\n                Optional.ofNullable(dataSet.getVisits()).orElse(Collections.emptyList())\n                        .stream()\n                        .map(DataSetMarshaller::toDomain)\n                        .collect(toList()));\n    }\n\n    static LocationData toDomain(DataSetLocation dataSetLocation) {\n        return new LocationData(\n                Coordinates.of(dataSetLocation.getLatitude(), dataSetLocation.getLongitude()),\n                dataSetLocation.getLabel());\n    }\n\n    static VehicleData toDomain(DataSetVehicle dataSetVehicle) {\n        return VehicleFactory.vehicleData(dataSetVehicle.name, dataSetVehicle.capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/dataset/DataSetVehicle.java",
    "content": "package org.optaweb.vehiclerouting.service.demo.dataset;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * Data set vehicle.\n */\npublic class DataSetVehicle {\n\n    @JsonProperty\n    final String name;\n    @JsonProperty\n    final int capacity;\n\n    @JsonCreator\n    public DataSetVehicle(@JsonProperty(\"name\") String name, @JsonProperty(\"capacity\") int capacity) {\n        this.name = name;\n        this.capacity = capacity;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/dataset/package-info.java",
    "content": "/**\n * Data set marshalling and unmarshalling.\n */\npackage org.optaweb.vehiclerouting.service.demo.dataset;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/demo/package-info.java",
    "content": "/**\n * Demo data set loading and exporting.\n */\npackage org.optaweb.vehiclerouting.service.demo;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/distance/DistanceCalculator.java",
    "content": "package org.optaweb.vehiclerouting.service.distance;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\n/**\n * Calculates distances between coordinates.\n */\npublic interface DistanceCalculator {\n\n    /**\n     * Calculate travel time in milliseconds.\n     *\n     * @param from origin\n     * @param to destination\n     * @return travel time in milliseconds\n     * @throws RoutingException when the distance between given coordinates cannot be calculated\n     */\n    long travelTimeMillis(Coordinates from, Coordinates to);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/distance/DistanceMatrixImpl.java",
    "content": "package org.optaweb.vehiclerouting.service.distance;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrix;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\n\n@ApplicationScoped\nclass DistanceMatrixImpl implements DistanceMatrix {\n\n    private final DistanceCalculator distanceCalculator;\n    private final Map<Location, Map<Long, Distance>> matrix = new HashMap<>();\n\n    @Inject\n    DistanceMatrixImpl(DistanceCalculator distanceCalculator) {\n        this.distanceCalculator = distanceCalculator;\n    }\n\n    @Override\n    public DistanceMatrixRow addLocation(Location newLocation) {\n        Map<Long, Distance> distancesToOthers = updateMatrixLazily(newLocation);\n        return locationId -> distancesToOthers.computeIfAbsent(locationId, wrongId -> {\n            throw new IllegalArgumentException(\n                    \"Distance from \" + newLocation\n                            + \" to \" + wrongId\n                            + \" hasn't been recorded.\\n\"\n                            + \"We only know distances to \" + distancesToOthers.keySet());\n        });\n    }\n\n    private Map<Long, Distance> updateMatrixLazily(Location location) {\n        // Matrix == distance rows.\n        // We're adding a whole new row with distances from the new location to existing ones.\n        // We're also creating a new column by \"appending\" a new cell to each existing row.\n        // This new column contains distances from each existing location to the new one.\n\n        return matrix.computeIfAbsent(location, newLocation -> {\n            // The map must be thread-safe because:\n            // - we're updating it from the parallel stream below\n            // - it is accessed from solver thread!\n            Map<Long, Distance> distancesToOthers = new ConcurrentHashMap<>(); // the new row\n\n            // distance to self is 0\n            distancesToOthers.put(newLocation.id(), Distance.ZERO);\n\n            // For all entries (rows) in the matrix:\n            matrix.entrySet().stream().parallel().forEach(distanceRow -> {\n                // Entry key is the existing (other) location.\n                Location other = distanceRow.getKey();\n                // Entry value is the data (cells) in the row (distances from the entry key location to any other).\n                Map<Long, Distance> distancesFromOther = distanceRow.getValue();\n                // Add a new cell to the row with the distance from the entry key location to the new location\n                // (results in a new column at the end of the loop).\n                distancesFromOther.put(newLocation.id(), calculateDistance(other, newLocation));\n                // Add a cell to the new distance's row.\n                distancesToOthers.put(other.id(), calculateDistance(newLocation, other));\n            });\n\n            return distancesToOthers;\n        });\n    }\n\n    private Distance calculateDistance(Location from, Location to) {\n        return Distance.ofMillis(distanceCalculator.travelTimeMillis(from.coordinates(), to.coordinates()));\n    }\n\n    @Override\n    public Distance distance(Location from, Location to) {\n        if (!matrix.containsKey(from)) {\n            throw new IllegalArgumentException(\"Unknown 'from' location (\" + from + \")\");\n        }\n        Map<Long, Distance> distanceRow = matrix.get(from);\n        if (!distanceRow.containsKey(to.id())) {\n            throw new IllegalArgumentException(\"Unknown 'to' location (\" + to + \")\");\n        }\n        return distanceRow.get(to.id());\n    }\n\n    @Override\n    public void put(Location from, Location to, Distance distance) {\n        matrix.computeIfAbsent(from, location -> new ConcurrentHashMap<>()).put(to.id(), distance);\n    }\n\n    @Override\n    public void removeLocation(Location location) {\n        // Remove the distance matrix row (distances from the removed location to others).\n        matrix.remove(location);\n        // TODO also remove the \"column\" of the matrix (distances from others to the removed location) to avoid memory\n        //  leak.\n        //  But this probably requires making DistanceMatrixRow immutable (otherwise there's a risk of NPEs in solver)\n        //  and update PlanningLocations' distance maps through problem fact changes.\n    }\n\n    @Override\n    public void clear() {\n        matrix.clear();\n    }\n\n    /**\n     * Number of rows in the matrix.\n     *\n     * @return number of rows\n     */\n    public int dimension() {\n        return matrix.size();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/distance/DistanceRepository.java",
    "content": "package org.optaweb.vehiclerouting.service.distance;\n\nimport java.util.Optional;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\n\n/**\n * Stores distances between locations.\n */\npublic interface DistanceRepository {\n\n    void saveDistance(Location from, Location to, Distance distance);\n\n    Optional<Distance> getDistance(Location from, Location to);\n\n    void deleteDistances(Location location);\n\n    void deleteAll();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/distance/RoutingException.java",
    "content": "package org.optaweb.vehiclerouting.service.distance;\n\npublic class RoutingException extends RuntimeException {\n\n    public RoutingException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public RoutingException(String message) {\n        super(message);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/distance/package-info.java",
    "content": "/**\n * Distance matrix calculation.\n */\npackage org.optaweb.vehiclerouting.service.distance;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/error/ErrorEvent.java",
    "content": "package org.optaweb.vehiclerouting.service.error;\n\nimport java.util.Objects;\n\npublic class ErrorEvent {\n\n    public final String message;\n\n    /**\n     * Create a new {@code ApplicationEvent}.\n     *\n     * @param source the object on which the event initially occurred or with\n     *        which the event is associated (never {@code null})\n     * @param message error message (never {@code null})\n     */\n    public ErrorEvent(Object source, String message) {\n        this.message = Objects.requireNonNull(message);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/error/ErrorListener.java",
    "content": "package org.optaweb.vehiclerouting.service.error;\n\nimport java.util.UUID;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Event;\nimport javax.enterprise.event.Observes;\nimport javax.inject.Inject;\n\n/**\n * Creates messages from error events and passes them to consumers.\n */\n@ApplicationScoped\npublic class ErrorListener {\n\n    private final Event<ErrorMessage> errorMessageEvent;\n\n    @Inject\n    public ErrorListener(Event<ErrorMessage> errorMessageEvent) {\n        this.errorMessageEvent = errorMessageEvent;\n    }\n\n    public void onErrorEvent(@Observes ErrorEvent event) {\n        errorMessageEvent.fire(ErrorMessage.of(UUID.randomUUID().toString(), event.message));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/error/ErrorMessage.java",
    "content": "package org.optaweb.vehiclerouting.service.error;\n\nimport java.util.Objects;\n\npublic class ErrorMessage {\n\n    /**\n     * Message ID (never {@code null}).\n     */\n    public final String id;\n    /**\n     * Message text (never {@code null}).\n     */\n    public final String text;\n\n    public static ErrorMessage of(String id, String text) {\n        return new ErrorMessage(id, text);\n    }\n\n    private ErrorMessage(String id, String text) {\n        this.id = Objects.requireNonNull(id);\n        this.text = Objects.requireNonNull(text);\n    }\n\n    @Override\n    public String toString() {\n        return \"ErrorMessage{\" +\n                \"id='\" + id + '\\'' +\n                \", text='\" + text + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/error/ErrorMessageConsumer.java",
    "content": "package org.optaweb.vehiclerouting.service.error;\n\n/**\n * Consumes error messages.\n */\npublic interface ErrorMessageConsumer {\n\n    /**\n     * Consume an error message.\n     *\n     * @param message error message\n     */\n    void consumeMessage(ErrorMessage message);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/error/package-info.java",
    "content": "/**\n * Handles error events. For example an uncaught exception can be turned into an error event that is sent to the client.\n */\npackage org.optaweb.vehiclerouting.service.error;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/DistanceMatrix.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\n\n/**\n * Holds distances between every pair of locations.\n */\npublic interface DistanceMatrix {\n\n    DistanceMatrixRow addLocation(Location location);\n\n    void removeLocation(Location location);\n\n    void clear();\n\n    Distance distance(Location from, Location to);\n\n    void put(Location from, Location to, Distance distance);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/DistanceMatrixRow.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\n\n/**\n * Contains {@link Distance distances} from the location associated with this row to other locations.\n */\npublic interface DistanceMatrixRow {\n\n    /**\n     * Distance from this row's location to the given location.\n     *\n     * @param locationId target location\n     * @return time it takes to travel to the given location\n     */\n    Distance distanceTo(long locationId);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/LocationPlanner.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport org.optaweb.vehiclerouting.domain.Location;\n\n/**\n * Optimizes the routing plan in response to location-related changes in the routing problem.\n */\npublic interface LocationPlanner {\n\n    void addLocation(Location location, DistanceMatrixRow distanceMatrixRow);\n\n    void removeLocation(Location location);\n\n    void removeAllLocations();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/LocationRepository.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\n\n/**\n * Defines repository operations on locations.\n */\npublic interface LocationRepository {\n\n    /**\n     * Create a location with a unique ID.\n     *\n     * @param coordinates location's coordinates\n     * @param description description of the location\n     * @return a new location\n     */\n    Location createLocation(Coordinates coordinates, String description);\n\n    /**\n     * Get all locations.\n     *\n     * @return all locations\n     */\n    List<Location> locations();\n\n    /**\n     * Remove a location with the given ID.\n     *\n     * @param id location ID\n     * @return the removed location\n     */\n    Location removeLocation(long id);\n\n    /**\n     * Remove all locations from the repository.\n     */\n    void removeAll();\n\n    /**\n     * Find a location by its ID.\n     *\n     * @param locationId location's ID\n     * @return an Optional containing location with the given ID or empty Optional if there is no location with such ID\n     */\n    Optional<Location> find(long locationId);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/LocationService.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport static java.util.Comparator.comparingLong;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Event;\nimport javax.inject.Inject;\nimport javax.transaction.Transactional;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.distance.DistanceRepository;\nimport org.optaweb.vehiclerouting.service.error.ErrorEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Performs location-related use cases.\n */\n@ApplicationScoped\npublic class LocationService {\n\n    private static final Logger logger = LoggerFactory.getLogger(LocationService.class);\n\n    private final LocationRepository repository;\n    private final DistanceRepository distanceRepository;\n    private final LocationPlanner planner; // TODO move to RoutingPlanService (SRP)\n    private final DistanceMatrix distanceMatrix;\n    private final Event<ErrorEvent> errorEvent;\n\n    @Inject\n    LocationService(\n            LocationRepository repository,\n            DistanceRepository distanceRepository,\n            LocationPlanner planner,\n            DistanceMatrix distanceMatrix,\n            Event<ErrorEvent> errorEvent) {\n        this.repository = repository;\n        this.distanceRepository = distanceRepository;\n        this.planner = planner;\n        this.distanceMatrix = distanceMatrix;\n        this.errorEvent = errorEvent;\n    }\n\n    public synchronized void addLocation(Location location) {\n        Objects.requireNonNull(location);\n        DistanceMatrixRow distanceMatrixRow = distanceMatrix.addLocation(location);\n        planner.addLocation(location, distanceMatrixRow);\n    }\n\n    @Transactional\n    public synchronized Optional<Location> createLocation(Coordinates coordinates, String description) {\n        Objects.requireNonNull(coordinates);\n        Objects.requireNonNull(description);\n        // TODO if (router.isLocationAvailable(coordinates))\n        Location location = repository.createLocation(coordinates, description);\n        Optional<DistanceMatrixRow> distanceMatrixRow = addToMatrix(location);\n        if (distanceMatrixRow.isPresent()) {\n            planner.addLocation(location, distanceMatrixRow.get());\n            return Optional.of(location);\n        } else {\n            repository.removeLocation(location.id());\n            return Optional.empty();\n        }\n    }\n\n    private Optional<DistanceMatrixRow> addToMatrix(Location location) {\n        try {\n            DistanceMatrixRow distanceMatrixRow = distanceMatrix.addLocation(location);\n            repository.locations().stream()\n                    .filter(existingLocation -> !existingLocation.equals(location))\n                    .forEach(existingLocation -> {\n                        distanceRepository.saveDistance(location, existingLocation,\n                                distanceMatrixRow.distanceTo(existingLocation.id()));\n                        distanceRepository.saveDistance(existingLocation, location,\n                                distanceMatrix.distance(existingLocation, location));\n                    });\n            return Optional.of(distanceMatrixRow);\n        } catch (Exception e) {\n            logger.error(\n                    \"Failed to calculate distances for location {}, it will be discarded\",\n                    location.fullDescription(), e);\n            errorEvent.fire(new ErrorEvent(\n                    this,\n                    \"Failed to calculate distances for location \" + location.fullDescription()\n                            + \", it will be discarded.\\n\" + e.toString()));\n            return Optional.empty();\n        }\n    }\n\n    @Transactional\n    public synchronized void removeLocation(long id) {\n        Optional<Location> optionalLocation = repository.find(id);\n        if (!optionalLocation.isPresent()) {\n            errorEvent.fire(new ErrorEvent(this, \"Location [\" + id + \"] cannot be removed because it doesn't exist.\"));\n            return;\n        }\n        Location removedLocation = optionalLocation.get();\n        List<Location> locations = repository.locations();\n        if (locations.size() > 1) {\n            Location depot = locations.stream()\n                    .min(comparingLong(Location::id))\n                    .orElseThrow(() -> new IllegalStateException(\n                            \"Impossible. Locations have size (\" + locations.size() + \") but the stream is empty.\"));\n            if (removedLocation.equals(depot)) {\n                errorEvent.fire(new ErrorEvent(this, \"You can only remove depot if there are no visits.\"));\n                return;\n            }\n        }\n\n        planner.removeLocation(removedLocation);\n        repository.removeLocation(id);\n        distanceMatrix.removeLocation(removedLocation);\n        distanceRepository.deleteDistances(removedLocation);\n    }\n\n    @Transactional\n    public synchronized void removeAll() {\n        planner.removeAllLocations();\n        repository.removeAll();\n        distanceMatrix.clear();\n        distanceRepository.deleteAll();\n    }\n\n    public void populateDistanceMatrix() {\n        repository.locations()\n                .forEach(from -> repository.locations().stream()\n                        .filter(to -> !to.equals(from))\n                        .forEach(to -> distanceMatrix.put(from, to, distanceRepository.getDistance(from, to)\n                                .orElseThrow(() -> new IllegalStateException(\"Distance from: [\" + from + \"] to: [\" + to\n                                        + \"] is missing in the distance repository. This should not happen.\")))));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/location/package-info.java",
    "content": "/**\n * Use cases that involve {@link org.optaweb.vehiclerouting.domain.Location locations}.\n */\npackage org.optaweb.vehiclerouting.service.location;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/region/BoundingBox.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport java.util.Objects;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\n/**\n * Bounding box.\n */\npublic class BoundingBox {\n\n    private final Coordinates southWest;\n    private final Coordinates northEast;\n\n    /**\n     * Create bounding box. The box must have non-zero dimensions and the corners must be south-west and north-east.\n     *\n     * @param southWest south-west corner (minimal latitude and longitude)\n     * @param northEast north-east corner (maximal latitude and longitude)\n     */\n    public BoundingBox(Coordinates southWest, Coordinates northEast) {\n        this.southWest = Objects.requireNonNull(southWest);\n        this.northEast = Objects.requireNonNull(northEast);\n        if (southWest.latitude().compareTo(northEast.latitude()) >= 0) {\n            throw new IllegalArgumentException(\n                    \"South-west corner latitude (\"\n                            + southWest.latitude()\n                            + \"N) must be less than north-east corner latitude (\"\n                            + northEast.latitude()\n                            + \"N)\");\n        }\n        if (southWest.longitude().compareTo(northEast.longitude()) >= 0) {\n            throw new IllegalArgumentException(\n                    \"South-west corner longitude (\"\n                            + southWest.longitude()\n                            + \"E) must be less than north-east corner longitude (\"\n                            + northEast.longitude()\n                            + \"E)\");\n        }\n    }\n\n    /**\n     * South-west corner of the bounding box.\n     *\n     * @return south-west corner (minimal latitude and longitude)\n     */\n    public Coordinates getSouthWest() {\n        return southWest;\n    }\n\n    /**\n     * North-east corner of the bounding box.\n     *\n     * @return north-east corner (maximal latitude and longitude)\n     */\n    public Coordinates getNorthEast() {\n        return northEast;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/region/Region.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\npublic interface Region {\n\n    BoundingBox getBounds();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/region/RegionProperties.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport io.smallrye.config.ConfigMapping;\n\n@ConfigMapping(prefix = \"app.region\")\npublic interface RegionProperties {\n\n    /**\n     * Get country codes specified for the loaded OSM file (working region).\n     * The codes are expected to be in the ISO 3166-1 alpha-2 format.\n     *\n     * @return list of country codes (never {@code null})\n     */\n    Optional<List<String>> countryCodes();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/region/RegionService.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport java.util.List;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\n\n/**\n * Provides information about the working region.\n */\n@ApplicationScoped\npublic class RegionService {\n\n    private final RegionProperties regionProperties;\n    private final Region region;\n\n    @Inject\n    RegionService(RegionProperties regionProperties, Region region) {\n        this.regionProperties = regionProperties;\n        this.region = region;\n    }\n\n    /**\n     * Country codes matching the working region.\n     *\n     * @return country codes (never {@code null})\n     */\n    public List<String> countryCodes() {\n        return regionProperties.countryCodes().orElse(List.of());\n    }\n\n    /**\n     * Bounding box of the working region.\n     *\n     * @return bounding box of the working region.\n     */\n    public BoundingBox boundingBox() {\n        return region.getBounds();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/region/package-info.java",
    "content": "/**\n * Provides information about the application's working region.\n */\npackage org.optaweb.vehiclerouting.service.region;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/reload/ReloadService.java",
    "content": "package org.optaweb.vehiclerouting.service.reload;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Observes;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\nimport io.quarkus.runtime.StartupEvent;\n\n/**\n * Reloads data from repositories when the application starts.\n */\n@ApplicationScoped\npublic class ReloadService {\n\n    private final VehicleRepository vehicleRepository;\n    private final VehicleService vehicleService;\n    private final LocationRepository locationRepository;\n    private final LocationService locationService;\n\n    @Inject\n    ReloadService(\n            VehicleRepository vehicleRepository,\n            VehicleService vehicleService,\n            LocationRepository locationRepository,\n            LocationService locationService) {\n        this.vehicleRepository = vehicleRepository;\n        this.vehicleService = vehicleService;\n        this.locationRepository = locationRepository;\n        this.locationService = locationService;\n    }\n\n    public void reload(@Observes StartupEvent startupEvent) {\n        vehicleRepository.vehicles().forEach(vehicleService::addVehicle);\n        locationService.populateDistanceMatrix();\n        locationRepository.locations().forEach(locationService::addLocation);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/reload/package-info.java",
    "content": "/**\n * Loads the application state from repositories when it starts.\n */\npackage org.optaweb.vehiclerouting.service.reload;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/route/RouteChangedEvent.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport org.optaweb.vehiclerouting.domain.Distance;\n\n/**\n * Event published when the routing plan has been updated either by discovering a better route or by a change\n * in the problem specification (vehicles, visits).\n */\npublic class RouteChangedEvent {\n\n    private final Distance distance;\n    private final List<Long> vehicleIds;\n    private final Long depotId;\n    private final List<Long> visitIds;\n    private final Collection<ShallowRoute> routes;\n\n    /**\n     * Create a new ApplicationEvent.\n     *\n     * @param source the object on which the event initially occurred (never {@code null})\n     * @param distance total distance of all vehicle routes\n     * @param vehicleIds vehicle IDs\n     * @param depotId depot ID (may be {@code null} if there are no locations)\n     * @param visitIds IDs of visits\n     * @param routes vehicle routes\n     */\n    public RouteChangedEvent(\n            Object source,\n            Distance distance,\n            List<Long> vehicleIds,\n            Long depotId,\n            List<Long> visitIds,\n            Collection<ShallowRoute> routes) {\n        this.distance = Objects.requireNonNull(distance);\n        this.vehicleIds = Objects.requireNonNull(vehicleIds);\n        this.depotId = depotId; // may be null (no depot)\n        this.visitIds = Objects.requireNonNull(visitIds);\n        this.routes = Objects.requireNonNull(routes);\n    }\n\n    /**\n     * IDs of all vehicles.\n     *\n     * @return vehicle IDs\n     */\n    public List<Long> vehicleIds() {\n        return vehicleIds;\n    }\n\n    /**\n     * Routes of all vehicles.\n     *\n     * @return vehicle routes\n     */\n    public Collection<ShallowRoute> routes() {\n        return routes;\n    }\n\n    /**\n     * Routing plan distance.\n     *\n     * @return distance (never {@code null})\n     */\n    public Distance distance() {\n        return distance;\n    }\n\n    /**\n     * The depot ID.\n     *\n     * @return depot ID\n     */\n    public Optional<Long> depotId() {\n        return Optional.ofNullable(depotId);\n    }\n\n    public List<Long> visitIds() {\n        return visitIds;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/route/RouteListener.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport static java.util.stream.Collectors.toList;\nimport static java.util.stream.Collectors.toMap;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Event;\nimport javax.enterprise.event.Observes;\nimport javax.inject.Inject;\n\nimport org.optaweb.vehiclerouting.Profiles;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.Route;\nimport org.optaweb.vehiclerouting.domain.RouteWithTrack;\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport io.quarkus.arc.profile.UnlessBuildProfile;\n\n/**\n * Handles route updates emitted by optimization plugin.\n */\n@ApplicationScoped\n@UnlessBuildProfile(Profiles.TEST)\npublic class RouteListener {\n\n    private static final Logger logger = LoggerFactory.getLogger(RouteListener.class);\n\n    private final Router router;\n    private final VehicleRepository vehicleRepository;\n    private final LocationRepository locationRepository;\n    private final Event<RoutingPlan> routingPlanEvent;\n\n    // TODO maybe remove state from the service and get best route from a repository\n    private RoutingPlan bestRoutingPlan;\n\n    @Inject\n    RouteListener(\n            Router router,\n            VehicleRepository vehicleRepository,\n            LocationRepository locationRepository,\n            Event<RoutingPlan> routingPlanEvent) {\n        this.router = router;\n        this.vehicleRepository = vehicleRepository;\n        this.locationRepository = locationRepository;\n        this.routingPlanEvent = routingPlanEvent;\n        bestRoutingPlan = RoutingPlan.empty();\n    }\n\n    // TODO maybe @ObservesAsync?\n    public void onApplicationEvent(@Observes RouteChangedEvent event) {\n        // TODO persist the best solution\n        Location depot = event.depotId().flatMap(locationRepository::find).orElse(null);\n        try {\n            // TODO Introduce problem revision (every modification increases revision number, event will only\n            //  be published if revision numbers match) to avoid looking for missing/extra vehicles/visits.\n            //  This will also make it possible to get rid of the try-catch approach.\n            Map<Long, Vehicle> vehicleMap = event.vehicleIds().stream()\n                    .collect(toMap(vehicleId -> vehicleId, this::findVehicleById));\n            Map<Long, Location> visitMap = event.visitIds().stream()\n                    .collect(toMap(visitId -> visitId, this::findLocationById));\n\n            List<RouteWithTrack> routes = event.routes().stream()\n                    // list of deep locations\n                    .map(shallowRoute -> new Route(\n                            vehicleMap.get(shallowRoute.vehicleId),\n                            findLocationById(shallowRoute.depotId),\n                            shallowRoute.visitIds.stream()\n                                    .map(visitMap::get)\n                                    .collect(toList())))\n                    // add tracks\n                    .map(route -> new RouteWithTrack(route, track(route.depot(), route.visits())))\n                    .collect(toList());\n            bestRoutingPlan = new RoutingPlan(\n                    event.distance(),\n                    new ArrayList<>(vehicleMap.values()),\n                    depot,\n                    new ArrayList<>(visitMap.values()),\n                    routes);\n            routingPlanEvent.fire(bestRoutingPlan);\n        } catch (IllegalStateException e) {\n            logger.warn(\"Discarding an outdated routing plan: {}\", e.toString());\n        }\n    }\n\n    private Vehicle findVehicleById(long id) {\n        return vehicleRepository.find(id).orElseThrow(() -> new IllegalStateException(\n                \"Vehicle {id=\" + id + \"} not found in the repository\"));\n    }\n\n    private Location findLocationById(long id) {\n        return locationRepository.find(id).orElseThrow(() -> new IllegalStateException(\n                \"Location {id=\" + id + \"} not found in the repository\"));\n    }\n\n    private List<List<Coordinates>> track(Location depot, List<Location> route) {\n        if (route.isEmpty()) {\n            return Collections.emptyList();\n        }\n        ArrayList<Location> itinerary = new ArrayList<>();\n        itinerary.add(depot);\n        itinerary.addAll(route);\n        itinerary.add(depot);\n        List<List<Coordinates>> paths = new ArrayList<>();\n        for (int i = 0; i < itinerary.size() - 1; i++) {\n            Location fromLocation = itinerary.get(i);\n            Location toLocation = itinerary.get(i + 1);\n            List<Coordinates> path = router.getPath(fromLocation.coordinates(), toLocation.coordinates());\n            paths.add(path);\n        }\n        return paths;\n    }\n\n    public RoutingPlan getBestRoutingPlan() {\n        return bestRoutingPlan;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/route/Router.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport java.util.List;\n\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\n/**\n * Provides paths between locations.\n */\npublic interface Router {\n\n    /**\n     * Get path between two locations.\n     *\n     * @param from starting location\n     * @param to destination\n     * @return list of coordinates describing the path between given locations.\n     */\n    List<Coordinates> getPath(Coordinates from, Coordinates to);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/route/ShallowRoute.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport static java.util.stream.Collectors.joining;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n// TODO maybe remove this once we fork planning domain from optaplanner-examples\n// because then we can hold a reference to the original location\n\n/**\n * Lightweight route description consisting of vehicle and location IDs instead of entities.\n * This makes it easier to quickly construct and share result of route optimization\n * without converting planning domain objects to business domain objects.\n * Specifically, some information may be lost when converting business domain objects to planning domain\n * because it's not needed for optimization (e.g. location address)\n * and so it's impossible to reconstruct the original business object without looking into the repository.\n */\npublic class ShallowRoute {\n\n    /**\n     * Vehicle ID.\n     */\n    public final long vehicleId;\n    /**\n     * Depot ID.\n     */\n    public final long depotId;\n    /**\n     * Visit IDs (immutable, never {@code null}).\n     */\n    public final List<Long> visitIds;\n\n    /**\n     * Create shallow route.\n     *\n     * @param vehicleId vehicle ID\n     * @param depotId depot ID\n     * @param visitIds visit IDs\n     */\n    public ShallowRoute(long vehicleId, long depotId, List<Long> visitIds) {\n        this.vehicleId = vehicleId;\n        this.depotId = depotId;\n        this.visitIds = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(visitIds)));\n    }\n\n    @Override\n    public String toString() {\n        String route = Stream.concat(Stream.of(depotId), visitIds.stream())\n                .map(Object::toString)\n                .collect(joining(\"->\", \"[\", \"]\"));\n        return vehicleId + \": \" + route;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/route/package-info.java",
    "content": "/**\n * Handles {@link org.optaweb.vehiclerouting.domain.RoutingPlan route} updates.\n */\npackage org.optaweb.vehiclerouting.service.route;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/vehicle/VehiclePlanner.java",
    "content": "package org.optaweb.vehiclerouting.service.vehicle;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\n\n/**\n * Optimizes the routing plan in response to vehicle-related changes in the routing problem.\n */\npublic interface VehiclePlanner {\n\n    void addVehicle(Vehicle vehicle);\n\n    void removeVehicle(Vehicle vehicle);\n\n    void removeAllVehicles();\n\n    void changeCapacity(Vehicle vehicle);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/vehicle/VehicleRepository.java",
    "content": "package org.optaweb.vehiclerouting.service.vehicle;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\n\n/**\n * Defines repository operations on vehicles.\n */\npublic interface VehicleRepository {\n\n    /**\n     * Create a vehicle with a unique ID.\n     *\n     * @param capacity vehicle's capacity\n     * @return a new vehicle\n     */\n    Vehicle createVehicle(int capacity);\n\n    /**\n     * Create a vehicle from the given data.\n     *\n     * @param vehicleData vehicle data\n     * @return a new vehicle\n     */\n    Vehicle createVehicle(VehicleData vehicleData);\n\n    /**\n     * Get all vehicles.\n     *\n     * @return all vehicles\n     */\n    List<Vehicle> vehicles();\n\n    /**\n     * Remove a vehicle with the given ID.\n     *\n     * @param id vehicle's ID\n     * @return the removed vehicle\n     */\n    Vehicle removeVehicle(long id);\n\n    /**\n     * Remove all vehicles from the repository.\n     */\n    void removeAll();\n\n    /**\n     * Find a vehicle by its ID.\n     *\n     * @param vehicleId vehicle's ID\n     * @return an Optional containing vehicle with the given ID or empty Optional if there is no vehicle with such ID\n     */\n    Optional<Vehicle> find(long vehicleId);\n\n    Vehicle changeCapacity(long vehicleId, int capacity);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/vehicle/VehicleService.java",
    "content": "package org.optaweb.vehiclerouting.service.vehicle;\n\nimport static java.util.Comparator.comparingLong;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.inject.Inject;\nimport javax.transaction.Transactional;\n\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\n\n@ApplicationScoped\npublic class VehicleService {\n\n    static final int DEFAULT_VEHICLE_CAPACITY = 10;\n\n    private final VehiclePlanner planner;\n    private final VehicleRepository vehicleRepository;\n\n    @Inject\n    public VehicleService(VehiclePlanner planner, VehicleRepository vehicleRepository) {\n        this.planner = planner;\n        this.vehicleRepository = vehicleRepository;\n    }\n\n    @Transactional\n    public Vehicle createVehicle() {\n        Vehicle vehicle = vehicleRepository.createVehicle(DEFAULT_VEHICLE_CAPACITY);\n        addVehicle(vehicle);\n        return vehicle;\n    }\n\n    @Transactional\n    public Vehicle createVehicle(VehicleData vehicleData) {\n        Vehicle vehicle = vehicleRepository.createVehicle(vehicleData);\n        addVehicle(vehicle);\n        return vehicle;\n    }\n\n    public void addVehicle(Vehicle vehicle) {\n        planner.addVehicle(Objects.requireNonNull(vehicle));\n    }\n\n    @Transactional\n    public void removeVehicle(long vehicleId) {\n        Vehicle vehicle = vehicleRepository.removeVehicle(vehicleId);\n        planner.removeVehicle(vehicle);\n    }\n\n    public synchronized void removeAnyVehicle() {\n        Optional<Vehicle> first = vehicleRepository.vehicles().stream().min(comparingLong(Vehicle::id));\n        first.map(Vehicle::id).ifPresent(this::removeVehicle);\n    }\n\n    @Transactional\n    public void removeAll() {\n        planner.removeAllVehicles();\n        vehicleRepository.removeAll();\n    }\n\n    @Transactional\n    public void changeCapacity(long vehicleId, int capacity) {\n        Vehicle updatedVehicle = vehicleRepository.changeCapacity(vehicleId, capacity);\n        planner.changeCapacity(updatedVehicle);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/java/org/optaweb/vehiclerouting/service/vehicle/package-info.java",
    "content": "/**\n * Performs use cases that involve vehicles.\n */\npackage org.optaweb.vehiclerouting.service.vehicle;\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/resources/.gitignore",
    "content": "/application-local.properties\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/resources/application.properties",
    "content": "# App configuration\napp.demo.data-set-dir=local/dataset\napp.region.country-codes=BE\napp.routing.osm-dir=local/openstreetmap\napp.routing.gh-dir=local/graphhopper\napp.routing.osm-file=belgium-latest.osm.pbf\napp.routing.engine=GRAPHHOPPER\n%test.app.routing.engine=GRAPHHOPPER\n\n# OptaPlanner\nquarkus.optaplanner.solver.daemon=true\nquarkus.optaplanner.solver.termination.spent-limit=1m\n\n# Enable CORS filter.\nquarkus.http.cors=true\n\n# Logging\nquarkus.log.level=INFO\nquarkus.log.min-level=DEBUG\nquarkus.log.category.\"org.optaweb\".level=${LOG_LEVEL_APP:INFO}\nquarkus.log.category.\"org.optaplanner\".level=${LOG_LEVEL_OPTAPLANNER:INFO}\nquarkus.log.category.\"org.drools\".level=${LOG_LEVEL_DROOLS:INFO}\nquarkus.log.category.\"org.hibernate\".level=${LOG_LEVEL_HIBERNATE:INFO}\nquarkus.log.category.\"org.jboss.resteasy\".level=${LOG_LEVEL_RESTEASY:INFO}\n\n# In development mode, the working directory is ./target but local files are expected to survive mvn clean,\n# so they should be one level above target.\n%dev.app.demo.data-set-dir=../local/dataset\n%dev.app.routing.osm-dir=../local/openstreetmap\n%dev.app.routing.gh-dir=../local/graphhopper\n\n############\n# Datasource\n############\n\n# [PostgreSQL] - recommended for production\n%postgresql.quarkus.datasource.db-kind=postgresql\n%postgresql.quarkus.datasource.jdbc.url=jdbc:postgresql://${DATABASE_HOST:postgresql}:5432/${DATABASE_NAME:optaweb_vrp_database}\n%postgresql.quarkus.datasource.username=${DATABASE_USER}\n%postgresql.quarkus.datasource.password=${DATABASE_PASSWORD}\n%postgresql.quarkus.hibernate-orm.database.generation=update\n\n# [Default] - works out of the box, good for development\n# - using an embedded DB with relative path: http://h2database.com/html/features.html#embedded_databases\n# - not closing the DB automatically: http://h2database.com/html/features.html#closing_a_database\nquarkus.datasource.db-kind=h2\nquarkus.datasource.jdbc.url=jdbc:h2:file:${app.persistence.h2-dir:../local/db}/${app.persistence.h2-filename:optaweb_vrp_database};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false\nquarkus.datasource.username=sa\nquarkus.datasource.password=\nquarkus.hibernate-orm.database.generation=update\n\n# [Tests]\n# - using an in-memory DB: http://h2database.com/html/features.html#in_memory_databases\n# - not closing the DB automatically: http://h2database.com/html/features.html#closing_a_database\n%test.quarkus.datasource.db-kind=h2\n%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE\n%test.quarkus.datasource.username=sa\n%test.quarkus.datasource.password=\n%test.quarkus.hibernate-orm.database.generation=create-drop\n\n%test.app.region.country-codes=AT,DE\n\n###############\n# Cypress tests\n###############\n%test-cypress.app.region.country-codes=DE\n%test-cypress.app.routing.osm-dir=target-classes/src/test/resources/org/optaweb/vehiclerouting/plugin/routing\n%test-cypress.app.routing.osm-file=planet_12.032,53.0171_12.1024,53.0491.osm.pbf\n%test-cypress.optaplanner.solver.termination.spent-limit=10s\n%test-cypress.quarkus.datasource.db-kind=h2\n%test-cypress.quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE\n%test-cypress.quarkus.datasource.username=sa\n%test-cypress.quarkus.datasource.password=\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/resources/org/optaweb/vehiclerouting/service/demo/belgium-cities.yaml",
    "content": "---\nname: \"Belgium cities\"\nvehicles:\n  - name: \"Vehicle 1\"\n    capacity: 10\n  - name: \"Vehicle 2\"\n    capacity: 10\n  - name: \"Vehicle 3\"\n    capacity: 10\n  - name: \"Vehicle 4\"\n    capacity: 10\n  - name: \"Vehicle 5\"\n    capacity: 10\n  - name: \"Vehicle 6\"\n    capacity: 10\ndepot:\n  label: \"Brussels\"\n  lat: 50.85\n  lng: 4.35\nvisits:\n  - label: \"Aalst\"\n    lat: 50.933333\n    lng: 4.033333\n  - label: \"Anderlecht\"\n    lat: 50.833333\n    lng: 4.333333\n  - label: \"Antwerp\"\n    lat: 51.217778\n    lng: 4.400278\n  - label: \"Beringen\"\n    lat: 51.033333\n    lng: 5.216667\n  - label: \"Bruges\"\n    lat: 51.216667\n    lng: 3.233333\n  - label: \"Charleroi\"\n    lat: 50.4\n    lng: 4.433333\n  - label: \"Chatelet\"\n    lat: 50.4\n    lng: 4.516667\n  - label: \"Dendermonde\"\n    lat: 51.033333\n    lng: 4.1\n  - label: \"Geel\"\n    lat: 51.166667\n    lng: 5.0\n  - label: \"Genk\"\n    lat: 50.966667\n    lng: 5.5\n  - label: \"Ghent\"\n    lat: 51.05\n    lng: 3.733333\n  - label: \"Halle\"\n    lat: 50.733333\n    lng: 4.233333\n  - label: \"Hasselt\"\n    lat: 50.93\n    lng: 5.34\n  - label: \"Ixelles\"\n    lat: 50.833333\n    lng: 4.366667\n  - label: \"Kortrijk\"\n    lat: 50.833333\n    lng: 3.266667\n  - label: \"La Louviere\"\n    lat: 50.466667\n    lng: 4.183333\n  - label: \"Leuven\"\n    lat: 50.883333\n    lng: 4.7\n  - label: \"Liege\"\n    lat: 50.633333\n    lng: 5.566667\n  - label: \"Lier\"\n    lat: 51.133333\n    lng: 4.566667\n  - label: \"Lokeren\"\n    lat: 51.1\n    lng: 3.983333\n  - label: \"Mechelen\"\n    lat: 51.016667\n    lng: 4.466667\n  - label: \"Molenbeek\"\n    lat: 50.857778\n    lng: 4.315833\n  - label: \"Mons\"\n    lat: 50.45\n    lng: 3.95\n  - label: \"Namur\"\n    lat: 50.466667\n    lng: 4.866667\n  - label: \"Ninove\"\n    lat: 50.833333\n    lng: 4.016667\n  - label: \"Roeselare\"\n    lat: 50.933333\n    lng: 3.116667\n  - label: \"Schaerbeek\"\n    lat: 50.866667\n    lng: 4.383333\n  - label: \"Seraing\"\n    lat: 50.583333\n    lng: 5.5\n  - label: \"Sint Niklaas\"\n    lat: 51.166667\n    lng: 4.133333\n  - label: \"Sint Truiden\"\n    lat: 50.8\n    lng: 5.183333\n  - label: \"Tienen\"\n    lat: 50.8\n    lng: 4.933333\n  - label: \"Tournai\"\n    lat: 50.6\n    lng: 3.383333\n  - label: \"Turnhout\"\n    lat: 51.316667\n    lng: 4.95\n  - label: \"Uccle\"\n    lat: 50.8\n    lng: 4.333333\n  - label: \"Verviers\"\n    lat: 50.583333\n    lng: 5.85\n  - label: \"Vilvoorde\"\n    lat: 50.933333\n    lng: 4.416667\n  - label: \"Waregem\"\n    lat: 50.883333\n    lng: 3.416667\n  - label: \"Wavre\"\n    lat: 50.716667\n    lng: 4.6\n  - label: \"Ypres\"\n    lat: 50.85\n    lng: 2.883333\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/main/resources/solverConfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<solver>\n  <!--<environmentMode>FULL_ASSERT</environmentMode>--><!-- To slowly prove there are no bugs in this code -->\n  <!--<moveThreadCount>AUTO</moveThreadCount>--><!-- To solve faster by saturating multiple CPU cores -->\n\n  <solutionClass>org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution</solutionClass>\n  <entityClass>org.optaweb.vehiclerouting.plugin.planner.domain.Standstill</entityClass>\n  <entityClass>org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit</entityClass>\n\n  <scoreDirectorFactory>\n    <constraintProviderClass>org.optaweb.vehiclerouting.plugin.planner.VehicleRoutingConstraintProvider</constraintProviderClass>\n    <initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>\n  </scoreDirectorFactory>\n\n  <constructionHeuristic>\n    <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>\n  </constructionHeuristic>\n  <localSearch>\n    <unionMoveSelector>\n      <changeMoveSelector/>\n      <swapMoveSelector/>\n      <subChainChangeMoveSelector>\n        <selectReversingMoveToo>true</selectReversingMoveToo>\n      </subChainChangeMoveSelector>\n      <subChainSwapMoveSelector>\n        <selectReversingMoveToo>true</selectReversingMoveToo>\n      </subChainSwapMoveSelector>\n      <!-- TODO use nearby selection to scale out -->\n    </unionMoveSelector>\n    <acceptor>\n      <lateAcceptanceSize>200</lateAcceptanceSize>\n    </acceptor>\n    <forager>\n      <acceptedCountLimit>1</acceptedCountLimit>\n    </forager>\n  </localSearch>\n</solver>\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/TestConfig.java",
    "content": "package org.optaweb.vehiclerouting;\n\nimport javax.enterprise.context.Dependent;\nimport javax.enterprise.inject.Produces;\n\nimport org.mockito.Mockito;\nimport org.optaweb.vehiclerouting.service.route.RouteListener;\n\nimport com.graphhopper.GraphHopper;\n\nimport io.quarkus.arc.profile.IfBuildProfile;\nimport io.quarkus.test.junit.QuarkusTest;\n\n@Dependent\npublic class TestConfig {\n\n    /**\n     * Creates a GraphHopper mock that may be used when running a {@link QuarkusTest @QuarkusTest}.\n     *\n     * @return mock GraphHopper\n     */\n    @IfBuildProfile(Profiles.TEST)\n    @Produces\n    public GraphHopper graphHopper() {\n        return Mockito.mock(GraphHopper.class);\n    }\n\n    /**\n     * Creates a mock route listener to avoid things like touching database and WebSocket.\n     *\n     * @return mock RouteListener\n     */\n    @IfBuildProfile(Profiles.TEST)\n    @Produces\n    public RouteListener routeListener() {\n        return Mockito.mock(RouteListener.class);\n    }\n\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/CoordinatesTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\n\nclass CoordinatesTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new Coordinates(null, BigDecimal.ZERO));\n        assertThatNullPointerException().isThrownBy(() -> new Coordinates(BigDecimal.ZERO, null));\n    }\n\n    @Test\n    void coordinates_should_be_equals_when_numerically_equal() {\n        Coordinates coordinates = new Coordinates(BigDecimal.valueOf(987.1234), BigDecimal.valueOf(-0.1111));\n        assertThat(coordinates).isEqualTo(coordinates);\n\n        BigDecimal ONE_POINT_ZERO = new BigDecimal(\"1.0\");\n        BigDecimal MINUS_ZERO = BigDecimal.ZERO.negate();\n\n        Coordinates coordinates01 = new Coordinates(BigDecimal.ZERO, BigDecimal.ONE);\n        assertThat(new Coordinates(MINUS_ZERO, ONE_POINT_ZERO))\n                .isEqualTo(coordinates01)\n                .hasSameHashCodeAs(coordinates01);\n\n        Coordinates coordinates10 = new Coordinates(BigDecimal.ONE, BigDecimal.ZERO);\n        assertThat(new Coordinates(ONE_POINT_ZERO, MINUS_ZERO))\n                .isEqualTo(coordinates10)\n                .hasSameHashCodeAs(coordinates10);\n    }\n\n    @Test\n    void should_not_equal() {\n        assertThat(new Coordinates(BigDecimal.ONE, BigDecimal.TEN))\n                .isNotEqualTo(null)\n                .isNotEqualTo(BigDecimal.valueOf(11))\n                .isNotEqualTo(new Coordinates(BigDecimal.ONE, BigDecimal.ONE))\n                .isNotEqualTo(new Coordinates(BigDecimal.TEN, BigDecimal.TEN));\n    }\n\n    @Test\n    void valueOf_and_getters() {\n        double latitude = Math.E;\n        double longitude = Math.PI;\n        Coordinates coordinates = Coordinates.of(latitude, longitude);\n        assertThat(coordinates.latitude()).isEqualTo(BigDecimal.valueOf(latitude));\n        assertThat(coordinates.longitude()).isEqualTo(BigDecimal.valueOf(longitude));\n    }\n\n    @Test\n    void toString_should_contain_latitude_and_longitude() {\n        String pi = \"3.14159265358979323846\";\n        assertThat(new Coordinates(BigDecimal.ONE, new BigDecimal(pi))).hasToString(\"[1, \" + pi + \"]\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/CountryCodeValidatorTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\nimport static org.optaweb.vehiclerouting.domain.CountryCodeValidator.validate;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\n\nclass CountryCodeValidatorTest {\n\n    @Test\n    void should_fail_on_invalid_country_codes() {\n        assertThatNullPointerException().isThrownBy(() -> validate(null));\n        assertThatNullPointerException().isThrownBy(() -> validate(Arrays.asList(\"US\", null, \"CA\")));\n        assertThatIllegalArgumentException().isThrownBy(() -> validate(Arrays.asList(\"XX\")));\n        assertThatIllegalArgumentException().isThrownBy(() -> validate(Arrays.asList(\"CZE\")));\n        assertThatIllegalArgumentException().isThrownBy(() -> validate(Arrays.asList(\"D\")));\n        assertThatIllegalArgumentException().isThrownBy(() -> validate(Arrays.asList(\"\")));\n        assertThatIllegalArgumentException().isThrownBy(() -> validate(Arrays.asList(\"US\", \"XY\", \"CA\")));\n    }\n\n    @Test\n    void should_ignore_case_and_convert_to_upper_case() {\n        assertThat(validate(Arrays.asList(\"us\"))).containsExactly(\"US\");\n    }\n\n    @Test\n    void should_allow_multiple_values() {\n        assertThat(validate(Arrays.asList(\"US\", \"ca\"))).containsExactly(\"US\", \"CA\");\n    }\n\n    @Test\n    void should_allow_empty_list() {\n        assertThat(validate(new ArrayList<>())).isEmpty();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/DistanceTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\nimport org.junit.jupiter.api.Test;\n\nclass DistanceTest {\n\n    @Test\n    void distance_millis_should_be_same_as_the_given_value() {\n        long millis = 123_999;\n        assertThat(Distance.ofMillis(millis).millis()).isEqualTo(millis);\n    }\n\n    @Test\n    void toString_should_contain_units_and_be_human_readable() {\n        assertThat(Distance.ofMillis(3600_000 * 37 + 60_000 * 3 + 24_000)).hasToString(\"37h 3m 24s 0ms\");\n        assertThat(Distance.ofMillis(3601_000)).hasToString(\"1h 0m 1s 0ms\");\n        assertThat(Distance.ofMillis(5_123)).hasToString(\"0h 0m 5s 123ms\");\n    }\n\n    @Test\n    void time_must_be_positive_or_zero() {\n        assertThatIllegalArgumentException().isThrownBy(() -> Distance.ofMillis(-1)).withMessageContaining(\"(-1)\");\n        assertThatCode(() -> Distance.ofMillis(0)).doesNotThrowAnyException();\n    }\n\n    @Test\n    void equals_hashCode() {\n        long millis = 37;\n        Distance distance = Distance.ofMillis(millis);\n        assertThat(distance)\n                .isEqualTo(distance)\n                .isEqualTo(Distance.ofMillis(millis))\n                .isNotEqualTo(null)\n                .isNotEqualTo(millis)\n                .isNotEqualTo(Distance.ofMillis(millis + 1))\n                .hasSameHashCodeAs(Distance.ofMillis(millis));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/LocationDataTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\n\nclass LocationDataTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new LocationData(null, \"\"));\n        assertThatNullPointerException().isThrownBy(() -> new LocationData(Coordinates.of(1, 1), null));\n    }\n\n    @Test\n    void locations_are_equal_if_they_have_same_properties() {\n        Coordinates coordinates0 = new Coordinates(BigDecimal.ZERO, BigDecimal.ZERO);\n        Coordinates coordinates1 = new Coordinates(BigDecimal.ONE, BigDecimal.ONE);\n        String description = \"test description\";\n        LocationData equalLocationData = new LocationData(coordinates0, description);\n\n        final LocationData locationData = new LocationData(coordinates0, description);\n\n        assertThat(locationData)\n                // different coordinates\n                .isNotEqualTo(new LocationData(coordinates1, description))\n                // different description\n                .isNotEqualTo(new LocationData(coordinates0, \"xyz\"))\n                // null\n                .isNotEqualTo(null)\n                // different type with equal properties\n                .isNotEqualTo(new Location(0, coordinates0, description))\n                // same object -> OK\n                .isEqualTo(locationData)\n                // same properties -> OK\n                .isEqualTo(equalLocationData)\n                // equal objects => same hashCode\n                .hasSameHashCodeAs(equalLocationData);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/LocationTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\n\nclass LocationTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new Location(0, null, \"\"));\n        assertThatNullPointerException().isThrownBy(() -> new Location(0, Coordinates.of(1, 1), null));\n    }\n\n    @Test\n    void locations_are_identified_based_on_id() {\n        final Coordinates coordinates0 = new Coordinates(BigDecimal.ZERO, BigDecimal.ZERO);\n        final Coordinates coordinates1 = new Coordinates(BigDecimal.ONE, BigDecimal.ONE);\n        final String description = \"test description\";\n        final long id = 0;\n\n        final Location location = new Location(id, coordinates0, description);\n\n        assertThat(location)\n                // different ID\n                .isNotEqualTo(new Location(1, coordinates0, description))\n                // null\n                .isNotEqualTo(null)\n                // different class\n                .isNotEqualTo(new LocationData(coordinates0, description))\n                // same object -> OK\n                .isEqualTo(location)\n                // same properties -> OK\n                .isEqualTo(new Location(id, coordinates0, description))\n                // same ID, different coordinate -> OK\n                .isEqualTo(new Location(id, coordinates1, description))\n                // same ID, different description -> OK\n                .isEqualTo(new Location(id, coordinates0, \"xyz\"));\n    }\n\n    @Test\n    void equal_locations_must_have_same_hashcode() {\n        long id = 1;\n        assertThat(new Location(id, Coordinates.of(1, 1), \"description 1\"))\n                .hasSameHashCodeAs(new Location(id, Coordinates.of(2, 2), \"description 2\"));\n    }\n\n    @Test\n    void constructor_without_description_should_create_empty_description() {\n        assertThat(new Location(7, Coordinates.of(3.14, 4.13)).description()).isEmpty();\n    }\n\n    @Test\n    void toString_should_contain_id_and_description() {\n        Coordinates coordinates = Coordinates.of(1, 1);\n\n        assertThat(new Location(7, coordinates, \"\")).hasToString(\"7\");\n        assertThat(new Location(5, coordinates, \"home\")).hasToString(\"5: 'home'\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/RouteTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\n\nclass RouteTest {\n\n    private final Vehicle vehicle = VehicleFactory.testVehicle(4);\n    private final Location depot = new Location(1, Coordinates.of(5, 5));\n    private final Location visit1 = new Location(2, Coordinates.of(5, 5));\n    private final Location visit2 = new Location(3, Coordinates.of(5, 5));\n\n    @Test\n    void constructor_args_not_null() {\n        assertThatNullPointerException().isThrownBy(() -> new Route(null, depot, Collections.emptyList()));\n        assertThatNullPointerException().isThrownBy(() -> new Route(vehicle, null, Collections.emptyList()));\n        assertThatNullPointerException().isThrownBy(() -> new Route(vehicle, depot, null));\n    }\n\n    @Test\n    void visits_should_not_contain_depot() {\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new Route(vehicle, depot, Arrays.asList(depot, visit1)))\n                .withMessageContaining(depot.toString());\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new Route(vehicle, depot, Arrays.asList(visit1, depot, visit2)))\n                .withMessageContaining(depot.toString());\n    }\n\n    @Test\n    void no_visit_should_be_visited_twice_by_the_same_vehicle() {\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new Route(vehicle, depot, Arrays.asList(visit1, visit1)))\n                .withMessageContaining(\"(1)\");\n    }\n\n    @Test\n    void cannot_modify_visits_externally() {\n        ArrayList<Location> visits = new ArrayList<>();\n        visits.add(visit1);\n        List<Location> routeVisits = new Route(vehicle, depot, visits).visits();\n\n        assertThatExceptionOfType(UnsupportedOperationException.class)\n                .isThrownBy(routeVisits::clear);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/RouteWithTrackTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static java.util.Collections.emptyList;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\n\nclass RouteWithTrackTest {\n\n    private final Vehicle vehicle = VehicleFactory.testVehicle(4);\n    private final Location depot = new Location(1, Coordinates.of(5, 5));\n    private final Location visit1 = new Location(2, Coordinates.of(5, 5));\n    private final Location visit2 = new Location(3, Coordinates.of(5, 5));\n\n    @Test\n    void constructor_args_not_null() {\n        Route route = new Route(vehicle, depot, emptyList());\n        assertThatNullPointerException().isThrownBy(() -> new RouteWithTrack(route, null));\n        assertThatNullPointerException().isThrownBy(() -> new RouteWithTrack(null, emptyList()));\n    }\n\n    @Test\n    void cannot_modify_track_externally() {\n        Route route = new Route(vehicle, depot, Arrays.asList(visit1, visit2));\n        ArrayList<List<Coordinates>> track = new ArrayList<>();\n        track.add(Arrays.asList(Coordinates.of(1.0, 2.0)));\n\n        List<List<Coordinates>> routeTrack = new RouteWithTrack(route, track).track();\n        assertThatExceptionOfType(UnsupportedOperationException.class)\n                .isThrownBy(routeTrack::clear);\n    }\n\n    @Test\n    void when_route_is_empty_track_must_be_empty() {\n        Route emptyRoute = new Route(vehicle, depot, emptyList());\n        ArrayList<List<Coordinates>> track = new ArrayList<>();\n        track.add(Arrays.asList(Coordinates.of(1.0, 2.0)));\n\n        assertThatIllegalArgumentException().isThrownBy(() -> new RouteWithTrack(emptyRoute, track));\n    }\n\n    @Test\n    void when_route_is_nonempty_track_must_be_nonempty() {\n        Route route = new Route(vehicle, depot, Arrays.asList(visit1, visit2));\n        ArrayList<List<Coordinates>> emptyTrack = new ArrayList<>();\n\n        assertThatIllegalArgumentException().isThrownBy(() -> new RouteWithTrack(route, emptyTrack));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/RoutingPlanTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\n\nclass RoutingPlanTest {\n\n    private final Distance distance = Distance.ofMillis(1);\n    private final Vehicle vehicle = VehicleFactory.testVehicle(1);\n    private final List<Vehicle> vehicles = singletonList(vehicle);\n    private final Location depot = new Location(1, Coordinates.of(5, 5));\n    private final Location visit = new Location(2, Coordinates.of(3, 3));\n    private final RouteWithTrack emptyRoute = new RouteWithTrack(new Route(vehicle, depot, emptyList()), emptyList());\n    // Track is not important (we don't check if track starts and ends in the depot and goes through all visits)\n    private final List<List<Coordinates>> nonEmptyTrack = singletonList(singletonList(Coordinates.of(5, 5)));\n\n    @Test\n    void constructor_args_not_null() {\n        assertThatNullPointerException()\n                .isThrownBy(() -> new RoutingPlan(null, vehicles, depot, emptyList(), emptyList()));\n        assertThatNullPointerException()\n                .isThrownBy(() -> new RoutingPlan(distance, null, depot, emptyList(), emptyList()));\n        assertThatNullPointerException()\n                .isThrownBy(() -> new RoutingPlan(distance, vehicles, depot, null, emptyList()));\n        assertThatNullPointerException()\n                .isThrownBy(() -> new RoutingPlan(distance, vehicles, depot, emptyList(), null));\n        // depot can be null\n        // TODO create a factory that will prevent passing a null depot accidentally\n    }\n\n    @Test\n    void no_visits_without_a_depot() {\n        List<List<Coordinates>> track = singletonList(singletonList(visit.coordinates()));\n        RouteWithTrack routeWithTrack = new RouteWithTrack(new Route(vehicle, depot, singletonList(visit)), track);\n        assertThatIllegalArgumentException().isThrownBy(() -> new RoutingPlan(\n                distance, vehicles, null, singletonList(visit), singletonList(routeWithTrack)));\n    }\n\n    @Test\n    void no_routes_without_a_depot() {\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new RoutingPlan(distance, vehicles, null, emptyList(), singletonList(emptyRoute)));\n    }\n\n    @Test\n    void there_must_be_one_route_per_vehicle_when_there_is_a_depot() {\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new RoutingPlan(distance, vehicles, depot, emptyList(), emptyList()))\n                .withMessageContaining(\"Vehicles (1): [\")\n                .withMessageContaining(\"Routes' vehicleIds (0): []\");\n    }\n\n    @Test\n    void routes_vehicle_references_must_be_consistent_with_vehicles_in_routing_plan() {\n        List<Vehicle> unexpectedVehicles = singletonList(VehicleFactory.testVehicle(vehicle.id() + 1));\n        List<RouteWithTrack> routes = singletonList(emptyRoute);\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> new RoutingPlan(distance, unexpectedVehicles, depot, emptyList(), routes))\n                .withMessageContaining(\"Vehicles (1): [\")\n                .withMessageContaining(\"Routes' vehicleIds (1): [\" + vehicle.id() + \"]\");\n    }\n\n    @Test\n    void routes_visit_references_must_be_consistent_with_visits_in_routing_plan() {\n        Vehicle vehicle1 = VehicleFactory.testVehicle(1);\n        Vehicle vehicle2 = VehicleFactory.testVehicle(2);\n\n        Location depot = new Location(100, Coordinates.of(0, 0), \"depot\");\n        Location visit1 = new Location(101, Coordinates.of(1, 1), \"visit1\");\n        Location visit2 = new Location(102, Coordinates.of(2, 2), \"visit2\");\n        Location visit3 = new Location(103, Coordinates.of(3, 3), \"visit3\");\n\n        assertThatCode(() -> new RoutingPlan(\n                distance,\n                emptyList(), // no vehicles\n                depot,\n                singletonList(visit1),\n                emptyList() // => no routes (no visits visited)\n        )).doesNotThrowAnyException();\n\n        assertThatIllegalArgumentException().isThrownBy(() -> new RoutingPlan(\n                distance,\n                singletonList(vehicle1),\n                depot,\n                asList(visit1, visit2),\n                singletonList(\n                        // visit3 extra\n                        new RouteWithTrack(new Route(vehicle1, depot, asList(visit1, visit2, visit3)), nonEmptyTrack))))\n                .withMessageContaining(visit3.toString());\n\n        Location visit4 = new Location(104, Coordinates.of(4, 4), \"visit4\");\n        assertThatIllegalArgumentException().isThrownBy(() -> new RoutingPlan(\n                distance,\n                asList(vehicle1, vehicle2),\n                depot,\n                asList(visit1, visit2, visit3),\n                // visit3 missing, visit4 extra\n                asList(\n                        new RouteWithTrack(new Route(vehicle1, depot, asList(visit1, visit4)), nonEmptyTrack),\n                        new RouteWithTrack(new Route(vehicle2, depot, singletonList(visit2)), nonEmptyTrack))))\n                .withMessageContaining(visit4.toString());\n    }\n\n    @Test\n    void cannot_modify_collections_externally() {\n        // Use modifiable collections as the input\n        ArrayList<Vehicle> vehicles = new ArrayList<>();\n        ArrayList<Location> visits = new ArrayList<>();\n        ArrayList<RouteWithTrack> routes = new ArrayList<>();\n        vehicles.add(vehicle);\n        visits.add(visit);\n        routes.add(new RouteWithTrack(new Route(vehicle, depot, singletonList(visit)), nonEmptyTrack));\n\n        RoutingPlan routingPlan = new RoutingPlan(distance, vehicles, depot, visits, routes);\n        List<Vehicle> planVehicles = routingPlan.vehicles();\n        List<Location> planVisits = routingPlan.visits();\n        List<RouteWithTrack> planRoutes = routingPlan.routes();\n\n        assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(planVehicles::clear);\n        assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(planVisits::clear);\n        assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(planRoutes::clear);\n    }\n\n    @Test\n    void empty_routing_plan_should_be_empty() {\n        RoutingPlan empty = RoutingPlan.empty();\n        assertThat(empty.distance()).isEqualTo(Distance.ZERO);\n        assertThat(empty.vehicles()).isEmpty();\n        assertThat(empty.depot()).isEmpty();\n        assertThat(empty.visits()).isEmpty();\n        assertThat(empty.routes()).isEmpty();\n    }\n\n    @Test\n    void isEmpty() {\n        assertThat(RoutingPlan.empty().isEmpty()).isTrue();\n        assertThat(new RoutingPlan(Distance.ZERO, emptyList(), depot, emptyList(), emptyList()).isEmpty()).isFalse();\n        assertThat(new RoutingPlan(Distance.ZERO, vehicles, null, emptyList(), emptyList()).isEmpty()).isFalse();\n        assertThat(new RoutingPlan(Distance.ZERO, vehicles, depot, emptyList(), singletonList(emptyRoute)).isEmpty())\n                .isFalse();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/VehicleDataTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\n\nclass VehicleDataTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new VehicleData(null, 1));\n    }\n\n    @Test\n    void vehicles_are_equal_if_they_have_same_properties() {\n        String name = \"vehicle name\";\n        int capacity = 20;\n\n        VehicleData vehicleData = new VehicleData(name, capacity);\n\n        assertThat(vehicleData)\n                // different name\n                .isNotEqualTo(new VehicleData(\"\", capacity))\n                // different capacity\n                .isNotEqualTo(new VehicleData(name, capacity + 1))\n                // null\n                .isNotEqualTo(null)\n                // different type with equal properties\n                .isNotEqualTo(new Vehicle(0, name, capacity))\n                // same object -> OK\n                .isEqualTo(vehicleData)\n                // same properties -> OK\n                .isEqualTo(new VehicleData(name, capacity));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/VehicleFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\n\nclass VehicleFactoryTest {\n\n    @Test\n    void createVehicle() {\n        long vehicleId = 4;\n        String name = \"Vehicle four\";\n        int capacity = 99;\n\n        Vehicle vehicle = VehicleFactory.createVehicle(vehicleId, name, capacity);\n\n        assertThat(vehicle.id()).isEqualTo(vehicleId);\n        assertThat(vehicle.name()).isEqualTo(name);\n        assertThat(vehicle.capacity()).isEqualTo(capacity);\n    }\n\n    @Test\n    void vehicleData() {\n        String name = \"vehicle name\";\n        int capacity = 1000;\n\n        VehicleData vehicleData = VehicleFactory.vehicleData(name, capacity);\n        assertThat(vehicleData.name()).isEqualTo(name);\n        assertThat(vehicleData.capacity()).isEqualTo(capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/domain/VehicleTest.java",
    "content": "package org.optaweb.vehiclerouting.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\n\nclass VehicleTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new Vehicle(0, null, 0));\n    }\n\n    @Test\n    void vehicles_are_identified_based_on_id() {\n        final long id = 0;\n        final String description = \"test description\";\n        final int capacity = 1;\n        final Vehicle vehicle = new Vehicle(id, description, capacity);\n\n        assertThat(vehicle)\n                // different ID\n                .isNotEqualTo(new Vehicle(id + 1, description, capacity))\n                // null\n                .isNotEqualTo(null)\n                // different class\n                .isNotEqualTo(id)\n                // same object -> OK\n                .isEqualTo(vehicle)\n                // same properties -> OK\n                .isEqualTo(new Vehicle(id, description, capacity))\n                // same ID, different description -> OK\n                .isEqualTo(new Vehicle(id, description + \"x\", capacity))\n                // same ID, different capacity -> OK\n                .isEqualTo(new Vehicle(id, description, capacity + 1));\n    }\n\n    @Test\n    void equal_vehicles_must_have_same_hashcode() {\n        long id = 1;\n        assertThat(new Vehicle(id, \"description 1\", 1))\n                .hasSameHashCodeAs(new Vehicle(id, \"description 2\", 2));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceEntityTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\n\nclass DistanceEntityTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        DistanceKey dKey = new DistanceKey(1, 2);\n        assertThatNullPointerException().isThrownBy(() -> new DistanceEntity(null, 100L));\n        assertThatNullPointerException().isThrownBy(() -> new DistanceEntity(dKey, null));\n    }\n\n    @Test\n    void equals() {\n        final long from = 10;\n        final long to = 2000;\n        final DistanceKey distanceKey = new DistanceKey(from, to);\n        final long distance = 50001;\n\n        DistanceEntity distanceEntity = new DistanceEntity(distanceKey, distance);\n        assertThat(distanceEntity)\n                .isEqualTo(distanceEntity)\n                .isEqualTo(new DistanceEntity(new DistanceKey(from, to), distance))\n                .isNotEqualTo(null)\n                .isNotEqualTo(distanceKey)\n                .isNotEqualTo(new DistanceEntity(distanceKey, distance + 1))\n                .isNotEqualTo(new DistanceEntity(new DistanceKey(to, from), distance));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceRepositoryImplTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\n\n@ExtendWith(MockitoExtension.class)\nclass DistanceRepositoryImplTest {\n\n    @Mock\n    private DistanceCrudRepository crudRepository;\n    @InjectMocks\n    private DistanceRepositoryImpl repository;\n    @Captor\n    private ArgumentCaptor<DistanceEntity> distanceEntityArgumentCaptor;\n\n    private final Location from = new Location(1, Coordinates.of(7, -4.0));\n    private final Location to = new Location(2, Coordinates.of(5, 9.0));\n\n    @Test\n    void should_save_distance() {\n        long distance = 956766417;\n        repository.saveDistance(from, to, Distance.ofMillis(distance));\n        verify(crudRepository).persist(distanceEntityArgumentCaptor.capture());\n        DistanceEntity distanceEntity = distanceEntityArgumentCaptor.getValue();\n        assertThat(distanceEntity.getDistance()).isEqualTo(distance);\n        assertThat(distanceEntity.getKey().getFromId()).isEqualTo(from.id());\n        assertThat(distanceEntity.getKey().getToId()).isEqualTo(to.id());\n    }\n\n    @Test\n    void should_return_distance_when_entity_is_found() {\n        DistanceKey distanceKey = new DistanceKey(from.id(), to.id());\n        long distance = 10305;\n        DistanceEntity distanceEntity = new DistanceEntity(distanceKey, distance);\n        when(crudRepository.findByIdOptional(distanceKey)).thenReturn(Optional.of(distanceEntity));\n        assertThat(repository.getDistance(from, to)).contains(Distance.ofMillis(distance));\n    }\n\n    @Test\n    void should_return_negative_number_when_distance_not_found() {\n        when(crudRepository.findByIdOptional(any(DistanceKey.class))).thenReturn(Optional.empty());\n        assertThat(repository.getDistance(from, to)).isEmpty();\n    }\n\n    @Test\n    void should_delete_distance_by_location_id() {\n        repository.deleteDistances(from);\n        verify(crudRepository).deleteByFromIdOrToId(from.id());\n    }\n\n    @Test\n    void should_delete_all_distances() {\n        repository.deleteAll();\n        verify(crudRepository).deleteAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/DistanceRepositoryIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\n\nimport io.quarkus.test.TestTransaction;\nimport io.quarkus.test.junit.QuarkusTest;\n\n@QuarkusTest\nclass DistanceRepositoryIntegrationTest {\n\n    @Inject\n    DistanceCrudRepository crudRepository;\n\n    private DistanceRepositoryImpl repository;\n\n    @BeforeEach\n    void setUp() {\n        repository = new DistanceRepositoryImpl(crudRepository);\n    }\n\n    @Test\n    @TestTransaction\n    void panache_repository_should_persist_and_delete_distances() {\n        DistanceKey key = new DistanceKey(1, 2);\n        DistanceEntity entity = new DistanceEntity(key, 730107L);\n\n        crudRepository.persist(entity);\n\n        assertThat(crudRepository.count()).isOne();\n        assertThat(crudRepository.findById(key)).isEqualTo(entity);\n\n        crudRepository.deleteById(key);\n        assertThat(crudRepository.count()).isZero();\n    }\n\n    static DistanceEntity distance(long fromId, long toId) {\n        return new DistanceEntity(new DistanceKey(fromId, toId), 1L);\n    }\n\n    @Test\n    @TestTransaction\n    void delete_by_fromId_or_toId() {\n        DistanceEntity distance23 = distance(2, 3);\n        DistanceEntity distance32 = distance(3, 2);\n\n        crudRepository.persist(distance(1, 2));\n        crudRepository.persist(distance(2, 1));\n        crudRepository.persist(distance23);\n        crudRepository.persist(distance32);\n        crudRepository.persist(distance(3, 1));\n        crudRepository.persist(distance(1, 3));\n\n        assertThat(crudRepository.count()).isEqualTo(6);\n        crudRepository.deleteByFromIdOrToId(1L);\n        assertThat(crudRepository.count()).isEqualTo(2);\n        assertThat(crudRepository.findAll().stream()).containsExactly(distance23, distance32);\n    }\n\n    @Test\n    @TestTransaction\n    void should_return_saved_distance() {\n        Location location1 = new Location(1, Coordinates.of(7, -4.0));\n        Location location2 = new Location(2, Coordinates.of(5, 9.0));\n\n        Distance distance = Distance.ofMillis(956766417);\n        repository.saveDistance(location1, location2, distance);\n        assertThat(repository.getDistance(location1, location2)).contains(distance);\n    }\n\n    @Test\n    void should_return_negative_number_when_distance_not_found() {\n        Location location1 = new Location(1, Coordinates.of(7, -4.0));\n        Location location2 = new Location(2, Coordinates.of(5, 9.0));\n\n        assertThat(repository.getDistance(location1, location2)).isEmpty();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/LocationEntityTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\n\nclass LocationEntityTest {\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new LocationEntity(0, null, BigDecimal.ZERO, \"\"));\n        assertThatNullPointerException().isThrownBy(() -> new LocationEntity(0, BigDecimal.ZERO, null, \"\"));\n        assertThatNullPointerException().isThrownBy(() -> new LocationEntity(0, BigDecimal.ZERO, BigDecimal.ONE, null));\n    }\n\n    @Test\n    void getters() {\n        int id = 10;\n        BigDecimal latitude = BigDecimal.valueOf(0.101);\n        BigDecimal longitude = BigDecimal.valueOf(101.0);\n        String description = \"Description.\";\n        LocationEntity locationEntity = new LocationEntity(id, latitude, longitude, description);\n        assertThat(locationEntity.getId()).isEqualTo(id);\n        assertThat(locationEntity.getLongitude()).isEqualTo(longitude);\n        assertThat(locationEntity.getLatitude()).isEqualTo(latitude);\n        assertThat(locationEntity.getDescription()).isEqualTo(description);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/LocationRepositoryImplTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\n\n@ExtendWith(MockitoExtension.class)\nclass LocationRepositoryImplTest {\n\n    @Mock\n    private LocationCrudRepository crudRepository;\n    @InjectMocks\n    private LocationRepositoryImpl repository;\n    @Captor\n    private ArgumentCaptor<LocationEntity> locationEntityCaptor;\n\n    private final Location testLocation = new Location(76, Coordinates.of(1.2, 3.4), \"description\");\n\n    private static LocationEntity locationEntity(Location location) {\n        return new LocationEntity(\n                location.id(),\n                location.coordinates().latitude(),\n                location.coordinates().longitude(),\n                location.description());\n    }\n\n    @Test\n    void should_create_location() {\n        // arrange\n        Coordinates savedCoordinates = Coordinates.of(0.00213, 32.777);\n        String savedDescription = \"new location\";\n\n        // act\n        Location newLocation = repository.createLocation(savedCoordinates, savedDescription);\n\n        // assert\n        // -- the correct values were used to save the entity\n        verify(crudRepository).persist(locationEntityCaptor.capture());\n        LocationEntity savedLocation = locationEntityCaptor.getValue();\n        assertThat(savedLocation.getLatitude()).isEqualTo(savedCoordinates.latitude());\n        assertThat(savedLocation.getLongitude()).isEqualTo(savedCoordinates.longitude());\n        assertThat(savedLocation.getDescription()).isEqualTo(savedDescription);\n\n        // -- created domain location has the expected values\n        assertThat(newLocation.coordinates()).isEqualTo(savedCoordinates);\n        assertThat(newLocation.description()).isEqualTo(savedDescription);\n    }\n\n    @Test\n    void remove_created_location_by_id() {\n        LocationEntity locationEntity = locationEntity(testLocation);\n        final long id = testLocation.id();\n        when(crudRepository.findByIdOptional(id)).thenReturn(Optional.of(locationEntity));\n\n        Location removed = repository.removeLocation(id);\n        assertThat(removed).isEqualTo(testLocation);\n        verify(crudRepository).deleteById(id);\n    }\n\n    @Test\n    void removing_nonexistent_location_should_fail() {\n        when(crudRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty());\n\n        // removing nonexistent location should fail and its ID should appear in the exception message\n        int uniqueNonexistentId = 7173;\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> repository.removeLocation(uniqueNonexistentId))\n                .withMessageContaining(String.valueOf(uniqueNonexistentId));\n    }\n\n    @Test\n    void remove_all_locations() {\n        repository.removeAll();\n        verify(crudRepository).deleteAll();\n    }\n\n    @Test\n    void get_all_locations() {\n        LocationEntity locationEntity = locationEntity(testLocation);\n        when(crudRepository.streamAll()).thenReturn(Stream.of(locationEntity));\n        assertThat(repository.locations()).containsExactly(testLocation);\n    }\n\n    @Test\n    void find_by_id() {\n        LocationEntity locationEntity = locationEntity(testLocation);\n        when(crudRepository.findByIdOptional(testLocation.id())).thenReturn(Optional.of(locationEntity));\n        assertThat(repository.find(testLocation.id())).contains(testLocation);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/LocationRepositoryIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\nimport java.math.BigDecimal;\n\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\n\nimport io.quarkus.test.TestTransaction;\nimport io.quarkus.test.junit.QuarkusTest;\n\n@QuarkusTest\nclass LocationRepositoryIntegrationTest {\n\n    @Inject\n    LocationCrudRepository crudRepository;\n    private LocationRepositoryImpl repository;\n\n    @BeforeEach\n    void setUp() {\n        repository = new LocationRepositoryImpl(crudRepository);\n    }\n\n    @Test\n    @TestTransaction\n    void db_schema() {\n        // https://wiki.openstreetmap.org/wiki/Node#Structure\n        final BigDecimal maxLatitude = new BigDecimal(\"90.0000000\");\n        final BigDecimal maxLongitude = new BigDecimal(\"214.7483647\");\n        final BigDecimal minLatitude = maxLatitude.negate();\n        final BigDecimal minLongitude = maxLongitude.negate();\n        final String description = \"...\";\n\n        LocationEntity minLocation = new LocationEntity(0, minLatitude, minLongitude, description);\n        LocationEntity maxLocation = new LocationEntity(0, maxLatitude, maxLongitude, description);\n        crudRepository.persist(minLocation);\n        crudRepository.persist(maxLocation);\n        assertThat(minLocation.getId()).isNotZero();\n        assertThat(maxLocation.getId()).isNotZero();\n\n        assertThat(crudRepository.findById(minLocation.getId())).isEqualTo(minLocation);\n        assertThat(crudRepository.findById(maxLocation.getId())).isEqualTo(maxLocation);\n    }\n\n    @Test\n    @TestTransaction\n    void remove_created_location() {\n        Coordinates coordinates = Coordinates.of(0.00213, 32.777);\n        assertThat(crudRepository.count()).isZero();\n        Location location = repository.createLocation(coordinates, \"\");\n        assertThat(location.coordinates()).isEqualTo(coordinates);\n        assertThat(crudRepository.count()).isOne();\n\n        Location removed = repository.removeLocation(location.id());\n        assertThat(removed).isEqualTo(location);\n\n        // removing the same location twice should fail\n        assertThatIllegalArgumentException().isThrownBy(() -> repository.removeLocation(location.id()));\n\n        // removing nonexistent location should fail and its ID should appear in the exception message\n        int uniqueNonexistentId = 7173;\n        assertThatIllegalArgumentException().isThrownBy(() -> repository.removeLocation(uniqueNonexistentId))\n                .withMessageContaining(String.valueOf(uniqueNonexistentId));\n    }\n\n    @Test\n    @TestTransaction\n    void get_and_remove_all_locations() {\n        int locationCount = 8;\n        for (int i = 0; i < locationCount; i++) {\n            repository.createLocation(Coordinates.of(1.0, i / 100.0), \"\");\n        }\n\n        assertThat(crudRepository.count()).isEqualTo(locationCount);\n\n        // get a sample location entity from the repository\n        LocationEntity testEntity = crudRepository\n                .findByIdOptional((long) locationCount)\n                .orElseThrow(IllegalStateException::new);\n        Location testLocation = new Location(\n                testEntity.getId(),\n                new Coordinates(testEntity.getLatitude(), testEntity.getLongitude()));\n\n        assertThat(repository.locations())\n                .hasSize(locationCount)\n                .contains(testLocation);\n\n        repository.removeAll();\n        assertThat(crudRepository.count()).isZero();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/VehicleEntityTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\n\nclass VehicleEntityTest {\n\n    @Test\n    void getters() {\n        long id = 321;\n        String name = \"Vehicle XY\";\n        int capacity = 11;\n        VehicleEntity vehicleEntity = new VehicleEntity(id, name, capacity);\n        assertThat(vehicleEntity.getId()).isEqualTo(id);\n        assertThat(vehicleEntity.getName()).isEqualTo(name);\n        assertThat(vehicleEntity.getCapacity()).isEqualTo(capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/persistence/VehicleRepositoryImplTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.persistence;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\n@ExtendWith(MockitoExtension.class)\nclass VehicleRepositoryImplTest {\n\n    @Mock\n    private VehicleCrudRepository crudRepository;\n    @InjectMocks\n    private VehicleRepositoryImpl repository;\n    @Captor\n    private ArgumentCaptor<VehicleEntity> vehicleEntityCaptor;\n\n    private final Vehicle testVehicle = VehicleFactory.createVehicle(19, \"vehicle name\", 1100);\n\n    private static VehicleEntity vehicleEntity(Vehicle vehicle) {\n        return new VehicleEntity(vehicle.id(), vehicle.name(), vehicle.capacity());\n    }\n\n    @Test\n    void should_create_vehicle() {\n        // arrange\n        int savedCapacity = 1;\n\n        // act\n        Vehicle newVehicle = repository.createVehicle(savedCapacity);\n\n        // assert\n        // -- the correct values were used to save the entity\n        verify(crudRepository).persist(vehicleEntityCaptor.capture());\n        VehicleEntity savedVehicle = vehicleEntityCaptor.getValue();\n        assertThat(savedVehicle.getName()).isNotNull();\n        assertThat(savedVehicle.getCapacity()).isEqualTo(savedCapacity);\n\n        // -- created domain vehicle has the expected values\n        assertThat(newVehicle.name()).isNotNull();\n        assertThat(newVehicle.capacity()).isEqualTo(savedCapacity);\n    }\n\n    @Test\n    void create_vehicle_from_given_data() {\n        // arrange\n        String name = \"x\";\n        int capacity = 111;\n        VehicleData vehicleData = VehicleFactory.vehicleData(name, capacity);\n\n        // act\n        Vehicle newVehicle = repository.createVehicle(vehicleData);\n\n        // assert\n        assertThat(newVehicle.name()).isEqualTo(name);\n        assertThat(newVehicle.capacity()).isEqualTo(capacity);\n    }\n\n    @Test\n    void remove_created_vehicle_by_id() {\n        VehicleEntity vehicleEntity = vehicleEntity(testVehicle);\n        final long id = testVehicle.id();\n        when(crudRepository.findByIdOptional(id)).thenReturn(Optional.of(vehicleEntity));\n\n        Vehicle removed = repository.removeVehicle(id);\n        assertThat(removed).isEqualTo(testVehicle);\n        verify(crudRepository).deleteById(id);\n    }\n\n    @Test\n    void removing_nonexistent_vehicle_should_fail() {\n        when(crudRepository.findByIdOptional(anyLong())).thenReturn(Optional.empty());\n\n        // removing nonexistent vehicle should fail and its ID should appear in the exception message\n        int uniqueNonexistentId = 7173;\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> repository.removeVehicle(uniqueNonexistentId))\n                .withMessageContaining(String.valueOf(uniqueNonexistentId));\n    }\n\n    @Test\n    void remove_all_vehicles() {\n        repository.removeAll();\n        verify(crudRepository).deleteAll();\n    }\n\n    @Test\n    void get_all_vehicles() {\n        VehicleEntity vehicleEntity = vehicleEntity(testVehicle);\n        when(crudRepository.streamAll()).thenReturn(Stream.of(vehicleEntity));\n        assertThat(repository.vehicles()).containsExactly(testVehicle);\n    }\n\n    @Test\n    void find_by_id() {\n        VehicleEntity vehicleEntity = vehicleEntity(testVehicle);\n        when(crudRepository.findByIdOptional(testVehicle.id())).thenReturn(Optional.of(vehicleEntity));\n        assertThat(repository.find(testVehicle.id())).contains(testVehicle);\n    }\n\n    @Test\n    void update() {\n        long vehicleId = 123;\n        String name = \"xy\";\n        int capacity = 80;\n\n        VehicleEntity vehicleEntity = new VehicleEntity(vehicleId, name, capacity - 10);\n        when(crudRepository.findByIdOptional(vehicleId)).thenReturn(Optional.of(vehicleEntity));\n\n        Vehicle vehicle = repository.changeCapacity(vehicleId, capacity);\n        verify(crudRepository).flush();\n\n        assertThat(vehicleEntity.getCapacity()).isEqualTo(capacity);\n\n        assertThat(vehicle.id()).isEqualTo(vehicleId);\n        assertThat(vehicle.name()).isEqualTo(name);\n        assertThat(vehicle.capacity()).isEqualTo(capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/DistanceMapImplTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\n\nclass DistanceMapImplTest {\n\n    @Test\n    void matrix_row_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new DistanceMapImpl(null));\n    }\n\n    @Test\n    void distance_map_should_return_value_from_distance_matrix_row() {\n        PlanningLocation location2 = PlanningLocationFactory.testLocation(2);\n        Distance distance = Distance.ofMillis(45000);\n        DistanceMatrixRow matrixRow = locationId -> distance;\n        DistanceMapImpl distanceMap = new DistanceMapImpl(matrixRow);\n        assertThat(distanceMap.distanceTo(location2)).isEqualTo(distance.millis());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/MockSolver.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.same;\nimport static org.mockito.Mockito.verify;\n\nimport org.mockito.Mockito;\nimport org.optaplanner.core.api.solver.change.ProblemChange;\nimport org.optaplanner.test.api.solver.change.MockProblemChangeDirector;\n\npublic class MockSolver<Solution_> {\n\n    private final Solution_ workingSolution;\n    private final MockProblemChangeDirector changeDirector;\n\n    public static <Solution_> MockSolver<Solution_> build(Solution_ solution) {\n        MockProblemChangeDirector spy = Mockito.spy(new MockProblemChangeDirector());\n        return new MockSolver<>(solution, spy);\n    }\n\n    private MockSolver(Solution_ workingSolution, MockProblemChangeDirector changeDirector) {\n        this.workingSolution = workingSolution;\n        this.changeDirector = changeDirector;\n    }\n\n    // ************************************************************************\n    // Problem change API from Solver.\n    // ************************************************************************\n\n    public void addProblemChange(ProblemChange<Solution_> problemChange) {\n        problemChange.doChange(workingSolution, changeDirector);\n    }\n\n    // ************************************************************************\n    // Lookup API from MockProblemChangeDirector.\n    // ************************************************************************\n\n    public MockProblemChangeDirector.LookUpMockBuilder whenLookingUp(Object forObject) {\n        return changeDirector.whenLookingUp(forObject);\n    }\n\n    // ************************************************************************\n    // Simplified verification API.\n    // ************************************************************************\n\n    public void verifyEntityAdded(Object entity) {\n        verify(changeDirector).addEntity(same(entity), any());\n    }\n\n    public void verifyEntityRemoved(Object entity) {\n        verify(changeDirector).removeEntity(same(entity), any());\n    }\n\n    public void verifyVariableChanged(Object entity, String variableName) {\n        verify(changeDirector).changeVariable(same(entity), eq(variableName), any());\n    }\n\n    public void verifyProblemFactAdded(Object fact) {\n        verify(changeDirector).addProblemFact(same(fact), any());\n    }\n\n    public void verifyProblemFactRemoved(Object fact) {\n        verify(changeDirector).removeProblemFact(same(fact), any());\n    }\n\n    public void verifyProblemPropertyChanged(Object entityOrFact) {\n        verify(changeDirector).changeProblemProperty(same(entityOrFact), any());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/RouteChangedEventPublisherTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.verify;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.testLocation;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory.testVehicle;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory.solutionFromVisits;\n\nimport javax.enterprise.event.Event;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.route.RouteChangedEvent;\nimport org.optaweb.vehiclerouting.service.route.ShallowRoute;\n\n@ExtendWith(MockitoExtension.class)\nclass RouteChangedEventPublisherTest {\n\n    @Mock\n    private Event<RouteChangedEvent> publisher;\n    @InjectMocks\n    private RouteChangedEventPublisher routeChangedEventPublisher;\n\n    @Test\n    void should_covert_solution_to_event_and_publish_it() {\n        routeChangedEventPublisher.publishSolution(SolutionFactory.emptySolution());\n        verify(publisher).fire(any(RouteChangedEvent.class));\n    }\n\n    @Test\n    void empty_solution_should_have_zero_routes_vehicles_etc() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n\n        RouteChangedEvent event = RouteChangedEventPublisher.solutionToEvent(solution, this);\n\n        assertThat(event.vehicleIds()).isEmpty();\n        assertThat(event.depotId()).isEmpty();\n        assertThat(event.visitIds()).isEmpty();\n        assertThat(event.routes()).isEmpty();\n        assertThat(event.distance()).isEqualTo(Distance.ZERO);\n    }\n\n    @Test\n    void solution_with_vehicles_and_no_depot_should_have_zero_routes() {\n        long vehicleId = 1;\n        PlanningVehicle vehicle = testVehicle(vehicleId);\n        VehicleRoutingSolution solution = solutionFromVisits(singletonList(vehicle), null, emptyList());\n\n        RouteChangedEvent event = RouteChangedEventPublisher.solutionToEvent(solution, this);\n\n        assertThat(event.vehicleIds()).containsExactly(vehicleId);\n        assertThat(event.depotId()).isEmpty();\n        assertThat(event.visitIds()).isEmpty();\n        assertThat(event.routes()).isEmpty();\n        assertThat(event.distance()).isEqualTo(Distance.ZERO);\n    }\n\n    @Test\n    void nonempty_solution_without_vehicles_should_have_zero_routes_but_contain_visits() {\n        long depotId = 1;\n        long visitId = 2;\n        VehicleRoutingSolution solution = solutionFromVisits(\n                emptyList(),\n                new PlanningDepot(testLocation(depotId)),\n                singletonList(testVisit(visitId)));\n\n        RouteChangedEvent event = RouteChangedEventPublisher.solutionToEvent(solution, this);\n\n        assertThat(event.vehicleIds()).isEmpty();\n        assertThat(event.depotId()).contains(depotId);\n        assertThat(event.visitIds()).containsExactly(visitId);\n        assertThat(event.routes()).isEmpty();\n        assertThat(event.distance()).isEqualTo(Distance.ZERO);\n    }\n\n    @Test\n    void initialized_solution_should_have_one_route_per_vehicle() {\n        // arrange\n        long vehicleId1 = 1001;\n        long vehicleId2 = 2001;\n        PlanningVehicle vehicle1 = testVehicle(vehicleId1);\n        PlanningVehicle vehicle2 = testVehicle(vehicleId2);\n\n        long depotId = 1;\n        long visitId1 = 2;\n        long visitId2 = 3;\n        PlanningDepot depot = new PlanningDepot(testLocation(depotId));\n        PlanningVisit visit1 = testVisit(visitId1);\n        PlanningVisit visit2 = testVisit(visitId2);\n\n        VehicleRoutingSolution solution = solutionFromVisits(\n                asList(vehicle1, vehicle2),\n                depot,\n                asList(visit1, visit2));\n\n        // Send both vehicles to both visits\n        // V1\n        //   \\\n        //    |---> visit1 ---> visit2\n        //   /\n        // V2\n        for (PlanningVehicle vehicle : solution.getVehicleList()) {\n            vehicle.setNextVisit(visit1);\n            visit1.setPreviousStandstill(vehicle);\n        }\n        visit1.setNextVisit(visit2);\n        visit2.setPreviousStandstill(visit1);\n\n        long softScore = -544564731;\n        solution.setScore(HardSoftLongScore.ofSoft(softScore));\n\n        // act\n        RouteChangedEvent event = RouteChangedEventPublisher.solutionToEvent(solution, this);\n\n        // assert\n        assertThat(event.routes()).hasSameSizeAs(solution.getVehicleList());\n        assertThat(event.routes().stream().mapToLong(value -> value.vehicleId))\n                .containsExactlyInAnyOrder(vehicleId1, vehicleId2);\n\n        for (ShallowRoute route : event.routes()) {\n            assertThat(route.depotId).isEqualTo(depot.getId());\n            // visits shouldn't include the depot\n            assertThat(route.visitIds).containsExactly(visitId1, visitId2);\n        }\n\n        assertThat(event.vehicleIds()).containsExactlyInAnyOrder(vehicleId1, vehicleId2);\n        assertThat(event.depotId()).contains(depotId);\n        assertThat(event.visitIds()).containsExactlyInAnyOrder(visitId1, visitId2);\n        assertThat(event.distance()).isEqualTo(Distance.ofMillis(-softScore));\n    }\n\n    @Test\n    void fail_fast_if_vehicles_next_visit_doesnt_exist() {\n        PlanningVehicle vehicle = testVehicle(1);\n        vehicle.setNextVisit(testVisit(2));\n\n        VehicleRoutingSolution solution = solutionFromVisits(\n                singletonList(vehicle),\n                new PlanningDepot(testLocation(1)),\n                singletonList(testVisit(3)));\n\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> RouteChangedEventPublisher.solutionToEvent(solution, this))\n                .withMessageContaining(\"Visit\");\n    }\n\n    @Test\n    void vehicle_without_a_depot_is_illegal_if_depot_exists() {\n        PlanningDepot depot = new PlanningDepot(testLocation(1));\n        PlanningVehicle vehicle = testVehicle(1);\n        VehicleRoutingSolution solution = solutionFromVisits(singletonList(vehicle), depot, emptyList());\n        vehicle.setDepot(null);\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> RouteChangedEventPublisher.solutionToEvent(solution, this))\n                .withMessageContaining(\"Vehicle\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/RouteOptimizerImplTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.optaweb.vehiclerouting.domain.VehicleFactory.createVehicle;\nimport static org.optaweb.vehiclerouting.domain.VehicleFactory.testVehicle;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.fromDomain;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\n\n@ExtendWith(MockitoExtension.class)\nclass RouteOptimizerImplTest {\n\n    private final DistanceMatrixRow matrixRow = locationId -> Distance.ZERO;\n    private final Location location1 = new Location(1, Coordinates.of(1.0, 0.1));\n    private final Location location2 = new Location(2, Coordinates.of(0.2, 2.2));\n    private final Location location3 = new Location(3, Coordinates.of(3.4, 5.6));\n\n    @Captor\n    private ArgumentCaptor<VehicleRoutingSolution> solutionArgumentCaptor;\n    @Captor\n    private ArgumentCaptor<PlanningVehicle> vehicleArgumentCaptor;\n\n    @Mock\n    private SolverManager solverManager;\n    @Mock\n    private RouteChangedEventPublisher routeChangedEventPublisher;\n    @InjectMocks\n    private RouteOptimizerImpl routeOptimizer;\n\n    @Test\n    void solution_with_depot_and_no_visits_should_be_published() {\n        // arrange\n        Long[] vehicleIds = { 2L, 3L, 5L, 7L, 11L };\n        Arrays.stream(vehicleIds).forEach(vehicleId -> routeOptimizer.addVehicle(testVehicle(vehicleId)));\n        clearInvocations(routeChangedEventPublisher);\n\n        // act\n        routeOptimizer.addLocation(location1, matrixRow);\n\n        // assert\n        verifyNoInteractions(solverManager);\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getVehicleList())\n                .extracting(PlanningVehicle::getId)\n                .containsExactlyInAnyOrder(vehicleIds);\n        assertThat(solution.getDepotList()).extracting(PlanningDepot::getId).containsExactly(location1.id());\n        assertThat(solution.getVisitList()).isEmpty();\n    }\n\n    @Test\n    void solution_with_vehicles_and_no_depot_should_be_published() {\n        // arrange\n        final long vehicleId = 7;\n        final Vehicle vehicle = testVehicle(vehicleId);\n\n        // act 1\n        routeOptimizer.addVehicle(vehicle);\n\n        // assert 1\n        verifyNoInteractions(solverManager);\n        VehicleRoutingSolution solutionWithOneVehicle = verifyPublishingPreliminarySolution();\n        assertThat(solutionWithOneVehicle.getVehicleList())\n                .extracting(PlanningVehicle::getId)\n                .containsExactly(vehicleId);\n        assertThat(solutionWithOneVehicle.getDepotList()).isEmpty();\n        assertThat(solutionWithOneVehicle.getVisitList()).isEmpty();\n\n        // act 2\n        clearInvocations(routeChangedEventPublisher);\n        routeOptimizer.removeVehicle(vehicle);\n\n        // assert 2\n        verifyNoInteractions(solverManager);\n        VehicleRoutingSolution emptySolution = verifyPublishingPreliminarySolution();\n        assertThat(emptySolution.getVehicleList()).isEmpty();\n        assertThat(emptySolution.getDepotList()).isEmpty();\n        assertThat(emptySolution.getVisitList()).isEmpty();\n    }\n\n    @Test\n    void removing_wrong_vehicle_should_fail_fast() {\n        // arrange\n        final long vehicleId = 7;\n        final Vehicle vehicle = testVehicle(vehicleId);\n        final Vehicle nonExistentVehicle = testVehicle(vehicleId + 1);\n        routeOptimizer.addVehicle(vehicle);\n\n        // act & assert\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> routeOptimizer.removeVehicle(nonExistentVehicle))\n                .withMessageContaining(\"exist\");\n    }\n\n    @Test\n    void removing_wrong_location_should_fail_fast() {\n        // no locations\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> routeOptimizer.removeLocation(location1))\n                .withMessageContaining(\"no locations\");\n\n        // only depot\n        routeOptimizer.addLocation(location1, matrixRow);\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> routeOptimizer.removeLocation(location3))\n                .withMessageContaining(\"exist\");\n\n        // depot and a visit\n        routeOptimizer.addLocation(location2, matrixRow);\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> routeOptimizer.removeLocation(location3))\n                .withMessageContaining(\"exist\");\n    }\n\n    @Test\n    void added_vehicle_should_be_moved_to_the_depot_even_if_solver_is_not_yet_solving() {\n        // arrange\n        // -- depot\n        routeOptimizer.addLocation(location1, matrixRow);\n        // -- vehicles\n        routeOptimizer.addVehicle(testVehicle(7));\n        routeOptimizer.addVehicle(testVehicle(8));\n\n        // act\n        // -- first visit\n        routeOptimizer.addLocation(location2, matrixRow);\n\n        // assert\n        VehicleRoutingSolution solution = verifySolverStartedWithSolution();\n        assertThat(solution.getVehicleList())\n                .hasSize(2)\n                .allMatch(vehicle -> vehicle.getDepot().getId() == location1.id());\n\n        assertThat(solution.getDepotList()).isNotEmpty();\n        assertThat(solution.getVisitList()).isNotEmpty();\n    }\n\n    @Test\n    void solver_should_start_when_vehicle_is_added_and_there_is_at_least_one_visit() {\n        // arrange\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verifyNoInteractions(solverManager);\n\n        // act\n        routeOptimizer.addVehicle(testVehicle(9));\n\n        // assert\n        VehicleRoutingSolution solution = verifySolverStartedWithSolution();\n        assertThat(solution.getVehicleList()).hasSize(1);\n        assertThat(solution.getVisitList()).hasSize(1);\n    }\n\n    @Test\n    void each_location_should_have_a_distance_map_after_it_is_added() {\n        long millis = 8079;\n        routeOptimizer.addLocation(location1, locationId -> Distance.ofMillis(millis));\n\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getDepotList()).hasSize(1);\n        assertThat(solution.getDepotList().get(0).getLocation().distanceTo(fromDomain(location2))).isEqualTo(millis);\n    }\n\n    @Test\n    void solver_should_start_when_two_locations_added_and_there_is_at_least_one_vehicle() {\n        // add 1 vehicle, 2 locations\n        routeOptimizer.addVehicle(testVehicle(1));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n\n        // solving has started after adding a second location (=> depot + visit)\n        VehicleRoutingSolution solution = verifySolverStartedWithSolution();\n\n        assertThat(solution.getDepotList()).hasSize(1);\n        assertThat(solution.getDepotList().get(0).getLocation().getId()).isEqualTo(location1.id());\n        assertThat(solution.getVisitList()).hasSize(1);\n        assertThat(solution.getVisitList().get(0).getLocation().getId()).isEqualTo(location2.id());\n    }\n\n    @Test\n    void solver_should_not_start_nor_stop_when_modifying_location_and_there_are_no_vehicles() {\n        // add 2 locations\n        routeOptimizer.addLocation(location1, matrixRow);\n        clearInvocations(routeChangedEventPublisher);\n        routeOptimizer.addLocation(location2, matrixRow);\n\n        // solving did not start due to missing vehicles\n        verify(solverManager, never()).startSolver(any());\n        // but preliminary solution is published\n        VehicleRoutingSolution solution1 = verifyPublishingPreliminarySolution();\n        assertThat(solution1.getVehicleList()).isEmpty();\n        assertThat(solution1.getDepotList()).hasSize(1);\n        assertThat(solution1.getVisitList()).hasSize(1);\n\n        // add a third location and remove another one\n        routeOptimizer.addLocation(location3, matrixRow);\n        clearInvocations(routeChangedEventPublisher);\n        routeOptimizer.removeLocation(location2);\n\n        // no interactions with solver (start/stop/problem fact changes) because\n        // it hasn't started (due to missing vehicles)\n        verifyNoInteractions(solverManager);\n        // but preliminary solution is published\n        VehicleRoutingSolution solution2 = verifyPublishingPreliminarySolution();\n        assertThat(solution2.getVehicleList()).isEmpty();\n        assertThat(solution1.getDepotList()).hasSize(1);\n        assertThat(solution1.getVisitList()).hasSize(1);\n    }\n\n    @Test\n    void solver_should_stop_and_publish_when_last_vehicle_is_removed() {\n        Vehicle vehicle = testVehicle(23);\n        routeOptimizer.addVehicle(vehicle);\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n        clearInvocations(routeChangedEventPublisher);\n\n        routeOptimizer.removeVehicle(vehicle);\n        verify(solverManager).stopSolver();\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getVehicleList()).isEmpty();\n    }\n\n    @Test\n    void solver_should_stop_when_locations_reduced_to_one() {\n        // add 1 vehicle, 2 locations\n        routeOptimizer.addVehicle(testVehicle(0));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n        clearInvocations(routeChangedEventPublisher);\n\n        // remove 1 location from running solver\n        routeOptimizer.removeLocation(location2);\n\n        verify(solverManager).stopSolver();\n\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getVisitList()).isEmpty();\n        assertThat(solution.getDepotList()).hasSize(1);\n        assertThat(solution.getVehicleList()).hasSize(1);\n    }\n\n    @Test\n    void removing_depot_impossible_when_there_are_other_locations() {\n        routeOptimizer.addVehicle(testVehicle(0));\n        // add 2 locations\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n\n        assertThatIllegalStateException()\n                .isThrownBy(() -> routeOptimizer.removeLocation(location1))\n                .withMessageContaining(\"depot\");\n    }\n\n    @Test\n    void when_depot_is_added_all_vehicles_should_be_moved_to_it() {\n        // given 2 vehicles\n        long vehicleId1 = 8;\n        long vehicleId2 = 113;\n        routeOptimizer.addVehicle(testVehicle(vehicleId1));\n        routeOptimizer.addVehicle(testVehicle(vehicleId2));\n        clearInvocations(routeChangedEventPublisher);\n\n        // when a depot is added\n        routeOptimizer.addLocation(location1, matrixRow);\n\n        // then all vehicles must be in the depot\n        VehicleRoutingSolution solution1 = verifyPublishingPreliminarySolution();\n        assertThat(solution1.getVehicleList())\n                .extracting(PlanningVehicle::getId)\n                .containsExactlyInAnyOrder(vehicleId1, vehicleId2);\n        assertThat(solution1.getVehicleList()).allMatch(vehicle -> vehicle.getDepot().getId() == location1.id());\n        assertThat(solution1.getDepotList()).extracting(PlanningDepot::getId).containsExactly(location1.id());\n\n        // if we remove the depot\n        clearInvocations(routeChangedEventPublisher);\n        routeOptimizer.removeLocation(location1);\n\n        // then published solution's depot list is empty\n        VehicleRoutingSolution solution2 = verifyPublishingPreliminarySolution();\n        assertThat(solution2.getVehicleList())\n                .extracting(PlanningVehicle::getId)\n                .containsExactlyInAnyOrder(vehicleId1, vehicleId2);\n        assertThat(solution2.getDepotList()).isEmpty();\n\n        // and it's possible to add a new depot\n        routeOptimizer.addLocation(location2, matrixRow);\n    }\n\n    @Test\n    void adding_location_to_running_solver_must_happen_through_problem_fact_change() {\n        // arrange\n        routeOptimizer.addVehicle(testVehicle(55));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n        // act\n        routeOptimizer.addLocation(location3, matrixRow);\n        // assert\n        verify(solverManager).addVisit(any(PlanningVisit.class));\n    }\n\n    @Test\n    void removing_location_from_solver_with_more_than_two_locations_must_happen_through_problem_fact_change() {\n        // arrange: set up a situation where solver is running with 1 depot and 2 visits\n        long vehicleId = 0;\n        routeOptimizer.addVehicle(testVehicle(vehicleId));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n\n        // add second visit to avoid stopping solver manager after removing a visit below\n        routeOptimizer.addLocation(location3, matrixRow);\n        verify(solverManager).addVisit(any(PlanningVisit.class));\n\n        // act\n        routeOptimizer.removeLocation(location2);\n\n        // assert\n        ArgumentCaptor<PlanningVisit> visitArgumentCaptor = ArgumentCaptor.forClass(PlanningVisit.class);\n        verify(solverManager).removeVisit(visitArgumentCaptor.capture());\n        assertThat(visitArgumentCaptor.getValue().getId()).isEqualTo(location2.id());\n        // solver still running\n        verify(solverManager, never()).stopSolver();\n    }\n\n    @Test\n    void adding_vehicle_to_running_solver_must_happen_through_problem_fact_change() {\n        // arrange\n        routeOptimizer.addVehicle(testVehicle(1));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n\n        // act\n        routeOptimizer.addVehicle(testVehicle(22));\n\n        // assert\n        verify(solverManager).addVehicle(vehicleArgumentCaptor.capture());\n        PlanningVehicle vehicle = vehicleArgumentCaptor.getValue();\n        assertThat(vehicle.getDepot().getId()).isEqualTo(location1.id());\n    }\n\n    @Test\n    void removing_vehicle_from_running_solver_with_more_than_one_vehicle_must_happen_through_problem_fact_change() {\n        // arrange: set up a situation where solver is running with 2 vehicles\n        final long vehicleId1 = 10;\n        final long vehicleId2 = 20;\n        routeOptimizer.addVehicle(testVehicle(vehicleId1));\n        routeOptimizer.addVehicle(testVehicle(vehicleId2));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n\n        // act\n        routeOptimizer.removeVehicle(testVehicle(vehicleId1));\n\n        // assert\n        verify(solverManager).removeVehicle(any(PlanningVehicle.class));\n        // solver still running\n        verify(solverManager, never()).stopSolver();\n    }\n\n    @Test\n    void changing_vehicle_capacity_should_take_effect_when_solver_is_started_or_be_published() {\n        // 1 depot, 1 vehicle\n        final long vehicleId = 1;\n        final int oldCapacity = 7;\n        final int newCapacity = 12;\n        Vehicle vehicle = createVehicle(vehicleId, \"\", oldCapacity);\n        routeOptimizer.addVehicle(vehicle);\n        routeOptimizer.addLocation(location1, matrixRow);\n        clearInvocations(routeChangedEventPublisher);\n\n        // change capacity when solver is not running\n        routeOptimizer.changeCapacity(createVehicle(vehicleId, \"\", newCapacity));\n        verifyNoInteractions(solverManager);\n        VehicleRoutingSolution preliminarySolution = verifyPublishingPreliminarySolution();\n        assertThat(preliminarySolution.getVehicleList().get(0).getCapacity()).isEqualTo(newCapacity);\n\n        // start solver\n        routeOptimizer.addLocation(location2, matrixRow);\n\n        VehicleRoutingSolution solution = verifySolverStartedWithSolution();\n        assertThat(solution.getVehicleList().get(0).getCapacity()).isEqualTo(newCapacity);\n    }\n\n    @Test\n    void changing_vehicle_capacity_must_happen_through_problem_fact_change_when_solver_is_running() {\n        // 1 vehicle, 1 depot, 1 visit\n        final int capacity = 14816;\n        final long vehicleId = 10;\n        routeOptimizer.addVehicle(testVehicle(vehicleId));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n\n        routeOptimizer.changeCapacity(createVehicle(vehicleId, \"\", capacity));\n\n        verify(solverManager).changeCapacity(any(PlanningVehicle.class));\n    }\n\n    @Test\n    void changing_vehicle_capacity_must_fail_fast_if_the_vehicle_does_not_exist() {\n        // 1 vehicle, 1 depot, 1 visit\n        final long vehicleId = 10;\n        routeOptimizer.addVehicle(testVehicle(vehicleId));\n\n        assertThatIllegalArgumentException()\n                .isThrownBy(() -> routeOptimizer.changeCapacity(testVehicle(vehicleId + 1)))\n                .withMessageContaining(\"exist\");\n    }\n\n    @Test\n    void remove_all_locations_should_stop_solver_and_publish_preliminary_solution() {\n        // set up a situation where solver is running with 1 depot and 2 visits\n        long vehicleId = 10;\n        routeOptimizer.addVehicle(testVehicle(vehicleId));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n        routeOptimizer.addLocation(location3, matrixRow);\n        clearInvocations(routeChangedEventPublisher);\n\n        routeOptimizer.removeAllLocations();\n\n        verify(solverManager).stopSolver();\n\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getVehicleList()).hasSize(1);\n        assertThat(solution.getDepotList()).isEmpty();\n        assertThat(solution.getVisitList()).isEmpty();\n    }\n\n    @Test\n    void remove_all_vehicles_should_stop_solver_and_publish_preliminary_solution() {\n        long vehicleId = 10;\n        routeOptimizer.addVehicle(testVehicle(vehicleId));\n        routeOptimizer.addLocation(location1, matrixRow);\n        routeOptimizer.addLocation(location2, matrixRow);\n        verify(solverManager).startSolver(any(VehicleRoutingSolution.class));\n        routeOptimizer.addLocation(location3, matrixRow);\n        clearInvocations(routeChangedEventPublisher);\n\n        routeOptimizer.removeAllVehicles();\n\n        verify(solverManager).stopSolver();\n\n        VehicleRoutingSolution solution = verifyPublishingPreliminarySolution();\n        assertThat(solution.getVehicleList()).isEmpty();\n        assertThat(solution.getDepotList()).hasSize(1);\n        assertThat(solution.getVisitList()).hasSize(2);\n    }\n\n    @Test\n    void removing_all_locations_should_not_fail_when_solver_is_not_solving() {\n        assertThatCode(() -> routeOptimizer.removeAllLocations()).doesNotThrowAnyException();\n    }\n\n    @Test\n    void removing_all_vehicles_should_not_fail_when_solver_is_not_solving() {\n        assertThatCode(() -> routeOptimizer.removeAllVehicles()).doesNotThrowAnyException();\n    }\n\n    private VehicleRoutingSolution verifyPublishingPreliminarySolution() {\n        verify(routeChangedEventPublisher).publishSolution(solutionArgumentCaptor.capture());\n        return solutionArgumentCaptor.getValue();\n    }\n\n    private VehicleRoutingSolution verifySolverStartedWithSolution() {\n        verify(solverManager).startSolver(solutionArgumentCaptor.capture());\n        return solutionArgumentCaptor.getValue();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/SolverExceptionTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport javax.enterprise.event.Event;\n\nimport org.assertj.core.api.ThrowableAssert.ThrowingCallable;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaplanner.core.api.solver.Solver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.error.ErrorEvent;\n\nimport com.google.common.util.concurrent.ListenableFutureTask;\nimport com.google.common.util.concurrent.ListeningExecutorService;\n\n@ExtendWith(MockitoExtension.class)\nclass SolverExceptionTest {\n\n    @Mock\n    private Solver<VehicleRoutingSolution> solver;\n    @Mock\n    private ListeningExecutorService executor;\n    @Mock\n    private Event<ErrorEvent> eventPublisher;\n    @Captor\n    ArgumentCaptor<ErrorEvent> errorEventArgumentCaptor;\n    @InjectMocks\n    private SolverManager solverManager;\n\n    @Test\n    void should_publish_error_if_solver_stops_solving_without_being_terminated() {\n        // arrange\n        // Prepare a future that will be returned by mock executor\n        ListenableFutureTask<VehicleRoutingSolution> task = ListenableFutureTask.create(SolutionFactory::emptySolution);\n        when(executor.submit(any(SolverManager.SolvingTask.class))).thenReturn(task);\n        // Run it synchronously (otherwise the test would be unreliable!)\n        task.run();\n\n        // act\n        solverManager.startSolver(SolutionFactory.emptySolution());\n\n        // assert\n        verify(eventPublisher).fire(errorEventArgumentCaptor.capture());\n        assertThat(errorEventArgumentCaptor.getValue().message).contains(\"This is a bug.\");\n    }\n\n    @Test\n    void should_not_publish_error_if_solver_is_terminated_early() {\n        // arrange\n        // Prepare a future that will be returned by mock executor\n        ListenableFutureTask<VehicleRoutingSolution> task = ListenableFutureTask.create(SolutionFactory::emptySolution);\n        when(executor.submit(any(SolverManager.SolvingTask.class))).thenReturn(task);\n        // Pretend the solver has been terminated by stopSolver()...\n        when(solver.isTerminateEarly()).thenReturn(true);\n\n        // act\n        solverManager.startSolver(SolutionFactory.emptySolution());\n        task.run(); // ...so that when this invokes the success callback, it won't publish an error\n\n        // assert\n        verifyNoInteractions(eventPublisher);\n    }\n\n    @Test\n    void should_propagate_any_exception_from_solver() {\n        // arrange\n        // Prepare a future that will be returned by mock executor\n        String exceptionMessage = \"msg 123\";\n        ListenableFutureTask<VehicleRoutingSolution> task = ListenableFutureTask.create(() -> {\n            throw new TestException(exceptionMessage);\n        });\n        when(executor.submit(any(SolverManager.SolvingTask.class))).thenReturn(task);\n        // act (1)\n        // Run it synchronously (otherwise the test would be unreliable!)\n        task.run();\n        solverManager.startSolver(SolutionFactory.emptySolution());\n\n        // assert (1)\n        verify(eventPublisher).fire(errorEventArgumentCaptor.capture());\n        assertThat(errorEventArgumentCaptor.getValue().message)\n                .contains(TestException.class.getName())\n                .contains(exceptionMessage);\n\n        PlanningVisit planningVisit = PlanningVisitFactory.testVisit(1);\n        PlanningVehicle planningVehicle = PlanningVehicleFactory.testVehicle(1);\n\n        // act & assert (2)\n        assertTestExceptionThrownDuringOperation(() -> solverManager.addVisit(planningVisit));\n        assertTestExceptionThrownDuringOperation(() -> solverManager.removeVisit(planningVisit));\n        assertTestExceptionThrownDuringOperation(() -> solverManager.addVehicle(planningVehicle));\n        assertTestExceptionThrownDuringOperation(() -> solverManager.removeVehicle(planningVehicle));\n\n        assertTestExceptionThrownWhenStoppingSolver(solverManager);\n    }\n\n    private static void assertTestExceptionThrownDuringOperation(ThrowingCallable runnable) {\n        assertTestExceptionThrownDuring(runnable, \"died\");\n    }\n\n    private static void assertTestExceptionThrownWhenStoppingSolver(SolverManager routeOptimizer) {\n        assertTestExceptionThrownDuring(routeOptimizer::stopSolver, \"stop\");\n    }\n\n    private static void assertTestExceptionThrownDuring(ThrowingCallable runnable, String message) {\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(runnable)\n                .withMessageContaining(message)\n                .withCauseInstanceOf(TestException.class);\n    }\n\n    private static class TestException extends RuntimeException {\n\n        TestException(String message) {\n            super(message);\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/SolverIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.testLocation;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.fromLocation;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory.emptySolution;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory.solutionFromVisits;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.TimeUnit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.optaplanner.core.api.solver.Solver;\nimport org.optaplanner.core.api.solver.SolverFactory;\nimport org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;\nimport org.optaplanner.core.api.solver.event.SolverEventListener;\nimport org.optaplanner.core.config.solver.SolverConfig;\nimport org.optaweb.vehiclerouting.plugin.planner.change.AddVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.change.RemoveVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nclass SolverIntegrationTest {\n\n    private static final Logger logger = LoggerFactory.getLogger(SolverIntegrationTest.class);\n    // Set a benevolent timeout to avoid issues in the CI environment.\n    private static final int PFC_PROPAGATION_TIMEOUT_MILLIS = 10_000;\n\n    private SolverConfig solverConfig;\n    private ExecutorService executor;\n    private ProblemChangeProcessingMonitor monitor;\n    private Future<VehicleRoutingSolution> futureSolution;\n\n    @BeforeEach\n    void setUp() {\n        solverConfig = SolverConfig.createFromXmlResource(Constants.SOLVER_CONFIG);\n        solverConfig.setDaemon(true);\n        executor = Executors.newSingleThreadExecutor();\n        monitor = new ProblemChangeProcessingMonitor();\n    }\n\n    @AfterEach\n    void tearDown() throws InterruptedException {\n        executor.shutdown();\n        executor.awaitTermination(1, TimeUnit.SECONDS);\n    }\n\n    @Disabled(\"Solver fails fast on empty value ranges\") // TODO file an OptaPlanner ticket for empty value ranges\n    @Test\n    void solver_in_daemon_mode_should_not_fail_on_empty_solution() {\n        Solver<VehicleRoutingSolution> solver =\n                SolverFactory.<VehicleRoutingSolution> create(solverConfig).buildSolver();\n        assertThat(solver.solve(emptySolution())).isNotNull();\n    }\n\n    // TODO remove vehicle, change capacity, change demand...\n\n    @Test\n    void removing_visits_should_not_fail() {\n        long distance = 1;\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1);\n        VehicleRoutingSolution solution = solutionFromVisits(\n                singletonList(vehicle),\n                new PlanningDepot(testLocation(1, location -> distance)),\n                singletonList(fromLocation(testLocation(2, location -> distance))));\n\n        Solver<VehicleRoutingSolution> solver =\n                SolverFactory.<VehicleRoutingSolution> create(solverConfig).buildSolver();\n        solver.addEventListener(monitor);\n        startSolver(solver, solution);\n\n        for (int id = 3; id < 6; id++) {\n            logger.info(\"Add visit ({})\", id);\n            monitor.beforeProblemChange();\n            solver.addProblemChange(new AddVisit(fromLocation(testLocation(id, location -> distance))));\n            assertThat(monitor.awaitAllProblemChanges(PFC_PROPAGATION_TIMEOUT_MILLIS)).isTrue();\n        }\n\n        List<Integer> visitIds = Arrays.asList(5, 2, 3);\n        for (int id : visitIds) {\n            logger.info(\"Remove visit ({})\", id);\n            assertThat(solver.isEveryProblemChangeProcessed()).isTrue();\n            monitor.beforeProblemChange();\n            solver.addProblemChange(new RemoveVisit(testVisit(id)));\n            assertThat(solver.isEveryProblemChangeProcessed()).isFalse(); // probably not 100% safe\n            // Notice that it's not possible to check individual problem fact changes completion.\n            // When we receive a BestSolutionChangedEvent with unprocessed PFCs,\n            // we don't know how many of them there are.\n            if (!monitor.awaitAllProblemChanges(PFC_PROPAGATION_TIMEOUT_MILLIS)) {\n                assertThat(terminateSolver(solver)).isNotNull();\n                fail(\"Problem fact change hasn't been completed\");\n            }\n        }\n\n        assertThat(terminateSolver(solver)).isNotNull();\n    }\n\n    private void startSolver(Solver<VehicleRoutingSolution> solver, VehicleRoutingSolution solution) {\n        futureSolution = executor.submit(() -> solver.solve(solution));\n    }\n\n    private VehicleRoutingSolution terminateSolver(Solver<VehicleRoutingSolution> solver) {\n        solver.terminateEarly();\n        try {\n            return futureSolution.get();\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            fail(\"Interrupted\", e);\n        } catch (ExecutionException e) {\n            fail(\"Solving failed\", e);\n        }\n        throw new AssertionError();\n    }\n\n    static class ProblemChangeProcessingMonitor implements SolverEventListener<VehicleRoutingSolution> {\n\n        private static final Logger logger = LoggerFactory.getLogger(ProblemChangeProcessingMonitor.class);\n\n        private final Semaphore problemChanges = new Semaphore(0);\n\n        void beforeProblemChange() {\n            int permitsDrained = problemChanges.drainPermits();\n            logger.debug(\"Before PFC (permits drained: {})\", permitsDrained);\n        }\n\n        boolean awaitAllProblemChanges(int milliseconds) {\n            // Available permits may rarely be > 0 if the PFC completes before we start waiting,\n            // or if the solution has improved since we called beforePFC() => the test is not completely reliable.\n            logger.debug(\"WAIT (completed PFCs: {})\", problemChanges.availablePermits());\n            try {\n                if (problemChanges.tryAcquire(milliseconds, TimeUnit.MILLISECONDS)) {\n                    logger.info(\"Problem Fact Change DONE\");\n                    return true;\n                }\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n                fail(\"Interrupted\", e);\n            }\n            return false;\n        }\n\n        @Override\n        public void bestSolutionChanged(BestSolutionChangedEvent<VehicleRoutingSolution> event) {\n            // This happens on solver thread\n            if (!event.isEveryProblemChangeProcessed()) {\n                logger.debug(\"UNPROCESSED\");\n            } else if (!event.getNewBestScore().isSolutionInitialized()) {\n                logger.debug(\"UNINITIALIZED ({})\", event.getNewBestScore());\n            } else {\n                logger.debug(\"New best solution (COMPLETE)\");\n                problemChanges.release();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/SolverManagerIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory.solutionFromVisits;\n\nimport java.util.concurrent.Semaphore;\n\nimport javax.enterprise.context.ApplicationScoped;\nimport javax.enterprise.event.Observes;\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.DistanceMap;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.service.route.RouteChangedEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport io.quarkus.test.junit.QuarkusTest;\nimport io.quarkus.test.junit.TestProfile;\n\n@QuarkusTest\n@TestProfile(SolverTestProfile.class)\nclass SolverManagerIntegrationTest {\n\n    @Inject\n    SolverManager solverManager;\n    @Inject\n    RouteChangedEventSemaphore routeChangedEventSemaphore;\n\n    private static DistanceMap mockDistanceMap() {\n        return location -> 60;\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void solver_should_be_in_daemon_mode() throws InterruptedException {\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1);\n        PlanningLocation depot = PlanningLocationFactory.testLocation(1, mockDistanceMap());\n        PlanningLocation visit = PlanningLocationFactory.testLocation(2, mockDistanceMap());\n        VehicleRoutingSolution solution = solutionFromVisits(\n                singletonList(vehicle),\n                new PlanningDepot(depot),\n                singletonList(PlanningVisitFactory.fromLocation(visit)));\n        solverManager.startSolver(solution);\n\n        // Waits until the solution is initialized. There is only 1 possible step => no more than 1 RouteChangedEvent.\n        routeChangedEventSemaphore.waitForRouteUpdate();\n        // The best solution has been updated. We know the score must be -1hard/-120soft because that's\n        // the only possible solution. The termination property is set exactly to this score => we know\n        // the solver is now terminated.\n\n        // If the solver is in daemon mode, it doesn't return from solve() although solving has ended.\n        // Instead, it's actively waiting for a PFC and will restart once it arrives from the outside (the test thread).\n        // If it's not in daemon mode, it returns from solve() method once the termination condition is met\n        // and the following PFC attempt fails.\n        assertThatCode(() -> solverManager.changeCapacity(vehicle)).doesNotThrowAnyException();\n    }\n\n    @ApplicationScoped\n    static class RouteChangedEventSemaphore {\n\n        private static final Logger logger = LoggerFactory.getLogger(RouteChangedEventSemaphore.class);\n        private final Semaphore semaphore = new Semaphore(0);\n\n        public void onApplicationEvent(@Observes RouteChangedEvent event) {\n            logger.info(\"DISTANCE: {}\", event.distance());\n            semaphore.release();\n        }\n\n        void waitForRouteUpdate() throws InterruptedException {\n            semaphore.acquire();\n            int remainingPermits = semaphore.availablePermits();\n            if (remainingPermits > 0) {\n                throw new IllegalStateException(\n                        \"Only 1 RouteChangedEvent was expected but there were at least \" + (remainingPermits + 1));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/SolverManagerTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.mockito.AdditionalAnswers.answer;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.stubbing.Answer1;\nimport org.optaplanner.core.api.solver.Solver;\nimport org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;\nimport org.optaweb.vehiclerouting.plugin.planner.change.AddVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.change.AddVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.change.ChangeVehicleCapacity;\nimport org.optaweb.vehiclerouting.plugin.planner.change.RemoveVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.change.RemoveVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.ListeningExecutorService;\n\n@ExtendWith(MockitoExtension.class)\nclass SolverManagerTest {\n\n    private final VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n    private final PlanningVehicle testVehicle = PlanningVehicleFactory.testVehicle(1);\n    private final PlanningVisit testVisit = PlanningVisitFactory.testVisit(1);\n\n    @Captor\n    private ArgumentCaptor<VehicleRoutingSolution> solutionArgumentCaptor;\n    @Mock\n    private BestSolutionChangedEvent<VehicleRoutingSolution> bestSolutionChangedEvent;\n    @Mock\n    private ListenableFuture<VehicleRoutingSolution> solverFuture;\n\n    @Mock\n    private Solver<VehicleRoutingSolution> solver;\n    @Mock\n    private ListeningExecutorService executor;\n    @Mock\n    private RouteChangedEventPublisher routeChangedEventPublisher;\n    @InjectMocks\n    private SolverManager solverManager;\n\n    private void returnSolverFutureWhenSolverIsStarted() {\n        // always run the runnable submitted to executor (that's what every executor does)\n        // we can then verify that solver.solve() has been called\n        when(executor.submit(any(SolverManager.SolvingTask.class))).thenAnswer(\n                answer((Answer1<Future<VehicleRoutingSolution>, SolverManager.SolvingTask>) callable -> {\n                    callable.call();\n                    return solverFuture;\n                }));\n    }\n\n    @Test\n    void should_listen_for_best_solution_events() {\n        verify(solver).addEventListener(solverManager);\n    }\n\n    @Test\n    void ignore_new_best_solutions_when_unprocessed_fact_changes() {\n        // arrange\n        when(bestSolutionChangedEvent.isEveryProblemChangeProcessed()).thenReturn(false);\n\n        // act\n        solverManager.bestSolutionChanged(bestSolutionChangedEvent);\n\n        // assert\n        verify(bestSolutionChangedEvent, never()).getNewBestSolution();\n        verify(routeChangedEventPublisher, never()).publishSolution(any());\n    }\n\n    @Test\n    void publish_new_best_solution_if_all_fact_changes_processed() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n        when(bestSolutionChangedEvent.isEveryProblemChangeProcessed()).thenReturn(true);\n        when(bestSolutionChangedEvent.getNewBestSolution()).thenReturn(solution);\n\n        solverManager.bestSolutionChanged(bestSolutionChangedEvent);\n\n        verify(routeChangedEventPublisher).publishSolution(solutionArgumentCaptor.capture());\n        VehicleRoutingSolution event = solutionArgumentCaptor.getValue();\n        assertThat(event).isSameAs(solution);\n    }\n\n    @Test\n    void startSolver_should_start_solver() {\n        returnSolverFutureWhenSolverIsStarted();\n        solverManager.startSolver(solution);\n        verify(solver).solve(solution);\n\n        // cannot start solver that is already solving\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.startSolver(solution));\n    }\n\n    @Test\n    void stopSolver_should_terminate_solver() {\n        returnSolverFutureWhenSolverIsStarted();\n        solverManager.startSolver(solution);\n        solverManager.stopSolver();\n        verify(solver).terminateEarly();\n\n        // another stopSolver() does nothing\n        solverManager.stopSolver();\n        // This verifies there were no more invocations of terminateEarly() without clearing all invocations.\n        // Not using Mockito.clearInvocations() only because it doesn't like generic arguments.\n        verify(solver).terminateEarly();\n    }\n\n    @Test\n    void reset_interrupted_flag() throws ExecutionException, InterruptedException {\n        returnSolverFutureWhenSolverIsStarted();\n        // start solver\n        solverManager.startSolver(solution);\n        when(solverFuture.isDone()).thenReturn(true);\n        when(solverFuture.get()).thenThrow(InterruptedException.class);\n\n        PlanningVisit visit = testVisit(0);\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.removeVisit(visit));\n        assertThat(Thread.interrupted()).isTrue();\n\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.stopSolver());\n        assertThat(Thread.interrupted()).isTrue();\n    }\n\n    @Test\n    void change_operations_should_fail_if_solver_has_not_started_yet() {\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.addVehicle(testVehicle))\n                .withMessageContaining(\"started\");\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.removeVehicle(testVehicle))\n                .withMessageContaining(\"started\");\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.changeCapacity(testVehicle))\n                .withMessageContaining(\"started\");\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.addVisit(testVisit))\n                .withMessageContaining(\"started\");\n        assertThatIllegalStateException()\n                .isThrownBy(() -> solverManager.removeVisit(testVisit))\n                .withMessageContaining(\"started\");\n    }\n\n    @Test\n    void change_operations_should_fail_is_solver_has_died() throws ExecutionException, InterruptedException {\n        returnSolverFutureWhenSolverIsStarted();\n        solverManager.startSolver(solution);\n        when(solverFuture.isDone()).thenReturn(true);\n        when(solverFuture.get()).thenThrow(ExecutionException.class);\n\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.addVehicle(testVehicle))\n                .withMessageContaining(\"died\");\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.removeVehicle(testVehicle))\n                .withMessageContaining(\"died\");\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.changeCapacity(testVehicle))\n                .withMessageContaining(\"died\");\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.addVisit(testVisit))\n                .withMessageContaining(\"died\");\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> solverManager.removeVisit(testVisit))\n                .withMessageContaining(\"died\");\n    }\n\n    @Test\n    void change_operations_should_submit_problem_fact_changes_to_solver() {\n        returnSolverFutureWhenSolverIsStarted();\n        solverManager.startSolver(solution);\n        when(solverFuture.isDone()).thenReturn(false);\n\n        solverManager.addVehicle(testVehicle);\n        verify(solver).addProblemChange(any(AddVehicle.class));\n\n        solverManager.removeVehicle(testVehicle);\n        verify(solver).addProblemChange(any(RemoveVehicle.class));\n\n        solverManager.changeCapacity(testVehicle);\n        verify(solver).addProblemChange(any(ChangeVehicleCapacity.class));\n\n        solverManager.addVisit(testVisit);\n        verify(solver).addProblemChange(any(AddVisit.class));\n\n        solverManager.removeVisit(testVisit);\n        verify(solver).addProblemChange(any(RemoveVisit.class));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/SolverTestProfile.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.quarkus.test.junit.QuarkusTestProfile;\n\npublic class SolverTestProfile implements QuarkusTestProfile {\n\n    @Override\n    public Map<String, String> getConfigOverrides() {\n        HashMap<String, String> config = new HashMap<>();\n        config.put(\"quarkus.optaplanner.solver.termination.best-score-limit\", \"-1hard/-120soft\");\n        return config;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/VehicleRoutingConstraintProviderTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner;\n\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.testLocation;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.fromLocation;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\nimport org.optaplanner.test.api.score.stream.ConstraintVerifier;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.DistanceMap;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.Standstill;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass VehicleRoutingConstraintProviderTest {\n\n    private final ConstraintVerifier<VehicleRoutingConstraintProvider, VehicleRoutingSolution> constraintVerifier =\n            ConstraintVerifier.build(\n                    new VehicleRoutingConstraintProvider(),\n                    VehicleRoutingSolution.class,\n                    Standstill.class,\n                    PlanningVisit.class);\n\n    private static DistanceMap distanceToAll(long distance) {\n        return location -> distance;\n    }\n\n    private static void route(PlanningVehicle vehicle, PlanningVisit... visits) {\n        Standstill previousStandstill = vehicle;\n\n        for (PlanningVisit visit : visits) {\n            visit.setVehicle(vehicle);\n            visit.setPreviousStandstill(previousStandstill);\n            previousStandstill.setNextVisit(visit);\n            previousStandstill = visit;\n        }\n    }\n\n    @Test\n    void vehicle_capacity_penalized_1vehicle_1visit() {\n        int demand = 100;\n        int capacity = 5;\n\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1, capacity);\n        vehicle.setDepot(new PlanningDepot(testLocation(1, distanceToAll(0))));\n\n        PlanningVisit visit = fromLocation(testLocation(2, distanceToAll(0)), demand);\n\n        route(vehicle, visit);\n\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit)\n                .penalizesBy(demand - capacity);\n    }\n\n    @Test\n    void vehicle_capacity_penalized_1vehicle_3visits() {\n        int demand1 = 4;\n        int demand2 = 3;\n        int demand3 = 9;\n        int capacity = 5;\n\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1, capacity);\n        vehicle.setDepot(new PlanningDepot(testLocation(0, distanceToAll(0))));\n\n        PlanningVisit visit1 = fromLocation(testLocation(1, distanceToAll(0)), demand1);\n        PlanningVisit visit2 = fromLocation(testLocation(2, distanceToAll(0)), demand2);\n        PlanningVisit visit3 = fromLocation(testLocation(3, distanceToAll(0)), demand3);\n\n        route(vehicle, visit1, visit2, visit3);\n\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit1, visit2, visit3)\n                .penalizesBy(demand1 + demand2 + demand3 - capacity);\n    }\n\n    @Test\n    void capacity_not_penalized_when_greater_or_equal_to_demand() {\n        int demand1 = 4;\n        int demand2 = 3;\n        int demand3 = 9;\n        int totalDemand = demand1 + demand2 + demand3;\n\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1, totalDemand);\n        vehicle.setDepot(new PlanningDepot(testLocation(0, distanceToAll(0))));\n\n        PlanningVisit visit1 = fromLocation(testLocation(1, distanceToAll(0)), demand1);\n        PlanningVisit visit2 = fromLocation(testLocation(2, distanceToAll(0)), demand2);\n        PlanningVisit visit3 = fromLocation(testLocation(3, distanceToAll(0)), demand3);\n\n        route(vehicle, visit1, visit2, visit3);\n\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit1, visit2, visit3)\n                .penalizesBy(0);\n\n        // test values near the constraint boundary\n        vehicle.setCapacity(totalDemand + 1);\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit1, visit2, visit3)\n                .penalizesBy(0);\n    }\n\n    @Test\n    void vehicles_capacity_constraint_should_work_for_multiple_vehicles() {\n        int demand1a = 11;\n        int demand1b = 12;\n        int demand1c = 14;\n        int demand2a = 3000;\n        int demand2b = 2500;\n        int demand2c = 8000;\n        int capacity1 = demand1a + demand1b + demand1c;\n        int capacity2 = demand2a + demand2b + demand2c;\n\n        PlanningDepot depot = new PlanningDepot(testLocation(0, distanceToAll(0)));\n\n        PlanningVehicle vehicle1 = PlanningVehicleFactory.testVehicle(1, capacity1);\n        vehicle1.setDepot(depot);\n        PlanningVehicle vehicle2 = PlanningVehicleFactory.testVehicle(2, capacity2);\n        vehicle2.setDepot(depot);\n\n        PlanningVisit visit1 = fromLocation(testLocation(1, distanceToAll(0)), demand1a);\n        PlanningVisit visit2 = fromLocation(testLocation(2, distanceToAll(0)), demand1b);\n        PlanningVisit visit3 = fromLocation(testLocation(3, distanceToAll(0)), demand1c);\n        PlanningVisit visit4 = fromLocation(testLocation(4, distanceToAll(0)), demand2a);\n        PlanningVisit visit5 = fromLocation(testLocation(5, distanceToAll(0)), demand2b);\n        PlanningVisit visit6 = fromLocation(testLocation(6, distanceToAll(0)), demand2c);\n\n        route(vehicle1, visit1, visit2, visit3);\n        route(vehicle2, visit4, visit5, visit6);\n\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit1, visit2, visit3, visit4, visit5, visit6)\n                .penalizesBy(0);\n\n        vehicle1.setCapacity(capacity1 - 3);\n        vehicle2.setCapacity(capacity2 - 7);\n\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::vehicleCapacity)\n                .given(visit1, visit2, visit3, visit4, visit5, visit6)\n                .penalizesBy(10);\n    }\n\n    @Test\n    void distance_2vehicles() {\n        int fromDepot1 = 1000;\n        int fromDepot2 = 2000;\n        PlanningDepot depot1 = new PlanningDepot(testLocation(0, distanceToAll(fromDepot1)));\n        PlanningDepot depot2 = new PlanningDepot(testLocation(0, distanceToAll(fromDepot2)));\n\n        PlanningVehicle vehicle1 = PlanningVehicleFactory.testVehicle(1, Integer.MAX_VALUE);\n        vehicle1.setDepot(depot1);\n        PlanningVehicle vehicle2 = PlanningVehicleFactory.testVehicle(1, Integer.MAX_VALUE);\n        vehicle2.setDepot(depot2);\n\n        int fromA = 17;\n        int fromB = 11;\n        int fromC = 37;\n        int fromD = 123;\n        int fromE = 77;\n        int fromF = 99;\n        PlanningVisit visitA = fromLocation(testLocation(1, distanceToAll(fromA)));\n        PlanningVisit visitB = fromLocation(testLocation(2, distanceToAll(fromB)));\n        PlanningVisit visitC = fromLocation(testLocation(3, distanceToAll(fromC)));\n        PlanningVisit visitD = fromLocation(testLocation(4, distanceToAll(fromD)));\n        PlanningVisit visitE = fromLocation(testLocation(5, distanceToAll(fromE)));\n        PlanningVisit visitF = fromLocation(testLocation(6, distanceToAll(fromF)));\n\n        route(vehicle1, visitA, visitB, visitC);\n        route(vehicle2, visitD, visitE, visitF);\n\n        // vehicle 1: depot→last\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromPreviousStandstill)\n                .given(vehicle1, visitA, visitB, visitC)\n                .penalizesBy(fromDepot1 + fromA + fromB);\n\n        // vehicle 1: last→depot\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromLastVisitToDepot)\n                .given(vehicle1, visitA, visitB, visitC)\n                .penalizesBy(fromC);\n\n        // vehicle 2: depot→last\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromPreviousStandstill)\n                .given(vehicle2, visitD, visitE, visitF)\n                .penalizesBy(fromDepot2 + fromD + fromE);\n\n        // vehicle 2: last→depot\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromLastVisitToDepot)\n                .given(vehicle2, visitD, visitE, visitF)\n                .penalizesBy(fromF);\n\n        // vehicles 1+2: depot→last\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromPreviousStandstill)\n                .given(vehicle1, vehicle2, visitA, visitB, visitC, visitD, visitE, visitF)\n                .penalizesBy(fromDepot1 + fromDepot2 + fromA + fromB + fromD + fromE);\n\n        // vehicles 1+2: last→depot\n        constraintVerifier.verifyThat(VehicleRoutingConstraintProvider::distanceFromLastVisitToDepot)\n                .given(vehicle1, vehicle2, visitA, visitB, visitC, visitD, visitE, visitF)\n                .penalizesBy(fromC + fromF);\n\n        // score\n        constraintVerifier.verifyThat()\n                .given(vehicle1, vehicle2, visitA, visitB, visitC, visitD, visitE, visitF)\n                .scores(HardSoftLongScore.ofSoft(\n                        -(fromDepot1 + fromDepot2 + fromA + fromB + fromC + fromD + fromE + fromF)));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/change/AddVehicleTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.plugin.planner.MockSolver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass AddVehicleTest {\n\n    @Test\n    void add_vehicle_should_add_vehicle() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(solution);\n\n        PlanningVehicle vehicle = PlanningVehicleFactory.testVehicle(1);\n        mockSolver.addProblemChange(new AddVehicle(vehicle));\n\n        assertThat(solution.getVehicleList()).containsExactly(vehicle);\n        mockSolver.verifyProblemFactAdded(vehicle);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/change/AddVisitTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.plugin.planner.MockSolver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass AddVisitTest {\n\n    @Test\n    void add_visit_should_add_location_and_create_visit() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(solution);\n\n        PlanningVisit visit = PlanningVisitFactory.testVisit(1);\n        mockSolver.addProblemChange(new AddVisit(visit));\n\n        mockSolver.verifyEntityAdded(visit);\n        assertThat(solution.getVisitList()).containsExactly(visit);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/change/ChangeVehicleCapacityTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.plugin.planner.MockSolver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass ChangeVehicleCapacityTest {\n\n    @Test\n    void change_vehicle_capacity() {\n        int oldCapacity = 100;\n        int newCapacity = 50;\n\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(SolutionFactory.emptySolution());\n\n        PlanningVehicle workingVehicle = PlanningVehicleFactory.testVehicle(1, oldCapacity);\n        PlanningVehicle changeVehicle = PlanningVehicleFactory.testVehicle(2, newCapacity);\n\n        mockSolver.whenLookingUp(changeVehicle).thenReturn(workingVehicle);\n\n        // do change\n        mockSolver.addProblemChange(new ChangeVehicleCapacity(changeVehicle));\n\n        assertThat(workingVehicle.getCapacity()).isEqualTo(newCapacity);\n        mockSolver.verifyProblemPropertyChanged(changeVehicle);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/change/RemoveVehicleTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\n\nimport java.util.Arrays;\nimport java.util.Collections;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaplanner.test.api.solver.change.MockProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.MockSolver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicle;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass RemoveVehicleTest {\n\n    @Test\n    void remove_vehicle() {\n        PlanningVehicle removedVehicle = PlanningVehicleFactory.testVehicle(1);\n        PlanningVehicle otherVehicle = PlanningVehicleFactory.testVehicle(2);\n\n        PlanningDepot depot = new PlanningDepot(PlanningLocationFactory.testLocation(1));\n\n        PlanningVisit firstVisit = testVisit(1);\n        PlanningVisit lastVisit = testVisit(2);\n\n        VehicleRoutingSolution solution = SolutionFactory.solutionFromVisits(\n                Arrays.asList(removedVehicle, otherVehicle),\n                depot,\n                Arrays.asList(firstVisit, lastVisit));\n\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(solution);\n\n        // V -> first -> last\n        removedVehicle.setNextVisit(firstVisit);\n        firstVisit.setPreviousStandstill(removedVehicle);\n        firstVisit.setVehicle(removedVehicle);\n        firstVisit.setNextVisit(lastVisit);\n        lastVisit.setPreviousStandstill(firstVisit);\n        lastVisit.setVehicle(removedVehicle);\n\n        // do change\n        mockSolver.addProblemChange(new RemoveVehicle(removedVehicle));\n\n        assertThat(firstVisit.getPreviousStandstill()).isNull();\n        assertThat(lastVisit.getPreviousStandstill()).isNull();\n        assertThat(solution.getVehicleList()).containsExactly(otherVehicle);\n\n        mockSolver.verifyVariableChanged(firstVisit, \"previousStandstill\");\n        mockSolver.verifyVariableChanged(lastVisit, \"previousStandstill\");\n        mockSolver.verifyProblemFactRemoved(removedVehicle);\n    }\n\n    @Test\n    void fail_fast_if_working_solution_vehicle_list_does_not_contain_working_vehicle() {\n        long removedId = 111L;\n        long wrongId = 222L;\n        PlanningVehicle removedVehicle = PlanningVehicleFactory.testVehicle(removedId);\n        PlanningVehicle wrongVehicle = PlanningVehicleFactory.testVehicle(wrongId);\n\n        PlanningDepot depot = new PlanningDepot(PlanningLocationFactory.testLocation(1));\n\n        VehicleRoutingSolution solution = SolutionFactory.solutionFromVisits(\n                Arrays.asList(wrongVehicle),\n                depot,\n                Collections.emptyList());\n\n        // do change\n        RemoveVehicle removeVehicle = new RemoveVehicle(removedVehicle);\n        assertThatIllegalStateException()\n                .isThrownBy(() -> removeVehicle.doChange(solution, new MockProblemChangeDirector()))\n                .withMessageMatching(\".*List .*\" + wrongId + \".* doesn't contain the working.*\" + removedId + \".*\");\n    }\n\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/change/RemoveVisitTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.change;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory.testVehicle;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaplanner.test.api.solver.change.MockProblemChangeDirector;\nimport org.optaweb.vehiclerouting.plugin.planner.MockSolver;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\n\nclass RemoveVisitTest {\n\n    @Test\n    void remove_last_visit() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n\n        PlanningVisit removedVisit = testVisit(1);\n        PlanningVisit otherVisit = testVisit(2);\n        solution.getVisitList().add(otherVisit);\n        solution.getVisitList().add(removedVisit);\n\n        // V -> other -> removed\n        otherVisit.setPreviousStandstill(testVehicle(10));\n        otherVisit.setNextVisit(removedVisit);\n        removedVisit.setPreviousStandstill(otherVisit);\n\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(solution);\n        mockSolver.whenLookingUp(removedVisit).thenReturn(removedVisit);\n\n        // do change\n        mockSolver.addProblemChange(new RemoveVisit(removedVisit));\n\n        mockSolver.verifyEntityRemoved(removedVisit);\n        assertThat(solution.getVisitList()).containsExactly(otherVisit);\n    }\n\n    @Test\n    void remove_middle_visit() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n\n        PlanningVisit firstVisit = testVisit(1);\n        PlanningVisit middleVisit = testVisit(2);\n        PlanningVisit lastVisit = testVisit(3);\n        solution.getVisitList().add(firstVisit);\n        solution.getVisitList().add(lastVisit);\n        solution.getVisitList().add(middleVisit);\n\n        // V -> first -> removed -> last\n        firstVisit.setPreviousStandstill(testVehicle(1));\n        firstVisit.setNextVisit(middleVisit);\n        middleVisit.setPreviousStandstill(firstVisit);\n        middleVisit.setNextVisit(lastVisit);\n        lastVisit.setPreviousStandstill(middleVisit);\n\n        PlanningVisit removedVisit = testVisit(2);\n\n        MockSolver<VehicleRoutingSolution> mockSolver = MockSolver.build(solution);\n        mockSolver.whenLookingUp(removedVisit).thenReturn(middleVisit);\n\n        // do change\n        mockSolver.addProblemChange(new RemoveVisit(removedVisit));\n\n        mockSolver.verifyVariableChanged(lastVisit, \"previousStandstill\");\n        mockSolver.verifyEntityRemoved(removedVisit);\n\n        assertThat(solution.getVisitList())\n                .hasSize(2)\n                .containsOnly(firstVisit, lastVisit);\n\n        // V -> first -> removed -> last\n        assertThat(lastVisit.getPreviousStandstill()).isEqualTo(firstVisit);\n    }\n\n    @Test\n    void fail_fast_if_working_solution_visit_list_does_not_contain_working_visit() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n\n        long removedId = 111L;\n        PlanningVisit removedVisit = testVisit(removedId);\n        long wrongId = 222L;\n        PlanningVisit wrongVisit = testVisit(wrongId);\n        wrongVisit.setPreviousStandstill(testVisit(10));\n        removedVisit.setNextVisit(wrongVisit);\n        solution.getVisitList().add(wrongVisit);\n\n        // do change\n        RemoveVisit removeVisit = new RemoveVisit(removedVisit);\n        assertThatIllegalStateException()\n                .isThrownBy(() -> removeVisit.doChange(solution, new MockProblemChangeDirector()))\n                .withMessageMatching(\".*List .*\" + wrongId + \".* doesn't contain the working.*\" + removedId + \".*\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningLocationFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\n\nclass PlanningLocationFactoryTest {\n\n    @Test\n    void planning_location_should_have_same_properties_as_domain_location() {\n        long id = 344;\n        double latitude = -20.5;\n        double longitude = 11.7;\n        long distance = 11234;\n        Location location = new Location(id, Coordinates.of(latitude, longitude));\n        PlanningLocation planningLocation = PlanningLocationFactory.fromDomain(location, otherLocation -> distance);\n        assertThat(planningLocation.getId()).isEqualTo(id);\n\n        PlanningLocation other = PlanningLocationFactory.testLocation(id + 1);\n        assertThat(planningLocation.distanceTo(other)).isEqualTo(distance);\n        assertThat(planningLocation.angleTo(other)).isNotZero();\n    }\n\n    @Test\n    void test_locations_distance_map_should_work() {\n        long distance = 11231;\n        PlanningLocation planningLocation = PlanningLocationFactory.testLocation(0, location -> distance);\n        assertThat(planningLocation.distanceTo(PlanningLocationFactory.testLocation(1))).isEqualTo(distance);\n    }\n\n    @Test\n    void test_location_without_distance_map_should_throw_exception() {\n        PlanningLocation planningLocation = PlanningLocationFactory.testLocation(0);\n        PlanningLocation otherLocation = PlanningLocationFactory.testLocation(1);\n        assertThatIllegalStateException().isThrownBy(() -> planningLocation.distanceTo(otherLocation));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningLocationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.offset;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.testLocation;\n\nimport java.util.HashMap;\n\nimport org.assertj.core.data.Offset;\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.plugin.planner.DistanceMapImpl;\n\nclass PlanningLocationTest {\n\n    @Test\n    void distance_to_location_should_equal_value_in_distance_map() {\n        HashMap<Long, Distance> distanceMap = new HashMap<>();\n        long otherId = 321;\n        long millis = 777777;\n        distanceMap.put(otherId, Distance.ofMillis(millis));\n        Location domainLocation = new Location(1, Coordinates.of(0, 0));\n\n        PlanningLocation planningLocation = new PlanningLocation(\n                domainLocation.id(),\n                domainLocation.coordinates().latitude().doubleValue(),\n                domainLocation.coordinates().longitude().doubleValue(),\n                new DistanceMapImpl(distanceMap::get));\n        assertThat(planningLocation.distanceTo(testLocation(otherId))).isEqualTo(millis);\n    }\n\n    @Test\n    void angle_from_depot_at_zero_should_be_atan2_of_latitude_longitude() {\n        PlanningLocation center = locationAt(0, 0);\n\n        assertThat(center.angleTo(locationAt(0, 1))).isZero();\n        assertThat(center.angleTo(locationAt(0, -1))).isEqualTo(Math.PI);\n        assertThat(center.angleTo(locationAt(1, 0))).isEqualTo(Math.PI / 2);\n        assertThat(center.angleTo(locationAt(-1, 0))).isEqualTo(-Math.PI / 2);\n        assertThat(center.angleTo(locationAt(-Double.MIN_VALUE, -1))).isEqualTo(-Math.PI);\n        assertThat(center.angleTo(locationAt(-0, 1))).isZero();\n    }\n\n    @Test\n    void angle_from_depot_on_real_coordinates_should_be_atan2_of_latitude_longitude() {\n        PlanningLocation depot = locationAt(1.77, -10.5);\n        Offset<Double> offset = offset(0.05);\n\n        assertThat(depot.angleTo(locationAt(1.76, -5))).isCloseTo(0, offset).isNegative();\n        assertThat(depot.angleTo(locationAt(100000, -1))).isCloseTo(Math.PI / 2, offset);\n    }\n\n    private static PlanningLocation locationAt(double latitude, double longitude) {\n        return new PlanningLocation(0, latitude, longitude, location -> 0);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVehicleFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVehicleFactory.fromDomain;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\nclass PlanningVehicleFactoryTest {\n\n    @Test\n    void planning_vehicle() {\n        long vehicleId = 2;\n        String name = \"not used\";\n        int capacity = 7;\n        Vehicle domainVehicle = VehicleFactory.createVehicle(vehicleId, name, capacity);\n\n        PlanningVehicle vehicle = fromDomain(domainVehicle);\n\n        assertThat(vehicle.getId()).isEqualTo(vehicleId);\n        assertThat(vehicle.getCapacity()).isEqualTo(capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVehicleTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\nimport org.junit.jupiter.api.Test;\n\nclass PlanningVehicleTest {\n\n    @Test\n    void get_future_visits_should_return_an_iterable_that_iterates_over_all_visits() {\n        PlanningVisit visit1 = new PlanningVisit();\n        PlanningVisit visit2 = new PlanningVisit();\n        PlanningVisit visit3 = new PlanningVisit();\n\n        PlanningVehicle vehicle = new PlanningVehicle();\n\n        vehicle.setNextVisit(visit1);\n        visit1.setNextVisit(visit2);\n        visit2.setNextVisit(visit3);\n\n        Iterable<PlanningVisit> futureVisits = vehicle.getFutureVisits();\n\n        assertThat(futureVisits).containsExactly(visit1, visit2, visit3);\n    }\n\n    @Test\n    void get_future_visits_should_throw_a_NoSuchElementException_when_there_are_no_more_visits() {\n        PlanningVehicle vehicle = new PlanningVehicle();\n\n        Iterator<PlanningVisit> futureVisits = vehicle.getFutureVisits().iterator();\n\n        assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(futureVisits::next);\n\n        PlanningVisit visit1 = new PlanningVisit();\n        PlanningVisit visit2 = new PlanningVisit();\n\n        vehicle.setNextVisit(visit1);\n        visit1.setNextVisit(visit2);\n\n        futureVisits = vehicle.getFutureVisits().iterator();\n        futureVisits.next();\n        futureVisits.next();\n\n        assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(futureVisits::next);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/PlanningVisitFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\n\nclass PlanningVisitFactoryTest {\n\n    @Test\n    void visit_should_have_same_id_as_location_and_default_demand() {\n        long id = 4;\n        PlanningLocation location = PlanningLocationFactory.testLocation(id);\n\n        PlanningVisit visit = PlanningVisitFactory.fromLocation(location);\n\n        assertThat(visit.getId()).isEqualTo(location.getId());\n        assertThat(visit.getLocation()).isEqualTo(location);\n        assertThat(visit.getDemand()).isEqualTo(PlanningVisitFactory.DEFAULT_VISIT_DEMAND);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/domain/SolutionFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.domain;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;\n\nclass SolutionFactoryTest {\n\n    @Test\n    void empty_solution_should_be_empty() {\n        VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n        assertThat(solution.getVisitList()).isEmpty();\n        assertThat(solution.getDepotList()).isEmpty();\n        assertThat(solution.getVehicleList()).isEmpty();\n        assertThat(solution.getScore()).isEqualTo(HardSoftLongScore.ZERO);\n    }\n\n    @Test\n    void solution_created_from_vehicles_depot_and_visits_should_be_consistent() {\n        PlanningVehicle vehicle = new PlanningVehicle();\n\n        PlanningLocation depotLocation = PlanningLocationFactory.testLocation(1);\n        PlanningDepot depot = new PlanningDepot(depotLocation);\n\n        PlanningVisit visit = PlanningVisitFactory.testVisit(2);\n\n        VehicleRoutingSolution solutionWithDepot = SolutionFactory.solutionFromVisits(\n                singletonList(vehicle),\n                depot,\n                singletonList(visit));\n        assertThat(solutionWithDepot.getVehicleList()).containsExactly(vehicle);\n        assertThat(vehicle.getDepot()).isEqualTo(depot);\n        assertThat(solutionWithDepot.getDepotList()).containsExactly(depot);\n        assertThat(solutionWithDepot.getVisitList()).hasSize(1);\n        assertThat(solutionWithDepot.getVisitList()).containsExactly(visit);\n        assertThat(solutionWithDepot.getVisitList().get(0).getLocation()).isEqualTo(visit.getLocation());\n\n        VehicleRoutingSolution solutionWithNoDepot = SolutionFactory.solutionFromVisits(\n                singletonList(vehicle),\n                null,\n                emptyList());\n        assertThat(solutionWithNoDepot.getDepotList()).isEmpty();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/planner/weight/DepotAngleVisitDifficultyWeightFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.planner.weight;\n\nimport static java.util.stream.Collectors.toList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocationFactory.fromDomain;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.fromLocation;\nimport static org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisitFactory.testVisit;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.plugin.planner.DistanceMapImpl;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningDepot;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningLocation;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.SolutionFactory;\nimport org.optaweb.vehiclerouting.plugin.planner.domain.VehicleRoutingSolution;\nimport org.optaweb.vehiclerouting.plugin.planner.weight.DepotAngleVisitDifficultyWeightFactory.DepotAngleVisitDifficultyWeight;\n\nclass DepotAngleVisitDifficultyWeightFactoryTest {\n\n    private final double depotY = 3.0;\n    private final double depotX = -50.0;\n\n    private final Map<Long, Distance> depotDistanceMap = new HashMap<>();\n    private final PlanningLocation depot;\n    private final VehicleRoutingSolution solution = SolutionFactory.emptySolution();\n    private final DepotAngleVisitDifficultyWeightFactory weightFactory = new DepotAngleVisitDifficultyWeightFactory();\n\n    DepotAngleVisitDifficultyWeightFactoryTest() {\n        Location depotLocation = new Location(0, Coordinates.of(depotY, depotX));\n        depot = fromDomain(depotLocation, new DistanceMapImpl(depotDistanceMap::get));\n        solution.getDepotList().add(new PlanningDepot(depot));\n    }\n\n    private PlanningLocation location(long id, double latitude, double longitude, long symmetricalDistance) {\n        return location(id, latitude, longitude, symmetricalDistance, symmetricalDistance);\n    }\n\n    private PlanningLocation location(\n            long id,\n            double latitude,\n            double longitude,\n            long depotToLocation,\n            long locationToDepot) {\n        depotDistanceMap.put(id, Distance.ofMillis(depotToLocation));\n        Map<Long, Distance> locationDistanceMap = new HashMap<>();\n        locationDistanceMap.put(depot.getId(), Distance.ofMillis(locationToDepot));\n        Location domainLocation = new Location(id, Coordinates.of(latitude, longitude));\n        return fromDomain(domainLocation, new DistanceMapImpl(locationDistanceMap::get));\n    }\n\n    private DepotAngleVisitDifficultyWeight weight(PlanningLocation location) {\n        return weightFactory.createSorterWeight(solution, fromLocation(location));\n    }\n\n    @Test\n    void visit_weights_should_be_ordered_by_angle_then_by_distance_then_by_id() {\n        // angle 0 (same as west) distance or ID will decide\n        PlanningLocation center1 = location(1, depotY, depotX, 0);\n        PlanningLocation center2 = location(2, depotY, depotX, 0);\n        PlanningLocation west = location(3, depotY, depotX - 100, 1);\n\n        // both east (same angle), distance will decide\n        // east1 is closer to depot than east2\n        PlanningLocation east1 = location(10, depotY, depotX + 37, 100);\n        PlanningLocation east2 = location(20, depotY, depotX + 110.011, 200);\n\n        // both north (same angle), distance will decide\n        // north1 is closer to depot than north2\n        PlanningLocation north1 = location(30, depotY + 30.0, depotX, 1);\n        PlanningLocation north2 = location(40, depotY + 60.0, depotX, 2);\n\n        // all different angle, distance doesn't matter\n        PlanningLocation sw1 = location(50, depotY - 100, depotX - 100, 10_000);\n        PlanningLocation south1 = location(60, depotY - 100, depotX, 10_000);\n        PlanningLocation se1 = location(70, depotY - 100, depotX + 100, 10_000);\n\n        // E < NE < N < NW < W < SW < S < SE < E (-π → π)\n        assertThat(Stream.of(north1, north2, center1, center2, west, sw1, south1, se1, east1, east2)\n                .map(this::weight)\n                .collect(toList())).isSorted();\n\n        assertThat(weight(north1)).isLessThan(weight(north2));\n        assertThat(weight(north2)).isGreaterThan(weight(north1));\n\n        assertThat(weight(center1)).isLessThan(weight(center2));\n        assertThat(weight(center2)).isGreaterThan(weight(center1));\n        assertThat(weight(center2)).isEqualByComparingTo(weight(center2));\n    }\n\n    @Test\n    void locations_with_asymmetrical_distances_should_be_sorted_by_round_trip_time() {\n        // coordinates only affect angle, distance is stored in the distance map\n        // round-trip: 191 (a < b, although depot→a > depot→b)\n        PlanningLocation a = location(101, depotY, depotX, 101, 90);\n        // round-trip: 200\n        PlanningLocation b = location(102, depotY, depotX, 100, 100);\n        // round-trip: 250 (c > b, although c→depot < b→depot)\n        PlanningLocation c = location(103, depotY, depotX, 200, 50);\n\n        assertThat(weight(a)).isLessThan(weight(b));\n        assertThat(weight(b)).isLessThan(weight(c));\n    }\n\n    @Test\n    void equals() {\n        long id = 3;\n        double angle = Math.PI;\n        long distance = 1000;\n        PlanningVisit visit = testVisit(id);\n        DepotAngleVisitDifficultyWeight weight = new DepotAngleVisitDifficultyWeight(visit, angle, distance);\n\n        assertThat(weight)\n                .isNotEqualTo(null)\n                .isNotEqualTo(this)\n                .isNotEqualTo(new DepotAngleVisitDifficultyWeight(testVisit(id + 1), angle, distance))\n                .isNotEqualTo(new DepotAngleVisitDifficultyWeight(testVisit(id), -angle, distance))\n                .isNotEqualTo(new DepotAngleVisitDifficultyWeight(testVisit(id), angle, distance - 1))\n                .isEqualTo(weight)\n                .isEqualTo(new DepotAngleVisitDifficultyWeight(visit, angle, distance));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/ClearResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n@ExtendWith(MockitoExtension.class)\nclass ClearResourceTest {\n\n    @Mock\n    private LocationService locationService;\n    @Mock\n    private VehicleService vehicleService;\n    @InjectMocks\n    private ClearResource clearResource;\n\n    @Test\n    void clear() {\n        clearResource.clear();\n        verify(locationService).removeAll();\n        verify(vehicleService).removeAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/DataSetDownloadResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport java.io.IOException;\n\nimport javax.ws.rs.core.HttpHeaders;\nimport javax.ws.rs.core.MultivaluedMap;\nimport javax.ws.rs.core.Response;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\n\n@ExtendWith(MockitoExtension.class)\nclass DataSetDownloadResourceTest {\n\n    @Mock\n    private DemoService demoService;\n    @InjectMocks\n    private DataSetDownloadResource controller;\n\n    @Test\n    void export() throws IOException {\n        // arrange\n        String msg = \"dummy string\";\n        when(demoService.exportDataSet()).thenReturn(msg);\n\n        // act\n        Response response = controller.exportDataSet();\n\n        // assert\n        assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());\n        MultivaluedMap<String, Object> headers = response.getHeaders();\n        // String.length() works here because the message is ASCII\n        assertThat(headers.getFirst(HttpHeaders.CONTENT_LENGTH)).isEqualTo(msg.length());\n        assertThat(headers.getFirst(HttpHeaders.CONTENT_TYPE)).isNotNull();\n        assertThat(headers.getFirst(HttpHeaders.CONTENT_TYPE).toString())\n                .isEqualToIgnoringWhitespace(\"text/x-yaml;charset=UTF-8\");\n        assertThat(headers.getFirst(HttpHeaders.CONTENT_DISPOSITION)).isNotNull();\n        String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION).toString();\n        assertThat(contentDisposition)\n                .startsWith(\"attachment;\")\n                .containsPattern(\"; *filename=\\\".*\\\\.yaml\\\"\");\n    }\n\n    @Test\n    void content_length_should_be_number_of_bytes() throws IOException {\n        // Nice illustration of the problem: https://sankhs.com/2016/03/17/content-length-http-headers/\n        // If the content-length header is less than number of bytes, part of the response body thrown away!\n        // So if we sent \"অhello\" with content-length: 6, the client (browser) would only present \"অhel\".\n\n        // arrange\n        String msg = \"অ\";\n        when(demoService.exportDataSet()).thenReturn(msg);\n\n        // act\n        Response response = controller.exportDataSet();\n\n        // assert\n        assertThat(response.getHeaderString(HttpHeaders.CONTENT_LENGTH)).isEqualTo(\"3\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/DemoResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\n\n@ExtendWith(MockitoExtension.class)\nclass DemoResourceTest {\n\n    @Mock\n    private DemoService demoService;\n    @InjectMocks\n    private DemoResource demoResource;\n\n    @Test\n    void demo() {\n        String problemName = \"xy\";\n        demoResource.loadDemo(problemName);\n        verify(demoService).loadDemo(problemName);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/LocationResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableLocation;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\n\n@ExtendWith(MockitoExtension.class)\nclass LocationResourceTest {\n\n    @Mock\n    private LocationService locationService;\n    @InjectMocks\n    private LocationResource locationResource;\n\n    @Test\n    void addLocation() {\n        Coordinates coords = Coordinates.of(0.0, 1.0);\n        String description = \"new location\";\n        PortableLocation request = new PortableLocation(321, coords.latitude(), coords.longitude(), description);\n        locationResource.addLocation(request);\n        verify(locationService).createLocation(coords, description);\n    }\n\n    @Test\n    void removeLocation() {\n        locationResource.deleteLocation(9L);\n        verify(locationService).removeLocation(9);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/ServerInfoResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.plugin.rest.model.PortableCoordinates;\nimport org.optaweb.vehiclerouting.plugin.rest.model.RoutingProblemInfo;\nimport org.optaweb.vehiclerouting.plugin.rest.model.ServerInfo;\nimport org.optaweb.vehiclerouting.service.demo.DemoService;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\nimport org.optaweb.vehiclerouting.service.region.RegionService;\n\n@ExtendWith(MockitoExtension.class)\nclass ServerInfoResourceTest {\n\n    @Mock\n    private RegionService regionService;\n    @Mock\n    private DemoService demoService;\n    @InjectMocks\n    private ServerInfoResource serverInfoResource;\n\n    @Test\n    void serverInfo() {\n        // arrange\n        List<String> countryCodes = Arrays.asList(\"XY\", \"WZ\");\n        when(regionService.countryCodes()).thenReturn(countryCodes);\n\n        Coordinates southWest = Coordinates.of(-1.0, -2.0);\n        Coordinates northEast = Coordinates.of(1.0, 2.0);\n        BoundingBox boundingBox = new BoundingBox(southWest, northEast);\n        when(regionService.boundingBox()).thenReturn(boundingBox);\n\n        Location depot = new Location(1, Coordinates.of(1.0, 7), \"Depot\");\n        List<Location> visits = Arrays.asList(new Location(2, Coordinates.of(2.0, 9), \"Visit\"));\n        List<Vehicle> vehicles = Arrays.asList(VehicleFactory.testVehicle(1));\n        String demoName = \"Testing problem\";\n        RoutingProblem routingProblem = new RoutingProblem(demoName, vehicles, depot, visits);\n        when(demoService.demos()).thenReturn(Arrays.asList(routingProblem));\n\n        // act\n        ServerInfo serverInfo = serverInfoResource.serverInfo();\n\n        // assert\n        assertThat(serverInfo.getCountryCodes()).isEqualTo(countryCodes);\n        assertThat(serverInfo.getBoundingBox()).containsExactly(\n                PortableCoordinates.fromCoordinates(southWest),\n                PortableCoordinates.fromCoordinates(northEast));\n        List<RoutingProblemInfo> demos = serverInfo.getDemos();\n        Assertions.assertThat(demos).hasSize(1);\n        RoutingProblemInfo demo = demos.get(0);\n        assertThat(demo.getName()).isEqualTo(demoName);\n        assertThat(demo.getVisits()).isEqualTo(visits.size());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/VehicleResourceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest;\n\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n@ExtendWith(MockitoExtension.class)\nclass VehicleResourceTest {\n\n    @Mock\n    private VehicleService vehicleService;\n    @InjectMocks\n    private VehicleResource vehicleResource;\n\n    @Test\n    void addVehicle() {\n        vehicleResource.addVehicle();\n        verify(vehicleService).createVehicle();\n    }\n\n    @Test\n    void removeVehicle() {\n        vehicleResource.removeVehicle(11L);\n        verify(vehicleService).removeVehicle(11);\n    }\n\n    @Test\n    void removeAnyVehicle() {\n        vehicleResource.removeAnyVehicle();\n        verify(vehicleService).removeAnyVehicle();\n    }\n\n    @Test\n    void changeCapacity() {\n        long vehicleId = 2000;\n        int capacity = 50;\n        vehicleResource.changeCapacity(vehicleId, capacity);\n        verify(vehicleService).changeCapacity(vehicleId, capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableCoordinatesTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\n\nclass PortableCoordinatesTest {\n\n    @Test\n    void marshal_to_json() {\n        // values are tweaked to enforce rounding to 5 decimal places\n        PortableCoordinates portableCoordinates = new PortableCoordinates(\n                BigDecimal.valueOf(0.123454321),\n                BigDecimal.valueOf(-44.444445111));\n        JacksonAssertions.assertThat(portableCoordinates).serializedIsEqualToJson(\"{\\\"lat\\\":0.12345,\\\"lng\\\":-44.44445}\");\n    }\n\n    @Test\n    void conversion_from_domain() {\n        Coordinates coordinates = Coordinates.of(0.04687, -88.8889);\n        PortableCoordinates portableCoordinates = PortableCoordinates.fromCoordinates(coordinates);\n        assertThat(portableCoordinates.getLatitude()).isEqualTo(coordinates.latitude());\n        assertThat(portableCoordinates.getLongitude()).isEqualTo(coordinates.longitude());\n\n        assertThatNullPointerException()\n                .isThrownBy(() -> PortableCoordinates.fromCoordinates(null))\n                .withMessageContaining(\"coordinates\");\n    }\n\n    @Test\n    void should_reduce_scale_if_needed() {\n        Coordinates coordinates = Coordinates.of(0.123450001, -88.999999999);\n        Coordinates scaledDown = Coordinates.of(0.12345, -89);\n        PortableCoordinates portableCoordinates = PortableCoordinates.fromCoordinates(coordinates);\n        assertThat(portableCoordinates.getLatitude()).isEqualTo(scaledDown.latitude());\n        assertThat(portableCoordinates.getLongitude()).isEqualByComparingTo(scaledDown.longitude());\n        // This would surprisingly fail because actual is -89 and expected is -89.0\n        // assertThat(portableCoordinates.getLongitude()).isEqualTo(scaledDown.longitude());\n    }\n\n    @Test\n    void equals_hashCode_toString() {\n        BigDecimal lat1 = BigDecimal.valueOf(10.0101);\n        BigDecimal lat2 = BigDecimal.valueOf(20.2323);\n        BigDecimal lon1 = BigDecimal.valueOf(-8.7);\n        BigDecimal lon2 = BigDecimal.valueOf(-7.8);\n        PortableCoordinates portableCoordinates = new PortableCoordinates(lat1, lon1);\n\n        assertThat(portableCoordinates)\n                // equals()\n                .isNotEqualTo(null)\n                .isNotEqualTo(new Coordinates(lat1, lon1))\n                .isNotEqualTo(new PortableCoordinates(lat1, lon2))\n                .isNotEqualTo(new PortableCoordinates(lat2, lon1))\n                .isEqualTo(portableCoordinates)\n                .isEqualTo(new PortableCoordinates(lat1, lon1))\n                // hasCode()\n                .hasSameHashCodeAs(new PortableCoordinates(lat1, lon1))\n                // toString()\n                .asString().contains(lat1.toPlainString(), lon1.toPlainString());\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableDistanceTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\n\nclass PortableDistanceTest {\n\n    @Test\n    void marshal_to_json() {\n        Distance distance = Distance.ofMillis(3_661_987);\n        PortableDistance portableDistance = PortableDistance.fromDistance(distance);\n        JacksonAssertions.assertThat(portableDistance).serializedIsEqualToJson(\"\\\"1h 1m 2s\\\"\");\n    }\n\n    @Test\n    void from_distance() {\n        assertThatNullPointerException().isThrownBy(() -> PortableDistance.fromDistance(null));\n    }\n\n    @Test\n    void equals_hashCode_toString() {\n        long millis = 173_000;\n        Distance distance = Distance.ofMillis(millis);\n        PortableDistance portableDistance = PortableDistance.fromDistance(distance);\n\n        assertThat(portableDistance)\n                // equals()\n                .isEqualTo(portableDistance)\n                .isEqualTo(PortableDistance.fromDistance(distance))\n                .isNotEqualTo(null)\n                .isNotEqualTo(millis)\n                .isNotEqualTo(PortableDistance.fromDistance(Distance.ofMillis(millis - 501)))\n                // hashCode()\n                .hasSameHashCodeAs(PortableDistance.fromDistance(distance))\n                // toString()\n                .asString().contains(\"0h 2m 53s\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableErrorMessageTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.service.error.ErrorMessage;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\nimport org.optaweb.vehiclerouting.util.junit.FileContent;\n\nclass PortableErrorMessageTest {\n\n    @Test\n    void marshal_to_json(@FileContent(\"portable-error-message.json\") String expectedJson) {\n        String id = \"c670dd37-62fb-4e86-95ed-c1f4953aaeaa\";\n        String text = \"Error message.\\nDetails.\";\n        PortableErrorMessage portableErrorMessage = PortableErrorMessage.fromMessage(ErrorMessage.of(id, text));\n        JacksonAssertions.assertThat(portableErrorMessage).serializedIsEqualToJson(expectedJson);\n    }\n\n    @Test\n    void factory_method() {\n        String id = \"id\";\n        String text = \"error\";\n        PortableErrorMessage portableErrorMessage = PortableErrorMessage.fromMessage(ErrorMessage.of(id, text));\n        assertThat(portableErrorMessage.getId()).isEqualTo(id);\n        assertThat(portableErrorMessage.getText()).isEqualTo(text);\n    }\n\n    @Test\n    void equals_hashCode_toString() {\n        String id = \"1\";\n        String text = \"error message\";\n        ErrorMessage message = ErrorMessage.of(id, text);\n        PortableErrorMessage portableErrorMessage = PortableErrorMessage.fromMessage(message);\n\n        assertThat(portableErrorMessage).isNotEqualTo(null)\n                // equals()\n                .isNotEqualTo(new PortableErrorMessage(\"\", text))\n                .isNotEqualTo(new PortableErrorMessage(id, \"\"))\n                .isNotEqualTo(message)\n                .isEqualTo(portableErrorMessage)\n                .isEqualTo(new PortableErrorMessage(id, text))\n                // hasCode()\n                .hasSameHashCodeAs(new PortableErrorMessage(id, text))\n                // toString()\n                .asString().contains(id, text);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableLocationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\nimport org.optaweb.vehiclerouting.util.junit.FileContent;\n\nclass PortableLocationTest {\n\n    private final PortableLocation portableLocation = new PortableLocation(\n            987,\n            BigDecimal.ONE,\n            BigDecimal.TEN,\n            \"Some Location\");\n\n    @Test\n    void marshal_to_json(@FileContent(\"portable-location.json\") String json) {\n        JacksonAssertions.assertThat(portableLocation).serializedIsEqualToJson(json);\n    }\n\n    @Test\n    void unmarshal_from_json(@FileContent(\"portable-location.json\") String json) {\n        JacksonAssertions.assertThat(json).deserializedIsEqualTo(portableLocation);\n    }\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(\n                () -> new PortableLocation(1, null, BigDecimal.ZERO, \"\"));\n        assertThatNullPointerException().isThrownBy(\n                () -> new PortableLocation(1, BigDecimal.ZERO, null, \"\"));\n        assertThatNullPointerException().isThrownBy(\n                () -> new PortableLocation(1, BigDecimal.ZERO, BigDecimal.ZERO, null));\n    }\n\n    @Test\n    void fromLocation() {\n        Location location = new Location(17, Coordinates.of(5.1, -0.0007), \"Hello, world!\");\n        PortableLocation portableLocation = PortableLocation.fromLocation(location);\n        assertThat(portableLocation.getId()).isEqualTo(location.id());\n        assertThat(portableLocation.getLatitude()).isEqualTo(location.coordinates().latitude());\n        assertThat(portableLocation.getLongitude()).isEqualTo(location.coordinates().longitude());\n        assertThat(portableLocation.getDescription()).isEqualTo(location.description());\n\n        assertThatNullPointerException()\n                .isThrownBy(() -> PortableLocation.fromLocation(null))\n                .withMessageContaining(\"location\");\n    }\n\n    @Test\n    void equals_hashCode_toString() {\n        long id = 123456;\n        String description = \"x y\";\n        BigDecimal lat1 = BigDecimal.valueOf(10.0101);\n        BigDecimal lat2 = BigDecimal.valueOf(20.2323);\n        BigDecimal lon1 = BigDecimal.valueOf(-8.7);\n        BigDecimal lon2 = BigDecimal.valueOf(-7.8);\n        PortableLocation portableLocation = new PortableLocation(id, lat1, lon1, description);\n\n        assertThat(portableLocation)\n                // equals()\n                .isNotEqualTo(null)\n                .isNotEqualTo(new Location(id, new Coordinates(lat1, lon1)))\n                .isNotEqualTo(new PortableLocation(id + 1, lat1, lon1, description))\n                .isNotEqualTo(new PortableLocation(id, lat1, lon2, description))\n                .isNotEqualTo(new PortableLocation(id, lat2, lon1, description))\n                .isNotEqualTo(new PortableLocation(id, lat1, lon1, \"y x\"))\n                .isEqualTo(portableLocation)\n                .isEqualTo(new PortableLocation(id, lat1, lon1, description))\n                // hasCode()\n                .hasSameHashCodeAs(new PortableLocation(id, lat1, lon1, description))\n                // toString()\n                .asString()\n                .contains(\n                        String.valueOf(id),\n                        lat1.toPlainString(),\n                        lon1.toPlainString(),\n                        description);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableRouteTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static java.util.Arrays.asList;\nimport static org.optaweb.vehiclerouting.plugin.rest.model.PortableCoordinates.fromCoordinates;\nimport static org.optaweb.vehiclerouting.plugin.rest.model.PortableLocation.fromLocation;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\nimport org.optaweb.vehiclerouting.util.junit.FileContent;\n\nclass PortableRouteTest {\n\n    @Test\n    void marshal_to_json(@FileContent(\"portable-route.json\") String expectedJson) {\n        PortableVehicle vehicle = new PortableVehicle(13, \"Vehicle\", 45317);\n        PortableLocation depot = visit(8, 42.6501218, -71.8835449, \"Test depot\");\n        PortableLocation visit1 = visit(100, 42.7066596, -72.4934873, \"Visit 1\");\n        PortableLocation visit2 = visit(200, 42.5543343, -71.4438280, \"Visit 2\");\n\n        PortableRoute portableRoute = new PortableRoute(\n                vehicle,\n                depot,\n                asList(visit1, visit2),\n                asList(\n                        asList(\n                                coordinates(42.65005, -71.88522),\n                                coordinates(42.64997, -71.88527)),\n                        asList(\n                                coordinates(42.64994, -71.88537),\n                                coordinates(42.64994, -71.88542))));\n        JacksonAssertions.assertThat(portableRoute).serializedIsEqualToJson(expectedJson);\n    }\n\n    private static PortableLocation visit(long id, double latitude, double longitude, String description) {\n        return fromLocation(new Location(id, Coordinates.of(latitude, longitude), description));\n    }\n\n    private static PortableCoordinates coordinates(double latitude, double longitude) {\n        return fromCoordinates(Coordinates.of(latitude, longitude));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableRoutingPlanFactoryTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.Route;\nimport org.optaweb.vehiclerouting.domain.RouteWithTrack;\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\nclass PortableRoutingPlanFactoryTest {\n\n    @Test\n    void portable_routing_plan_empty() {\n        PortableRoutingPlan portablePlan = PortableRoutingPlanFactory.fromRoutingPlan(RoutingPlan.empty());\n        assertThat(portablePlan.getDistance()).isEqualTo(PortableDistance.fromDistance(Distance.ZERO));\n        assertThat(portablePlan.getVehicles()).isEmpty();\n        assertThat(portablePlan.getDepot()).isNull();\n        assertThat(portablePlan.getRoutes()).isEmpty();\n    }\n\n    @Test\n    void portable_routing_plan_with_two_routes() {\n        // arrange\n        final Coordinates coordinates1 = Coordinates.of(0.0, 0.1);\n        final Coordinates coordinates2 = Coordinates.of(2.0, -0.2);\n        final Coordinates coordinates3 = Coordinates.of(3.3, -3.3);\n        final Coordinates checkpoint12 = Coordinates.of(12, 12);\n        final Coordinates checkpoint21 = Coordinates.of(21, 21);\n        final Coordinates checkpoint13 = Coordinates.of(13, 13);\n        final Coordinates checkpoint31 = Coordinates.of(31, 31);\n        List<Coordinates> segment12 = asList(coordinates1, checkpoint12, coordinates2);\n        List<Coordinates> segment21 = asList(coordinates2, checkpoint21, coordinates1);\n        List<Coordinates> segment13 = asList(coordinates1, checkpoint13, coordinates3);\n        List<Coordinates> segment31 = asList(coordinates3, checkpoint31, coordinates1);\n\n        final Location location1 = new Location(1, coordinates1);\n        final Location location2 = new Location(2, coordinates2);\n        final Location location3 = new Location(3, coordinates3);\n        final Distance distance = Distance.ofMillis(5);\n\n        final Vehicle vehicle1 = VehicleFactory.createVehicle(1, \"Vehicle 1\", 100);\n        final Vehicle vehicle2 = VehicleFactory.createVehicle(2, \"Vehicle 2\", 200);\n\n        RouteWithTrack route1 = new RouteWithTrack(\n                new Route(vehicle1, location1, singletonList(location2)),\n                asList(segment12, segment21));\n        RouteWithTrack route2 = new RouteWithTrack(\n                new Route(vehicle2, location1, singletonList(location3)),\n                asList(segment13, segment31));\n\n        RoutingPlan routingPlan = new RoutingPlan(\n                distance,\n                asList(vehicle1, vehicle2),\n                location1,\n                asList(location2, location3),\n                asList(route1, route2));\n\n        // act\n        PortableRoutingPlan portableRoutingPlan = PortableRoutingPlanFactory.fromRoutingPlan(routingPlan);\n\n        // assert\n        // -- plan.distance\n        assertThat(portableRoutingPlan.getDistance()).isEqualTo(PortableDistance.fromDistance(distance));\n        // -- plan.depot\n        assertThat(portableRoutingPlan.getDepot()).isEqualTo(PortableLocation.fromLocation(location1));\n        // -- plan.visits\n        assertThat(portableRoutingPlan.getVisits()).containsExactlyInAnyOrder(\n                PortableLocation.fromLocation(location2),\n                PortableLocation.fromLocation(location3));\n        // -- plan.routes\n        assertThat(portableRoutingPlan.getRoutes()).hasSize(2);\n        // -- plan.vehicles\n        assertThat(portableRoutingPlan.getVehicles()).containsExactlyInAnyOrder(\n                PortableVehicle.fromVehicle(vehicle1),\n                PortableVehicle.fromVehicle(vehicle2));\n\n        // -- plan.routes[1]\n        PortableRoute portableRoute1 = portableRoutingPlan.getRoutes().get(0);\n\n        assertThat(portableRoute1.getVehicle()).isEqualTo(PortableVehicle.fromVehicle(vehicle1));\n        assertThat(portableRoute1.getDepot()).isEqualTo(PortableLocation.fromLocation(location1));\n        assertThat(portableRoute1.getVisits()).containsExactly(\n                PortableLocation.fromLocation(location2));\n        assertThat(portableRoute1.getTrack()).hasSize(2);\n        assertThat(portableRoute1.getTrack().get(0)).containsExactly(\n                PortableCoordinates.fromCoordinates(location1.coordinates()),\n                PortableCoordinates.fromCoordinates(checkpoint12),\n                PortableCoordinates.fromCoordinates(location2.coordinates()));\n        assertThat(portableRoute1.getTrack().get(1)).containsExactly(\n                PortableCoordinates.fromCoordinates(location2.coordinates()),\n                PortableCoordinates.fromCoordinates(checkpoint21),\n                PortableCoordinates.fromCoordinates(location1.coordinates()));\n\n        // -- plan.routes[2]\n        PortableRoute portableRoute2 = portableRoutingPlan.getRoutes().get(1);\n\n        assertThat(portableRoute2.getVehicle()).isEqualTo(PortableVehicle.fromVehicle(vehicle2));\n        assertThat(portableRoute2.getDepot()).isEqualTo(PortableLocation.fromLocation(location1));\n        assertThat(portableRoute2.getVisits()).containsExactly(\n                PortableLocation.fromLocation(location3));\n        assertThat(portableRoute2.getTrack()).hasSize(2);\n        assertThat(portableRoute2.getTrack().get(0)).containsExactly(\n                PortableCoordinates.fromCoordinates(location1.coordinates()),\n                PortableCoordinates.fromCoordinates(checkpoint13),\n                PortableCoordinates.fromCoordinates(location3.coordinates()));\n        assertThat(portableRoute2.getTrack().get(1)).containsExactly(\n                PortableCoordinates.fromCoordinates(location3.coordinates()),\n                PortableCoordinates.fromCoordinates(checkpoint31),\n                PortableCoordinates.fromCoordinates(location1.coordinates()));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/rest/model/PortableVehicleTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.rest.model;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.util.jackson.JacksonAssertions;\n\nclass PortableVehicleTest {\n\n    @Test\n    void marshall_to_json() {\n        long id = 321;\n        String name = \"Pink: {XY-123} \\\"B\\\"\";\n        int capacity = 78;\n        PortableVehicle portableVehicle = new PortableVehicle(id, name, capacity);\n        String jsonTemplate = \"{\\\"id\\\":%d,\\\"name\\\":\\\"%s\\\",\\\"capacity\\\":%d}\";\n        String expected = String.format(jsonTemplate, id, name.replaceAll(\"\\\"\", \"\\\\\\\\\\\"\"), capacity);\n        JacksonAssertions.assertThat(portableVehicle).serializedIsEqualToJson(expected);\n    }\n\n    @Test\n    void constructor_params_must_not_be_null() {\n        assertThatNullPointerException().isThrownBy(() -> new PortableVehicle(1, null, 2));\n    }\n\n    @Test\n    void fromVehicle() {\n        long id = 321;\n        String name = \"Pink XY-123 B\";\n        int capacity = 31;\n        PortableVehicle portableVehicle = PortableVehicle.fromVehicle(VehicleFactory.createVehicle(id, name, capacity));\n        assertThat(portableVehicle.getId()).isEqualTo(id);\n        assertThat(portableVehicle.getName()).isEqualTo(name);\n        assertThat(portableVehicle.getCapacity()).isEqualTo(capacity);\n\n        assertThatNullPointerException()\n                .isThrownBy(() -> PortableVehicle.fromVehicle(null))\n                .withMessageContaining(\"vehicle\");\n    }\n\n    @Test\n    void equals_hashCode_toString() {\n        long id = 123456;\n        String name = \"x y\";\n        int capacity = 444111;\n        PortableVehicle portableVehicle = new PortableVehicle(id, name, capacity);\n\n        assertThat(portableVehicle)\n                // equals()\n                .isNotEqualTo(null)\n                .isNotEqualTo(VehicleFactory.createVehicle(id, name, capacity))\n                .isNotEqualTo(new PortableVehicle(id + 1, name, capacity))\n                .isNotEqualTo(new PortableVehicle(id, name + \"z\", capacity))\n                .isNotEqualTo(new PortableVehicle(id, name, capacity + 1))\n                .isEqualTo(portableVehicle)\n                .isEqualTo(new PortableVehicle(id, name, capacity))\n                // hasCode()\n                .hasSameHashCodeAs(new PortableVehicle(id, name, capacity))\n                // toString()\n                .asString()\n                .contains(\n                        String.valueOf(id),\n                        name,\n                        String.valueOf(capacity));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/routing/AirDistanceRouterTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\n\nclass AirDistanceRouterTest {\n\n    @Test\n    void travel_time_should_be_distance_divided_by_speed() {\n        AirDistanceRouter router = new AirDistanceRouter();\n        Coordinates from = Coordinates.of(0, 0);\n        Coordinates to = Coordinates.of(3, 4); // √(3² + 4²) = 5\n        long travelTimeMillis = router.travelTimeMillis(from, to);\n        assertThat(travelTimeMillis).isEqualTo((long) (5\n                * AirDistanceRouter.KILOMETERS_PER_DEGREE\n                / AirDistanceRouter.TRAVEL_SPEED_KPH\n                * AirDistanceRouter.MILLIS_IN_ONE_HOUR));\n    }\n\n    @Test\n    void bounding_box_is_the_whole_globe() {\n        BoundingBox bounds = new AirDistanceRouter().getBounds();\n        assertThat(bounds.getSouthWest()).isEqualTo(Coordinates.of(-90, -180));\n        assertThat(bounds.getNorthEast()).isEqualTo(Coordinates.of(90, 180));\n    }\n\n    @Test\n    void path_from_a_to_b_should_be_the_line_ab() {\n        AirDistanceRouter router = new AirDistanceRouter();\n        Coordinates from = Coordinates.of(0, 0);\n        Coordinates to = Coordinates.of(3, 4);\n        assertThat(router.getPath(from, to)).containsExactly(from, to);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/routing/GraphHopperIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport static org.assertj.core.api.Assertions.assertThatCode;\n\nimport java.nio.file.Path;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport com.graphhopper.GraphHopper;\nimport com.graphhopper.config.Profile;\n\nclass GraphHopperIntegrationTest {\n\n    private static final String OSM_PBF = \"planet_12.032,53.0171_12.1024,53.0491.osm.pbf\";\n\n    @Test\n    void graphhopper_should_import_and_load_osm_file_successfully(@TempDir Path tempDir) {\n        Path graphhopperDir = tempDir.resolve(\"graphhopper\");\n        GraphHopper graphHopper = new GraphHopper();\n        graphHopper.setGraphHopperLocation(graphhopperDir.toString());\n        graphHopper.setOSMFile(GraphHopperIntegrationTest.class.getResource(OSM_PBF).getFile());\n        graphHopper.setProfiles(new Profile(Constants.GRAPHHOPPER_PROFILE).setVehicle(\"car\").setWeighting(\"fastest\"));\n        assertThatCode(graphHopper::importOrLoad).doesNotThrowAnyException();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/routing/GraphHopperRouterTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.service.region.BoundingBox;\n\nimport com.graphhopper.GHRequest;\nimport com.graphhopper.GHResponse;\nimport com.graphhopper.GraphHopper;\nimport com.graphhopper.ResponsePath;\nimport com.graphhopper.storage.BaseGraph;\nimport com.graphhopper.util.PointList;\nimport com.graphhopper.util.shapes.BBox;\n\n@ExtendWith(MockitoExtension.class)\nclass GraphHopperRouterTest {\n\n    private final PointList pointList = new PointList();\n    private final Coordinates from = Coordinates.of(-Double.MIN_VALUE, Double.MIN_VALUE);\n    private final Coordinates to = Coordinates.of(Double.MAX_VALUE, -Double.MAX_VALUE);\n    @Mock\n    private GraphHopper graphHopper;\n    @Mock\n    private GHResponse ghResponse;\n    @Mock\n    private ResponsePath pathWrapper;\n    @Mock\n    private BaseGraph baseGraph;\n\n    private void whenRouteReturnResponse() {\n        when(graphHopper.route(any(GHRequest.class))).thenReturn(ghResponse);\n    }\n\n    private void whenBestReturnPath() {\n        when(ghResponse.getBest()).thenReturn(pathWrapper);\n    }\n\n    @Test\n    void travel_time_should_return_graphhopper_time() {\n        // arrange\n        whenRouteReturnResponse();\n        whenBestReturnPath();\n        long travelTimeMillis = 135 * 60 * 60 * 1000;\n        when(pathWrapper.getTime()).thenReturn(travelTimeMillis);\n\n        // act & assert\n        assertThat(new GraphHopperRouter(graphHopper).travelTimeMillis(from, to)).isEqualTo(travelTimeMillis);\n    }\n\n    @Test\n    void getDistance_should_throw_exception_when_no_route_exists() {\n        // arrange\n        whenRouteReturnResponse();\n        when(ghResponse.hasErrors()).thenReturn(true);\n        when(ghResponse.getErrors()).thenReturn(Collections.singletonList(new RuntimeException()));\n        GraphHopperRouter graphHopperRouter = new GraphHopperRouter(graphHopper);\n\n        // act & assert\n        assertThatThrownBy(() -> graphHopperRouter.travelTimeMillis(from, to))\n                .isNotInstanceOf(NullPointerException.class)\n                .isInstanceOf(RuntimeException.class)\n                .hasMessageContaining(\"No route\");\n    }\n\n    @Test\n    void getRoute_should_return_graphhopper_route() {\n        // arrange\n        whenRouteReturnResponse();\n        whenBestReturnPath();\n        when(pathWrapper.getPoints()).thenReturn(pointList);\n\n        Coordinates coordinates1 = Coordinates.of(1, 1);\n        Coordinates coordinates2 = Coordinates.of(Math.E, Math.PI);\n        Coordinates coordinates3 = Coordinates.of(0.1, 1.0 / 3.0);\n\n        pointList.add(coordinates1.latitude().doubleValue(), coordinates1.longitude().doubleValue());\n        pointList.add(coordinates2.latitude().doubleValue(), coordinates2.longitude().doubleValue());\n        pointList.add(coordinates3.latitude().doubleValue(), coordinates3.longitude().doubleValue());\n\n        // act & assert\n        List<Coordinates> route = new GraphHopperRouter(graphHopper).getPath(from, to);\n        assertThat(route).containsExactly(\n                coordinates1,\n                coordinates2,\n                coordinates3);\n    }\n\n    @Test\n    void should_return_graphHopper_bounds() {\n        when(graphHopper.getBaseGraph()).thenReturn(baseGraph);\n        double minLat_Y = -90;\n        double minLon_X = -180;\n        double maxLat_Y = 90;\n        double maxLon_X = 180;\n        BBox bbox = new BBox(minLon_X, maxLon_X, minLat_Y, maxLat_Y);\n        when(baseGraph.getBounds()).thenReturn(bbox);\n\n        BoundingBox boundingBox = new GraphHopperRouter(graphHopper).getBounds();\n\n        assertThat(boundingBox.getSouthWest()).isEqualTo(Coordinates.of(minLat_Y, minLon_X));\n        assertThat(boundingBox.getNorthEast()).isEqualTo(Coordinates.of(maxLat_Y, maxLon_X));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/plugin/routing/RoutingConfigTest.java",
    "content": "package org.optaweb.vehiclerouting.plugin.routing;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nimport java.nio.file.Path;\n\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nclass RoutingConfigTest {\n\n    @Test\n    void should_throw_exception_when_url_is_malformed() {\n        Path osmFile = Mockito.mock(Path.class);\n        String malformedUrl = \"x+y\";\n        assertThatExceptionOfType(RoutingEngineException.class)\n                .isThrownBy(() -> RoutingConfig.downloadOsmFile(malformedUrl, osmFile))\n                .withMessageContaining(\"malformed\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/demo/DemoServiceTest.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.service.demo.dataset.DataSetMarshaller;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\n@ExtendWith(MockitoExtension.class)\nclass DemoServiceTest {\n\n    @Mock\n    private RoutingProblemList routingProblems;\n    @Mock\n    private LocationService locationService;\n    @Mock\n    private LocationRepository locationRepository;\n    @Mock\n    private VehicleService vehicleService;\n    @Mock\n    private VehicleRepository vehicleRepository;\n    @Mock\n    private DataSetMarshaller dataSetMarshaller;\n    @InjectMocks\n    private DemoService demoService;\n\n    @Captor\n    private ArgumentCaptor<RoutingProblem> routingProblemCaptor;\n\n    private final String problemName = \"Testing problem\";\n    private final List<VehicleData> vehicles = Arrays.asList(\n            VehicleFactory.vehicleData(\"v1\", 10),\n            VehicleFactory.vehicleData(\"v2\", 10));\n    private final Location depot = new Location(1, Coordinates.of(1.0, 7), \"Depot\");\n    private final List<Location> visits = Arrays.asList(new Location(2, Coordinates.of(2.0, 9), \"Visit\"));\n    private final RoutingProblem routingProblem = new RoutingProblem(problemName, vehicles, depot, visits);\n\n    @Test\n    void demos_should_return_routing_problems() {\n        // arrange\n        when(routingProblems.all()).thenReturn(Arrays.asList(routingProblem));\n        // act\n        Collection<RoutingProblem> problems = demoService.demos();\n        // assert\n        assertThat(problems).containsExactly(routingProblem);\n    }\n\n    @Test\n    void loadDemo() {\n        // arrange\n        Location location = new Location(10, Coordinates.of(1, 2));\n        when(routingProblems.byName(problemName)).thenReturn(routingProblem);\n        when(locationService.createLocation(any(Coordinates.class), anyString())).thenReturn(Optional.of(location));\n        // act\n        demoService.loadDemo(problemName);\n        // assert\n        verify(locationService, times(routingProblem.visits().size() + 1))\n                .createLocation(any(Coordinates.class), anyString());\n        verify(vehicleService, times(routingProblem.vehicles().size()))\n                .createVehicle(any(VehicleData.class));\n    }\n\n    @Test\n    void retry_when_adding_location_fails() {\n        when(routingProblems.byName(problemName)).thenReturn(routingProblem);\n        when(locationService.createLocation(any(Coordinates.class), anyString())).thenReturn(Optional.empty());\n        assertThatExceptionOfType(RuntimeException.class)\n                .isThrownBy(() -> demoService.loadDemo(problemName))\n                .withMessageContaining(depot.coordinates().toString());\n        verify(locationService, times(DemoService.MAX_TRIES)).createLocation(any(Coordinates.class), anyString());\n    }\n\n    @Test\n    void export_should_marshal_routing_plans_with_locations_and_vehicles_from_repository() {\n        Location depot = new Location(0, Coordinates.of(1.0, 2.0), \"Depot\");\n        Location visit1 = new Location(1, Coordinates.of(11.0, 22.0), \"Visit 1\");\n        Location visit2 = new Location(2, Coordinates.of(22.0, 33.0), \"Visit 2\");\n        Vehicle vehicle1 = VehicleFactory.createVehicle(11, \"Vehicle 1\", 100);\n        Vehicle vehicle2 = VehicleFactory.createVehicle(12, \"Vehicle 2\", 200);\n        when(locationRepository.locations()).thenReturn(Arrays.asList(depot, visit1, visit2));\n        when(vehicleRepository.vehicles()).thenReturn(Arrays.asList(vehicle1, vehicle2));\n\n        demoService.exportDataSet();\n\n        RoutingProblem routingProblem = verifyAndCaptureMarshalledProblem();\n        assertThat(routingProblem.name()).isNotNull();\n        assertThat(routingProblem.depot()).contains(depot);\n        assertThat(routingProblem.visits()).containsExactly(visit1, visit2);\n        assertThat(routingProblem.vehicles()).containsExactly(vehicle1, vehicle2);\n    }\n\n    @Test\n    void export_should_marshal_empty_routing_plan_when_repositories_empty() {\n        String result = \"empty routing plan\";\n        when(locationRepository.locations()).thenReturn(Collections.emptyList());\n        when(vehicleRepository.vehicles()).thenReturn(Collections.emptyList());\n        when(dataSetMarshaller.marshal(any())).thenReturn(result);\n\n        assertThat(demoService.exportDataSet()).isEqualTo(result);\n\n        RoutingProblem routingProblem = verifyAndCaptureMarshalledProblem();\n        assertThat(routingProblem.name()).isNotNull();\n        assertThat(routingProblem.depot()).isEmpty();\n        assertThat(routingProblem.visits()).isEmpty();\n        assertThat(routingProblem.vehicles()).isEmpty();\n    }\n\n    private RoutingProblem verifyAndCaptureMarshalledProblem() {\n        verify(dataSetMarshaller).marshal(routingProblemCaptor.capture());\n        return routingProblemCaptor.getValue();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/demo/RoutingProblemListTest.java",
    "content": "package org.optaweb.vehiclerouting.service.demo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\n\nclass RoutingProblemListTest {\n\n    @Test\n    void should_validate_constructor_arguments() {\n        assertThatNullPointerException().isThrownBy(() -> new RoutingProblemList(null));\n    }\n\n    @Test\n    void should_fail_on_duplicate_problem_names() {\n        String name = \"DUPLICATE_NAME\";\n        RoutingProblem p1 = new RoutingProblem(name, Collections.emptyList(), null, Collections.emptyList());\n        RoutingProblem p2 = new RoutingProblem(name, Collections.emptyList(), null, Collections.emptyList());\n        assertThatIllegalStateException()\n                .isThrownBy(() -> new RoutingProblemList(Stream.of(p1, p2)))\n                .withMessageContaining(name);\n    }\n\n    @Test\n    void all_by_name_should_return_expected_problems() {\n        List<VehicleData> vehicles = Collections.emptyList();\n        Location depot = new Location(0, Coordinates.of(10, -20));\n        List<Location> visits = Collections.emptyList();\n        String name1 = \"Problem A\";\n        String name2 = \"Problem B\";\n        RoutingProblemList routingProblemList = new RoutingProblemList(Stream.of(\n                new RoutingProblem(name1, vehicles, depot, visits),\n                new RoutingProblem(name2, vehicles, depot, visits)));\n\n        assertThat(routingProblemList.all()).extracting(\"name\").containsExactlyInAnyOrder(name1, name2);\n\n        assertThat(routingProblemList.byName(name1).name()).isEqualTo(name1);\n\n        assertThatIllegalArgumentException().isThrownBy(() -> routingProblemList.byName(\"Unknown problem\"));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/demo/dataset/DataSetMarshallerTest.java",
    "content": "package org.optaweb.vehiclerouting.service.demo.dataset;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.optaweb.vehiclerouting.service.demo.dataset.DataSetMarshaller.toDataSet;\nimport static org.optaweb.vehiclerouting.service.demo.dataset.DataSetMarshaller.toDomain;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.LocationData;\nimport org.optaweb.vehiclerouting.domain.RoutingProblem;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nclass DataSetMarshallerTest {\n\n    @Test\n    void unmarshal_data_set() throws IOException {\n        DataSet dataSet;\n        try (InputStream inputStream = DataSetMarshallerTest.class.getResourceAsStream(\"test-belgium.yaml\")) {\n            dataSet = new DataSetMarshaller().unmarshalToDataSet(\n                    new InputStreamReader(inputStream, StandardCharsets.UTF_8));\n        }\n        assertThat(dataSet).isNotNull();\n\n        assertThat(dataSet.getName()).isEqualTo(\"Belgium test\");\n        assertThat(dataSet.getDepot()).isNotNull();\n        assertThat(dataSet.getDepot().getLabel()).isEqualTo(\"Brussels\");\n        assertThat(dataSet.getDepot().getLatitude()).isEqualTo(50.85);\n        assertThat(dataSet.getDepot().getLongitude()).isEqualTo(4.35);\n        assertThat(dataSet.getVisits())\n                .extracting(\"label\")\n                .containsExactlyInAnyOrder(\"Aalst\", \"Châtelet\", \"La Louvière\", \"Sint-Niklaas\", \"Ypres\");\n        assertThat(dataSet.getVehicles())\n                .extracting(dataSetVehicle -> dataSetVehicle.name, dataSetVehicle -> dataSetVehicle.capacity)\n                .containsExactlyInAnyOrder(\n                        tuple(\"vehicle 1\", 10),\n                        tuple(\"vehicle 2\", 12),\n                        tuple(\"vehicle 3\", 1_000_000));\n    }\n\n    @Test\n    void marshal_data_set() {\n        DataSet dataSet = new DataSet();\n        String name = \"Test data set\";\n        dataSet.setName(name);\n        DataSetLocation depot = new DataSetLocation(\"Depot\", -1.1, -9.9);\n        DataSetLocation location1 = new DataSetLocation(\"Location 1\", 1.0, 0.1);\n        DataSetLocation location2 = new DataSetLocation(\"Location 2\", 2.0, 0.2);\n        dataSet.setDepot(depot);\n        dataSet.setVisits(Arrays.asList(location1, location2));\n        DataSetVehicle vehicle1 = new DataSetVehicle(\"Vehicle 1\", 123);\n        DataSetVehicle vehicle2 = new DataSetVehicle(\"Vehicle 2\", 222);\n        dataSet.setVehicles(Arrays.asList(vehicle1, vehicle2));\n\n        String yaml = new DataSetMarshaller().marshal(dataSet);\n        assertThat(yaml)\n                .contains(\"name: \\\"\" + name)\n                .contains(\n                        depot.getLabel(),\n                        location1.getLabel(),\n                        location2.getLabel(),\n                        vehicle1.name,\n                        vehicle2.name,\n                        String.valueOf(vehicle1.capacity),\n                        String.valueOf(vehicle2.capacity));\n    }\n\n    @Test\n    void should_rethrow_exception_from_object_mapper() throws IOException {\n        ObjectMapper objectMapper = mock(ObjectMapper.class);\n        when(objectMapper.readValue(any(Reader.class), eq(DataSet.class))).thenThrow(IOException.class);\n        assertThatIllegalStateException()\n                .isThrownBy(() -> new DataSetMarshaller(objectMapper).unmarshalToDataSet(mock(Reader.class)))\n                .withRootCauseExactlyInstanceOf(IOException.class);\n\n        when(objectMapper.writeValueAsString(any(DataSet.class))).thenThrow(JsonProcessingException.class);\n        assertThatIllegalStateException()\n                .isThrownBy(() -> new DataSetMarshaller(objectMapper).marshal(new DataSet()))\n                .withRootCauseExactlyInstanceOf(JsonProcessingException.class);\n    }\n\n    @Test\n    void location_conversion() {\n        double lat = -1.0;\n        double lng = 50.2;\n        String description = \"some location\";\n\n        // domain -> data set\n        DataSetLocation dataSetLocation = toDataSet(new LocationData(Coordinates.of(lat, lng), description));\n        assertThat(dataSetLocation.getLatitude()).isEqualTo(lat);\n        assertThat(dataSetLocation.getLongitude()).isEqualTo(lng);\n        assertThat(dataSetLocation.getLabel()).isEqualTo(description);\n\n        // data set -> domain\n        LocationData location = toDomain(dataSetLocation);\n        assertThat(location).isEqualTo(new LocationData(Coordinates.of(lat, lng), description));\n    }\n\n    @Test\n    void routing_problem_conversion() {\n        VehicleData vehicle = VehicleFactory.vehicleData(\"vehicle\", 10);\n        List<VehicleData> vehicles = Arrays.asList(vehicle);\n        LocationData depot = new LocationData(Coordinates.of(60.1, 5.78), \"Depot\");\n        LocationData visit = new LocationData(Coordinates.of(1.06, 8.75), \"Visit\");\n        List<LocationData> visits = Arrays.asList(visit);\n        String name = \"some data set\";\n\n        // domain -> data set\n        DataSet dataSet = toDataSet(new RoutingProblem(name, vehicles, depot, visits));\n        assertThat(dataSet.getName()).isEqualTo(name);\n        assertThat(dataSet.getVehicles()).hasSameSizeAs(vehicles);\n        assertThat(toDomain(dataSet.getVehicles().get(0))).isEqualTo(vehicle);\n        assertThat(toDomain(dataSet.getDepot())).isEqualTo(depot);\n        assertThat(dataSet.getVisits()).hasSameSizeAs(visits);\n        assertThat(toDomain(dataSet.getVisits().get(0))).isEqualTo(visit);\n\n        // data set -> domain\n        RoutingProblem routingProblem = toDomain(dataSet);\n        assertThat(routingProblem.name()).isEqualTo(name);\n        assertThat(routingProblem.vehicles()).containsExactly(vehicle);\n        assertThat(routingProblem.depot()).contains(depot);\n        assertThat(routingProblem.visits()).containsExactly(visit);\n    }\n\n    @Test\n    void should_convert_empty_data_set_correctly() {\n        DataSet emptyDataSet = new DataSet();\n        RoutingProblem routingProblem = toDomain(emptyDataSet);\n        assertThat(routingProblem.name()).isEmpty();\n        assertThat(routingProblem.depot()).isEmpty();\n        assertThat(routingProblem.vehicles()).isEmpty();\n        assertThat(routingProblem.visits()).isEmpty();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/distance/DistanceMatrixImplTest.java",
    "content": "package org.optaweb.vehiclerouting.service.distance;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nimport java.math.BigDecimal;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.location.DistanceMatrixRow;\n\n@ExtendWith(MockitoExtension.class)\nclass DistanceMatrixImplTest {\n\n    @Mock\n    private DistanceCalculator distanceCalculator;\n    @InjectMocks\n    private DistanceMatrixImpl distanceMatrix;\n\n    @Test\n    void should_calculate_distance_map() {\n        DistanceMatrixImpl distanceMatrix = new DistanceMatrixImpl(new MockDistanceCalculator());\n\n        Location l0 = location(100, 0);\n        Location l1 = location(111, 1);\n        Location l9neg = location(321, -9);\n\n        DistanceMatrixRow matrixRow0 = distanceMatrix.addLocation(l0);\n\n        // distance to self\n        assertThat(matrixRow0.distanceTo(l0.id())).isEqualTo(Distance.ZERO);\n        assertThat(distanceMatrix.distance(l0, l0)).isEqualTo(Distance.ZERO);\n        // distance to not yet registered location\n        assertThatIllegalArgumentException().isThrownBy(() -> matrixRow0.distanceTo(l1.id()));\n        assertThatIllegalArgumentException().isThrownBy(() -> distanceMatrix.distance(l0, l1));\n        assertThatIllegalArgumentException().isThrownBy(() -> distanceMatrix.distance(l1, l0));\n        assertThatIllegalArgumentException().isThrownBy(() -> distanceMatrix.distance(l1, l1));\n\n        DistanceMatrixRow matrixRow1 = distanceMatrix.addLocation(l1);\n        // distance to self\n        assertThat(matrixRow1.distanceTo(l1.id())).isEqualTo(Distance.ZERO);\n        assertThat(distanceMatrix.distance(l1, l1)).isEqualTo(Distance.ZERO);\n\n        // distance 0 <-> 1\n        assertThat(matrixRow1.distanceTo(l0.id())).isEqualTo(Distance.ofMillis(1));\n        assertThat(distanceMatrix.distance(l0, l1)).isEqualTo(Distance.ofMillis(1));\n        assertThat(matrixRow0.distanceTo(l1.id())).isEqualTo(Distance.ofMillis(1));\n        assertThat(distanceMatrix.distance(l1, l0)).isEqualTo(Distance.ofMillis(1));\n\n        DistanceMatrixRow matrixRow9 = distanceMatrix.addLocation(l9neg);\n\n        // distances -9 -> {0, 1}\n        assertThat(matrixRow9.distanceTo(l0.id())).isEqualTo(Distance.ofMillis(9));\n        assertThat(distanceMatrix.distance(l9neg, l0)).isEqualTo(Distance.ofMillis(9));\n        assertThat(matrixRow9.distanceTo(l1.id())).isEqualTo(Distance.ofMillis(10));\n        assertThat(distanceMatrix.distance(l9neg, l1)).isEqualTo(Distance.ofMillis(10));\n        // distances {0, 1} -> -9\n        assertThat(matrixRow0.distanceTo(l9neg.id())).isEqualTo(Distance.ofMillis(9));\n        assertThat(distanceMatrix.distance(l0, l9neg)).isEqualTo(Distance.ofMillis(9));\n        assertThat(matrixRow1.distanceTo(l9neg.id())).isEqualTo(Distance.ofMillis(10));\n        assertThat(distanceMatrix.distance(l1, l9neg)).isEqualTo(Distance.ofMillis(10));\n\n        // clear the map\n        assertThat(distanceMatrix.dimension()).isEqualTo(3);\n        distanceMatrix.clear();\n        assertThat(distanceMatrix.dimension()).isZero();\n        Location l500 = location(500, 500);\n        DistanceMatrixRow matrixRow500 = distanceMatrix.addLocation(l500);\n        assertThatIllegalArgumentException().isThrownBy(() -> matrixRow500.distanceTo(l0.id()));\n        assertThatIllegalArgumentException().isThrownBy(() -> distanceMatrix.distance(l500, l0));\n        assertThatIllegalArgumentException().isThrownBy(() -> matrixRow9.distanceTo(l500.id()));\n        assertThatIllegalArgumentException().isThrownBy(() -> distanceMatrix.distance(l9neg, l500));\n    }\n\n    @Test\n    void should_calculate_distance_only_once() {\n        Location l1 = location(100, -1);\n        Location l2 = location(111, 20);\n        long dist12 = 12;\n        long dist21 = 21;\n        when(distanceCalculator.travelTimeMillis(l1.coordinates(), l2.coordinates())).thenReturn(dist12);\n        when(distanceCalculator.travelTimeMillis(l2.coordinates(), l1.coordinates())).thenReturn(dist21);\n\n        // No calculation for the first location.\n        distanceMatrix.addLocation(l1);\n        verifyNoInteractions(distanceCalculator);\n\n        // Calculation happens for the first time.\n        distanceMatrix.addLocation(l2);\n        verify(distanceCalculator).travelTimeMillis(l1.coordinates(), l2.coordinates());\n        verify(distanceCalculator).travelTimeMillis(l2.coordinates(), l1.coordinates());\n\n        // No calculation if the matrix is already populated.\n        DistanceMatrixRow row21 = distanceMatrix.addLocation(l2);\n        assertThat(row21.distanceTo(l1.id())).isEqualTo(Distance.ofMillis(dist21));\n\n        DistanceMatrixRow row12 = distanceMatrix.addLocation(l1);\n        assertThat(row12.distanceTo(l2.id())).isEqualTo(Distance.ofMillis(dist12));\n\n        verifyNoMoreInteractions(distanceCalculator);\n    }\n\n    @Test\n    void should_remove_distance_row_from_matrix_and_repository_when_location_removed() {\n        // arrange\n        Location l1 = location(1, 1);\n        Location l2 = location(2, 2);\n        when(distanceCalculator.travelTimeMillis(l1.coordinates(), l2.coordinates())).thenThrow(new RoutingException(\"dummy\"));\n\n        distanceMatrix.addLocation(l1);\n        assertThatExceptionOfType(RoutingException.class).isThrownBy(() -> distanceMatrix.addLocation(l2));\n        assertThat(distanceMatrix.dimension()).isEqualTo(1);\n\n        // act & assert\n        distanceMatrix.removeLocation(l1);\n        assertThat(distanceMatrix.dimension()).isZero();\n\n        distanceMatrix.addLocation(l2);\n        assertThat(distanceMatrix.dimension()).isEqualTo(1);\n    }\n\n    @Test\n    void get_distance_after_put() {\n        Location from = location(1, 1);\n        Location to = location(2, 2);\n        Distance distance = Distance.ofMillis(2000);\n        distanceMatrix.put(from, to, distance);\n        assertThat(distanceMatrix.distance(from, to)).isEqualTo(distance);\n        verifyNoInteractions(distanceCalculator);\n    }\n\n    private static Location location(long id, int longitude) {\n        return new Location(id, new Coordinates(BigDecimal.ZERO, BigDecimal.valueOf(longitude)));\n    }\n\n    private static class MockDistanceCalculator implements DistanceCalculator {\n\n        @Override\n        public long travelTimeMillis(Coordinates from, Coordinates to) {\n            // imagine 1D space (all locations on equator)\n            return (long) Math.abs(to.longitude().doubleValue() - from.longitude().doubleValue());\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/error/ErrorListenerTest.java",
    "content": "package org.optaweb.vehiclerouting.service.error;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.verify;\n\nimport javax.enterprise.event.Event;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass ErrorListenerTest {\n\n    @Captor\n    private ArgumentCaptor<ErrorMessage> argumentCaptor;\n\n    @Test\n    void should_pass_error_message_to_consumer(@Mock Event<ErrorMessage> errorMessageEvent) {\n        // arrange\n        String text = \"error\";\n        ErrorListener errorListener = new ErrorListener(errorMessageEvent);\n        // act\n        errorListener.onErrorEvent(new ErrorEvent(this, text));\n        // assert\n        verify(errorMessageEvent).fire(argumentCaptor.capture());\n        ErrorMessage capturedMessage = argumentCaptor.getValue();\n        assertThat(capturedMessage.text).isEqualTo(text);\n        assertThat(capturedMessage.id).isNotNull();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/location/LocationServiceIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Optional;\n\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\n\nimport io.quarkus.test.junit.QuarkusTest;\nimport io.quarkus.test.junit.mockito.InjectMock;\n\n@QuarkusTest\nclass LocationServiceIntegrationTest {\n\n    @InjectMock\n    DistanceMatrix distanceMatrix;\n    @Inject\n    LocationService locationService;\n\n    @Test\n    void location_service_should_be_transactional() {\n        when(distanceMatrix.addLocation(any())).thenReturn(locationId -> Distance.ZERO);\n        when(distanceMatrix.distance(any(), any())).thenReturn(Distance.ZERO);\n        locationService.addLocation(new Location(1000, Coordinates.of(-1, 12)));\n        locationService.createLocation(Coordinates.of(12, -1), \"location 1\");\n        Optional<Location> location = locationService.createLocation(Coordinates.of(32, -5), \"location 2\");\n        assertThat(location).isNotEmpty();\n        locationService.populateDistanceMatrix();\n        locationService.removeLocation(location.get().id());\n        locationService.removeAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/location/LocationServiceTest.java",
    "content": "package org.optaweb.vehiclerouting.service.location;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport javax.enterprise.event.Event;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.service.distance.DistanceRepository;\nimport org.optaweb.vehiclerouting.service.error.ErrorEvent;\n\n@ExtendWith(MockitoExtension.class)\nclass LocationServiceTest {\n\n    @Mock\n    private LocationRepository repository;\n    @Mock\n    private DistanceRepository distanceRepository;\n    @Mock\n    private LocationPlanner planner;\n    @Mock\n    private DistanceMatrix distanceMatrix;\n    @Mock\n    private Event<ErrorEvent> errorEvent;\n    @InjectMocks\n    private LocationService locationService;\n\n    private final Coordinates coordinates = Coordinates.of(0.0, 1.0);\n    private final Location location = new Location(1, coordinates);\n\n    @Test\n    void createLocation_should_validate_arguments() {\n        assertThatNullPointerException().isThrownBy(() -> locationService.createLocation(null, \"x\"));\n        assertThatNullPointerException().isThrownBy(() -> locationService.createLocation(coordinates, null));\n    }\n\n    @Test\n    void createLocation(@Mock DistanceMatrixRow matrixRow) {\n        Distance distance = Distance.ofMillis(123);\n        Location existingLocation = new Location(2, coordinates);\n        when(repository.locations()).thenReturn(Arrays.asList(existingLocation));\n        String description = \"new location\";\n        when(repository.createLocation(coordinates, description)).thenReturn(location);\n        when(distanceMatrix.addLocation(any())).thenReturn(matrixRow);\n        when(distanceMatrix.distance(any(), any())).thenReturn(distance);\n        when(matrixRow.distanceTo(anyLong())).thenReturn(distance);\n\n        assertThat(locationService.createLocation(coordinates, description)).contains(location);\n\n        verify(repository).createLocation(coordinates, description);\n        verify(distanceMatrix).addLocation(location);\n        verify(distanceRepository).saveDistance(existingLocation, location, distance);\n        verify(distanceRepository).saveDistance(location, existingLocation, distance);\n        verify(planner).addLocation(location, matrixRow);\n    }\n\n    @Test\n    void addLocation_should_validate_arguments() {\n        assertThatNullPointerException().isThrownBy(() -> locationService.addLocation(null));\n    }\n\n    @Test\n    void addLocation(@Mock DistanceMatrixRow matrixRow) {\n        when(distanceMatrix.addLocation(any())).thenReturn(matrixRow);\n\n        locationService.addLocation(location);\n\n        verifyNoInteractions(repository);\n        verifyNoInteractions(distanceRepository);\n        verify(distanceMatrix).addLocation(location);\n        verify(planner).addLocation(location, matrixRow);\n    }\n\n    @Test\n    void removing_depot_should_be_successful_when_it_is_the_last_location() {\n        when(repository.locations()).thenReturn(Collections.singletonList(location));\n        when(repository.find(location.id())).thenReturn(Optional.of(location));\n\n        locationService.removeLocation(location.id());\n\n        verify(repository).removeLocation(location.id());\n        verify(distanceRepository).deleteDistances(location);\n        verify(planner).removeLocation(location);\n        verifyNoInteractions(errorEvent);\n        // TODO remove location from distance matrix\n    }\n\n    @Test\n    void removing_nonexistent_location_should_publish_error() {\n        when(repository.find(location.id())).thenReturn(Optional.empty());\n\n        locationService.removeLocation(location.id());\n\n        verifyNoInteractions(planner);\n        verify(repository, never()).removeLocation(anyLong());\n        verify(distanceRepository, never()).deleteDistances(any(Location.class));\n        verify(errorEvent).fire(any(ErrorEvent.class));\n    }\n\n    @Test\n    void removing_depot_when_there_are_other_locations_should_publish_error() {\n        Location depot = new Location(1, coordinates);\n        Location visit = new Location(2, coordinates);\n        when(repository.locations()).thenReturn(Arrays.asList(depot, visit));\n        when(repository.find(depot.id())).thenReturn(Optional.of(depot));\n\n        locationService.removeLocation(depot.id());\n\n        verifyNoInteractions(planner);\n        verifyNoInteractions(distanceMatrix);\n        verify(repository, never()).removeLocation(anyLong());\n        verify(distanceRepository, never()).deleteDistances(any(Location.class));\n        verify(errorEvent).fire(any(ErrorEvent.class));\n    }\n\n    @Test\n    void removing_visit_should_be_successful() {\n        Location depot = new Location(1, coordinates);\n        Location visit = new Location(2, coordinates);\n        when(repository.locations()).thenReturn(Arrays.asList(depot, visit));\n        when(repository.find(visit.id())).thenReturn(Optional.of(visit));\n\n        locationService.removeLocation(visit.id());\n\n        verify(planner).removeLocation(visit);\n        verify(distanceMatrix).removeLocation(visit);\n        verify(repository).removeLocation(visit.id());\n        verify(distanceRepository).deleteDistances(visit);\n        verifyNoInteractions(errorEvent);\n    }\n\n    @Test\n    void clear() {\n        locationService.removeAll();\n        verify(planner).removeAllLocations();\n        verify(repository).removeAll();\n        verify(distanceRepository).deleteAll();\n        verify(distanceMatrix).clear();\n    }\n\n    @Test\n    void should_not_optimize_and_roll_back_if_distance_calculation_fails() {\n        when(repository.createLocation(coordinates, \"\")).thenReturn(location);\n        doThrow(new RuntimeException(\"test exception\")).when(distanceMatrix).addLocation(location);\n\n        assertThat(locationService.createLocation(coordinates, \"\")).isEmpty();\n        verifyNoInteractions(planner);\n        verifyNoInteractions(distanceRepository);\n        // publish error event\n        verify(errorEvent).fire(any(ErrorEvent.class));\n        // roll back\n        verify(repository).removeLocation(location.id());\n    }\n\n    @Test\n    void populate_matrix_should_read_all_distances() {\n        Location depot = new Location(1, coordinates);\n        Location visit1 = new Location(2, coordinates);\n        Location visit2 = new Location(3, coordinates);\n        when(repository.locations()).thenReturn(Arrays.asList(depot, visit1, visit2));\n        when(distanceRepository.getDistance(any(Location.class), any(Location.class))).thenReturn(Optional.of(Distance.ZERO));\n\n        locationService.populateDistanceMatrix();\n\n        verify(distanceRepository, times(6)).getDistance(any(Location.class), any(Location.class));\n        verify(distanceMatrix, times(6)).put(any(Location.class), any(Location.class), any(Distance.class));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/region/BoundingBoxTest.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\nclass BoundingBoxTest {\n\n    @Test\n    void validate_southwest_and_northeast_arguments() {\n        // 1───┐\n        // │ ↘ │\n        // └───2\n        assertThatIllegalArgumentException().isThrownBy(() -> new BoundingBox(\n                Coordinates.of(9.9, -1.0), // NW\n                Coordinates.of(1.0, 1.01) // SE\n        )).withMessageMatching(\".*\\\\(9\\\\.9N.*\\\\(1\\\\.0N.*\");\n        // 2───┐\n        // │ ↖ │\n        // └───1\n        assertThatIllegalArgumentException().isThrownBy(() -> new BoundingBox(\n                Coordinates.of(-1.0, 9.9), // SE\n                Coordinates.of(1.01, 1.0) // NW\n        )).withMessageMatching(\".*\\\\(9\\\\.9E.*\\\\(1\\\\.0E.*\");\n        // ┌───1\n        // │ ↙ │\n        // 2───┘\n        assertThatIllegalArgumentException().isThrownBy(() -> new BoundingBox(\n                Coordinates.of(9.9, 9.9), // NE\n                Coordinates.of(1.0, 1.0) // SW\n        )).withMessageMatching(\".*\\\\(9\\\\.9N.*\\\\(1\\\\.0N.*\");\n    }\n\n    @Test\n    void should_fail_if_bounding_box_has_zero_dimension() {\n        //\n        // ╶───╴\n        //\n        assertThatIllegalArgumentException().isThrownBy(() -> new BoundingBox(\n                Coordinates.of(0.0, 1.0),\n                Coordinates.of(0.0, 2.0))).withMessageMatching(\".*\\\\(0\\\\.0N.*\\\\(0\\\\.0N.*\");\n        //   ╷\n        //   │\n        //   ╵\n        assertThatIllegalArgumentException().isThrownBy(() -> new BoundingBox(\n                Coordinates.of(0.0, 10.0),\n                Coordinates.of(1.0, 10.0))).withMessageMatching(\".*\\\\(10\\\\.0E.*\\\\(10\\\\.0E.*\");\n    }\n\n    @Test\n    void constructor_args_not_null() {\n        assertThatNullPointerException().isThrownBy(() -> new BoundingBox(null, Coordinates.of(1.0, 1.0)));\n        assertThatNullPointerException().isThrownBy(() -> new BoundingBox(Coordinates.of(1.0, 1.0), null));\n    }\n\n    @Test\n    void should_work_with_southwest_and_northeast() {\n        // ┌───2\n        // │ ↗ │\n        // 1───┘\n        Coordinates sw = Coordinates.of(-10.0, -100.0);\n        Coordinates ne = Coordinates.of(20.0, -2.0);\n        BoundingBox boundingBox = new BoundingBox(sw, ne);\n        assertThat(boundingBox.getSouthWest()).isEqualTo(sw);\n        assertThat(boundingBox.getNorthEast()).isEqualTo(ne);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/region/RegionPropertiesTest.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.List;\n\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.Test;\n\nimport io.quarkus.test.junit.QuarkusTest;\n\n@QuarkusTest\nclass RegionPropertiesTest {\n\n    @Inject\n    RegionProperties regionProperties;\n\n    @Test\n    void test() {\n        assertThat(regionProperties.countryCodes()).contains(List.of(\"AT\", \"DE\"));\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/region/RegionServiceTest.java",
    "content": "package org.optaweb.vehiclerouting.service.region;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\n\n@ExtendWith(MockitoExtension.class)\nclass RegionServiceTest {\n\n    @Mock\n    private RegionProperties regionProperties;\n    @Mock\n    private Region region;\n    @InjectMocks\n    private RegionService regionService;\n\n    @Test\n    void should_return_country_codes_from_properties() {\n        List<String> countryCodes = Arrays.asList(\"XY\", \"WZ\");\n        when(regionProperties.countryCodes()).thenReturn(Optional.of(countryCodes));\n\n        assertThat(regionService.countryCodes()).isEqualTo(countryCodes);\n    }\n\n    @Test\n    void should_return_graphHopper_bounds() {\n        BoundingBox boundingBox = new BoundingBox(Coordinates.of(-1, -2), Coordinates.of(3, 4));\n        when(region.getBounds()).thenReturn(boundingBox);\n\n        assertThat(regionService.boundingBox()).isEqualTo(boundingBox);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/reload/ReloadServiceTest.java",
    "content": "package org.optaweb.vehiclerouting.service.reload;\n\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.location.LocationService;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleService;\n\nimport io.quarkus.runtime.StartupEvent;\n\n@ExtendWith(MockitoExtension.class)\nclass ReloadServiceTest {\n\n    @Mock\n    private VehicleRepository vehicleRepository;\n    @Mock\n    private VehicleService vehicleService;\n    @Mock\n    private LocationRepository locationRepository;\n    @Mock\n    private LocationService locationService;\n    @InjectMocks\n    private ReloadService reloadService;\n\n    @Mock\n    StartupEvent event;\n\n    private final Vehicle vehicle = VehicleFactory.createVehicle(193, \"Vehicle 193\", 100);\n    private final List<Vehicle> persistedVehicles = Arrays.asList(vehicle, vehicle);\n    private final Location location = new Location(1, Coordinates.of(0.0, 1.0));\n    private final List<Location> persistedLocations = Arrays.asList(location, location, location);\n\n    @Test\n    void should_reload_on_startup() {\n        when(vehicleRepository.vehicles()).thenReturn(persistedVehicles);\n        when(locationRepository.locations()).thenReturn(persistedLocations);\n\n        reloadService.reload(event);\n\n        verify(vehicleRepository).vehicles();\n        verify(vehicleService, times(persistedVehicles.size())).addVehicle(vehicle);\n        verify(locationRepository).locations();\n        verify(locationService, times(persistedLocations.size())).addLocation(location);\n        verify(locationService).populateDistanceMatrix();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/route/RouteListenerTest.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\n\nimport javax.enterprise.event.Event;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Coordinates;\nimport org.optaweb.vehiclerouting.domain.Distance;\nimport org.optaweb.vehiclerouting.domain.Location;\nimport org.optaweb.vehiclerouting.domain.RouteWithTrack;\nimport org.optaweb.vehiclerouting.domain.RoutingPlan;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\nimport org.optaweb.vehiclerouting.service.location.LocationRepository;\nimport org.optaweb.vehiclerouting.service.vehicle.VehicleRepository;\n\n@ExtendWith(MockitoExtension.class)\nclass RouteListenerTest {\n\n    @Mock\n    private Router router;\n    @Mock\n    private VehicleRepository vehicleRepository;\n    @Mock\n    private LocationRepository locationRepository;\n    @Mock\n    private Event<RoutingPlan> routingPlanEvent;\n    @Captor\n    private ArgumentCaptor<RoutingPlan> routeArgumentCaptor;\n    @InjectMocks\n    private RouteListener routeListener;\n\n    @Test\n    void new_listener_should_return_empty_best_route() {\n        assertThat(routeListener.getBestRoutingPlan().isEmpty()).isTrue();\n    }\n\n    @Test\n    void event_with_no_routes_should_be_consumed_as_an_empty_routing_plan() {\n        final long vehicleId = 12;\n        final Vehicle vehicle = VehicleFactory.testVehicle(vehicleId);\n        when(vehicleRepository.find(vehicleId)).thenReturn(Optional.of(vehicle));\n        RouteChangedEvent event = new RouteChangedEvent(\n                this,\n                Distance.ZERO,\n                singletonList(vehicleId),\n                null,\n                emptyList(),\n                emptyList());\n        routeListener.onApplicationEvent(event);\n        verifyNoInteractions(router);\n\n        RoutingPlan routingPlan = verifyAndCaptureConsumedPlan();\n        assertThat(routingPlan.vehicles()).containsExactly(vehicle);\n        assertThat(routingPlan.depot()).isEmpty();\n        assertThat(routingPlan.visits()).isEmpty();\n        assertThat(routingPlan.routes()).isEmpty();\n    }\n\n    @Test\n    void event_with_no_visits_and_a_depot_should_be_consumed_as_plan_with_empty_routes() {\n        final Coordinates depotCoordinates = Coordinates.of(0.0, 0.1);\n        final Location depot = new Location(1, depotCoordinates);\n        final long vehicleId = 448;\n        final Vehicle vehicle = VehicleFactory.testVehicle(vehicleId);\n        ShallowRoute route = new ShallowRoute(vehicle.id(), depot.id(), emptyList());\n        when(vehicleRepository.find(vehicleId)).thenReturn(Optional.of(vehicle));\n        when(locationRepository.find(depot.id())).thenReturn(Optional.of(depot));\n\n        RouteChangedEvent event = new RouteChangedEvent(\n                this,\n                Distance.ofMillis(5000),\n                singletonList(vehicleId),\n                depot.id(),\n                emptyList(),\n                singletonList(route));\n        routeListener.onApplicationEvent(event);\n\n        verifyNoInteractions(router);\n\n        RoutingPlan routingPlan = verifyAndCaptureConsumedPlan();\n        assertThat(routingPlan.vehicles()).containsExactly(vehicle);\n        assertThat(routingPlan.depot()).contains(depot);\n        assertThat(routingPlan.visits()).isEmpty();\n        assertThat(routingPlan.routes()).hasSize(1);\n        RouteWithTrack routeWithTrack = routingPlan.routes().iterator().next();\n        assertThat(routeWithTrack.depot()).isEqualTo(depot);\n        assertThat(routeWithTrack.vehicle()).isEqualTo(vehicle);\n        assertThat(routeWithTrack.visits()).isEmpty();\n        assertThat(routeWithTrack.track()).isEmpty();\n    }\n\n    @Test\n    void listener_should_pass_routing_plan_to_consumer_when_an_update_event_occurs() {\n        final Coordinates depotCoordinates = Coordinates.of(0.0, 0.1);\n        final Coordinates visitCoordinates = Coordinates.of(2.0, -0.2);\n        final Coordinates checkpoint1 = Coordinates.of(12, 12);\n        final Coordinates checkpoint2 = Coordinates.of(21, 21);\n        List<Coordinates> path1 = Arrays.asList(depotCoordinates, checkpoint1, checkpoint2, visitCoordinates);\n        List<Coordinates> path2 = Arrays.asList(visitCoordinates, checkpoint2, checkpoint1, depotCoordinates);\n        when(router.getPath(depotCoordinates, visitCoordinates)).thenReturn(path1);\n        when(router.getPath(visitCoordinates, depotCoordinates)).thenReturn(path2);\n\n        final long vehicleId = -5;\n        final Vehicle vehicle = VehicleFactory.testVehicle(vehicleId);\n        final Location depot = new Location(1, depotCoordinates);\n        final Location visit = new Location(2, visitCoordinates);\n        final Distance distance = Distance.ofMillis(11);\n        when(vehicleRepository.find(vehicleId)).thenReturn(Optional.of(vehicle));\n        when(locationRepository.find(depot.id())).thenReturn(Optional.of(depot));\n        when(locationRepository.find(visit.id())).thenReturn(Optional.of(visit));\n\n        ShallowRoute route = new ShallowRoute(vehicle.id(), depot.id(), singletonList(visit.id()));\n        RouteChangedEvent event = new RouteChangedEvent(\n                this,\n                distance,\n                singletonList(vehicleId),\n                depot.id(),\n                singletonList(visit.id()),\n                singletonList(route));\n\n        routeListener.onApplicationEvent(event);\n\n        RoutingPlan routingPlan = verifyAndCaptureConsumedPlan();\n        assertThat(routingPlan.distance()).isEqualTo(distance);\n        assertThat(routingPlan.vehicles()).containsExactly(vehicle);\n        assertThat(routingPlan.depot()).contains(depot);\n        assertThat(routingPlan.visits()).containsExactly(visit);\n        assertThat(routingPlan.routes()).hasSize(1);\n        RouteWithTrack routeWithTrack = routingPlan.routes().iterator().next();\n        assertThat(routeWithTrack.vehicle()).isEqualTo(vehicle);\n        assertThat(routeWithTrack.depot()).isEqualTo(depot);\n        assertThat(routeWithTrack.visits()).containsExactly(visit);\n        assertThat(routeWithTrack.track()).containsExactly(path1, path2);\n\n        assertThat(routeListener.getBestRoutingPlan()).isEqualTo(routingPlan);\n    }\n\n    @Test\n    void should_discard_update_gracefully_if_one_of_the_locations_no_longer_exist() {\n        final Vehicle vehicle = VehicleFactory.testVehicle(3);\n        final Location depot = new Location(1, Coordinates.of(1.0, 2.0));\n        final Location visit = new Location(2, Coordinates.of(-1.0, -2.0));\n        when(vehicleRepository.find(vehicle.id())).thenReturn(Optional.of(vehicle));\n        when(locationRepository.find(depot.id())).thenReturn(Optional.of(depot));\n        when(locationRepository.find(visit.id())).thenReturn(Optional.empty());\n\n        ShallowRoute route = new ShallowRoute(vehicle.id(), depot.id(), singletonList(visit.id()));\n        RouteChangedEvent event = new RouteChangedEvent(\n                this,\n                Distance.ofMillis(1),\n                singletonList(vehicle.id()),\n                depot.id(),\n                singletonList(visit.id()),\n                singletonList(route));\n\n        // precondition\n        assertThat(routeListener.getBestRoutingPlan().isEmpty()).isTrue();\n\n        // must not throw exception\n        routeListener.onApplicationEvent(event);\n\n        verify(router, never()).getPath(any(), any());\n        verify(routingPlanEvent, never()).fire(any());\n\n        assertThat(routeListener.getBestRoutingPlan().isEmpty()).isTrue();\n    }\n\n    @Test\n    void should_discard_update_gracefully_if_one_of_the_vehicles_no_longer_exist() {\n        final Vehicle vehicle = VehicleFactory.testVehicle(3);\n        final Location depot = new Location(1, Coordinates.of(1.0, 2.0));\n        final Location visit = new Location(2, Coordinates.of(-1.0, -2.0));\n        when(vehicleRepository.find(vehicle.id())).thenReturn(Optional.empty());\n        when(locationRepository.find(depot.id())).thenReturn(Optional.of(depot));\n\n        ShallowRoute route = new ShallowRoute(vehicle.id(), depot.id(), singletonList(visit.id()));\n        RouteChangedEvent event = new RouteChangedEvent(\n                this,\n                Distance.ofMillis(1),\n                singletonList(vehicle.id()),\n                depot.id(),\n                singletonList(visit.id()),\n                singletonList(route));\n\n        // precondition\n        assertThat(routeListener.getBestRoutingPlan().isEmpty()).isTrue();\n\n        // must not throw exception\n        routeListener.onApplicationEvent(event);\n\n        verify(router, never()).getPath(any(), any());\n        verify(routingPlanEvent, never()).fire(any());\n\n        assertThat(routeListener.getBestRoutingPlan().isEmpty()).isTrue();\n    }\n\n    private RoutingPlan verifyAndCaptureConsumedPlan() {\n        verify(routingPlanEvent).fire(routeArgumentCaptor.capture());\n        return routeArgumentCaptor.getValue();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/route/ShallowRouteTest.java",
    "content": "package org.optaweb.vehiclerouting.service.route;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Test;\n\nclass ShallowRouteTest {\n\n    @Test\n    void shallow_route_to_string() {\n        ShallowRoute shallowRoute = new ShallowRoute(200L, 100L, Arrays.asList(93L, 92L, 91L));\n        assertThat(shallowRoute.toString()).containsSubsequence(\"200\", \"100\", \"93\", \"92\", \"91\");\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/vehicle/VehicleServiceIntegrationTest.java",
    "content": "package org.optaweb.vehiclerouting.service.vehicle;\n\nimport javax.inject.Inject;\n\nimport org.junit.jupiter.api.Test;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\nimport io.quarkus.test.junit.QuarkusTest;\n\n@QuarkusTest\nclass VehicleServiceIntegrationTest {\n\n    @Inject\n    VehicleService vehicleService;\n\n    @Test\n    void vehicle_service_should_be_transactional() {\n        vehicleService.addVehicle(VehicleFactory.testVehicle(1000));\n        Vehicle vehicle1 = vehicleService.createVehicle(VehicleFactory.vehicleData(\"vehicle\", 1));\n        Vehicle vehicle2 = vehicleService.createVehicle();\n        vehicleService.changeCapacity(vehicle2.id(), vehicle2.capacity() + 100);\n        vehicleService.createVehicle();\n        vehicleService.removeVehicle(vehicle1.id());\n        vehicleService.removeAnyVehicle();\n        vehicleService.removeAll();\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/service/vehicle/VehicleServiceTest.java",
    "content": "package org.optaweb.vehiclerouting.service.vehicle;\n\nimport static java.util.Arrays.asList;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNullPointerException;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.optaweb.vehiclerouting.domain.Vehicle;\nimport org.optaweb.vehiclerouting.domain.VehicleData;\nimport org.optaweb.vehiclerouting.domain.VehicleFactory;\n\n@ExtendWith(MockitoExtension.class)\nclass VehicleServiceTest {\n\n    @Captor\n    private ArgumentCaptor<Vehicle> vehicleArgumentCaptor;\n    @Mock\n    private VehiclePlanner planner;\n    @Mock\n    private VehicleRepository vehicleRepository;\n    @InjectMocks\n    private VehicleService vehicleService;\n\n    @Test\n    void create_default_vehicle() {\n        final long vehicleId = 63;\n        final String name = \"Veh5\";\n        final int capacity = VehicleService.DEFAULT_VEHICLE_CAPACITY * 2 + 29;\n        final Vehicle vehicle = VehicleFactory.createVehicle(vehicleId, name, capacity);\n        // verify that new vehicle is created with correct initial name and capacity\n        when(vehicleRepository.createVehicle(VehicleService.DEFAULT_VEHICLE_CAPACITY)).thenReturn(vehicle);\n\n        assertThat(vehicleService.createVehicle()).isEqualTo(vehicle);\n\n        // verify that vehicle provided by repository is passed to planner\n        verify(planner).addVehicle(vehicleArgumentCaptor.capture());\n        Vehicle newVehicle = vehicleArgumentCaptor.getValue();\n        assertThat(newVehicle.id()).isEqualTo(vehicleId);\n        assertThat(newVehicle.name()).isEqualTo(name);\n        assertThat(newVehicle.capacity()).isEqualTo(capacity);\n    }\n\n    @Test\n    void createVehicle() {\n        final long vehicleId = 63;\n        final String name = \"Veh5\";\n        final int capacity = 101;\n        VehicleData vehicleData = VehicleFactory.vehicleData(name, capacity);\n        final Vehicle vehicle = VehicleFactory.createVehicle(vehicleId, name, capacity);\n        when(vehicleRepository.createVehicle(vehicleData)).thenReturn(vehicle);\n\n        assertThat(vehicleService.createVehicle(vehicleData)).isEqualTo(vehicle);\n\n        // verify that vehicle provided by repository is passed to planner\n        verify(planner).addVehicle(vehicle);\n    }\n\n    @Test\n    void addVehicle_should_validate_arguments() {\n        assertThatNullPointerException().isThrownBy(() -> vehicleService.addVehicle(null));\n    }\n\n    @Test\n    void addVehicle() {\n        final Vehicle vehicle = VehicleFactory.testVehicle(1);\n\n        vehicleService.addVehicle(vehicle);\n\n        verifyNoInteractions(vehicleRepository);\n        verify(planner).addVehicle(vehicle);\n    }\n\n    @Test\n    void removeVehicle() {\n        final long vehicleId = 8;\n        final Vehicle vehicle = VehicleFactory.testVehicle(vehicleId);\n        when(vehicleRepository.removeVehicle(vehicleId)).thenReturn(vehicle);\n\n        vehicleService.removeVehicle(vehicleId);\n\n        verify(vehicleRepository).removeVehicle(vehicleId);\n        verify(planner).removeVehicle(vehicle);\n    }\n\n    @Test\n    void removeAnyVehicle_should_remove_oldest_vehicle() {\n        final long vehicleId1 = 1;\n        final long vehicleId2 = 2;\n        final long vehicleId3 = 3;\n        final Vehicle vehicle1 = VehicleFactory.testVehicle(vehicleId1);\n        final Vehicle vehicle2 = VehicleFactory.testVehicle(vehicleId2);\n        final Vehicle vehicle3 = VehicleFactory.testVehicle(vehicleId3);\n        when(vehicleRepository.vehicles()).thenReturn(asList(vehicle3, vehicle1, vehicle2));\n        when(vehicleRepository.removeVehicle(vehicleId1)).thenReturn(vehicle1);\n\n        vehicleService.removeAnyVehicle();\n\n        verify(vehicleRepository).removeVehicle(vehicleId1);\n        verify(planner).removeVehicle(vehicle1);\n    }\n\n    @Test\n    void removeAll() {\n        vehicleService.removeAll();\n        verify(planner).removeAllVehicles();\n        verify(vehicleRepository).removeAll();\n    }\n\n    @Test\n    void changeCapacity() {\n        final long vehicleId = 1;\n        final int capacity = 123;\n        final Vehicle vehicle = VehicleFactory.createVehicle(vehicleId, \"1\", capacity);\n        when(vehicleRepository.changeCapacity(vehicleId, capacity)).thenReturn(vehicle);\n\n        vehicleService.changeCapacity(vehicleId, capacity);\n\n        verify(vehicleRepository).changeCapacity(vehicleId, capacity);\n\n        verify(planner).changeCapacity(vehicleArgumentCaptor.capture());\n        assertThat(vehicleArgumentCaptor.getValue().capacity()).isEqualTo(capacity);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/util/jackson/JacksonAssertions.java",
    "content": "package org.optaweb.vehiclerouting.util.jackson;\n\npublic class JacksonAssertions {\n\n    public static <T> ObjectAssert<T> assertThat(T actual) {\n        return new ObjectAssert<>(actual);\n    }\n\n    public static JsonAssert assertThat(String actual) {\n        return new JsonAssert(actual);\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/util/jackson/JsonAssert.java",
    "content": "package org.optaweb.vehiclerouting.util.jackson;\n\nimport org.assertj.core.api.Assertions;\nimport org.assertj.core.api.StringAssert;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class JsonAssert extends StringAssert {\n\n    private final ObjectMapper objectMapper = new ObjectMapper();\n\n    protected JsonAssert(String actual) {\n        super(actual);\n    }\n\n    public void deserializedIsEqualTo(Object expected) {\n        isNotNull();\n        try {\n            Object value = objectMapper.readValue(actual, expected.getClass());\n            Assertions.assertThat(value).isEqualTo(expected);\n        } catch (JsonProcessingException e) {\n            throw new AssertionError(\"ObjectMapper.readValue(actual, expected.getClass()) called with actual: <\" + actual\n                    + \"> and expected: <\" + e\n                    + \"> threw an exception.\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/util/jackson/ObjectAssert.java",
    "content": "package org.optaweb.vehiclerouting.util.jackson;\n\nimport org.assertj.core.api.AbstractAssert;\nimport org.assertj.core.api.Assertions;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\npublic class ObjectAssert<T> extends AbstractAssert<ObjectAssert<T>, T> {\n\n    private final ObjectMapper objectMapper = new ObjectMapper();\n\n    protected ObjectAssert(T actual) {\n        super(actual, ObjectAssert.class);\n    }\n\n    public void serializedIsEqualToJson(String expected) {\n        isNotNull();\n        try {\n            String actualSerialized = objectMapper.writeValueAsString(actual);\n            Assertions.assertThat(actualSerialized).isEqualToIgnoringWhitespace(expected);\n        } catch (JsonProcessingException e) {\n            throw new AssertionError(\"ObjectMapper.writeValueAsString(actual) called with actual: <\" + actual\n                    + \"> threw an exception.\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/util/junit/FileContent.java",
    "content": "package org.optaweb.vehiclerouting.util.junit;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\n\n@Target({ ElementType.FIELD, ElementType.PARAMETER })\n@Retention(RetentionPolicy.RUNTIME)\n@ExtendWith(FileContentExtension.class)\npublic @interface FileContent {\n\n    String value();\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/java/org/optaweb/vehiclerouting/util/junit/FileContentExtension.java",
    "content": "package org.optaweb.vehiclerouting.util.junit;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.ParameterContext;\nimport org.junit.jupiter.api.extension.ParameterResolutionException;\nimport org.junit.jupiter.api.extension.ParameterResolver;\n\npublic class FileContentExtension implements ParameterResolver {\n\n    @Override\n    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)\n            throws ParameterResolutionException {\n        return parameterContext.isAnnotated(FileContent.class)\n                && parameterContext.getParameter().getType().equals(String.class);\n    }\n\n    @Override\n    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)\n            throws ParameterResolutionException {\n        String resourceName = parameterContext.findAnnotation(FileContent.class).orElseThrow().value();\n        URL resource = parameterContext.getTarget().orElseThrow().getClass().getResource(resourceName);\n        if (resource == null) {\n            throw new AssertionError(\"Resource <\" + resourceName + \"> cannot be loaded.\");\n        }\n        try {\n            return Files.readString(Path.of(resource.toURI()));\n        } catch (URISyntaxException | IOException e) {\n            throw new AssertionError(\"Failed to read resource <\" + resourceName + \">.\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/mockito-extensions/README.adoc",
    "content": "The Mockito extension file link:org.mockito.plugins.MockMaker[org.mockito.plugins.MockMaker]\nenables\nhttps://javadoc.io/page/org.mockito/mockito-core/latest/org/mockito/Mockito.html#39[\nMocking final types].\n\nNeeded to mock `com.graphhopper.storage.GraphHopperStorage`.\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/org/optaweb/vehiclerouting/plugin/rest/model/portable-error-message.json",
    "content": "{\n  \"id\": \"c670dd37-62fb-4e86-95ed-c1f4953aaeaa\",\n  \"text\": \"Error message.\\nDetails.\"\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/org/optaweb/vehiclerouting/plugin/rest/model/portable-location.json",
    "content": "{\n  \"id\": 987,\n  \"lat\": 1,\n  \"lng\": 10,\n  \"description\": \"Some Location\"\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/org/optaweb/vehiclerouting/plugin/rest/model/portable-route.json",
    "content": "{\n  \"vehicle\": {\"id\": 13, \"name\": \"Vehicle\", \"capacity\": 45317},\n  \"depot\": {\"id\": 8, \"lat\": 42.6501218, \"lng\": -71.8835449, \"description\": \"Test depot\"},\n  \"visits\": [\n    {\"id\": 100, \"lat\": 42.7066596, \"lng\": -72.4934873, \"description\": \"Visit 1\"},\n    {\"id\": 200, \"lat\": 42.5543343, \"lng\": -71.443828, \"description\": \"Visit 2\"}\n  ],\n  \"track\": [[[42.65005, -71.88522], [42.64997, -71.88527]], [[42.64994, -71.88537], [42.64994, -71.88542]]]\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/org/optaweb/vehiclerouting/plugin/routing/CREDITS.adoc",
    "content": "All `.osm.pbf` files in this folder contain data\nlink:https://www.openstreetmap.org/copyright[copyrighted by OpenStreetMap contributors]\nand licensed under\nlink:https://opendatacommons.org/licenses/odbl/[Open Database License (ODbL)].\n"
  },
  {
    "path": "optaweb-vehicle-routing-backend/src/test/resources/org/optaweb/vehiclerouting/service/demo/dataset/test-belgium.yaml",
    "content": "---\nname: \"Belgium test\"\ndepot:\n  label: \"Brussels\"\n  lat: 50.85\n  lng: 4.35\nvisits:\n  - label: \"Aalst\"\n    lat: 50.933333\n    lng: 4.033333\n  - label: \"Châtelet\"\n    lat: 50.4\n    lng: 4.516667\n  - label: \"La Louvière\"\n    lat: 50.466667\n    lng: 4.183333\n  - label: \"Sint-Niklaas\"\n    lat: 51.166667\n    lng: 4.133333\n  # random order\n  - lng: 2.883333\n    lat: 50.85\n    label: \"Ypres\"\nvehicles:\n  - name: \"vehicle 1\"\n    capacity: 10\n  - name: \"vehicle 2\"\n    capacity: 12\n  - name: \"vehicle 3\"\n    capacity: 1000000\n"
  },
  {
    "path": "optaweb-vehicle-routing-distribution/.gitignore",
    "content": "/target\n/local\n"
  },
  {
    "path": "optaweb-vehicle-routing-distribution/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaweb.vehiclerouting</groupId>\n    <artifactId>optaweb-vehicle-routing</artifactId>\n    <version>8.35.0.Final</version>\n  </parent>\n\n  <artifactId>optaweb-vehicle-routing-distribution</artifactId>\n  <packaging>pom</packaging>\n\n  <name>OptaWeb Vehicle Routing Distribution</name>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.optaweb.vehiclerouting</groupId>\n      <artifactId>optaweb-vehicle-routing-docs</artifactId>\n      <type>zip</type>\n    </dependency>\n    <dependency>\n      <groupId>org.optaweb.vehiclerouting</groupId>\n      <artifactId>optaweb-vehicle-routing-standalone</artifactId>\n      <classifier>quarkus-app</classifier>\n      <type>zip</type>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <descriptors>\n            <descriptor>src/main/assembly/assembly-optaweb-vehicle-routing.xml</descriptor>\n          </descriptors>\n          <appendAssemblyId>false</appendAssemblyId>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "optaweb-vehicle-routing-distribution/src/main/assembly/assembly-optaweb-vehicle-routing.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<assembly xmlns=\"http://maven.apache.org/ASSEMBLY/2.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xsi:schemaLocation=\"http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd\">\n\n  <id>assembly-optaweb-vehicle-routing</id>\n  <formats>\n    <format>zip</format>\n  </formats>\n\n  <includeBaseDirectory>true</includeBaseDirectory>\n\n  <files>\n    <file>\n      <source>../LICENSE.txt</source>\n      <outputDirectory/>\n    </file>\n    <file>\n      <source>src/main/resources/README.adoc</source>\n      <outputDirectory/>\n    </file>\n    <file>\n      <source>../runLocally.sh</source>\n      <outputDirectory>bin</outputDirectory>\n      <filtered>true</filtered>\n    </file>\n  </files>\n\n  <fileSets>\n    <fileSet><!-- Note: going outside the module dir is bad, but it is not fetching generated files -->\n      <useDefaultExcludes>false</useDefaultExcludes>\n      <directory>..</directory>\n      <outputDirectory>sources</outputDirectory>\n      <excludes>\n        <!-- Maven build output -->\n        <exclude>**/target/**</exclude>\n        <!-- Local, Git-ignored files -->\n        <exclude>**/local/**</exclude>\n        <!-- Maven profiler reports -->\n        <exclude>**/.profiler/**</exclude>\n        <!-- IDE configs -->\n        <exclude>**/.project</exclude>\n        <exclude>**/.idea/**</exclude>\n        <exclude>**/*.ipr</exclude>\n        <exclude>**/*.iws</exclude>\n        <exclude>**/*.iml</exclude>\n        <exclude>**/nbproject/**</exclude>\n        <exclude>**/.vscode/**</exclude>\n        <!-- OS-specific garbage -->\n        <exclude>**/.DS_Store</exclude>\n        <!-- Git repository -->\n        <exclude>.git/**</exclude>\n        <!-- Front end module build output, dependencies, reports -->\n        <exclude>optaweb-vehicle-routing-frontend/.scannerwork/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/build/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/coverage/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/cypress/screenshots/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/cypress/videos/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/docker/build/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/node/**</exclude>\n        <exclude>optaweb-vehicle-routing-frontend/node_modules/**</exclude>\n        <!--\n          TODO: Fix Jenkins jobs that build and deploy snapshots not to clone upstream repositories\n                under the root of the target repository.\n        -->\n        <exclude>upstream-repos/**</exclude>\n      </excludes>\n    </fileSet>\n  </fileSets>\n\n  <dependencySets>\n    <dependencySet>\n      <includes>\n        <include>org.optaweb.vehiclerouting:optaweb-vehicle-routing-standalone:zip:quarkus-app</include>\n      </includes>\n      <outputDirectory>bin</outputDirectory>\n      <unpack>true</unpack>\n      <useStrictFiltering>true</useStrictFiltering>\n      <useProjectArtifact>false</useProjectArtifact>\n    </dependencySet>\n    <dependencySet>\n      <includes>\n        <include>org.optaweb.vehiclerouting:optaweb-vehicle-routing-docs:zip</include>\n      </includes>\n      <outputDirectory>reference_manual</outputDirectory>\n      <unpack>true</unpack>\n      <!-- Don't include the artifact produced during the current project's build. -->\n      <useProjectArtifact>false</useProjectArtifact>\n      <!-- Fail the build if any include/exclude patterns aren't used to filter an actual artifact. -->\n      <useStrictFiltering>true</useStrictFiltering>\n    </dependencySet>\n  </dependencySets>\n</assembly>\n"
  },
  {
    "path": "optaweb-vehicle-routing-distribution/src/main/resources/README.adoc",
    "content": "= Welcome to OptaWeb Vehicle Routing\n\n== Quick start\n\nIf you want to run the application without building it from source, go to `bin` directory and use the run script.\nYou only need Java Runtime Environment (JRE).\n\n== Documentation\n\nRead the complete documentation under `reference_manual` directory in HTML or PDF format.\n\n== Build from source\n\nThe distribution contains complete project sources under the `sources` directory.\nYou can build the project with Maven.\nIf you want to start hacking on the distributed sources,\nit might be a good idea to initialize a Git repository first so that you can keep track of your changes.\nYou can do that with\n\n----\ncd sources/\ngit init && git add . && git commit -m 'Initial commit'\n----\n\nFind out more in `sources/README.adoc` or in the documentation.\n\n== Run on OpenShift\n\nBuild the project first.\nThen deploy it to OpenShift using the `runOnOpenShift.sh` script under `sources` directory.\n\n== Get help\n\nIn case you need any kind of help, please go to https://www.optaplanner.org/community/getHelp.html.\nYou will find a way to contact us whether you need to ask a question, report a bug or start a discussion.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/.gitignore",
    "content": "/target\n/local\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaweb.vehiclerouting</groupId>\n    <artifactId>optaweb-vehicle-routing</artifactId>\n    <version>8.35.0.Final</version>\n  </parent>\n\n  <artifactId>optaweb-vehicle-routing-docs</artifactId>\n  <packaging>pom</packaging>\n\n  <name>OptaWeb Vehicle Routing Documentation</name>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.asciidoctor</groupId>\n        <artifactId>asciidoctor-maven-plugin</artifactId>\n        <configuration>\n          <!-- Needs to be overridden to avoid rendering each adoc separately. -->\n          <sourceDocumentName>index.adoc</sourceDocumentName>\n          <attributes>\n            <revnumber>${project.version}</revnumber>\n            <imagesDir>.</imagesDir>\n          </attributes>\n        </configuration>\n        <executions>\n          <execution>\n            <id>generate-single-html</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>process-asciidoc</goal>\n            </goals>\n            <configuration>\n              <backend>html5</backend>\n              <attributes>\n                <source-highlighter>highlightjs</source-highlighter>\n              </attributes>\n              <outputDirectory>${project.build.directory}/generated-docs/html_single</outputDirectory>\n            </configuration>\n          </execution>\n          <execution>\n            <id>generate-pdf</id>\n            <phase>generate-resources</phase>\n            <goals>\n              <goal>process-asciidoc</goal>\n            </goals>\n            <configuration>\n              <backend>pdf</backend>\n              <attributes>\n                <source-highlighter>coderay</source-highlighter><!-- highlightjs does not work in PDF. -->\n              </attributes>\n              <outputDirectory>${project.build.directory}/generated-docs/pdf</outputDirectory>\n              <outputFile>${project.artifactId}.pdf</outputFile>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>package-generated-docs</id>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n            <configuration>\n              <finalName>${project.artifactId}-${project.version}</finalName>\n              <appendAssemblyId>false</appendAssemblyId>\n              <descriptors>\n                <descriptor>src/main/assembly/assembly-generated-docs-zip.xml</descriptor>\n              </descriptors>\n              <archive>\n                <addMavenDescriptor>true</addMavenDescriptor>\n              </archive>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <!-- Turn off PDF generation for faster Maven builds. Useful when testing work in progress. -->\n    <profile>\n      <id>htmlOnly</id>\n      <properties>\n        <!-- Skip assembly to avoid failure due to the missing PDF file. -->\n        <assembly.skipAssembly>true</assembly.skipAssembly>\n      </properties>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.asciidoctor</groupId>\n            <artifactId>asciidoctor-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <!-- Do not generate PDF output. -->\n                <id>generate-pdf</id>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/appendix-backend-architecture.adoc",
    "content": "[appendix]\n[[backend-architecture]]\n= Back end architecture\n\nDomain model and use cases are essential for the application.\nWe put domain model at the center of the architecture and surround it by the application layer that embeds use cases.\nFunctions such as route optimization, distance calculation, persistence, and network communication are considered implementation details\nand are placed at the outermost layer of the architecture.\n\n.Diagram of application layers\nimage::backend-layers.svg[align=\"center\"]\n\n== Code organization\n\nThe back end code is organized in three layers outlined above.\n\n[literal]\n....\norg.optaweb.vehiclerouting\n├── domain\n├── plugin          # Infrastructure layer\n│   ├── persistence\n│   ├── planner\n│   ├── routing\n│   └── websocket\n└── service         # Application layer\n    ├── demo\n    ├── distance\n    ├── location\n    ├── region\n    ├── reload\n    ├── route\n    └── vehicle\n....\n\nThe `service` package contains the application layer that implements use cases.\nThe `plugin` package contains the infrastructure layer.\n\nCode in each layer is further organized by function.\nThis means that each service or plugin has its own package.\n\n== Dependency rules\n\nCompile-time dependencies are only allowed to point from outer layers towards the center.\nFollowing this rule helps to keep the domain model independent of underlying frameworks and other implementation details and model the behavior of business entities more precisely.\nWith presentation and persistence being pushed out to the periphery, it is easier to test the behavior of business entities and use cases.\n\nThe domain has no dependencies.\n\nServices only depend on the domain.\nIf a service needs to send a result (for example to the database or to the client), it uses an output boundary interface.\nIts implementation is injected by the https://quarkus.io/guides/cdi[CDI container].\n\nPlugins depend on services in two ways.\nFirstly, they invoke services based on events such as a user input or a route update coming from the optimization engine.\nServices are injected into plugins which moves the burden of their construction and dependency resolution to the IoC container.\nSecondly, plugins implement service output boundary interfaces to handle use case results, for example persisting changes to the database or sending a response to the web UI.\n\n== The `domain` package\n\nThe `domain` package contains _business objects_ that model the domain of this project, for example `Location`, `Vehicle`, `Route`.\nThese objects are strictly business-oriented and must not be influenced by any tools and frameworks, for example object-relational mapping tools and web service frameworks.\n\n== The `service` package\n\nThe `service` package contains classes that implement _use cases_.\nA use case describes something that you want to do, for example adding new location, changing vehicle capacity, or finding coordinates for an address.\nThe business rules that govern use cases are expressed using the domain objects.\n\nServices often need to interact with plugins in the outer layer, such as persistence, web, and optimization.\nTo satisfy the dependency rules between layers, the interaction between services and plugins is expressed in terms of interfaces that define the dependencies of a service.\nA plugin can satisfy a dependency of a service by providing a bean that implements the service's boundary interface.\nThe CDI container creates an instance of the plugin bean and injects it to the service at runtime.\nThis is an example of the inversion of control principle.\n\n== The `plugin` package\n\nThe `plugin` package contains infrastructure functions such as optimization, persistence, routing, and network.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/appendix-backend-config.adoc",
    "content": "[appendix]\n[[backend-configuration-properties]]\n= Back end configuration properties\n\n[cols=\"m,d,a,d\",options=\"header\"]\n|===\n\n|Property\n|Type\n|Example\n|Description\n\n|app.demo.data-set-dir\n|Relative or absolute path\n|/home/user/{data-dir-name}/dataset\n|Custom <<user-guide#creating-custom-data-sets,data sets>> are loaded from this directory.\nDefaults to `local/dataset`.\n\n|app.persistence.h2-dir\n|Relative or absolute path\n|/home/user/{data-dir-name}/db\n|The directory used by H2 to store the database file.\nDefaults to `local/db`.\n\n|app.region.country-codes\n|List of https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2[ISO 3166-1 alpha-2] country codes\n|`US`, `GB,IE`, `DE,AT,CH`, may be empty\n|Restricts geosearch results.\n\n|app.routing.engine\n|Enumeration\n|`air`, `graphhopper`\n|Routing engine implementation.\nDefaults to `graphhopper`.\n\n|app.routing.gh-dir\n|Relative or absolute path\n|/home/user/{data-dir-name}/graphhopper\n|The directory used by GraphHopper to store road network graphs.\nDefaults to `local/graphhopper`.\n\n|app.routing.osm-dir\n|Relative or absolute path\n|/home/user/{data-dir-name}/openstreetmap\n|The directory that contains OSM files.\nDefaults to `local/openstreetmap`.\n\n|app.routing.osm-file\n|File name\n|belgium-latest.osm.pbf\n|Name of the OSM file that should be loaded by GraphHopper.\nThe file must be placed under `app.routing.osm-dir`.\n\n|optaplanner.solver.termination.spent-limit\n|java.time.Duration\n|* 1m\n* 150s\n* P2dT21h (PnDTnHnMn.nS)\n|How long the solver should run after a location change occurs.\n\n|server.address\n|IP address or hostname\n|10.0.0.123, my-vrp.geo-1.openshiftapps.com\n|Network address to which the server should bind.\n\n|server.port\n|Port number\n|4000, 8081\n|Server HTTP port.\n\n|===\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/attributes.adoc",
    "content": ":data-dir-name: .optaweb-vehicle-routing\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/contributing.adoc",
    "content": "[[contributing]]\n= Contributing to OptaWeb Vehicle Routing\n\n== Formatting OptaWeb Vehicle Routing source code using Maven\n\nThe OptaWeb Vehicle Routing back end has a strictly enforced code style.\nCode formatting is performed by the Eclipse code formatter, using configuration files from the OptaPlanner project.\n\nBy default, when you execute the `./mvnw install` command, the code is formatted automatically.\nThis is important because when you submit a pull request, the continuous integration (CI) build fails if running the formatter results in any code changes.\nTherefore, it is recommended that you always run a full Maven build before submitting a pull request.\n\n.Procedure\n. To run a full Maven build, enter the following command:\n+\n[source]\n----\n./mvnw install\n----\n+\n. Optional: To run the formatter without performing a full build, enter the following command:\n+\n[source]\n----\n./mvnw process-sources\n----\n\n== Formatting OptaWeb Vehicle Routing source code using an IDE\n\nYou can use Eclipse IDE or IntelliJ IDEA to format the source code without having to invoke the Maven build.\nThis allows you to make small changes and commit often while making sure that the source code is formatted properly and the Maven build is successful after each commit.\n\nYou must configure your IDE to use the Eclipse formatter plugin and select the formatter configuration used by the Maven plugin that formats the source code automatically during the Maven build.\nThe formatter configuration files are stored in the `build/optaplanner-ide-config/src/main/resources` directory under the root of the OptaPlanner repository.\n\n=== Eclipse IDE setup\n\n.Prerequisites\n* The OptaPlanner repository is cloned on your computer.\n\nComplete the following steps to configure Eclipse IDE:\n\n.Procedure\n. Open menu:Window[Preferences] and then navigate to menu:Java[Code Style > Formatter].\n. Click *Import...* and select `optaplanner/build/optaplanner-ide-config/src/main/resources/eclipse-format.xml`.\n. Navigate to menu:Java[Code Style > Organize Imports].\n. Click *Import...* and select the `optaplanner/build/optaplanner-ide-config/src/main/resources/eclipse.importorder`.\n. Click *Apply and Close*.\n\n=== IntelliJ IDEA setup\n\n.Prerequisites\n* The OptaPlanner repository is cloned on your computer.\n\nComplete the following steps to configure IntelliJ IDEA:\n\n.Procedure\n. Open the *Settings* or *Preferences* window:\n* For Windows and Linux, select menu:File[Settings].\n* For macOS, select menu:IntelliJ IDEA[Preferences].\n. Navigate to *Plugins* and install the https://plugins.jetbrains.com/plugin/6546-eclipse-code-formatter[Eclipse Code Formatter Plugin] from the Marketplace.\n. Restart your IDE.\n. Open the *Settings* (or *Preferences*) window again and navigate to menu:Other Settings[Eclipse Code Formatter].\n. Configure the Eclipse Code Formatter:\n.. Select *Use the Eclipse Code Formatter* to enable the plugin.\n.. In the *Eclipse formatter config* section, select the *Eclipse workspace/project folder or config file* option, click *Browse...* and then select `optaplanner/build/optaplanner-ide-config/src/main/resources/eclipse-format.xml`.\n.. Make sure the *Optimize Imports* box is ticked, then select the *From file* option and browse for `optaplanner/build/optaplanner-ide-config/src/main/resources/eclipse.importorder`.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/development-guide.adoc",
    "content": "[[development-guide]]\n= Development guide\n\n== Project structure\n\nThe project is a multi-module Maven project.\n\n.Module dependency tree diagram\nimage::modules.dot.svg[align=\"center\"]\n\nAt the bottom of the module tree there are the back end and front end modules, which contain the application source code.\n\nThe standalone module is an assembly module that combines the back end and front end into a single executable JAR file.\n\nThe distribution module represents the final assembly step.\nIt takes the standalone application and the documentation and wraps them in an archive that is easy to distribute.\n\n== Developing OptaWeb Vehicle Routing\n\nThe back end and front end are separate projects that can be built and deployed separately.\nIn fact, they are written in completely different languages and built with different tools.\nBoth projects have tools that provide a modern developer experience with fast turn-around between code changes and the running application.\n\nIn the next sections you will learn how to run both back end and front end projects in development mode.\n\n[[backend]]\n== Back end\n\n////\n- OptaPlanner, GraphHopper\n- Quarkus\n- Configuration (`application.properties`, `application-*.properties`)\n- Package structure\n- DevTools\n- Docker\n////\n\nThe back end module contains a server-side application that uses OptaPlanner to *optimize vehicle routes*.\nOptimization is a CPU-intensive computation that must avoid any I/O operations in order to perform to its full potential.\nBecause one of the chief objectives is to minimize the travel cost, either time or distance, we need to keep the travel cost information in RAM memory.\nWhile solving, OptaPlanner needs to know the travel cost between every pair of locations entered by the user.\nThis information is stored in a structure called the _distance matrix_.\n\nWhen a new location is entered, we calculate the travel cost between the new location and every other location that has been entered so far, and store the travel cost in the distance matrix.\nThe travel cost calculation is performed by a routing engine called https://github.com/graphhopper/graphhopper[GraphHopper].\n\nFinally, the back end module implements additional supporting functionality, such as:\n\n- persistence,\n- WebSocket connection for the front end,\n- data set loading, export, and import.\n\nIn the next sections you will learn how to configure and run the back end in development mode.\nTo learn more about the back end code architecture, see <<appendix-backend-architecture#backend-architecture>>.\n\n[[run-quarkus-maven-plugin]]\n=== Running the back end using the Quarkus Maven plugin\n\n.Prerequisites\n- Java 11 or higher is <<quickstart#install-java,installed>>.\n- The data directory is set up.\n- An OSM file is downloaded.\n// TODO application-local.properties\n\nYou can manually <<run-noscript#data-dir-setup,set up the data directory>> and <<run-noscript#download-osm,download the OSM file>> or you can use the <<run-locally#run-locally-sh,run script>> to complete these tasks.\n\n.Procedure\nTo run the back end in development mode, enter the following command:\n\n[source,shell]\n----\nmvn compile quarkus:dev\n----\n\n=== Quarkus development mode\n\nIn development mode, the back end automatically restarts whenever you refresh the browser tab where the front end runs if there are any changes in the back-end source code or configuration.\n\nLearn more about https://quarkus.io/guides/maven-tooling#development-mode[Quarkus development mode].\n\n=== Running the back end from IntelliJ IDEA Ultimate\n\nIntelliJ IDEA Ultimate has a bundled Quarkus plugin that automatically creates run configurations for modules using the Quarkus framework.\nUse the *optaweb-vehicle-routing-backend* run configuration to run the back end.\n\nLearn more about https://www.jetbrains.com/help/idea/quarkus.html#run-app[running Quarkus applications] in IntelliJ IDEA Ultimate.\n\n[[backend-configuration]]\n=== Configuration\n\n==== The `application.properties` file\n\nThe base configuration is stored in the `application.properties` file, under `/src/main/resources/`.\nThis file is under version control.\nUse it to permanently store the default configuration and to define Quarkus profiles.\n\n==== System properties\n\nTo override the default configuration temporarily, use system properties (`-Dproperty=value`).\n\n==== The `.env` file\n\nTo override the default configuration permanently, for example to store a configuration that is specific to your development environment, use the `optaweb-vehicle-routing-backend/.env` file.\nThis file is excluded from version control and so it does not exist when you clone the repository.\nUse `optaweb-vehicle-routing-backend/.env-example` to initialize your own `optaweb-vehicle-routing-backend/.env` file.\nYou can make changes in the `.env` file without  affecting the Git working tree.\n\nSee the complete list of <<appendix-backend-config#backend-configuration-properties>>.\n\nSee also the complete list of https://quarkus.io/guides/all-config[common application properties] available in Quarkus.\n\n=== Logging\n\nOptaWeb uses the SLF4J API and Logback as the logging framework.\nFor more information, see https://quarkus.io/guides/logging[Quarkus - Configuring Logging].\n\n[[frontend]]\n== Front end\n\n////\n- PatternFly, Leaflet\n- Npm, React, Redux, TypeScript, ESLint, Cypress, `ncu`\n- Chrome, plugins\n- Docker\n////\n\nThe front end project was bootstrapped with https://create-react-app.dev/[Create React App].\nCreate React App provides a number of scripts and dependencies that help with development and with building the application for production.\n\n=== Setting up the development environment\n\n.Procedure\n. On Fedora, run the following command to install npm:\n+\n[source,shell]\n----\nsudo dnf install npm\n----\n\nFor more information about installing npm, see https://docs.npmjs.com/downloading-and-installing-node-js-and-npm[Downloading and installing Node.js and npm].\n\n=== Install npm dependencies\n\nUnlike Maven, the npm package manager installs dependencies in `node_modules` under the project directory and does that only when requested by running `npm install`.\nWhenever the dependencies listed in `package.json` change (for example when you pull changes to the main branch) you must run `npm install` before you run the development server.\n\n.Procedure\n. Change directory to the front end module:\n+\n[source,shell]\n----\ncd optaweb-vehicle-routing-frontend\n----\n\n. Install dependencies:\n+\n[source,shell]\n----\nnpm install\n----\n\n=== Running the development server\n\n.Prerequisites\n- npm is installed.\n- npm dependencies are installed.\n\n.Procedure\n. Run the development server:\n+\n[source,shell]\n----\nnpm start\n----\n\n. Open http://localhost:3000/ in a web browser.\nBy default, the `npm start` command attempts to open this URL in your default browser.\n\n[TIP]\n.Prevent `npm start` from launching your default browser\n====\nIf you don't want `npm start` to open a new browser tab each time you run it, export an environment variable `BROWSER=none`.\n\nYou can use `.env.local` file to make this preference permanent.\nTo do that, enter the following command:\n\n[source,shell]\n----\necho BROWSER=none >> .env.local\n----\n====\n\nThe browser refreshes the page whenever you make changes in the front end source code.\nThe development server process running in the terminal picks up the changes as well and prints compilation and lint errors to the console.\n\n=== Running tests\n\n.Procedure\n. Run `npm test`.\n\n=== Changing the back end location\n\nUse an environment variable called `REACT_APP_BACKEND_URL` to change the back end URL when running `npm start` or `npm run build`.\nFor example:\n\n[literal]\n....\nREACT_APP_BACKEND_URL=http://10.0.0.123:8081\n....\n\nNote that environment variables will be \"`baked`\" inside the JavaScript bundle during the npm build, so you need to know the back end location before you build and deploy the front end.\n\nLearn more about the React environment variables in https://create-react-app.dev/docs/adding-custom-environment-variables/[Adding Custom Environment Variables].\n\n== Building the project\n\nRun `./mvnw install` or `mvn install`.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/index.adoc",
    "content": "= Using OptaWeb Vehicle Routing\nThe OptaPlanner Team <https://www.optaplanner.org/community/team.html>\n:doctype: book\n:experimental:\n:icons: font\n:sectanchors:\n:sectlinks:\n:sectnums:\n:toc: left\n:toclevels: 3\n\n[.normal]\nAs a developer, you can use OptaWeb Vehicle Routing to optimize your vehicle fleet deliveries.\nIn this guide, you will create and run a sample OptaWeb Vehicle Routing application.\n\ninclude::introduction.adoc[leveloffset=+1]\n\ninclude::quickstart.adoc[leveloffset=+1]\n\ninclude::run-locally.adoc[leveloffset=+1]\n\ninclude::run-noscript.adoc[leveloffset=+1]\n\ninclude::run-openshift.adoc[leveloffset=+1]\n\ninclude::user-guide.adoc[leveloffset=+1]\n\ninclude::development-guide.adoc[leveloffset=+1]\n\ninclude::contributing.adoc[leveloffset=+1]\n\ninclude::appendix-backend-architecture.adoc[leveloffset=+1]\n\ninclude::appendix-backend-config.adoc[leveloffset=+1]\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/introduction.adoc",
    "content": "= Introduction\n\n== What is OptaWeb Vehicle Routing?\n\nThe main purpose of many types of businesses is to transport various types of cargo.\nThe goal of these businesses is to deliver a piece of cargo from the loading point to a destination and use its vehicle fleet in the most efficient way.\nThis type of optimization problem is referred to as the https://www.optaplanner.org/learn/useCases/vehicleRoutingProblem.html[vehicle routing problem] (VRP) and has many variations.\n\nhttps://www.optaplanner.org/[OptaPlanner] can solve many of these vehicle routing variations and provides solution examples.\nOptaPlanner enables developers to focus on modeling business rules and requirements instead of learning https://en.wikipedia.org/wiki/Constraint_programming[constraint programming] theory.\nOptaWeb Vehicle Routing expands OptaPlanner's vehicle routing capabilities by providing a reference implementation that answers questions such as these:\n\n* Where do I get the distances and travel times?\n* How do I visualize the solution on a map?\n* How do I build an application that runs in the cloud?\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/modules.dot",
    "content": "digraph {\n  graph [bgcolor=\"transparent\"]\n  node [fontname=\"sans-serif\", fontsize=18, style=filled, fillcolor=\"#f8f8f8\"];\n  \"distribution\" -> \"docs\";\n  \"distribution\" -> \"standalone\";\n  \"standalone\" -> \"backend\";\n  \"standalone\" -> \"frontend\";\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/quickstart.adoc",
    "content": "= Quickstart\n\nYou can get up and running with OptaWeb Vehicle Routing in just a few steps.\nIn this chapter you will download the OptaWeb Vehicle Routing distribution archive containing a binary build of OptaWeb Vehicle Routing.\nYou will use a Bash script to run the binary without having to build the project.\n\n// TODO make this a prerequisite of build procedure\n//==== Internet access\n//\n//You need internet access when you build and run the application.\n//The application source code depends on Maven and NPM packages that will be downloaded during build.\n//When the application runs it uses third party, public services such as link:https://www.openstreetmap.org/about[OpenStreetMap]\n//to display map tiles and provide search results.\n\n[[install-java]]\n== Install Java 11 or higher\n\n*Java SE 11 or higher* must be installed on your system before you can use OptaWeb Vehicle Routing.\n\nNOTE: It is recommended that you install Java SE Development Kit (JDK) because it is necessary in order to build OptaWeb Vehicle Routing from the source.\nHowever, if you have a binary distribution of OptaWeb Vehicle Routing, you only need the Java SE Runtime Environment (JRE).\n\n.Procedure\n. To verify the current Java installation, enter the following command:\n+\n[source,shell]\n----\njava -version\n----\n\n. If necessary, install OpenJDK 11.\n* To install OpenJDK 11 on Fedora, enter the following command:\n+\n[source,shell]\n----\nsudo dnf install java-11-openjdk-devel\n----\n\n* To install OpenJDK on other platforms, follow instructions at https://openjdk.java.net/install/.\n\n== Download distribution archive\n\nDownload the OptaWeb Vehicle Routing distribution archive, available from the OptaPlanner website, to quickly evaluate OptaWeb Vehicle Routing without having to set up build tools.\n\nNOTE: If you want to modify OptaWeb Vehicle Routing and build it yourself or contribute to upstream, see <<development-guide#development-guide>>.\n\n.Procedure\n. Go to https://www.optaplanner.org/download/download.html\nand click the *OptaWeb Vehicle Routing* tab.\n. Click *Download OptaWeb Vehicle Routing {revnumber}*.\n+\n.OptaPlanner download page\nimage::download.png[OptaPlanner download page at www.optaplanner.org,align=\"center\"]\n+\n. Extract the downloaded distribution ZIP file.\nThe archive contains source files and a binary build of OptaWeb Vehicle Routing as well as the OptaWeb Vehicle Routing documentation.\n+\n.Content of the OptaWeb Vehicle Routing distribution archive\nimage::distribution.png[Content of the OptaWeb Vehicle Routing distribution archive,align=\"center\"]\n\n== Run OptaWeb Vehicle Routing\n\nAfter you download OptaWeb Vehicle Routing and extract the distribution archive, use the `runLocally.sh` script to run it.\n\nNOTE: If the standalone JAR is not part of the distribution, build the project from source by using the `sources` directory.\nYou can use the `sources` directory inside the distribution as if you have cloned the source repository from GitHub.\n// TODO build instructions\n\nNOTE: If Bash is not available on your system, continue to <<run-noscript#run-noscript>>.\n\n.Prerequisites\n* Internet access is available.\nWhen OptaWeb Vehicle Routing runs it uses third-party public services such as link:https://www.openstreetmap.org/about[OpenStreetMap] to display map tiles and provide search results.\n* Java 11 or higher is installed.\n* OptaWeb Vehicle Routing distribution archive is downloaded and extracted.\n\n.Procedure\nEnter the following command:\n\n[source,bash]\n----\n./bin/runLocally.sh\n----\n\nThe script will download an OSM file that is needed to work with the sample data set that is included with the application.\nThe script also has an interactive mode you can use to download additional regions.\nSee <<run-locally#run-locally-sh>> to learn more about the script.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/run-locally.adoc",
    "content": "[[run-locally-sh]]\n= Run locally using the script\n\nLinux and macOS users can use a Bash script called `runLocally.sh` to run OptaWeb Vehicle Routing.\nThe script automates some setup steps that would otherwise have to be carried out manually.\nThe script will:\n\n* Create the data directory.\n* Download selected OSM files from Geofabrik.\n* Try to associate a country code with each downloaded OSM file automatically.\n* Build the project if the standalone JAR file does not exist.\n* Launch OptaWeb Vehicle Routing by taking a single region argument or by selecting the region interactively.\n\n== Quickstart mode\n\nIn quickstart mode, the script downloads the region that is required to work with the built-in data set.\nThis is the easiest way to get started.\nTo use the quickstart mode, run the script with no arguments.\n\n.Prerequisites\n* `optaweb-vehicle-routing` repository is cloned on your computer.\n* Internet access is available.\n* Java 11 or higher is installed.\n\n.Procedure\n. Change directory to the project root.\n. Run `./runLocally.sh`.\n. Confirm the download of the OSM file needed to work with the built-in data set.\n\nThe application starts after the OSM file is downloaded.\nOpen http://localhost:8080 in a web browser to work with OptaWeb Vehicle Routing.\n\nNOTE: The first start may take a few minutes because the OSM file needs to be imported by GraphHopper and stored as a road network graph.\nSubsequent runs will load the graph from the file system without importing the OSM file and will be significantly faster.\n\n== Interactive mode\n\nUsing the interactive mode, you can see the list of downloaded OSM files and country codes assigned to each region.\nYou can use the interactive mode to download additional OSM files from Geofabrik without visiting the website and choosing a destination for the download.\n\n=== Download a new region using the script\n\n.Procedure\n. Run `./runLocally.sh -i`.\n. Enter `d` to show the download menu.\n. Go to a region by entering its ID and then entering `e`.\n. Repeat the previous step until you see a list with the region you want to download.\n. Download a region by entering its ID and then entering `d`.\n\n[WARNING]\n.Using large OSM files\n====\nFor the best user experience, use smaller regions such as individual European or US states.\nUsing OSM files larger than 1 GB will require significant RAM size and take a lot of time (up to several hours) for the initial processing.\n====\n\n=== Select a region and run OptaWeb Vehicle Routing\n\n.Procedure\n. Run `./runLocally.sh -i`.\n. Select a region from the list of downloaded regions by entering its ID.\n. Confirm the project build if it hasn't been built yet.\n. Confirm starting OptaWeb Vehicle Routing using the selected region.\n\n== Non-interactive mode\n\nUse the non-interactive mode to specify an existing region and start OptaWeb Vehicle Routing with a single command.\nThis is useful for switching between regions quickly or when doing a demo.\n\n.Procedure\nRun `./runLocally.sh <REGION>`.\n\n////\n== Air distance mode\n\nOptaWeb Vehicle Routing can work in air distance mode that calculates travel times based on the distance between two coordinates.\nUse this mode in situations where you need to get OptaWeb Vehicle Routing up and running as quickly as possible and do not want to use an OSM (OpenStreetMap) file.\nAir distance mode is only useful if you need to smoke-test OptaWeb Vehicle Routing and you do not need accurate travel times.\n\n.Procedure\nRun the `runLocally.sh` script with `--air` argument to start OptaWeb Vehicle Routing in air distance mode:\n\n[source,bash]\n----\n./bin/run.sh --air\n----\n////\n\n== Tweak the data directory\n\n* To use a different data directory, write its absolute path to the `.DATA_DIR_LAST` file at the project root.\n\n* To change country codes associated with a region, edit the corresponding file under `_DATA_DIR_/country_codes/`.\n+\nFor example, you could have downloaded an OSM file for Scotland, for which the script fails to guess the country code.\nIn this case, set the content of `_DATA_DIR_/country_codes/scotland-latest` to `GB`.\n\n* To remove a region, delete the corresponding OSM file from `_DATA_DIR_/openstreetmap/` and GraphHopper directory from `_DATA_DIR_/graphhopper/`.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/run-noscript.adoc",
    "content": "[[run-noscript]]\n= Run locally without the script\n\ninclude::attributes.adoc[]\n\nFollow this section if you cannot use <<run-locally#run-locally-sh,runLocally.sh>> to run OptaWeb Vehicle Routing because Bash is not available on your system.\n\n[[download-osm]]\n== Download routing data\n\nThe routing engine requires geographical data to calculate the time it takes vehicles to travel between locations.\nYou must download and store OSM (OpenStreetMap) data files on the local file system before you run OptaWeb Vehicle Routing.\n\nNOTE: The OSM data files are typically between 100 MB to 1 GB and take time to download so it is a good idea to download the files before building or starting the OptaWeb Vehicle Routing application.\n\n.Procedure\n. Open https://download.geofabrik.de/ in a web browser.\n. Click a region in the *Sub Region* list, for example *Europe*.\nThe subregion's page opens.\n. In the *Sub Regions* table, download the OSM file (`.osm.pbf`) for a country, for example Belgium.\n\n[[data-dir-setup]]\n== Create data directory structure\n\nOptaWeb Vehicle Routing reads and writes several types of data on the file system.\nIt reads OSM (OpenStreetMap) files from the `openstreetmap` directory, writes a road network graph to the `graphhopper` directory, and persists user data in a directory called `db`.\nCreate a new directory dedicated to storing all of these data to make it easier to upgrade to a newer version of OptaWeb Vehicle Routing in the future and continue working with the data you created previously.\n\n.Procedure\n. Create the `openstreetmap` directory in your user account `home` directory, for example:\n+\n[source,subs=\"attributes+\"]\n----\n$HOME/{data-dir-name}\n└── openstreetmap\n----\n\n. Move all of your downloaded OSM files (files with the extension `.osm.pbf`) to the `openstreetmap` directory.\n\nThe rest of the directory structure will be created by the OptaWeb Vehicle Routing application when it runs for the first time.\nAfter that, your directory structure will look similar to the following example:\n\n// TODO maybe replace this with a screenshot, doesn't look good in PDF.\n[source,subs=\"attributes+\"]\n----\n$HOME/{data-dir-name}\n├── db\n│   └── vrp.mv.db\n├── graphhopper\n│   └── belgium-latest\n└── openstreetmap\n    └── belgium-latest.osm.pbf\n----\n\n== Run using the `java` command\n\n.Prerequisites\n* Internet access is available.\nWhen OptaWeb Vehicle Routing runs it uses third-party public services such as link:https://www.openstreetmap.org/about[OpenStreetMap] to display map tiles and provide search results.\n* Java 11 or higher is installed.\n* The data directory is created at `$HOME/{data-dir-name}`.\n* A subdirectory called `openstreetmap` with at least one OSM file exists.\n* A country code to use in search queries is identified.\n\n.Procedure\n\nEnter the following command:\n\n[source,subs=\"attributes+\"]\n----\njava \\\n-Dapp.demo.data-set-dir=$HOME/{data-dir-name}/dataset \\\n-Dapp.persistence.h2-dir=$HOME/{data-dir-name}/db \\\n-Dapp.routing.gh-dir=$HOME/{data-dir-name}/graphhopper \\\n-Dapp.routing.osm-dir=$HOME/{data-dir-name}/openstreetmap \\\n-Dapp.routing.osm-file=belgium-latest.osm.pbf \\\n-Dapp.region.country-codes=BE \\\n-jar optaweb-vehicle-routing-standalone/target/quarkus-app/quarkus-run.jar\n----\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/run-openshift.adoc",
    "content": "[[run-openshift]]\n= Run on OpenShift\n\nLinux and macOS users can use the `runOnOpenShift.sh` Bash script to install OptaWeb Vehicle Routing on OpenShift.\n\n== Running on a local OpenShift cluster\n\nUse https://developers.redhat.com/products/codeready-containers[Red Hat CodeReady Containers]\nto easily set up a single-node OpenShift 4 cluster on your local computer.\n\n.Prerequisites\nYou have successfully built the project with Maven.\n\n.Procedure\n. To install CRC, follow the link:https://code-ready.github.io/crc/[Red Hat CodeReady Containers Getting Started Guide].\n\n. When the cluster starts, perform the following steps:\n\n.. Add the OpenShift command-line interface (`oc`) to your `$PATH`:\n+\n[source,shell]\n----\neval $(crc oc-env)\n----\n\n.. Log in as `developer`:\n+\n[source,shell]\n----\noc login -u developer -p developer https://api.crc.testing:6443\n----\n\n.. Create a new project:\n+\n[source,subs=\"quotes\"]\n----\noc new-project _project_name_\n----\n\n.. Run the script:\n+\n[source,subs=\"quotes\"]\n----\n./runOnOpenShift.sh _osm_file_name_ _country_code_list_ _osm_file_download_url_\n----\n\n.. Enter the following command for information about how to use the script:\n+\n[source,shell]\n----\n./runOnOpenShift.sh --help\n----\n\n=== Updating the deployed application with local changes\n\n==== Back end\n\n. Change the source code and build the back end module with Maven.\n. Start OpenShift build:\n\n[source,shell]\n----\ncd optaweb-vehicle-routing-backend\noc start-build backend --from-dir=. --follow\n----\n\n==== Front end\n\n. Change the source code and build the front end module with npm.\n. Start OpenShift build:\n\n[source,shell]\n----\ncd optaweb-vehicle-routing-frontend\noc start-build frontend --from-dir=docker --follow\n----\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/asciidoc/user-guide.adoc",
    "content": "= Using OptaWeb Vehicle Routing\n\nIn the OptaWeb Vehicle Routing application, you can mark a number of locations on the map.\nThe first location is assumed to be the depot.\nVehicles must deliver goods from this depot to every other location that you marked.\n\nYou can set the number of vehicles and the carrying capacity of every vehicle.\nHowever, the route is not guaranteed to use all vehicles.\nThe application uses as many vehicles as required for an optimal route.\n\nThe current version has certain limitations:\n\n* Every delivery to a location is supposed to take 1 point of vehicle capacity.\nFor example, a vehicle with a capacity of 10 can visit up to 10 locations before returning to the depot.\n* Setting custom names of vehicles and locations is not supported.\n\n== Creating a route\n\nTo create an optimal route, use the *Demo* tab of the user interface.\n\n. Click *Demo* to open the *Demo* tab.\n. Use the blue icon:minus-square[role=\"blue\"] and icon:plus-square[role=\"blue\"] buttons above the map to set the number of vehicles.\nEach vehicle has a default capacity of 10.\n. Use the icon:plus-square-o[] button on the map to zoom in as necessary.\n+\n[NOTE]\n====\nDo not double-click to zoom in.\nA double click also creates a location.\n====\n+\n. Click a location for the depot.\n. Click other locations on the map for delivery points.\n. If you want to delete a location:\n.. Hover the mouse cursor over the location to see the location name.\n.. Find the location name in the list in the left part of the screen.\n.. Click the icon:times[role=\"blue\"] icon next to the name.\n\nEvery time you add or remove a location or change the number of vehicles, the application creates and displays a new optimal route.\nIf the solution uses several vehicles, the application shows the route for every vehicle in a different color.\n\n== Viewing and setting other details\n\nYou can use other tabs of the user interface to view and set additional details.\n\n* In the *Vehicles* tab, you can view, add, and remove vehicles, and also set the capacity for every vehicle.\n* In the *Visits* tab, you can view and remove locations.\n* In the *Route* tab, you can select every vehicle and view the route for this vehicle.\n\n[[creating-custom-data-sets]]\n== Creating custom data sets\n\nThere is a built-in demo data set consisting of a several large Belgian cities.\nIf you want to have more demos offered by the *Load demo* dropdown, you can prepare your own data sets.\nTo do that, follow these steps:\n\n. Add a depot and a number of visits by clicking on the map or using geosearch.\n. Click *Export* and save the file in the _data set_ directory.\n+\n[NOTE]\n====\nData set directory is where the `app.demo.data-set-dir` property points to.\n\nIf the application is running through the <<run-locally#run-locally-sh,run script>>, it will be set to `$HOME/{data-dir-name}/dataset`.\n\nOtherwise, the property will be taken from `application.properties` and defaults to `optaweb-vehicle-routing-backend/local/dataset`.\n====\n. Edit the YAML file and choose a unique name for the data set.\n. Restart the back end.\n\nAfter you restart the back end, files in the _data set_ directory will be made available in the *Load demo* dropdown.\n\n== Troubleshooting\n\nIf the application behaves unexpectedly, review the back end terminal output log.\n\nTo resolve issues, remove the back end database:\n\n. Stop the back end by pressing kbd:[Ctrl+C] in the back end terminal window.\n. Remove the directory `optaweb-vehicle-routing/optaweb-vehicle-routing-backend/local/db`.\n"
  },
  {
    "path": "optaweb-vehicle-routing-docs/src/main/assembly/assembly-generated-docs-zip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<assembly xmlns=\"http://maven.apache.org/ASSEMBLY/2.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xsi:schemaLocation=\"http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd\">\n\n  <id>zip-with-generated-docs</id>\n  <formats>\n    <format>zip</format>\n  </formats>\n\n  <includeBaseDirectory>false</includeBaseDirectory>\n\n  <fileSets>\n    <fileSet>\n      <directory>${project.build.directory}/generated-docs/html_single/</directory>\n      <outputDirectory>html_single/</outputDirectory>\n    </fileSet>\n  </fileSets>\n  <files>\n    <file>\n      <source>${project.build.directory}/generated-docs/pdf/optaweb-vehicle-routing-docs.pdf</source>\n      <outputDirectory>pdf/</outputDirectory>\n    </file>\n  </files>\n</assembly>\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/.editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n# Matches multiple files with brace expansion notation\n# Set default charset\n[*.{js,py}]\ncharset = utf-8\n\n\n# Indentation override for all JS under lib directory\n[src/**.js]\nindent_style = space\nindent_size = 2\n\n# Matches the exact files either package.json or .travis.yml\n[{package.json,.travis.yml}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/.eslintignore",
    "content": "/cypress/support\n/cypress/plugins\nregisterServiceWorker.ts\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/.eslintrc.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"project\": \"./tsconfig.json\"\n  },\n  \"plugins\": [\n    \"@typescript-eslint\",\n    \"jest\",\n    \"jest-dom\"\n  ],\n  \"env\": {\n    \"browser\": true,\n    \"jest/globals\": true,\n    \"node\": true\n  },\n  \"settings\": {\n    \"react\": {\n      \"version\": \"detect\"\n    },\n    \"linkComponents\": [\n      // Components used as alternatives to <a> for linking, eg. <Link to={ url } />\n      \"Hyperlink\",\n      {\"name\": \"Link\", \"linkAttribute\": \"to\"}\n    ]\n  },\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:jest-dom/recommended\",\n    \"react-app\",\n    \"airbnb-typescript\"\n  ],\n  \"rules\": {\n    \"@typescript-eslint/explicit-member-accessibility\": [\"error\", {\"accessibility\": \"no-public\"}],\n    \"@typescript-eslint/indent\": [\"error\", 2],\n    \"@typescript-eslint/no-empty-interface\": \"off\",\n    \"@typescript-eslint/no-explicit-any\": \"off\",\n    \"@typescript-eslint/no-use-before-define\": \"off\",\n    \"import/no-extraneous-dependencies\": [\n      \"error\",\n      {\n        \"devDependencies\": [\n          \"**/*.test.ts\",\n          \"**/*.test.tsx\",\n          \"src/store/mockStore.ts\",\n          \"src/setupTests.ts\"\n        ]\n      }\n    ],\n    \"import/prefer-default-export\": \"off\",\n    \"max-len\": [\"error\", {\"code\": 120}],\n    \"no-console\": \"warn\",\n    \"object-curly-newline\": [\n      \"error\", {\n        \"ImportDeclaration\": {\"multiline\": true},\n        \"ExportDeclaration\": {\"multiline\": true}\n      }\n    ],\n    \"react/destructuring-assignment\": \"off\",\n    \"react/jsx-props-no-spreading\": \"off\",\n    \"react/prop-types\": \"off\",\n    \"react/react-in-jsx-scope\": \"off\",\n    \"react/sort-comp\": \"off\"\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/.gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n/target\n/local\n\n# dependencies\n/node_modules\n# frontend-maven-plugin\n/node\n\n# testing\n/coverage\n\n# production\n/build\n\n# .env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/.prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/README.adoc",
    "content": ":david-project: https://david-dm.org/kiegroup/optaweb-vehicle-routing\n:david-path: path=optaweb-vehicle-routing-frontend\n:david-deps: {david-project}/status.svg?{david-path}\n:david-devDeps: {david-project}/dev-status.svg?{david-path}\n:david-link: {david-project}?{david-path}\n\n[[optaweb-vehicle-routing-frontend]]\n= OptaWeb Vehicle Routing front end\n\nimage:{david-deps}[\"dependencies Status\",link=\"{david-link}\"]\nimage:{david-devDeps}[\"devDependencies Status\",link=\"{david-link}&type=dev\"]\n\n[[available-scripts]]\n== Available scripts\n\nIn the project directory, you can run:\n\n[[npm-start]]\n=== `npm start`\n\nRuns the app in the development mode.\nOpen http://localhost:3000 to view it in the browser.\n\nThe page will reload if you make edits.\nYou will also see any lint errors in the console.\n\n[[npm-test]]\n=== `npm test`\n\nLaunches the test runner in the interactive watch mode.\nSee the section about https://create-react-app.dev/docs/running-tests/[running tests] for more information.\n\n[[npm-run-lint]]\n=== `npm run lint`\n\nCheck the whole `src/` directory for linter errors.\nSome IDEs are difficult to be configured to be 100% compliant with the project linter configuration.\nUse `npm run lint:fix` to resolve the edge cases that your IDE cannot handle.\n\n[[npm-run-build]]\n=== `npm run build`\n\nBuilds the app for production to the `build` folder.\nIt correctly bundles React in production mode and optimizes the build for the best performance.\n\nSee the section about https://create-react-app.dev/docs/deployment/[deployment] for more information.\n\n[[browserslist-support]]\n== Browserslist support\n\nDevelopers set versions list in queries like `last 2 version` to be free from updating versions manually.\nBrowserslist will use http://caniuse.com/[Can I Use] data for this queries.\n\nCheck the project current browser query on\nhttps://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+ie%3C%3D11%2Cnot+op_mini+all[browserl.ist]\nor by running `npx browserslist`.\n\nExample:\n\n[source,json]\n----\n{\n  \"browserslist\": [\n    \">0.2%\",\n    \"not dead\",\n    \"not ie <= 11\",\n    \"not op_mini all\"\n  ]\n}\n----\n\n[[learn-more]]\n== Learn more\n\nTo learn React, check out the https://reactjs.org/[React documentation].\n\nThis project was bootstrapped with\nhttps://github.com/facebook/create-react-app[Create React App].\n\nLearn more on\nhttps://github.com/browserslist/browserslist#readme[Browserslist].\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/.eslintrc.json",
    "content": "{\n  \"plugins\": [\n    \"cypress\"\n  ],\n  \"env\": {\n    \"cypress/globals\": true\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/.gitignore",
    "content": "screenshots\nvideos\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/fixtures/example.json",
    "content": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mock data for responses to routes\"\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/fixtures/response-garz.json",
    "content": "[\n  {\n    \"place_id\": 1243027,\n    \"licence\": \"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright\",\n    \"osm_type\": \"node\",\n    \"osm_id\": 311384628,\n    \"boundingbox\": [\n      \"53.0183353\",\n      \"53.0583353\",\n      \"12.0651506\",\n      \"12.1051506\"\n    ],\n    \"lat\": \"53.0383353\",\n    \"lon\": \"12.0851506\",\n    \"display_name\": \"Garz, Hoppenrade, Plattenburg, Prignitz, Brandenburg, Germany\",\n    \"class\": \"place\",\n    \"type\": \"hamlet\",\n    \"importance\": 0.35,\n    \"icon\": \"https://nominatim.openstreetmap.org/ui/mapicons//poi_place_village.p.20.png\"\n  }\n]\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/fixtures/response-hoppenrade.json",
    "content": "[\n  {\n    \"place_id\": 283087710,\n    \"licence\": \"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright\",\n    \"osm_type\": \"relation\",\n    \"osm_id\": 9656282,\n    \"boundingbox\": [\n      \"53.0147994\",\n      \"53.0556191\",\n      \"12.0157931\",\n      \"12.1143161\"\n    ],\n    \"lat\": \"53.03502605\",\n    \"lon\": \"12.069659320997278\",\n    \"display_name\": \"Hoppenrade, Plattenburg, Prignitz, Brandenburg, Germany\",\n    \"class\": \"boundary\",\n    \"type\": \"administrative\",\n    \"importance\": 0.4,\n    \"icon\": \"https://nominatim.openstreetmap.org/ui/mapicons//poi_boundary_administrative.p.20.png\"\n  },\n  {\n    \"place_id\": 536362,\n    \"licence\": \"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright\",\n    \"osm_type\": \"node\",\n    \"osm_id\": 240025900,\n    \"boundingbox\": [\n      \"53.019054\",\n      \"53.059054\",\n      \"12.042413\",\n      \"12.082413\"\n    ],\n    \"lat\": \"53.039054\",\n    \"lon\": \"12.062413\",\n    \"display_name\": \"Hoppenrade, Plattenburg, Prignitz, Brandenburg, 19339, Germany\",\n    \"class\": \"place\",\n    \"type\": \"hamlet\",\n    \"importance\": 0.35,\n    \"icon\": \"https://nominatim.openstreetmap.org/ui/mapicons//poi_place_village.p.20.png\"\n  }\n]\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/integration/fromLocationsToRoute.js",
    "content": "describe('Locations can be added and route is computed', () => {\n  const cities = ['Garz', 'Hoppenrade'];\n\n  /**\n   * Adds a location by searching for a city of a given name.\n   * @param { string } name - city name\n   */\n  const addCity = (name) => {\n    cy.get('[data-cy=geosearch-text-input]').type(name);\n    // TODO: replace by mocking the request (search depends on a 3rd-party service)\n    cy.get('[data-cy=geosearch-location-item-button-0]', { timeout: 60000 }).click();\n  };\n\n  /**\n   * Clears locations by clicking on the 'Clear' button.\n   */\n  const clearLocations = () => {\n    // Add one city to make sure there is a location in the list and the clear button shows up\n    addCity('Garz');\n    cy.get('[data-cy=demo-clear-button]').click({ force: true });\n    cy.wait('@postClear');\n  };\n\n  /**\n   * Waits for a websocket connection to be established.\n   */\n  const visitDemo = () => {\n    cy.visit('/');\n    cy.get('a[href=\"/demo\"]').click();\n  };\n\n  before(() => {\n    cy.intercept('POST', '**/api/clear').as('postClear');\n    cities.forEach((city) => cy.intercept(\n      'GET',\n      `https://nominatim.openstreetmap.org/search?*q=${city}`,\n      { fixture: `response-${city.toLowerCase()}.json` },\n    ));\n    visitDemo();\n    clearLocations();\n  });\n\n  it('Locations added via clicking on a map are added to a route', () => {\n    cities.forEach((city) => {\n      addCity(city);\n    });\n\n    cy.get('[data-cy=demo-add-vehicle]').click();\n    cy.get('a[href=\"/routes\"]').click();\n    cy.get('[data-cy=location-list]').find('li').should((list) => {\n      cities.forEach((city) => expect(list).to.contain(city));\n    });\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/plugins/index.js",
    "content": "// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\nmodule.exports = (on, config) => {\n  // `on` is used to hook into various events Cypress emits\n  // `config` is the resolved Cypress config\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add(\"login\", (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add(\"drag\", { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add(\"dismiss\", { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This is will overwrite an existing command --\n// Cypress.Commands.overwrite(\"visit\", (originalFn, url, options) => { ... })\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\nimport './commands';\n\n// Import commands.js using ES2015 syntax:\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/cypress.json",
    "content": "{\n  \"baseUrl\": \"http://localhost:3000\",\n  \"reporter\": \"junit\",\n  \"reporterOptions\": {\n    \"mochaFile\": \"target/test-reports/TEST-cypress.xml\",\n    \"jenkinsMode\": true\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/docker/.gitignore",
    "content": "build\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/docker/Dockerfile",
    "content": "FROM docker.io/library/nginx:1.17.5\nCOPY nginx.conf /etc/nginx\nCOPY default.conf /tmp/default.template\nARG BACKEND_URL=http://backend:8080\nRUN envsubst '${BACKEND_URL}' < /tmp/default.template > /etc/nginx/conf.d/default.conf \\\n        && rm /tmp/default.template \\\n# Make directories used by nginx owned and writable by the root group to support arbitrary user ID.\n# See: https://docs.openshift.com/container-platform/4.2/openshift_images/create-images.html\n        && chgrp 0 /var/cache/nginx/ \\\n        && chmod g=u /var/cache/nginx/ \\\n        && chgrp 0 /var/run/ \\\n        && chmod g=u /var/run/\nCOPY build /usr/share/nginx/html\nEXPOSE 8080\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/docker/default.conf",
    "content": "server {\n    listen       8080;\n    server_name  localhost;\n\n    #charset koi8-r;\n    #access_log  /var/log/nginx/host.access.log  main;\n\n    location / {\n        root   /usr/share/nginx/html;\n        index  index.html index.htm;\n\n        # Workaround for client-side routing:\n        # - https://create-react-app.dev/docs/deployment/#serving-apps-with-client-side-routing\n        # - https://stackoverflow.com/questions/43951720/react-router-and-nginx\n        try_files $uri /index.html;\n    }\n\n    location /api {\n        proxy_pass ${BACKEND_URL}/api;\n    }\n\n    # EventSource configuration:\n    # - https://stackoverflow.com/questions/13672743/eventsource-server-sent-events-through-nginx\n    location /api/events {\n        proxy_pass ${BACKEND_URL}/api/events;\n        proxy_set_header Connection '';\n        proxy_http_version 1.1;\n        chunked_transfer_encoding off;\n        proxy_buffering off;\n        proxy_cache off;\n        proxy_read_timeout 40h; # Otherwise the connection will be closed after 60s.\n    }\n\n    #error_page  404              /404.html;\n\n    # redirect server error pages to the static page /50x.html\n    #\n    error_page   500 502 503 504  /50x.html;\n    location = /50x.html {\n        root   /usr/share/nginx/html;\n    }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/docker/nginx.conf",
    "content": "\nworker_processes  1;\n\nerror_log  /var/log/nginx/error.log warn;\npid        /var/run/nginx.pid;\n\n\nevents {\n    worker_connections  1024;\n}\n\n\nhttp {\n    include       /etc/nginx/mime.types;\n    default_type  application/octet-stream;\n\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  /var/log/nginx/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    keepalive_timeout  65;\n\n    #gzip  on;\n\n    include /etc/nginx/conf.d/*.conf;\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/package.json",
    "content": "{\n  \"name\": \"optaweb-vehicle-routing-frontend\",\n  \"homepage\": \".\",\n  \"private\": true,\n  \"license\": \"Apache-2.0\",\n  \"dependencies\": {\n    \"@patternfly/patternfly\": \"~4.219.2\",\n    \"@patternfly/react-core\": \"~4.250.1\",\n    \"@patternfly/react-icons\": \"~4.92.10\",\n    \"leaflet\": \"^1.6.0\",\n    \"leaflet-geosearch\": \"^3.7.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-leaflet\": \"^2.7.0\",\n    \"react-redux\": \"^8.0.5\",\n    \"react-router\": \"^5.3.4\",\n    \"react-router-dom\": \"^5.3.4\",\n    \"react-scripts\": \"^5.0.1\",\n    \"redux\": \"^4.2.0\",\n    \"redux-devtools-extension\": \"^2.13.8\",\n    \"redux-logger\": \"^3.0.6\",\n    \"redux-thunk\": \"^2.4.2\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"postbuild\": \"shx rm -rf docker/build && shx cp -r build docker\",\n    \"test\": \"react-scripts test --reporters=default --reporters=jest-junit\",\n    \"eject\": \"react-scripts eject\",\n    \"coverage\": \"npm run test -- --coverage --watchAll=false\",\n    \"update-snapshots\": \"npm run test -- -u --watchAll=false\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"lint\": \"eslint --ext .js,.ts,.tsx src/ cypress/\",\n    \"lint:fix\": \"npm run lint -- --fix\",\n    \"cypress:open\": \"cypress open\",\n    \"cypress:run\": \"cypress run\"\n  },\n  \"jest-junit\": {\n    \"outputDirectory\": \"./target/test-reports\",\n    \"outputName\": \"TEST-frontend.xml\",\n    \"suiteName\": \"org.optaweb.vehiclerouting.frontend.tests\",\n    \"suiteNameTemplate\": \"{filepath}\",\n    \"classNameTemplate\": \"org.optaweb.vehiclerouting.frontend.{filename}.{classname}\",\n    \"titleTemplate\": \"{title}\",\n    \"ancestorSeparator\": \".\"\n  },\n  \"devDependencies\": {\n    \"@testing-library/jest-dom\": \"^5.16.5\",\n    \"@testing-library/react\": \"^13.4.0\",\n    \"@testing-library/user-event\": \"^14.4.3\",\n    \"@types/jest\": \"^27.5.2\",\n    \"@types/leaflet\": \"^1.5.12\",\n    \"@types/node\": \"^13.13.5\",\n    \"@types/react\": \"~18.0.25\",\n    \"@types/react-dom\": \"^18.0.9\",\n    \"@types/react-leaflet\": \"^2.5.1\",\n    \"@types/react-router-dom\": \"^5.3.3\",\n    \"@types/react-test-renderer\": \"^18.0.0\",\n    \"@types/redux-logger\": \"^3.0.9\",\n    \"@types/redux-mock-store\": \"^1.0.3\",\n    \"cypress\": \"^7.0.1\",\n    \"eslint-config-airbnb-typescript\": \"~17.0.0\",\n    \"eslint-config-react-app\": \"^7.0.1\",\n    \"eslint-plugin-cypress\": \"^2.12.1\",\n    \"eslint-plugin-jest-dom\": \"^4.0.3\",\n    \"eventsourcemock\": \"^2.0.0\",\n    \"fetch-mock-jest\": \"^1.5.1\",\n    \"history\": \"^4.10.1\",\n    \"jest-junit\": \"^10.0.0\",\n    \"node-fetch\": \"^2.6.1\",\n    \"react-test-renderer\": \"^18.2.0\",\n    \"redux-mock-store\": \"^1.5.4\",\n    \"shx\": \"^0.3.4\",\n    \"typescript\": \"~4.1\",\n    \"use-sync-external-store\": \"^1.2.0\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaweb.vehiclerouting</groupId>\n    <artifactId>optaweb-vehicle-routing</artifactId>\n    <version>8.35.0.Final</version>\n  </parent>\n\n  <artifactId>optaweb-vehicle-routing-frontend</artifactId>\n  <packaging>war</packaging>\n\n  <name>OptaWeb Vehicle Routing Frontend</name>\n\n  <properties>\n    <sonar.sources>src</sonar.sources>\n    <sonar.test.inclusions>**/*test.ts,**/*test.tsx</sonar.test.inclusions>\n    <sonar.javascript.lcov.reportPaths>coverage/lcov.info</sonar.javascript.lcov.reportPaths>\n  </properties>\n\n  <build>\n    <finalName>optaweb-vehicle-routing-frontend</finalName>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.github.eirslett</groupId>\n          <artifactId>frontend-maven-plugin</artifactId>\n          <version>${version.frontend-maven-plugin}</version>\n          <configuration>\n            <nodeVersion>${version.node}</nodeVersion>\n            <npmVersion>${version.npm}</npmVersion>\n          </configuration>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n    <plugins>\n      <plugin>\n        <groupId>com.github.eirslett</groupId>\n        <artifactId>frontend-maven-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>install node and npm</id>\n            <phase>initialize</phase>\n            <goals>\n              <goal>install-node-and-npm</goal>\n            </goals>\n          </execution>\n          <execution>\n            <id>npm install</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>npm</goal>\n            </goals>\n            <configuration>\n              <arguments>install</arguments>\n              <environmentVariables>\n                <!--\n                  Do not download Cypress binary during `npm install`. It's large (0.5G) and it's only needed locally\n                  when running `npm run cypress:run`. When building the project with Maven (e.g. on Jenkins), Cypress\n                  tests are executed in a Docker container so the binary downloaded by the npm package is not used.\n                 -->\n                <CYPRESS_INSTALL_BINARY>0</CYPRESS_INSTALL_BINARY>\n              </environmentVariables>\n            </configuration>\n          </execution>\n          <execution>\n            <id>npm run typecheck</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>npm</goal>\n            </goals>\n            <configuration>\n              <arguments>run typecheck</arguments>\n            </configuration>\n          </execution>\n          <execution>\n            <id>npm run lint</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>npm</goal>\n            </goals>\n            <configuration>\n              <arguments>run lint</arguments>\n            </configuration>\n          </execution>\n          <execution>\n            <id>npm test</id>\n            <phase>test</phase>\n            <goals>\n              <goal>npm</goal>\n            </goals>\n            <configuration>\n              <arguments>run coverage</arguments>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <artifactId>maven-resources-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>copy-resources</id>\n            <phase>prepare-package</phase>\n            <goals>\n              <goal>copy-resources</goal>\n            </goals>\n            <configuration>\n              <outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>\n              <resources>\n                <resource>\n                  <directory>build</directory>\n                </resource>\n              </resources>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n  <profiles>\n    <profile>\n      <!-- Do npm run build by default but allow to skip it (mvn -P-npmBuild) to speed up local builds. -->\n      <id>npmBuild</id>\n      <!-- Active unless deactivated (https://stackoverflow.com/a/57141425/1691152). -->\n      <activation>\n        <file>\n          <exists>.</exists>\n        </file>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.github.eirslett</groupId>\n            <artifactId>frontend-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>npm run build</id>\n                <phase>prepare-package</phase>\n                <goals>\n                  <goal>npm</goal>\n                </goals>\n                <configuration>\n                  <arguments>run build</arguments>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>productized</id>\n      <activation>\n        <property>\n          <name>productized</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.github.eirslett</groupId>\n            <artifactId>frontend-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>lock-treatment-tool execution</id>\n                <phase>initialize</phase>\n                <goals>\n                  <goal>npm</goal>\n                </goals>\n                <configuration>\n                  <arguments>exec @kie/lock-treatment-tool@^0.2.2 --</arguments>\n                </configuration>\n              </execution>\n              <execution>\n                <id>lock-treatment-tool final cleanup</id>\n                <phase>prepare-package</phase>\n                <goals>\n                  <goal>npm</goal>\n                </goals>\n                <configuration>\n                  <arguments>exec @kie/lock-treatment-tool@^0.2.2 --</arguments>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"theme-color\" content=\"#000000\">\n    <!--\n      manifest.json provides metadata used when your web app is added to the\n      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\">\n    <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/favicon.ico\">\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/leaflet@1.3.3/dist/leaflet.css\"\n          integrity=\"sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==\"\n          crossorigin=\"\"/>\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>OptaWeb Vehicle Routing</title>\n  </head>\n  <body>\n    <noscript>\n      You need to enable JavaScript to run this app.\n    </noscript>\n    <div id=\"root\" style=\"height: 100%\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/public/manifest.json",
    "content": "{\n  \"short_name\": \"OptaWeb VRP\",\n  \"name\": \"OptaWeb Vehicle Routing\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/@types/eventsourcemock.d.ts",
    "content": "// See https://github.com/gcedo/eventsourcemock\n\ndeclare module 'eventsourcemock' {\n  interface EventSource {\n    onopen: () => unknown;\n    onerror: () => unknown;\n\n    emit(eventName: string, messageEvent?: MessageEvent);\n\n    emitOpen();\n  }\n\n  let sources: { [key: string]: EventSource };\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/common.ts",
    "content": "export const backendUrl = process.env.REACT_APP_BACKEND_URL;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/index.css",
    "content": "body {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/index.tsx",
    "content": "import '@patternfly/react-core/dist/styles/base.css';\nimport { backendUrl } from 'common';\nimport * as ReactDOM from 'react-dom';\nimport { Provider } from 'react-redux';\nimport { BrowserRouter } from 'react-router-dom';\nimport './index.css';\nimport { unregister } from './registerServiceWorker';\nimport { configureStore } from './store';\nimport App from './ui/App';\n\nconst store = configureStore({\n  backendUrl: `${backendUrl}/api`,\n});\n\nReactDOM.render(\n  <Provider store={store}>\n    <BrowserRouter>\n      <App />\n    </BrowserRouter>\n  </Provider>,\n  document.getElementById('root') as HTMLElement,\n);\n\nunregister();\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/react-app-env.d.ts",
    "content": "/* eslint-disable spaced-comment */\n/// <reference types=\"react-scripts\" />\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/registerServiceWorker.ts",
    "content": "// In production, we register a service worker to serve assets from local cache.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on the 'N+1' visit to a page, since previously\n// cached resources are updated in the background.\n\n// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.\n// This link also includes instructions on opting out of this behavior.\n\nconst isLocalhost = Boolean(\n  window.location.hostname === 'localhost' ||\n    // [::1] is the IPv6 localhost address.\n    window.location.hostname === '[::1]' ||\n    // 127.0.0.1/8 is considered localhost for IPv4.\n    window.location.hostname.match(\n      /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,\n    ),\n);\n\nexport default function register() {\n  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n    // The URL constructor is available in all browsers that support SW.\n    const publicUrl = new URL(\n      process.env.PUBLIC_URL!,\n      window.location.toString(),\n    );\n    if (publicUrl.origin !== window.location.origin) {\n      // Our service worker won't work if PUBLIC_URL is on a different origin\n      // from what our page is served on. This might happen if a CDN is used to\n      // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374\n      return;\n    }\n\n    window.addEventListener('load', () => {\n      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n      if (isLocalhost) {\n        // This is running on localhost. Lets check if a service worker still exists or not.\n        checkValidServiceWorker(swUrl);\n\n        // Add some additional logging to localhost, pointing developers to the\n        // service worker/PWA documentation.\n        navigator.serviceWorker.ready.then(() => {\n          console.log(\n            'This web app is being served cache-first by a service ' +\n              'worker. To learn more, visit https://goo.gl/SC7cgQ',\n          );\n        });\n      } else {\n        // Is not local host. Just register service worker\n        registerValidSW(swUrl);\n      }\n    });\n  }\n}\n\nfunction registerValidSW(swUrl: string) {\n  navigator.serviceWorker\n    .register(swUrl)\n    .then((registration) => {\n      registration.onupdatefound = () => {\n        const installingWorker = registration.installing;\n        if (installingWorker) {\n          installingWorker.onstatechange = () => {\n            if (installingWorker.state === 'installed') {\n              if (navigator.serviceWorker.controller) {\n                // At this point, the old content will have been purged and\n                // the fresh content will have been added to the cache.\n                // It's the perfect time to display a 'New content is\n                // available; please refresh.' message in your web app.\n                console.log('New content is available; please refresh.');\n              } else {\n                // At this point, everything has been precached.\n                // It's the perfect time to display a\n                // 'Content is cached for offline use.' message.\n                console.log('Content is cached for offline use.');\n              }\n            }\n          };\n        }\n      };\n    })\n    .catch((error) => {\n      console.error('Error during service worker registration:', error);\n    });\n}\n\nfunction checkValidServiceWorker(swUrl: string) {\n  // Check if the service worker can be found. If it can't reload the page.\n  fetch(swUrl)\n    .then((response) => {\n      // Ensure service worker exists, and that we really are getting a JS file.\n      if (\n        response.status === 404 ||\n        response.headers.get('content-type')!.indexOf('javascript') === -1\n      ) {\n        // No service worker found. Probably a different app. Reload the page.\n        navigator.serviceWorker.ready.then((registration) => {\n          registration.unregister().then(() => {\n            window.location.reload();\n          });\n        });\n      } else {\n        // Service worker found. Proceed as normal.\n        registerValidSW(swUrl);\n      }\n    })\n    .catch(() => {\n      console.log(\n        'No internet connection found. App is running in offline mode.',\n      );\n    });\n}\n\nexport function unregister() {\n  if ('serviceWorker' in navigator) {\n    navigator.serviceWorker.ready.then((registration) => {\n      registration.unregister();\n    });\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/setupTests.ts",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\nimport EventSource from 'eventsourcemock';\n\nObject.defineProperty(window, 'EventSource', {\n  value: EventSource,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/actions.ts",
    "content": "import { Viewport } from 'react-leaflet';\nimport { ActionFactory } from '../types';\nimport { ActionType, ResetViewportAction, UpdateViewportAction } from './types';\n\nexport const updateViewport: ActionFactory<Viewport, UpdateViewportAction> = (viewport) => ({\n  type: ActionType.UPDATE_VIEWPORT,\n  value: viewport,\n});\n\nexport const resetViewport: ActionFactory<void, ResetViewportAction> = () => ({\n  type: ActionType.RESET_VIEWPORT,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/client.test.ts",
    "content": "import { Viewport } from 'react-leaflet';\nimport * as actions from './actions';\nimport reducer from './index';\nimport { initialViewportState } from './reducers';\nimport { UserViewport } from './types';\n\nconst zoom = 13;\nconst center: [number, number] = [1, -3];\nconst userViewport: UserViewport = {\n  isDirty: true,\n  zoom,\n  center,\n};\n\ndescribe('Client reducer', () => {\n  it('update viewport', () => {\n    expect(\n      reducer(initialViewportState, actions.updateViewport({ zoom, center })),\n    ).toEqual(userViewport);\n    expect(\n      reducer(initialViewportState, actions.updateViewport({ zoom: null, center })),\n    ).toEqual(initialViewportState);\n    expect(\n      reducer(initialViewportState, actions.updateViewport({ zoom, center: null })),\n    ).toEqual(initialViewportState);\n    expect(\n      reducer(initialViewportState, actions.updateViewport(null as unknown as Viewport)),\n    ).toEqual(initialViewportState);\n  });\n\n  it('reset viewport', () => {\n    expect(\n      reducer(userViewport, actions.resetViewport()),\n    ).toEqual(initialViewportState);\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/index.ts",
    "content": "import * as clientOperations from './operations';\nimport { clientReducer } from './reducers';\n\nexport { clientOperations };\n\nexport default clientReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/operations.ts",
    "content": "import * as actions from './actions';\n\nexport const { updateViewport, resetViewport } = actions;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/reducers.ts",
    "content": "import { ActionType, UserViewport, ViewportAction } from './types';\n\nexport const initialViewportState: UserViewport = {\n  isDirty: false,\n  center: [0, 0],\n  zoom: 2,\n};\n\n// eslint-disable-next-line @typescript-eslint/default-param-last\nexport const clientReducer = (state = initialViewportState, action: ViewportAction): UserViewport => {\n  switch (action.type) {\n    case ActionType.UPDATE_VIEWPORT: {\n      if (!action.value || !action.value.zoom || !action.value.center) {\n        return state;\n      }\n      return { isDirty: true, zoom: action.value.zoom, center: action.value.center };\n    }\n    case ActionType.RESET_VIEWPORT: {\n      return initialViewportState;\n    }\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/client/types.ts",
    "content": "import { Viewport as LeafletViewport } from 'react-leaflet';\nimport { Action } from 'redux';\n\nexport enum ActionType {\n  UPDATE_VIEWPORT = 'UPDATE_VIEWPORT',\n  RESET_VIEWPORT = 'RESET_VIEWPORT',\n}\n\nexport interface UpdateViewportAction extends Action<ActionType.UPDATE_VIEWPORT> {\n  value: LeafletViewport;\n}\n\nexport interface ResetViewportAction extends Action<ActionType.RESET_VIEWPORT> {\n}\n\nexport interface UserViewport {\n  isDirty: boolean;\n  center: [number, number];\n  zoom: number;\n}\n\nexport type ViewportAction =\n  | UpdateViewportAction\n  | ResetViewportAction;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/actions.ts",
    "content": "import { ActionFactory } from '../types';\nimport { ActionType, FinishLoadingAction, RequestDemoAction } from './types';\n\nexport const requestDemo: ActionFactory<string, RequestDemoAction> = (name) => ({\n  type: ActionType.REQUEST_DEMO,\n  name,\n});\n\nexport const finishLoading: ActionFactory<void, FinishLoadingAction> = () => ({\n  type: ActionType.FINISH_LOADING,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/demo.test.ts",
    "content": "import { mockStore } from '../mockStore';\nimport { Vehicle } from '../route/types';\nimport { AppState } from '../types';\nimport { WebSocketConnectionStatus } from '../websocket/types';\nimport * as actions from './actions';\nimport reducer, { demoOperations } from './index';\nimport { Demo } from './types';\n\ndescribe('Demo operations', () => {\n  it('demo request should call loadDemo() on client', () => {\n    const { store, client } = mockStore(state);\n    const demoName = 'demo name';\n\n    // verify requestDemo operation calls the client\n    store.dispatch(demoOperations.requestDemo(demoName));\n    expect(client.loadDemo).toHaveBeenCalledTimes(1);\n\n    expect(store.getActions()).toEqual([actions.requestDemo(demoName)]);\n  });\n});\n\ndescribe('Demo reducers', () => {\n  it('request demo', () => {\n    const demoName = 'some name';\n    const initialState: Demo = { isLoading: false, demoName: null };\n    const expectedState: Demo = { isLoading: true, demoName };\n    expect(\n      reducer(initialState, actions.requestDemo(demoName)),\n    ).toEqual(expectedState);\n  });\n\n  it('start loading when loading requested by someone else', () => {\n    const demoName = 'some name';\n    const initialState: Demo = { isLoading: false, demoName: null };\n    const expectedState: Demo = { isLoading: true, demoName };\n    expect(\n      reducer(initialState, actions.requestDemo(demoName)),\n    ).toEqual(expectedState);\n  });\n\n  it('loading flag should be cleared when demo is loaded', () => {\n    const demoName = 'some name';\n    const initialState: Demo = { isLoading: true, demoName };\n    const expectedState: Demo = { isLoading: false, demoName };\n    expect(\n      reducer(initialState, actions.finishLoading()),\n    ).toEqual(expectedState);\n  });\n});\n\nconst vehicle1: Vehicle = { id: 1, name: 'v1', capacity: 5 };\nconst visit1 = {\n  id: 1,\n  lat: 1.345678,\n  lng: 1.345678,\n};\nconst visit2 = {\n  id: 2,\n  lat: 2.345678,\n  lng: 2.345678,\n};\nconst visit3 = {\n  id: 3,\n  lat: 3.676111,\n  lng: 3.568333,\n};\n\nconst state: AppState = {\n  connectionStatus: WebSocketConnectionStatus.CLOSED,\n  messages: [],\n  serverInfo: {\n    boundingBox: null,\n    countryCodes: [],\n    demos: [],\n  },\n  userViewport: {\n    isDirty: false,\n    zoom: 1,\n    center: [0, 0],\n  },\n  demo: {\n    demoName: null,\n    isLoading: false,\n  },\n  plan: {\n    distance: '10',\n    vehicles: [vehicle1],\n    depot: null,\n    visits: [visit1, visit2, visit3],\n    routes: [{\n      vehicle: vehicle1,\n      visits: [visit1, visit2, visit3],\n\n      track: [[0.111222, 0.222333], [0.444555, 0.555666]],\n    }],\n  },\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/index.ts",
    "content": "import * as demoOperations from './operations';\nimport { demoReducer } from './reducers';\n\nexport { demoOperations };\n\nexport default demoReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/operations.ts",
    "content": "import { ThunkCommandFactory } from '../types';\nimport * as actions from './actions';\nimport { RequestDemoAction } from './types';\n\nexport const { finishLoading } = actions;\n\nexport const requestDemo: ThunkCommandFactory<string, RequestDemoAction> = (\n  (name) => (dispatch, _getState, client) => {\n    dispatch(actions.requestDemo(name));\n    client.loadDemo(name);\n  });\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/reducers.ts",
    "content": "import { ActionType, Demo, DemoAction } from './types';\n\nconst initialState: Demo = {\n  isLoading: false,\n  demoName: null,\n};\n\n// eslint-disable-next-line @typescript-eslint/default-param-last\nexport const demoReducer = (state = initialState, action: DemoAction): Demo => {\n  switch (action.type) {\n    case ActionType.REQUEST_DEMO: {\n      return { ...initialState, isLoading: true, demoName: action.name };\n    }\n    case ActionType.FINISH_LOADING: {\n      return { ...state, isLoading: false };\n    }\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/demo/types.ts",
    "content": "import { Action } from 'redux';\n\nexport enum ActionType {\n  REQUEST_DEMO = 'REQUEST_DEMO',\n  FINISH_LOADING = 'FINISH_LOADING',\n}\n\nexport interface RequestDemoAction extends Action<ActionType.REQUEST_DEMO> {\n  readonly name: string;\n}\n\nexport interface FinishLoadingAction extends Action<ActionType.FINISH_LOADING> {\n}\n\nexport type DemoAction =\n  | RequestDemoAction\n  | FinishLoadingAction;\n\nexport interface Demo {\n  readonly isLoading: boolean;\n  readonly demoName: string | null;\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/index.ts",
    "content": "export * from './store';\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/actions.ts",
    "content": "import { ActionFactory } from '../types';\nimport { ActionType, MessagePayload, ReadMessageAction, ReceiveMessageAction } from './types';\n\nexport const receiveMessage: ActionFactory<MessagePayload, ReceiveMessageAction> = (payload) => ({\n  type: ActionType.RECEIVE_MESSAGE,\n  payload,\n});\n\nexport const readMessage: ActionFactory<string, ReadMessageAction> = (id) => ({\n  type: ActionType.READ_MESSAGE,\n  id,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/index.ts",
    "content": "import * as messageActions from './actions';\nimport { messageReducer } from './reducers';\nimport * as messageSelectors from './selectors';\n\nexport { messageActions, messageSelectors };\n\nexport default messageReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/message.test.ts",
    "content": "import { Message, MessagePayload } from 'store/message/types';\nimport * as actions from './actions';\nimport reducer, { messageSelectors } from './index';\n\nconst messages: Message[] = [\n  { id: '1', text: '', status: 'read' },\n  { id: '2', text: '', status: 'new' },\n  { id: '3', text: '', status: 'read' },\n  { id: '4', text: '', status: 'new' },\n  { id: '5', text: '', status: 'new' },\n];\n\ndescribe('Message reducer', () => {\n  it(\n    'should return initial state when previous state is undefined',\n    () => expect(reducer(undefined, actions.readMessage(''))).toEqual([]),\n  );\n\n  it('receiving a message should add it with the status equal to NEW', () => {\n    const message: MessagePayload = { id: 'xy', text: 'message text' };\n    expect(\n      reducer([], actions.receiveMessage(message)),\n    ).toEqual([{ ...message, status: 'new' }]);\n  });\n\n  it('reading a message should update its status to READ', () => {\n    const updatedMessages = [...messages];\n    updatedMessages[1] = { ...messages[1], status: 'read' };\n    expect(\n      reducer(messages, actions.readMessage('2')),\n    ).toEqual(updatedMessages);\n  });\n});\n\ndescribe('Message selectors', () => {\n  it('select new messages', () => {\n    expect(\n      messageSelectors.getNewMessages(messages),\n    ).toEqual([\n      { id: '2', text: '', status: 'new' },\n      { id: '4', text: '', status: 'new' },\n      { id: '5', text: '', status: 'new' },\n    ]);\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/reducers.ts",
    "content": "import { ActionType, Message, MessageAction } from './types';\n\n// eslint-disable-next-line @typescript-eslint/default-param-last\nexport const messageReducer = (state: Message[] = [], action: MessageAction): Message[] => {\n  switch (action.type) {\n    case ActionType.RECEIVE_MESSAGE: {\n      return [...state, { ...action.payload, status: 'new' }];\n    }\n    case ActionType.READ_MESSAGE: {\n      return state.map((message) => (\n        message.id === action.id ? { ...message, status: 'read' } : message\n      ));\n    }\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/selectors.ts",
    "content": "import { Message } from 'store/message/types';\n\nexport const getNewMessages = (messages: Message[]) => messages.filter((message) => message.status === 'new');\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/message/types.ts",
    "content": "import { Action } from 'redux';\n\nexport enum ActionType {\n  RECEIVE_MESSAGE = 'RECEIVE_MESSAGE',\n  READ_MESSAGE = 'READ_MESSAGE',\n}\n\nexport interface ReceiveMessageAction extends Action<ActionType.RECEIVE_MESSAGE> {\n  readonly payload: MessagePayload;\n}\n\nexport interface ReadMessageAction extends Action<ActionType.READ_MESSAGE> {\n  readonly id: string;\n}\n\nexport type MessageAction = ReceiveMessageAction | ReadMessageAction;\n\nexport interface MessagePayload {\n  readonly id: string;\n  readonly text: string;\n}\n\nexport interface Message extends MessagePayload {\n  readonly status: 'new' | 'read';\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/mockStore.ts",
    "content": "import { Middleware } from 'redux';\nimport createMockStore, { MockStoreCreator } from 'redux-mock-store';\nimport thunk, { ThunkDispatch } from 'redux-thunk';\nimport WebSocketClient from '../websocket/WebSocketClient';\nimport { UpdateRouteAction } from './route/types';\nimport { AppState } from './types';\nimport { WebSocketAction } from './websocket/types';\n\njest.mock('../websocket/WebSocketClient');\n\nexport const mockStore = (state: AppState) => {\n  const client = new WebSocketClient('');\n  const middlewares: Middleware[] = [thunk.withExtraArgument(client)];\n  type DispatchExts = ThunkDispatch<AppState, WebSocketClient, WebSocketAction | UpdateRouteAction>;\n  const mockStoreCreator: MockStoreCreator<AppState, DispatchExts> = (\n    createMockStore<AppState, DispatchExts>(middlewares)\n  );\n  return { store: mockStoreCreator(state), client };\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/actions.ts",
    "content": "import { ActionFactory } from '../types';\nimport {\n  ActionType,\n  AddLocationAction,\n  AddVehicleAction,\n  ClearRouteAction,\n  DeleteLocationAction,\n  DeleteVehicleAction,\n  LatLngWithDescription,\n  RoutingPlan,\n  UpdateRouteAction,\n} from './types';\n\nexport const addVehicle: ActionFactory<void, AddVehicleAction> = () => ({\n  type: ActionType.ADD_VEHICLE,\n});\n\nexport const deleteVehicle: ActionFactory<number, DeleteVehicleAction> = (id) => ({\n  type: ActionType.DELETE_VEHICLE,\n  value: id,\n});\n\nexport const addLocation: ActionFactory<LatLngWithDescription, AddLocationAction> = (location) => ({\n  type: ActionType.ADD_LOCATION,\n  value: location,\n});\n\nexport const deleteLocation: ActionFactory<number, DeleteLocationAction> = (id) => ({\n  type: ActionType.DELETE_LOCATION,\n  value: id,\n});\n\nexport const clearRoute: ActionFactory<void, ClearRouteAction> = () => ({\n  type: ActionType.CLEAR_SOLUTION,\n});\n\nexport const updateRoute: ActionFactory<RoutingPlan, UpdateRouteAction> = (plan) => ({\n  plan,\n  type: ActionType.UPDATE_ROUTING_PLAN,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/index.ts",
    "content": "import * as routeOperations from './operations';\nimport { routeReducer } from './reducers';\nimport * as routeSelectors from './selectors';\n\nexport { routeOperations, routeSelectors };\n\nexport default routeReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/operations.ts",
    "content": "import { ThunkCommandFactory } from '../types';\nimport * as actions from './actions';\nimport {\n  AddLocationAction,\n  AddVehicleAction,\n  ClearRouteAction,\n  DeleteLocationAction,\n  DeleteVehicleAction,\n  LatLngWithDescription,\n  VehicleCapacity,\n} from './types';\n\nexport const { updateRoute } = actions;\n\nexport const addLocation: ThunkCommandFactory<LatLngWithDescription, AddLocationAction> = (\n  (location) => (dispatch, _getState, client) => {\n    dispatch(actions.addLocation(location));\n    client.addLocation(location);\n  });\n\nexport const deleteLocation: ThunkCommandFactory<number, DeleteLocationAction> = (\n  (locationId) => (dispatch, _getState, client) => {\n    dispatch(actions.deleteLocation(locationId));\n    client.deleteLocation(locationId);\n  });\n\nexport const addVehicle: ThunkCommandFactory<void, AddVehicleAction> = (\n  () => (dispatch, _getState, client) => {\n    dispatch(actions.addVehicle());\n    client.addVehicle();\n  });\n\nexport const deleteVehicle: ThunkCommandFactory<number, DeleteVehicleAction> = (\n  (vehicleId) => (dispatch, _getState, client) => {\n    dispatch(actions.deleteVehicle(vehicleId));\n    client.deleteVehicle(vehicleId);\n  });\n\nexport const deleteAnyVehicle: ThunkCommandFactory<void, never> = (\n  () => (_dispatch, _getState, client) => {\n    client.deleteAnyVehicle();\n  });\n\nexport const changeVehicleCapacity: ThunkCommandFactory<VehicleCapacity, never> = (\n  ({ vehicleId, capacity }: VehicleCapacity) => (_dispatch, _getState, client) => {\n    client.changeVehicleCapacity(vehicleId, capacity);\n  });\n\nexport const clearRoute: ThunkCommandFactory<void, ClearRouteAction> = (\n  () => (dispatch, _getState, client) => {\n    dispatch(actions.clearRoute());\n    client.clear();\n  });\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/reducers.ts",
    "content": "import { ActionType, RouteAction, RoutingPlan } from './types';\n\nexport const initialRouteState: RoutingPlan = {\n  distance: 'no data',\n  vehicles: [],\n  depot: null,\n  visits: [],\n  routes: [],\n};\n\n// eslint-disable-next-line @typescript-eslint/default-param-last\nexport const routeReducer = (state = initialRouteState, action: RouteAction): RoutingPlan => {\n  switch (action.type) {\n    case ActionType.UPDATE_ROUTING_PLAN: {\n      return action.plan;\n    }\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/route.test.ts",
    "content": "import { mockStore } from '../mockStore';\nimport { AppState } from '../types';\nimport { WebSocketConnectionStatus } from '../websocket/types';\nimport * as actions from './actions';\nimport reducer, { routeOperations, routeSelectors } from './index';\nimport { initialRouteState } from './reducers';\nimport { LatLngWithDescription, Vehicle, VehicleCapacity } from './types';\n\ndescribe('Route operations', () => {\n  it('clearRoute() should call client', () => {\n    const { store, client } = mockStore(state);\n\n    store.dispatch(routeOperations.clearRoute());\n\n    expect(store.getActions()).toEqual([actions.clearRoute()]);\n    expect(client.clear).toHaveBeenCalledTimes(1);\n  });\n\n  it('deleteLocation() should call client', () => {\n    const { store, client } = mockStore(state);\n    const id = 3214;\n\n    store.dispatch(routeOperations.deleteLocation(id));\n\n    expect(store.getActions()).toEqual([actions.deleteLocation(id)]);\n    expect(client.deleteLocation).toHaveBeenCalledTimes(1);\n    expect(client.deleteLocation).toHaveBeenCalledWith(id);\n  });\n\n  it('deleteVehicle() should call client', () => {\n    const { store, client } = mockStore(state);\n    const id = 5;\n\n    store.dispatch(routeOperations.deleteVehicle(id));\n\n    expect(store.getActions()).toEqual([actions.deleteVehicle(id)]);\n    expect(client.deleteVehicle).toHaveBeenCalledTimes(1);\n    expect(client.deleteVehicle).toHaveBeenCalledWith(id);\n  });\n\n  it('deleteAnyVehicle() should call client', () => {\n    const { store, client } = mockStore(state);\n\n    store.dispatch(routeOperations.deleteAnyVehicle());\n\n    expect(store.getActions()).toEqual([]);\n    expect(client.deleteAnyVehicle).toHaveBeenCalledTimes(1);\n  });\n\n  it('addLocation() should call client', () => {\n    const { store, client } = mockStore(state);\n    const location: LatLngWithDescription = { lat: 11.01, lng: -35.79, description: 'new location' };\n\n    store.dispatch(routeOperations.addLocation(location));\n\n    expect(store.getActions()).toEqual([actions.addLocation(location)]);\n    expect(client.addLocation).toHaveBeenCalledTimes(1);\n    expect(client.addLocation).toHaveBeenCalledWith(location);\n  });\n\n  it('addVehicle() should call client', () => {\n    const { store, client } = mockStore(state);\n\n    store.dispatch(routeOperations.addVehicle());\n\n    expect(store.getActions()).toEqual([actions.addVehicle()]);\n    expect(client.addVehicle).toHaveBeenCalledTimes(1);\n    expect(client.addVehicle).toHaveBeenCalledWith();\n  });\n\n  it('changeVehicleCapacity() should call client', () => {\n    const { store, client } = mockStore(state);\n\n    const capacityChange: VehicleCapacity = { vehicleId: 5, capacity: 50 };\n    store.dispatch(routeOperations.changeVehicleCapacity(capacityChange));\n\n    expect(store.getActions()).toEqual([]);\n    expect(client.changeVehicleCapacity).toHaveBeenCalledWith(capacityChange.vehicleId, capacityChange.capacity);\n  });\n});\n\ndescribe('Route reducers', () => {\n  it('clear route', () => {\n    expect(\n      reducer(state.plan, actions.clearRoute()),\n    ).toEqual(state.plan);\n  });\n\n  it('add location', () => {\n    expect(\n      reducer(state.plan, actions.addLocation({\n        lat: 1,\n        lng: -1,\n        description: 'description',\n      })),\n    ).toEqual(state.plan);\n  });\n\n  it('delete location', () => {\n    expect(\n      reducer(state.plan, actions.deleteLocation(1)),\n    ).toEqual(state.plan);\n  });\n\n  it('add vehicle', () => {\n    expect(\n      reducer(state.plan, actions.addVehicle()),\n    ).toEqual(state.plan);\n  });\n\n  it('delete vehicle', () => {\n    expect(\n      reducer(state.plan, actions.deleteVehicle(7)),\n    ).toEqual(state.plan);\n  });\n\n  it('update route', () => {\n    expect(\n      reducer(initialRouteState, actions.updateRoute(state.plan)),\n    ).toEqual(state.plan);\n  });\n});\n\ndescribe('Route selectors', () => {\n  it('select total capacity', () => {\n    expect(\n      routeSelectors.totalCapacity(state.plan),\n    ).toEqual(vehicle1.capacity + vehicle2.capacity);\n  });\n  it('select total demand', () => {\n    expect(\n      routeSelectors.totalDemand(state.plan),\n      // Currently the default demand is 1 per visit.\n    ).toEqual(state.plan.visits.length);\n  });\n});\n\nconst vehicle1: Vehicle = { id: 1, name: 'v1', capacity: 5 };\nconst vehicle2: Vehicle = { id: 2, name: 'v2', capacity: 5 };\nconst visit1 = {\n  id: 1,\n  lat: 1.345678,\n  lng: 1.345678,\n};\nconst visit2 = {\n  id: 2,\n  lat: 2.345678,\n  lng: 2.345678,\n};\nconst visit3 = {\n  id: 3,\n  lat: 3.676111,\n  lng: 3.568333,\n};\nconst visit4 = {\n  id: 4,\n  lat: 4.345678,\n  lng: 4.345678,\n};\nconst visit5 = {\n  id: 5,\n  lat: 5.345678,\n  lng: 5.345678,\n};\n\nconst state: AppState = {\n  connectionStatus: WebSocketConnectionStatus.CLOSED,\n  messages: [],\n  serverInfo: {\n    boundingBox: null,\n    countryCodes: [],\n    demos: [],\n  },\n  userViewport: {\n    isDirty: false,\n    zoom: 1,\n    center: [0, 0],\n  },\n  demo: {\n    demoName: null,\n    isLoading: false,\n  },\n  plan: {\n    distance: '10',\n    vehicles: [\n      vehicle1,\n      vehicle2,\n    ],\n    depot: null,\n    visits: [visit1, visit2, visit3, visit4, visit5],\n    routes: [{\n      vehicle: vehicle1,\n      visits: [visit1, visit2, visit3],\n      track: [[0.111222, 0.222333], [0.444555, 0.555666]],\n    }, {\n      vehicle: vehicle2,\n      visits: [visit4, visit5],\n\n      track: [[0.41, 0.42], [0.51, 0.52]],\n    }],\n  },\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/selectors.ts",
    "content": "import { RoutingPlan } from 'store/route/types';\n\nexport const totalCapacity = (plan: RoutingPlan) => plan.vehicles\n  .map((vehicle) => vehicle.capacity)\n  .reduce((a, b) => a + b, 0);\n\nexport const totalDemand = (plan: RoutingPlan) => plan.visits.length;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/route/types.ts",
    "content": "import { Action } from 'redux';\n\nexport interface LatLng {\n  readonly lat: number;\n  readonly lng: number;\n}\n\nexport interface LatLngWithDescription extends LatLng {\n  description: string;\n}\n\nexport interface Location extends LatLng {\n  readonly id: number;\n  // TODO decide between optional, nullable and more complex structure (displayName, fullDescription, address, ...)\n  readonly description?: string;\n}\n\nexport interface Vehicle {\n  readonly id: number;\n  readonly name: string;\n  readonly capacity: number;\n}\n\nexport interface Route {\n  readonly vehicle: Vehicle; // TODO change to vehicleId\n  readonly visits: Location[];\n}\n\nexport type LatLngTuple = [number, number];\n\nexport interface RouteWithTrack extends Route {\n  readonly track: LatLngTuple[];\n}\n\nexport interface RoutingPlan {\n  readonly distance: string;\n  readonly vehicles: Vehicle[];\n  readonly depot: Location | null;\n  readonly visits: Location[];\n  readonly routes: RouteWithTrack[];\n}\n\nexport enum ActionType {\n  UPDATE_ROUTING_PLAN = 'UPDATE_ROUTING_PLAN',\n  DELETE_LOCATION = 'DELETE_LOCATION',\n  ADD_LOCATION = 'ADD_LOCATION',\n  ADD_VEHICLE = 'ADD_VEHICLE',\n  DELETE_VEHICLE = 'DELETE_VEHICLE',\n  CLEAR_SOLUTION = 'CLEAR_SOLUTION',\n}\n\nexport interface AddLocationAction extends Action<ActionType.ADD_LOCATION> {\n  readonly value: LatLngWithDescription;\n}\n\nexport interface AddVehicleAction extends Action<ActionType.ADD_VEHICLE> {\n}\n\nexport interface ClearRouteAction extends Action<ActionType.CLEAR_SOLUTION> {\n}\n\nexport interface DeleteLocationAction extends Action<ActionType.DELETE_LOCATION> {\n  readonly value: number;\n}\n\nexport interface DeleteVehicleAction extends Action<ActionType.DELETE_VEHICLE> {\n  readonly value: number;\n}\n\nexport interface VehicleCapacity {\n  vehicleId: number;\n  capacity: number;\n}\n\nexport interface UpdateRouteAction extends Action<ActionType.UPDATE_ROUTING_PLAN> {\n  readonly plan: RoutingPlan;\n}\n\nexport type RouteAction =\n  | AddLocationAction\n  | AddVehicleAction\n  | DeleteLocationAction\n  | DeleteVehicleAction\n  | UpdateRouteAction\n  | ClearRouteAction;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/actions.ts",
    "content": "import { ActionFactory } from '../types';\nimport { ActionType, ServerInfo, ServerInfoAction } from './types';\n\nexport const serverInfo: ActionFactory<ServerInfo, ServerInfoAction> = (info) => ({\n  type: ActionType.SERVER_INFO,\n  value: info,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/index.ts",
    "content": "import * as serverOperations from './operations';\nimport { routeReducer } from './reducers';\n\nexport { serverOperations };\n\nexport default routeReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/operations.ts",
    "content": "import { resetViewport } from '../client/actions';\nimport { ResetViewportAction } from '../client/types';\nimport { ThunkCommandFactory } from '../types';\nimport * as actions from './actions';\nimport { ServerInfo, ServerInfoAction } from './types';\n\nexport const serverInfo: ThunkCommandFactory<ServerInfo, ServerInfoAction | ResetViewportAction> = (\n  (info) => (dispatch) => {\n    dispatch(resetViewport());\n    dispatch(actions.serverInfo(info));\n  });\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/reducers.ts",
    "content": "import { ActionType, ServerInfo, ServerInfoAction } from './types';\n\nexport const initialServerState: ServerInfo = {\n  boundingBox: null,\n  countryCodes: [],\n  demos: [],\n};\n\n// eslint-disable-next-line @typescript-eslint/default-param-last\nexport const routeReducer = (state = initialServerState, action: ServerInfoAction): ServerInfo => {\n  switch (action.type) {\n    case ActionType.SERVER_INFO: {\n      return action.value;\n    }\n    default:\n      return state;\n  }\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/server.test.ts",
    "content": "import * as actions from './actions';\nimport reducer from './index';\nimport { initialServerState } from './reducers';\nimport { ServerInfo } from './types';\n\ndescribe('Server reducer', () => {\n  const serverInfo: ServerInfo = {\n    boundingBox: null,\n    countryCodes: ['CZ', 'SK'],\n    demos: [{ name: 'Demo name', visits: 10 }],\n  };\n\n  it('server info', () => {\n    expect(\n      reducer(initialServerState, actions.serverInfo(serverInfo)),\n    ).toEqual(serverInfo);\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/server/types.ts",
    "content": "import { Action } from 'redux';\nimport { LatLng } from '../route/types';\n\nexport enum ActionType {\n  SERVER_INFO = 'SERVER_INFO',\n}\n\nexport interface ServerInfoAction extends Action<ActionType.SERVER_INFO> {\n  value: ServerInfo;\n}\n\nexport interface Demo {\n  name: string;\n  visits: number;\n}\n\nexport type BoundingBox = [LatLng, LatLng];\n\nexport interface ServerInfo {\n  boundingBox: BoundingBox | null;\n  countryCodes: string[];\n  demos: Demo[];\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/store.ts",
    "content": "import { applyMiddleware, combineReducers, createStore, Store } from 'redux';\n// it's possible to disable the extension in production\n// by importing from redux-devtools-extension/developmentOnly\nimport { composeWithDevTools } from 'redux-devtools-extension';\nimport { createLogger } from 'redux-logger';\nimport thunk from 'redux-thunk';\nimport WebSocketClient from 'websocket/WebSocketClient';\nimport clientReducer from './client';\nimport demoReducer from './demo';\nimport messageReducer from './message';\nimport routeReducer from './route';\nimport serverInfoReducer from './server';\nimport { AppState } from './types';\nimport connectionReducer from './websocket';\n\nexport interface StoreConfig {\n  readonly backendUrl: string;\n}\n\nexport function configureStore(\n  { backendUrl }: StoreConfig,\n  preloadedState?: AppState,\n): Store<AppState> {\n  const webSocketClient = new WebSocketClient(backendUrl);\n\n  const middlewares = [thunk.withExtraArgument(webSocketClient), createLogger()];\n  const middlewareEnhancer = applyMiddleware(...middlewares);\n\n  const enhancers = [middlewareEnhancer];\n  const composedEnhancers = composeWithDevTools(...enhancers);\n\n  // map reducers to state slices\n  const rootReducer = combineReducers<AppState>({\n    connectionStatus: connectionReducer,\n    messages: messageReducer,\n    serverInfo: serverInfoReducer,\n    demo: demoReducer,\n    plan: routeReducer,\n    userViewport: clientReducer,\n  });\n\n  return createStore(\n    rootReducer,\n    preloadedState,\n    composedEnhancers,\n  );\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/types.ts",
    "content": "import { Action } from 'redux';\nimport { ThunkAction } from 'redux-thunk';\nimport { Message } from 'store/message/types';\nimport WebSocketClient from 'websocket/WebSocketClient';\nimport { UserViewport } from './client/types';\nimport { Demo } from './demo/types';\nimport { RoutingPlan } from './route/types';\nimport { ServerInfo } from './server/types';\nimport { WebSocketConnectionStatus } from './websocket/types';\n\n/**\n * ThunkCommand is a ThunkAction that has no result (it's typically something like\n * `Promise<ActionAfterDataFetched>`, but sending messages over WebSocket usually has no response\n * (with the exception of subscribe), so most of our operations are void).\n *\n * @template A Type of action(s) allowed to be dispatched.\n */\nexport type ThunkCommand<A extends Action> = ThunkAction<void, AppState, WebSocketClient, A>;\n\n/**\n * Factory method that takes a value and creates an @type {Action}.\n *\n * @template V value type\n * @template A action type\n */\nexport type ActionFactory<V, A extends Action> = V extends void ?\n  // https://stackoverflow.com/questions/55646272/conditional-method-parameters-based-on-generic-type\n  () => A : // nullary\n  (value: V) => A; // unary\n\n/**\n * Factory method that takes a value and creates a @type {ThunkCommand}.\n *\n * @template V value type\n * @template A action type\n */\nexport type ThunkCommandFactory<V, A extends Action> = V extends void ?\n  () => ThunkCommand<A> : // nullary\n  (value: V) => ThunkCommand<A>; // unary\n\nexport interface AppState {\n  readonly serverInfo: ServerInfo;\n  readonly messages: Message[];\n  readonly plan: RoutingPlan;\n  readonly connectionStatus: WebSocketConnectionStatus;\n  readonly demo: Demo;\n  readonly userViewport: UserViewport;\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/actions.ts",
    "content": "import { ActionFactory } from '../types';\nimport { ActionType, InitWsConnectionAction, WsConnectionFailureAction, WsConnectionSuccessAction } from './types';\n\nexport const initWsConnection: ActionFactory<void, InitWsConnectionAction> = () => ({\n  type: ActionType.WS_CONNECT,\n});\n\nexport const wsConnectionSuccess: ActionFactory<void, WsConnectionSuccessAction> = () => ({\n  type: ActionType.WS_CONNECT_SUCCESS,\n});\n\nexport const wsConnectionFailure: ActionFactory<string, WsConnectionFailureAction> = (error) => ({\n  type: ActionType.WS_CONNECT_FAILURE,\n  error,\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/index.ts",
    "content": "import * as websocketOperations from './operations';\nimport { wsReducer } from './reducers';\n\nexport { websocketOperations };\n\nexport default wsReducer;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/operations.ts",
    "content": "import { demoOperations } from '../demo';\nimport { FinishLoadingAction } from '../demo/types';\nimport { messageActions } from '../message';\nimport { MessageAction } from '../message/types';\nimport { routeOperations } from '../route';\nimport { UpdateRouteAction } from '../route/types';\nimport { serverOperations } from '../server';\nimport { ServerInfoAction } from '../server/types';\nimport { ThunkCommandFactory } from '../types';\nimport * as actions from './actions';\nimport { WebSocketAction } from './types';\n\ntype ConnectClientThunkAction =\n  | WebSocketAction\n  | MessageAction\n  | UpdateRouteAction\n  | FinishLoadingAction\n  | ServerInfoAction;\n\n/**\n * Connect the client to WebSocket.\n */\nexport const connectClient: ThunkCommandFactory<void, ConnectClientThunkAction> = (\n  () => (dispatch, getState, client) => {\n    // Dispatch the WS connection initializing event.\n    dispatch(actions.initWsConnection());\n    client.connect(\n      // On connection, subscribe to the route topic.\n      () => {\n        dispatch(actions.wsConnectionSuccess());\n        client.subscribeToServerInfo((serverInfo) => {\n          dispatch(serverOperations.serverInfo(serverInfo));\n        });\n        client.subscribeToErrorTopic((errorMessage) => {\n          dispatch(messageActions.receiveMessage(errorMessage));\n        });\n        client.subscribeToRoute((plan) => {\n          dispatch(routeOperations.updateRoute(plan));\n          if (getState().demo.isLoading) {\n            // TODO handle the case when serverInfo doesn't contain demo with the given name\n            //      (that could only be possible due to a bug in the code)\n            const demo = getState().serverInfo.demos.filter((value) => value.name === getState().demo.demoName)[0];\n            if (plan.visits.length === demo.visits) {\n              dispatch(demoOperations.finishLoading());\n            }\n          }\n        });\n      },\n      // On error, schedule a one-time reconnection attempt.\n      (err) => {\n        // TODO try to pass the original err object or test it here and\n        //      dispatch different actions based on its properties (Frame vs. CloseEvent, reason etc.)\n        dispatch(actions.wsConnectionFailure(JSON.stringify(err)));\n        setTimeout(() => dispatch(connectClient()), 1000);\n      },\n    );\n  });\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/reducers.ts",
    "content": "import { ActionType, WebSocketAction, WebSocketConnectionStatus } from './types';\n\nexport const wsReducer = (\n// eslint-disable-next-line @typescript-eslint/default-param-last\n  (state = WebSocketConnectionStatus.CLOSED, action: WebSocketAction): WebSocketConnectionStatus => {\n    switch (action.type) {\n      case ActionType.WS_CONNECT_SUCCESS: {\n        return WebSocketConnectionStatus.OPEN;\n      }\n      case ActionType.WS_CONNECT_FAILURE: {\n        return WebSocketConnectionStatus.ERROR;\n      }\n      default:\n        return state;\n    }\n  });\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/types.ts",
    "content": "import { Action } from 'redux';\n\nexport enum WebSocketConnectionStatus {\n  OPEN = 'OPEN',\n  CLOSED = 'CLOSED',\n  ERROR = 'ERROR',\n}\n\nexport enum ActionType {\n  WS_CONNECT = 'WS_CONNECT',\n  WS_CONNECT_SUCCESS = 'WS_CONNECT_SUCCESS',\n  WS_CONNECT_FAILURE = 'WS_CONNECT_FAILURE',\n}\n\nexport interface InitWsConnectionAction extends Action<ActionType.WS_CONNECT> {\n}\n\nexport interface WsConnectionSuccessAction extends Action<ActionType.WS_CONNECT_SUCCESS> {\n}\n\nexport interface WsConnectionFailureAction extends Action<ActionType.WS_CONNECT_FAILURE> {\n  readonly error: string;\n}\n\nexport type WebSocketAction =\n  | InitWsConnectionAction\n  | WsConnectionFailureAction\n  | WsConnectionSuccessAction;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/store/websocket/websocket.test.ts",
    "content": "import { MessagePayload } from 'store/message/types';\nimport { resetViewport } from '../client/actions';\nimport { UserViewport } from '../client/types';\nimport { demoOperations } from '../demo';\nimport { receiveMessage } from '../message/actions';\nimport { mockStore } from '../mockStore';\nimport { routeOperations } from '../route';\nimport { RoutingPlan, Vehicle } from '../route/types';\nimport { serverInfo } from '../server/actions';\nimport { ServerInfo } from '../server/types';\nimport { AppState } from '../types';\nimport * as actions from './actions';\nimport reducer, { websocketOperations } from './index';\nimport { WebSocketConnectionStatus } from './types';\n\nconst uninitializedCallbackCapture = () => {\n  throw new Error('Error callback is uninitialized');\n};\n\nconst userViewport: UserViewport = {\n  isDirty: true,\n  zoom: 1,\n  center: [0, 0],\n};\n\nbeforeEach(() => {\n  jest.useFakeTimers();\n  jest.spyOn(global, 'setTimeout');\n});\n\ndescribe('WebSocket client operations', () => {\n  it('should fail connection and reconnect when client crashes', () => {\n    let errorCallbackCapture: (err: any) => void = uninitializedCallbackCapture;\n    let successCallbackCapture: () => void = uninitializedCallbackCapture;\n    let subscribeCallbackCapture: (plan: RoutingPlan) => void = uninitializedCallbackCapture;\n\n    const { store, client } = mockStore(state);\n\n    client.connect = jest.fn().mockImplementation((successCallback, errorCallback) => {\n      successCallbackCapture = successCallback;\n      errorCallbackCapture = errorCallback;\n    });\n\n    client.subscribeToRoute = jest.fn().mockImplementation((callback) => {\n      subscribeCallbackCapture = callback;\n    });\n\n    store.dispatch(websocketOperations.connectClient());\n    expect(store.getActions()).toEqual([actions.initWsConnection()]);\n\n    // simulate client disconnection\n    const testError = { error: 'TEST_ERROR' };\n    errorCallbackCapture(testError);\n    expect(store.getActions()).toEqual([\n      actions.initWsConnection(),\n      actions.wsConnectionFailure(JSON.stringify(testError)),\n    ]);\n\n    store.clearActions();\n\n    // verify reconnection has been scheduled\n    expect(setTimeout).toHaveBeenCalledTimes(1);\n    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);\n    jest.runOnlyPendingTimers();\n    expect(store.getActions()).toEqual([actions.initWsConnection()]);\n\n    // pretend client will reconnect successfully on the next attempt\n    successCallbackCapture();\n    expect(store.getActions()).toEqual([\n      actions.initWsConnection(),\n      actions.wsConnectionSuccess(),\n    ]);\n    expect(client.subscribeToRoute).toHaveBeenCalledTimes(1);\n\n    store.clearActions();\n\n    // simulate response to subscription\n    subscribeCallbackCapture(emptyPlan);\n    expect(store.getActions()).toEqual([routeOperations.updateRoute(emptyPlan)]);\n  });\n\n  it('should finish demo loading when all locations are loaded', () => {\n    const stateWithDemo: AppState = {\n      ...state,\n      serverInfo: {\n        boundingBox: null,\n        countryCodes: [],\n        demos: [{\n          name: 'demo',\n          visits: nonEmptyPlan.visits.length,\n        }],\n      },\n      demo: {\n        demoName: 'demo',\n        isLoading: true,\n      },\n    };\n\n    const { store, client } = mockStore(stateWithDemo);\n\n    let successCallbackCapture: () => void = uninitializedCallbackCapture;\n    client.connect = jest.fn().mockImplementation((successCallback) => {\n      successCallbackCapture = successCallback;\n    });\n\n    let routeSubscriptionCallback: (plan: RoutingPlan) => void = uninitializedCallbackCapture;\n    client.subscribeToRoute = jest.fn().mockImplementation((callback) => {\n      routeSubscriptionCallback = callback;\n    });\n\n    // connect the client\n    store.dispatch(websocketOperations.connectClient());\n    expect(store.getActions()).toEqual([actions.initWsConnection()]);\n\n    // simulate successful client connection\n    successCallbackCapture();\n    expect(store.getActions()).toEqual([\n      actions.initWsConnection(),\n      actions.wsConnectionSuccess(),\n    ]);\n\n    // should be subscribed to all topics\n    expect(client.subscribeToRoute).toHaveBeenCalledTimes(1);\n\n    store.clearActions();\n\n    // simulate receiving plan with number of visits matching the expected demo size\n    routeSubscriptionCallback(nonEmptyPlan);\n    // FINISH_LOADING should be dispatched\n    expect(store.getActions()).toEqual([\n      routeOperations.updateRoute(nonEmptyPlan),\n      demoOperations.finishLoading(),\n    ]);\n  });\n\n  it('should dispatch server info and reset viewport', () => {\n    const { store, client } = mockStore(state);\n\n    let successCallbackCapture: () => void = uninitializedCallbackCapture;\n    client.connect = jest.fn().mockImplementation((successCallback) => {\n      successCallbackCapture = successCallback;\n    });\n\n    let serverInfoSubscriptionCallback: (info: ServerInfo) => void = uninitializedCallbackCapture;\n    client.subscribeToServerInfo = jest.fn().mockImplementation((callback) => {\n      serverInfoSubscriptionCallback = callback;\n    });\n\n    // successfully connect the client\n    store.dispatch(websocketOperations.connectClient());\n    successCallbackCapture();\n\n    // should be subscribed serverInfo topic\n    expect(client.subscribeToServerInfo).toHaveBeenCalledTimes(1);\n\n    store.clearActions();\n\n    // when server info arrives\n    const info: ServerInfo = {\n      boundingBox: null,\n      countryCodes: ['AB', 'XY'],\n      demos: [{ name: 'Demo name', visits: 20 }],\n    };\n    serverInfoSubscriptionCallback(info);\n\n    // action should be dispatched\n    expect(store.getActions()).toEqual([\n      resetViewport(),\n      serverInfo(info),\n    ]);\n  });\n\n  it('should dispatch errors', () => {\n    const { store, client } = mockStore(state);\n\n    let successCallbackCapture: () => void = uninitializedCallbackCapture;\n    client.connect = jest.fn().mockImplementation((successCallback) => {\n      successCallbackCapture = successCallback;\n    });\n\n    let errorTopicSubscriptionCallback: (message: MessagePayload) => void = uninitializedCallbackCapture;\n    client.subscribeToErrorTopic = jest.fn().mockImplementation((callback) => {\n      errorTopicSubscriptionCallback = callback;\n    });\n\n    // successfully connect the client\n    store.dispatch(websocketOperations.connectClient());\n    successCallbackCapture();\n\n    // should be subscribed error topic\n    expect(client.subscribeToErrorTopic).toHaveBeenCalledTimes(1);\n\n    store.clearActions();\n\n    // when error message arrives\n    const message: MessagePayload = { id: '1', text: '2' };\n    errorTopicSubscriptionCallback(message);\n\n    // action should be dispatched\n    expect(store.getActions()).toEqual([\n      receiveMessage(message),\n    ]);\n  });\n});\n\ndescribe('WebSocket reducers', () => {\n  it('connection success should open connection status', () => {\n    expect(\n      reducer(WebSocketConnectionStatus.CLOSED, actions.wsConnectionSuccess()),\n    ).toEqual(WebSocketConnectionStatus.OPEN);\n  });\n\n  it('connection failure should fail connection status', () => {\n    expect(\n      reducer(WebSocketConnectionStatus.OPEN, actions.wsConnectionFailure('test error')),\n    ).toEqual(WebSocketConnectionStatus.ERROR);\n  });\n});\n\nconst emptyPlan: RoutingPlan = {\n  distance: '',\n  vehicles: [],\n  depot: null,\n  visits: [],\n  routes: [],\n};\n\nconst vehicle1: Vehicle = { id: 1, name: 'v1', capacity: 5 };\nconst vehicle2: Vehicle = { id: 2, name: 'v2', capacity: 5 };\nconst visit1 = {\n  id: 1,\n  lat: 1.345678,\n  lng: 1.345678,\n};\nconst visit2 = {\n  id: 2,\n  lat: 2.345678,\n  lng: 2.345678,\n};\nconst visit3 = {\n  id: 3,\n  lat: 3.676111,\n  lng: 3.568333,\n};\nconst visit4 = {\n  id: 4,\n  lat: 4.345678,\n  lng: 4.345678,\n};\nconst visit5 = {\n  id: 5,\n  lat: 5.345678,\n  lng: 5.345678,\n};\nconst visit6 = {\n  id: 6,\n  lat: 6.676111,\n  lng: 6.568333,\n};\nconst nonEmptyPlan: RoutingPlan = {\n  distance: '1.0',\n  vehicles: [\n    vehicle1,\n    vehicle2,\n  ],\n  depot: visit1,\n  visits: [visit2, visit3, visit4, visit5, visit6],\n  routes: [], // not important for the test\n};\n\nconst state: AppState = {\n  connectionStatus: WebSocketConnectionStatus.CLOSED,\n  messages: [],\n  serverInfo: {\n    boundingBox: null,\n    countryCodes: [],\n    demos: [],\n  },\n  demo: {\n    demoName: null,\n    isLoading: false,\n  },\n  plan: emptyPlan,\n  userViewport,\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/App.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport App from './App';\n\ndescribe('App', () => {\n  it('should render correctly', () => {\n    const app = shallow(<App />);\n    expect(toJson(app)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/App.tsx",
    "content": "import { Page, PageSection } from '@patternfly/react-core';\nimport * as React from 'react';\nimport { Route, Switch } from 'react-router-dom';\nimport Alerts from 'ui/components/Alerts';\nimport Background from './components/Background';\nimport { ConnectionManager } from './connection';\nimport Header from './header/Header';\nimport { Demo, Route as RoutePage, Vehicles, Visits } from './pages';\n\nexport const pagesByPath = [\n  { path: { canonical: '/demo', aliases: ['/'] }, page: Demo, label: 'Demo' },\n  { path: { canonical: '/vehicles', aliases: [] }, page: Vehicles, label: 'Vehicles' },\n  { path: { canonical: '/visits', aliases: [] }, page: Visits, label: 'Visits' },\n  { path: { canonical: '/routes', aliases: [] }, page: RoutePage, label: 'Routes' },\n];\n\nconst App: React.FC = () => (\n  <>\n    <ConnectionManager />\n    <Alerts />\n    <Page header={<Header />}>\n      <Background />\n      <PageSection\n        style={{\n          display: 'flex',\n          flexDirection: 'column',\n          overflowY: 'auto',\n          height: '100%',\n        }}\n      >\n        <Switch>\n          {pagesByPath.map(({ path, page }) => ([path.canonical, ...path.aliases].map((p) => (\n            <Route\n              key={p}\n              path={p}\n              exact\n              component={page}\n            />\n          ))))}\n        </Switch>\n      </PageSection>\n    </Page>\n  </>\n);\n\nexport default App;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/__snapshots__/App.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`App should render correctly 1`] = `\n<React.Fragment>\n  <Memo(Connect(ConnectionManager)) />\n  <Memo(Connect(Alerts)) />\n  <Page\n    defaultManagedSidebarIsOpen={true}\n    getBreakpoint={[Function]}\n    getVerticalBreakpoint={[Function]}\n    header={<Header />}\n    isBreadcrumbWidthLimited={false}\n    isManagedSidebar={false}\n    isNotificationDrawerExpanded={false}\n    mainTabIndex={-1}\n    onNotificationDrawerExpand={[Function]}\n    onPageResize={[Function]}\n  >\n    <Background />\n    <PageSection\n      style={\n        Object {\n          \"display\": \"flex\",\n          \"flexDirection\": \"column\",\n          \"height\": \"100%\",\n          \"overflowY\": \"auto\",\n        }\n      }\n    >\n      <Switch>\n        <Route\n          component={\n            Object {\n              \"$$typeof\": Symbol(react.memo),\n              \"WrappedComponent\": [Function],\n              \"compare\": null,\n              \"type\": [Function],\n            }\n          }\n          exact={true}\n          path=\"/demo\"\n        />\n        <Route\n          component={\n            Object {\n              \"$$typeof\": Symbol(react.memo),\n              \"WrappedComponent\": [Function],\n              \"compare\": null,\n              \"type\": [Function],\n            }\n          }\n          exact={true}\n          path=\"/\"\n        />\n        <Route\n          component={\n            Object {\n              \"$$typeof\": Symbol(react.memo),\n              \"WrappedComponent\": [Function],\n              \"compare\": null,\n              \"type\": [Function],\n            }\n          }\n          exact={true}\n          path=\"/vehicles\"\n        />\n        <Route\n          component={\n            Object {\n              \"$$typeof\": Symbol(react.memo),\n              \"WrappedComponent\": [Function],\n              \"compare\": null,\n              \"type\": [Function],\n            }\n          }\n          exact={true}\n          path=\"/visits\"\n        />\n        <Route\n          component={\n            Object {\n              \"$$typeof\": Symbol(react.memo),\n              \"WrappedComponent\": [Function],\n              \"compare\": null,\n              \"type\": [Function],\n            }\n          }\n          exact={true}\n          path=\"/routes\"\n        />\n      </Switch>\n    </PageSection>\n  </Page>\n</React.Fragment>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Alerts.test.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport * as React from 'react';\nimport { Alerts, Props } from 'ui/components/Alerts';\nimport { shallow, toJson } from 'ui/shallow-test-util';\n\ndescribe('Alerts', () => {\n  it('should call readMessage() when alert is closed', async () => {\n    const props: Props = {\n      messages: [\n        { id: '1', text: 'msg 1', status: 'new' },\n        { id: '2', text: 'msg 2', status: 'new' },\n      ],\n      readMessage: jest.fn(),\n    };\n    const user = userEvent.setup();\n    // TODO add a shallow render test\n    const alerts = render(<Alerts {...props} />);\n    expect(alerts).toMatchSnapshot();\n\n    await user.click(screen.getAllByTitle('Close alert')[1]);\n\n    expect(props.readMessage).toHaveBeenCalledWith('2');\n  });\n\n  it('should not render if there are no messages', () => {\n    const props: Props = {\n      messages: [],\n      readMessage: jest.fn(),\n    };\n    const alerts = shallow(<Alerts {...props} />);\n    expect(toJson(alerts)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Alerts.tsx",
    "content": "import { Alert, AlertActionCloseButton, AlertGroup } from '@patternfly/react-core';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { messageActions, messageSelectors } from 'store/message';\nimport { Message } from 'store/message/types';\nimport { AppState } from 'store/types';\n\ninterface StateProps {\n  messages: Message[];\n}\n\nconst mapStateToProps = ({ messages }: AppState): StateProps => ({\n  messages: messageSelectors.getNewMessages(messages),\n});\n\ninterface DispatchProps {\n  readMessage: typeof messageActions.readMessage;\n}\n\nconst mapDispatchToProps: DispatchProps = {\n  readMessage: messageActions.readMessage,\n};\n\nexport type Props = StateProps & DispatchProps;\n\nexport const Alerts: React.FC<Props> = ({ messages, readMessage }: Props) => (\n  messages.length > 0 ? (\n    <AlertGroup isToast>\n      {messages\n        .map(({ id, text }) => (\n          <Alert\n            key={id}\n            variant=\"danger\"\n            title=\"Error\"\n            isLiveRegion\n            actionClose={(\n              <AlertActionCloseButton\n                title=\"Close alert\"\n                onClose={() => readMessage(id)}\n              />\n            )}\n          >\n            {text}\n          </Alert>\n        ))}\n    </AlertGroup>\n  ) : null\n);\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Alerts);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Background.tsx",
    "content": "import { BackgroundImage } from '@patternfly/react-core';\nimport pfBackground1200 from '@patternfly/react-core/dist/styles/assets/images/pfbg_1200.jpg';\nimport pfBackground576 from '@patternfly/react-core/dist/styles/assets/images/pfbg_576.jpg';\nimport pfBackground1152 from '@patternfly/react-core/dist/styles/assets/images/pfbg_576@2x.jpg';\nimport pfBackground768 from '@patternfly/react-core/dist/styles/assets/images/pfbg_768.jpg';\nimport pfBackground1536 from '@patternfly/react-core/dist/styles/assets/images/pfbg_768@2x.jpg';\nimport * as React from 'react';\n\nconst images = {\n  xs: pfBackground576,\n  sm: pfBackground768,\n  xs2x: pfBackground1152,\n  lg: pfBackground1200,\n  sm2x: pfBackground1536,\n};\n\nconst Background: React.FC = () => <BackgroundImage src={images} />;\n\nexport default Background;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/DemoDropdown.css",
    "content": "/* Override dropdown menu z-index to be higher than Leaflet map's z-index */\n.pf-c-dropdown__menu {\n  --pf-c-dropdown__menu--ZIndex: 2000;\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/DemoDropdown.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport { DemoDropdown, Props } from './DemoDropdown';\n\ndescribe('Demo dropdown button', () => {\n  it('should render correctly with a couple of demos', () => {\n    const props: Props = {\n      demos: ['demo 1', 'demo 2'],\n      onSelect: jest.fn(),\n    };\n    const dropdown = shallow(<DemoDropdown {...props} />);\n    expect(toJson(dropdown)).toMatchSnapshot();\n  });\n\n  it('should be disabled with empty demos', () => {\n    const props: Props = {\n      demos: [],\n      onSelect: jest.fn(),\n    };\n    const dropdown = shallow(<DemoDropdown {...props} />);\n    expect(toJson(dropdown)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/DemoDropdown.tsx",
    "content": "import { Dropdown, DropdownItem, DropdownPosition, DropdownToggle } from '@patternfly/react-core';\nimport * as React from 'react';\nimport './DemoDropdown.css';\n\nexport interface Props {\n  demos: string[];\n  onSelect: (name: string) => void;\n}\n\nconst dropdownItems = (demos: string[]): React.ReactNode[] => demos.map((value) => (\n  <DropdownItem key={value}>\n    {value}\n  </DropdownItem>\n));\n\nexport const DemoDropdown: React.FC<Props> = ({ demos, onSelect }) => {\n  const [isOpen, setOpen] = React.useState(false);\n  return (\n    <Dropdown\n      style={{ marginBottom: 16, marginLeft: 16 }}\n      position={DropdownPosition.right}\n      isOpen={isOpen}\n      dropdownItems={dropdownItems(demos)}\n      onSelect={(e) => {\n        setOpen(false);\n        if (e && e.currentTarget) {\n          onSelect(e.currentTarget.innerText);\n        }\n      }}\n      toggle={(\n        <DropdownToggle\n          isPrimary\n          disabled={demos.length === 0}\n          onToggle={() => setOpen(!isOpen)}\n        >\n          Load demo\n        </DropdownToggle>\n      )}\n    />\n  );\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Location.test.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport Location, { LocationProps } from './Location';\n\ndescribe('Location Component', () => {\n  it('should call handlers', async () => {\n    const props: LocationProps = {\n      id: 10,\n      description: 'x',\n      removeDisabled: false,\n      removeHandler: jest.fn(),\n      selectHandler: jest.fn(),\n    };\n    render(<Location {...props} />);\n\n    const user = userEvent.setup();\n\n    await user.click(screen.getByRole('button'));\n\n    expect(props.removeHandler).toHaveBeenCalledTimes(1);\n    expect(props.selectHandler).toHaveBeenCalledTimes(1);\n  });\n\n  it('should render correctly', () => {\n    const props: LocationProps = {\n      id: 10,\n      description: 'x',\n      removeDisabled: false,\n      removeHandler: jest.fn(),\n      selectHandler: jest.fn(),\n    };\n    const location = shallow(<Location {...props} />);\n    expect(toJson(location)).toMatchSnapshot();\n  });\n\n  it('should render correctly when description is missing', () => {\n    const props: LocationProps = {\n      id: 11,\n      description: null,\n      removeDisabled: false,\n      removeHandler: jest.fn(),\n      selectHandler: jest.fn(),\n    };\n    const location = shallow(<Location {...props} />);\n    expect(toJson(location)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Location.tsx",
    "content": "import { Button, DataListCell, DataListItem, DataListItemRow, Tooltip } from '@patternfly/react-core';\nimport { TimesIcon } from '@patternfly/react-icons';\nimport * as React from 'react';\n\nexport interface LocationProps {\n  id: number;\n  description: string | null;\n  removeDisabled: boolean;\n  removeHandler: (id: number) => void;\n  selectHandler: (id: number) => void;\n}\n\nconst Location: React.FC<LocationProps> = ({\n  id,\n  description,\n  removeDisabled,\n  removeHandler,\n  selectHandler,\n}) => {\n  const [clicked, setClicked] = React.useState(false);\n\n  function shorten(text: string) {\n    const first = text.replace(/,.*/, '').trim();\n    const short = first.substring(0, Math.min(20, first.length)).trim();\n    if (short.length < first.length) {\n      return `${short}...`;\n    }\n    return short;\n  }\n\n  return (\n    <DataListItem\n      isExpanded={false}\n      aria-labelledby={`location-${id}`}\n      onMouseEnter={() => selectHandler(id)}\n      onMouseLeave={() => selectHandler(NaN)}\n    >\n      <DataListItemRow>\n        <DataListCell isFilled>\n          {(description && (\n            <Tooltip content={description}>\n              <span id={`location-${id}`}>{shorten(description)}</span>\n            </Tooltip>\n          ))\n          || <span id={`location-${id}`}>{`Location ${id}`}</span>}\n        </DataListCell>\n        <DataListCell isFilled={false}>\n          <Button\n            type=\"button\"\n            variant=\"link\"\n            isDisabled={removeDisabled || clicked}\n            onClick={() => {\n              setClicked(true);\n              removeHandler(id);\n            }}\n          >\n            <TimesIcon />\n          </Button>\n        </DataListCell>\n      </DataListItemRow>\n    </DataListItem>\n  );\n};\n\nexport default Location;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/LocationList.css",
    "content": ".pf-c-data-list__cell {\n  --pf-c-data-list__cell-cell--PaddingTop: var(--pf-global--spacer--sm);\n  --pf-c-data-list__cell--PaddingTop: var(--pf-global--spacer--md);\n  --pf-c-data-list__cell--PaddingBottom: var(--pf-global--spacer--sm);\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/LocationList.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport LocationList, { LocationListProps } from './LocationList';\n\ndescribe('Location List Component', () => {\n  it('should render correctly with no routes', () => {\n    const props: LocationListProps = {\n      removeHandler: jest.fn(),\n      selectHandler: jest.fn(),\n      depot: null,\n      visits: [],\n    };\n    const locationList = shallow(<LocationList {...props} />);\n    expect(toJson(locationList)).toMatchSnapshot();\n  });\n\n  it('should render correctly with a few routes', () => {\n    const props: LocationListProps = {\n      removeHandler: jest.fn(),\n      selectHandler: jest.fn(),\n      depot: {\n        id: 1,\n        lat: 1.345678,\n        lng: 1.345678,\n        description: 'Depot',\n      },\n      visits: [\n        {\n          id: 2,\n          lat: 2.345678,\n          lng: 2.345678,\n          description: 'Visit 1',\n        },\n        {\n          id: 3,\n          lat: 3.676111,\n          lng: 3.568333,\n          description: 'Visit 2',\n        },\n      ],\n    };\n    const locationList = shallow(<LocationList {...props} />);\n    expect(toJson(locationList)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/LocationList.tsx",
    "content": "import { Bullseye, DataList } from '@patternfly/react-core';\nimport * as React from 'react';\nimport { Location } from 'store/route/types';\nimport LocationItem from './Location';\nimport './LocationList.css';\n\nexport interface LocationListProps {\n  removeHandler: (id: number) => void;\n  selectHandler: (id: number) => void;\n  depot: Location | null;\n  visits: Location[];\n}\n\nconst renderEmptyLocationList: React.FC<LocationListProps> = () => (\n  <DataList aria-label=\"Empty location list\">\n    <Bullseye>No locations</Bullseye>\n  </DataList>\n);\n\nconst renderLocationList: React.FC<LocationListProps> = ({\n  depot,\n  visits,\n  removeHandler,\n  selectHandler,\n}) => (\n  <div style={{ overflowY: 'auto' }} data-cy=\"location-list\">\n    <DataList\n      aria-label=\"List of locations\"\n    >\n      {depot && (\n        <LocationItem\n          key={depot.id}\n          id={depot.id}\n          description={depot.description || null}\n          removeDisabled={visits.length > 0}\n          removeHandler={removeHandler}\n          selectHandler={selectHandler}\n        />\n      )}\n      {visits\n        .slice(0) // clone the array because\n        // sort is done in place (that would affect the route)\n        .sort((a, b) => a.id - b.id)\n        .map((visit) => (\n          <LocationItem\n            key={visit.id}\n            id={visit.id}\n            description={visit.description || null}\n            removeDisabled={false}\n            removeHandler={removeHandler}\n            selectHandler={selectHandler}\n          />\n        ))}\n    </DataList>\n  </div>\n);\n\nconst LocationList: React.FC<LocationListProps> = (props) => (\n  props.visits.length === 0 && props.depot === null\n    ? renderEmptyLocationList(props)\n    : renderLocationList(props)\n);\n\nexport default LocationList;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/LocationMarker.test.tsx",
    "content": "import { render } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Location } from 'store/route/types';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport LocationMarker, { Props } from './LocationMarker';\n\nconst location: Location = {\n  id: 1,\n  lat: 1.345678,\n  lng: 1.345678,\n};\n\ndescribe('Location Marker', () => {\n  it('render depot', () => {\n    const props: Props = {\n      removeHandler: jest.fn(),\n      isDepot: true,\n      isSelected: false,\n      location,\n    };\n    const locationMarker = shallow(<LocationMarker {...props} />);\n    expect(toJson(locationMarker)).toMatchSnapshot();\n  });\n\n  it('render visit', () => {\n    const props: Props = {\n      removeHandler: jest.fn(),\n      isDepot: false,\n      isSelected: false,\n      location,\n    };\n    const locationMarker = shallow(<LocationMarker {...props} />);\n    expect(toJson(locationMarker)).toMatchSnapshot();\n  });\n\n  it('selected visit should show a tooltip', () => {\n    const props: Props = {\n      removeHandler: jest.fn(),\n      isDepot: false,\n      isSelected: true,\n      location,\n    };\n    const locationMarker = shallow(<LocationMarker {...props} />);\n    expect(toJson(locationMarker)).toMatchSnapshot();\n  });\n\n  xit('should call remove handler when clicked', async () => {\n    const props: Props = {\n      removeHandler: jest.fn(),\n      isDepot: false,\n      isSelected: true,\n      location,\n    };\n    // FIXME Cannot read property 'addLayer' of undefined\n    render(<LocationMarker {...props} />);\n\n    userEvent.setup();\n    // TODO\n    // await user.click(screen.find(Marker));\n    expect(props.removeHandler).toBeCalled();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/LocationMarker.tsx",
    "content": "import * as L from 'leaflet';\nimport * as React from 'react';\nimport { Marker, Tooltip } from 'react-leaflet';\nimport { Location } from 'store/route/types';\n\nconst homeIcon = L.icon({\n  iconAnchor: [12, 12],\n  iconSize: [24, 24],\n  iconUrl: 'if_big_house-home_2222740.png',\n  popupAnchor: [0, -10],\n  shadowAnchor: [16, 2],\n  shadowSize: [50, 16],\n  shadowUrl: 'if_big_house-home_2222740_shadow.png',\n});\n\nconst defaultIcon = new L.Icon.Default();\n\nexport interface Props {\n  location: Location;\n  isDepot: boolean;\n  isSelected: boolean;\n  removeHandler: (id: number) => void;\n}\n\nconst LocationMarker: React.FC<Props> = ({\n  location,\n  isDepot,\n  isSelected,\n  removeHandler,\n}) => {\n  const icon = isDepot ? homeIcon : defaultIcon;\n  return (\n    <Marker\n      key={location.id}\n      position={location}\n      icon={icon}\n      onClick={() => removeHandler(location.id)}\n    >\n      <Tooltip\n        // `permanent` is a static property (this is a React-Leaflet-specific\n        // approach: https://react-leaflet.js.org/docs/en/components). Changing `permanent` prop\n        // doesn't result in calling `setPermanent()` on the Leaflet element after the Tooltip component is mounted.\n        // We're using `key` to force re-rendering of Tooltip when `isSelected` changes. A similar use case for\n        // the `key` property is described here:\n        // https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html\n        // #recommendation-fully-uncontrolled-component-with-a-key\n        key={isSelected ? 'selected' : ''}\n        permanent={isSelected}\n      >\n        {`Location ${location.id} [Lat=${location.lat}, Lng=${location.lng}]`}\n      </Tooltip>\n    </Marker>\n  );\n};\n\nexport default LocationMarker;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/RouteMap.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport RouteMap, { Props } from './RouteMap';\n\ndescribe('Route Map', () => {\n  it('should show the whole world when bounding box is null', () => {\n    const props: Props = {\n      updateViewport: jest.fn,\n      clickHandler: jest.fn(),\n      removeHandler: jest.fn(),\n      selectedId: 1,\n      depot: {\n        id: 1,\n        lat: 1.345678,\n        lng: 1.345678,\n      },\n      visits: [],\n      routes: [{\n        visits: [],\n        track: [],\n      }],\n      boundingBox: null,\n      userViewport: {\n        isDirty: false,\n        zoom: 4,\n        center: [1, 1],\n      },\n    };\n    const routeMap = shallow(<RouteMap {...props} />);\n    expect(toJson(routeMap)).toMatchSnapshot();\n  });\n\n  it('should pan and zoom to show bounding box if viewport is not dirty', () => {\n    const depot = {\n      id: 1,\n      lat: 1.345678,\n      lng: 1.345678,\n    };\n    const visit2 = {\n      id: 2,\n      lat: 2.345678,\n      lng: 2.345678,\n    };\n    const visit3 = {\n      id: 3,\n      lat: 3.676111,\n      lng: 3.568333,\n    };\n    const props: Props = {\n      updateViewport: jest.fn(),\n      clickHandler: jest.fn(),\n      removeHandler: jest.fn(),\n      selectedId: 1,\n      boundingBox: [{ lat: -1, lng: -2 }, { lat: 10, lng: 20 }],\n      userViewport: {\n        isDirty: false,\n        zoom: 4,\n        center: [1, 1],\n      },\n      depot,\n      visits: [visit2, visit3],\n      routes: [{\n        visits: [visit2, visit3],\n        track: [[0.111222, 0.222333], [0.444555, 0.555666]],\n      }],\n    };\n    const routeMap = shallow(<RouteMap {...props} />);\n    expect(toJson(routeMap)).toMatchSnapshot();\n  });\n\n  it('should ignore bounds if viewport is dirty', () => {\n    const depot = {\n      id: 1,\n      lat: 1.345678,\n      lng: 1.345678,\n    };\n    const props: Props = {\n      updateViewport: jest.fn(),\n      clickHandler: jest.fn(),\n      removeHandler: jest.fn(),\n      selectedId: NaN,\n      boundingBox: [{ lat: -1, lng: -2 }, { lat: 10, lng: 20 }],\n      userViewport: {\n        isDirty: true,\n        zoom: 4,\n        center: [1, 1],\n      },\n      depot,\n      visits: [],\n      routes: [],\n    };\n    const routeMap = shallow(<RouteMap {...props} />);\n    // Map's bounds should be undefined\n    expect(toJson(routeMap)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/RouteMap.tsx",
    "content": "import * as L from 'leaflet';\nimport * as React from 'react';\nimport { Map, Polyline, Rectangle, TileLayer, ZoomControl } from 'react-leaflet';\nimport { UserViewport } from 'store/client/types';\nimport { Location, RouteWithTrack } from 'store/route/types';\nimport { BoundingBox } from 'store/server/types';\nimport LocationMarker from './LocationMarker';\n\ntype Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;\n\nexport interface Props {\n  selectedId: number;\n  clickHandler: (event: L.LeafletMouseEvent) => void;\n  removeHandler: (id: number) => void;\n  depot: Location | null;\n  visits: Location[];\n  routes: Omit<RouteWithTrack, 'vehicle'>[];\n  boundingBox: BoundingBox | null;\n  userViewport: UserViewport;\n  updateViewport: (viewport: UserViewport) => void;\n}\n\n// TODO unlimited unique (random) colors\nconst colors = ['deepskyblue', 'crimson', 'seagreen', 'slateblue', 'gold', 'darkorange'];\n\nfunction color(index: number) {\n  return colors[index % colors.length];\n}\n\nconst RouteMap: React.FC<Props> = ({\n  boundingBox,\n  userViewport,\n  selectedId,\n  depot,\n  visits,\n  routes,\n  clickHandler,\n  removeHandler,\n  updateViewport,\n}) => {\n  const bounds = boundingBox ? new L.LatLngBounds(boundingBox[0], boundingBox[1]) : undefined;\n  // do not use bounds if user's viewport is dirty\n  const mapBounds = userViewport.isDirty ? undefined : bounds;\n  // TODO make TileLayer URL configurable\n  // @ts-expect-error Cypress exists on window during Cypress test runs\n  const tileLayerUrl = window.Cypress ? 'test-mode-empty-url' : 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';\n  return (\n    <Map\n      bounds={mapBounds}\n      viewport={userViewport}\n      onViewportChanged={updateViewport}\n      onClick={clickHandler}\n      // FIXME use height: 100%\n      style={{ width: '100%', height: 'calc(100vh - 176px)' }}\n      zoomControl={false} // hide the default zoom control which is on top left\n    >\n      <TileLayer\n        attribution=\"&copy; <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors\"\n        url={tileLayerUrl}\n      />\n      <ZoomControl position=\"topright\" />\n      {depot && (\n        <LocationMarker\n          location={depot}\n          isDepot\n          isSelected={depot.id === selectedId}\n          removeHandler={removeHandler}\n        />\n      )}\n      {visits.map((location) => (\n        <LocationMarker\n          key={location.id}\n          location={location}\n          isDepot={false}\n          isSelected={location.id === selectedId}\n          removeHandler={removeHandler}\n        />\n      ))}\n      {routes.map((route, index) => (\n        <Polyline\n          // eslint-disable-next-line react/no-array-index-key\n          key={index} // FIXME use unique id (not iteration index)\n          positions={route.track}\n          fill={false}\n          color={color(index)}\n          smoothFactor={3}\n          weight={9}\n          opacity={0.6666}\n        />\n      ))}\n      {bounds && (\n        <Rectangle\n          bounds={bounds}\n          color=\"seagreen\"\n          fill={false}\n          dashArray=\"10,5\"\n          weight={1}\n        />\n      )}\n    </Map>\n  );\n};\n\nexport default RouteMap;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/SearchBox.test.tsx",
    "content": "import { render } from '@testing-library/react';\nimport { OpenStreetMapProvider } from 'leaflet-geosearch';\nimport { RawResult } from 'leaflet-geosearch/lib/providers/openStreetMapProvider';\nimport { SearchResult } from 'leaflet-geosearch/lib/providers/provider';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport SearchBox, { Props, Result } from './SearchBox';\n\njest.mock('leaflet-geosearch');\n\nbeforeEach(() => {\n  // Clear all instances and calls to constructor and all methods:\n  (OpenStreetMapProvider as unknown as jest.MockInstance<OpenStreetMapProvider, []>).mockClear();\n  jest.useFakeTimers();\n});\n\nconst searchProviderMock = (\n  () => (OpenStreetMapProvider as unknown as jest.MockInstance<OpenStreetMapProvider, []>).mock\n);\n\ndescribe('Search box', () => {\n  it('should render text input initially', () => {\n    const props: Props = {\n      addHandler: jest.fn(),\n      boundingBox: null,\n      countryCodeSearchFilter: ['XY'],\n      searchDelay: 1,\n    };\n    const searchBox = shallow(<SearchBox {...props} />);\n    expect(toJson(searchBox)).toMatchSnapshot();\n  });\n\n  it('should show results when query is entered', async () => {\n    const props: Props = {\n      addHandler: jest.fn(),\n      boundingBox: null,\n      countryCodeSearchFilter: ['XY'],\n      searchDelay: 1,\n    };\n    // const user = userEvent.setup();\n\n    render(<SearchBox {...props} />);\n    expect(searchProviderMock().instances).toHaveLength(1);\n    // FIXME user.type() times out\n    /*\n    // text input change triggers a component update\n    await user.type(screen.getByLabelText('geosearch text input'), 'London');\n    // which in turn creates a new searchProvider instance\n    expect(searchProviderMock().instances).toHaveLength(2);\n    // so we can't provide the mock implementation earlier than here\n    searchProviderMock().instances[1].search = jest.fn().mockImplementation(() => searchResults);\n    await jest.runAllTimers();\n    expect(searchProviderMock().instances[1].search).toHaveBeenCalledTimes(1);\n    */\n    // FIXME test state\n    // expect((searchBox.state() as State).results).toHaveLength(searchResults.length);\n    // expect((searchBox.state() as State).results[0].id).toEqual(searchResults[0].raw.place_id);\n    // expect((searchBox.state() as State).attributions).toEqual(licenses);\n    // expect(toJson(searchBox)).toMatchSnapshot();\n  });\n\n  it('should hide results when query is empty', () => {\n    const props: Props = {\n      addHandler: jest.fn(),\n      boundingBox: null,\n      countryCodeSearchFilter: ['XY'],\n      searchDelay: 1,\n    };\n\n    shallow(<SearchBox {...props} />);\n    expect(searchProviderMock().instances).toHaveLength(1);\n\n    // FIXME test state\n    /*\n    // when there are non-empty results\n    searchBox.setState({ results: stateResults, attributions: licenses });\n    expect(searchProviderMock().instances).toHaveLength(2);\n    expect((searchBox.state() as State).results).toEqual(stateResults);\n    expect((searchBox.state() as State).attributions).toEqual(licenses);\n\n    // and an empty query is issued\n    const emptyQuery = ' ';\n    searchBox.find(SearchInput).simulate('change', emptyQuery);\n    expect(searchProviderMock().instances).toHaveLength(3);\n    expect(toJson(searchBox)).toMatchSnapshot();\n\n    // search is not invoked\n    expect(searchProviderMock().instances[2].search).toHaveBeenCalledTimes(0);\n    // and results are cleared\n    expect(searchBox.state()).toEqual({ query: emptyQuery, results: [], attributions: [] });\n    */\n  });\n\n  it('should invoke add handler with the selected result and clear results', () => {\n    const mockAddHandler = jest.fn();\n    const props: Props = {\n      addHandler: mockAddHandler,\n      boundingBox: null,\n      countryCodeSearchFilter: ['XY'],\n      searchDelay: 1,\n    };\n\n    shallow(<SearchBox {...props} />);\n\n    /*\n    // when there are non-empty results\n    searchBox.setState({ results: stateResults, attributions: licenses });\n    expect(toJson(searchBox)).toMatchSnapshot();\n\n    const resultItems = searchBox.findWhere(\n      (node) => node.key() !== null && node.key().startsWith('place-id-'),\n    );\n    expect(resultItems).toHaveLength(stateResults.length);\n\n    const selection = Math.floor(stateResults.length / 2);\n    resultItems.at(selection).find(Button).simulate('click');\n    expect(props.addHandler).toHaveBeenLastCalledWith(stateResults[selection]);\n\n    expect(searchBox.state()).toEqual({ query: '', results: [], attributions: [] });\n    */\n  });\n});\n\nconst licenses = ['License 1', 'License 2'];\n\n// FIXME\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst searchResults: SearchResult<RawResult>[] = [{\n  label: 'London, ON, Canada',\n  x: 101,\n  y: 102,\n  bounds: [[1, 2], [3, 4]],\n  // @ts-expect-error discrepancy between leaflet-geosearch API (expects license) and the actual Nominatim data\n  raw: { place_id: 'raw-place-id-1', licence: licenses[0] },\n}, {\n  label: 'London, OH, USA',\n  x: 201,\n  y: 202,\n  bounds: [[1, 2], [3, 4]],\n  // @ts-expect-error discrepancy between leaflet-geosearch API (expects license) and the actual Nominatim data\n  raw: { place_id: 'raw-place-id-2', licence: licenses[1] },\n}, {\n  label: 'London, KY, USA',\n  x: 301,\n  y: 302,\n  bounds: [[1, 2], [3, 4]],\n  // @ts-expect-error discrepancy between leaflet-geosearch API (expects license) and the actual Nominatim data\n  raw: { place_id: 'raw-place-id-3', licence: licenses[1] },\n}, {\n  label: 'London, UK',\n  x: 401,\n  y: 402,\n  bounds: [[1, 2], [3, 4]],\n  // @ts-expect-error discrepancy between leaflet-geosearch API (expects license) and the actual Nominatim data\n  raw: { place_id: 'raw-place-id-4', licence: licenses[0] },\n}];\n\n// FIXME\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst stateResults: Result[] = [{\n  id: 'place-id-1A',\n  address: 'Address 1',\n  latLng: { lat: 11, lng: 12 },\n}, {\n  id: 'place-id-2B',\n  address: 'Address 2',\n  latLng: { lat: 21, lng: 22 },\n}, {\n  id: 'place-id-3C',\n  address: 'Address 3',\n  latLng: { lat: 31, lng: 32 },\n}, {\n  id: 'place-id-4D',\n  address: 'Address 4',\n  latLng: { lat: 41, lng: 42 },\n}, {\n  id: 'place-id-5E',\n  address: 'Address 5',\n  latLng: { lat: 51, lng: 52 },\n}];\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/SearchBox.tsx",
    "content": "import '@patternfly/patternfly/patternfly.css';\nimport { Button, SearchInput, Text, TextContent, TextVariants } from '@patternfly/react-core';\nimport { PlusSquareIcon } from '@patternfly/react-icons';\nimport { OpenStreetMapProvider } from 'leaflet-geosearch';\nimport { OpenStreetMapProviderOptions } from 'leaflet-geosearch/lib/providers/openStreetMapProvider';\nimport * as React from 'react';\nimport { LatLng } from 'store/route/types';\nimport { BoundingBox } from 'store/server/types';\n\nexport interface Result {\n  id: string;\n  address: string;\n  latLng: LatLng;\n}\n\nexport interface Props {\n  searchDelay: number;\n  boundingBox: BoundingBox | null;\n  countryCodeSearchFilter: string[];\n  addHandler: (result: Result) => void;\n}\n\nexport interface State {\n  query: string;\n  results: Result[];\n  attributions: string[];\n}\n\n// Nominatim API: viewbox=<x1>,<y1>,<x2>,<y2> (x is longitude, y is latitude).\ntype ViewBox = [number, number, number, number];\n\nconst viewBox: (bb: BoundingBox) => ViewBox = (bb: BoundingBox) => [bb[0].lng, bb[0].lat, bb[1].lng, bb[1].lat];\n\nconst providerOptions = (props: Props): OpenStreetMapProviderOptions => ({\n  params: {\n    countrycodes: props.countryCodeSearchFilter.toString(),\n    viewbox: props.boundingBox ? viewBox(props.boundingBox).toString() : '',\n    bounded: !!props.boundingBox,\n  },\n});\n\nclass SearchBox extends React.Component<Props, State> {\n  // eslint-disable-next-line max-len\n  // https://github.com/airbnb/javascript/blob/eslint-config-airbnb-v18.1.0/packages/eslint-config-airbnb/rules/react.js#L489\n  // TODO remove this suppression once the TODO above is resolved:\n  // eslint-disable-next-line react/static-property-placement\n  static defaultProps: Pick<Props, 'searchDelay'> = {\n    searchDelay: 500,\n  };\n\n  private searchProvider: OpenStreetMapProvider;\n\n  private timeoutId: number | null;\n\n  constructor(props: Props) {\n    super(props);\n\n    this.state = {\n      query: '',\n      results: [],\n      attributions: [],\n    };\n\n    this.searchProvider = new OpenStreetMapProvider(providerOptions(props));\n    this.timeoutId = null;\n\n    this.handleTextInputChange = this.handleTextInputChange.bind(this);\n    this.handleClick = this.handleClick.bind(this);\n  }\n\n  componentDidUpdate() {\n    this.searchProvider = new OpenStreetMapProvider(providerOptions(this.props));\n  }\n\n  componentWillUnmount() {\n    if (this.timeoutId) {\n      window.clearTimeout(this.timeoutId);\n    }\n  }\n\n  handleTextInputChange(query: string) {\n    if (this.timeoutId) {\n      window.clearTimeout(this.timeoutId);\n    }\n    if (query.trim() !== '') {\n      this.timeoutId = window.setTimeout(\n        async () => {\n          const searchResults = await this.searchProvider.search({ query });\n          if (this.state.query !== query) {\n            return;\n          }\n          this.setState({\n            results: searchResults\n              .map((result) => ({\n                id: result.raw.place_id,\n                address: result.label,\n                latLng: { lat: result.y, lng: result.x },\n              })),\n            attributions: searchResults\n              // eslint-disable-next-line max-len\n              // @ts-expect-error discrepancy between leaflet-geosearch API (expects license) and the actual Nominatim data\n              .map((result) => result.raw.licence)\n              // filter out duplicate elements\n              .filter((value, index, array) => array.indexOf(value) === index),\n          });\n        },\n        this.props.searchDelay,\n      );\n      this.setState({ query });\n    } else {\n      this.setState({ query, results: [], attributions: [] });\n    }\n  }\n\n  handleClick(index: number) {\n    this.props.addHandler(this.state.results[index]);\n    this.setState({\n      query: '',\n      results: [],\n      attributions: [],\n    });\n    // TODO focus text input\n  }\n\n  render() {\n    const { attributions, query, results } = this.state;\n    return (\n      <>\n        <SearchInput\n          style={{ marginBottom: 10 }}\n          value={query}\n          placeholder=\"Search to add a location...\"\n          aria-label=\"geosearch text input\"\n          onChange={this.handleTextInputChange}\n          data-cy=\"geosearch-text-input\"\n        />\n        {results.length > 0 && (\n          <div className=\"pf-c-options-menu pf-m-expanded\" style={{ zIndex: 1100 }}>\n            <ul className=\"pf-c-options-menu__menu\">\n              {results.map((result, index) => (\n                <li key={result.id}>\n                  <div className=\"pf-c-options-menu__menu-item\">\n                    {result.address}\n                    <Button\n                      className=\"pf-c-options-menu__menu-item-icon\"\n                      variant=\"link\"\n                      type=\"button\"\n                      onClick={() => this.handleClick(index)}\n                      data-cy={`geosearch-location-item-button-${index}`}\n                    >\n                      <PlusSquareIcon />\n                    </Button>\n                  </div>\n                </li>\n              ))}\n\n              <li className=\"pf-c-options-menu__separator\" role=\"separator\" />\n\n              {attributions.map((attribution) => (\n                <li\n                  key={`attrib: ${attribution}`}\n                  className=\"pf-c-options-menu__menu-item pf-m-disabled\"\n                >\n                  <TextContent>\n                    <Text\n                      component={TextVariants.small}\n                    >\n                      {attribution}\n                    </Text>\n                  </TextContent>\n                </li>\n              ))}\n            </ul>\n          </div>\n        )}\n      </>\n    );\n  }\n}\n\nexport default SearchBox;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Vehicle.test.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { VehicleCapacity } from 'store/route/types';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport Vehicle, { VehicleProps } from './Vehicle';\n\ndescribe('Vehicle Component', () => {\n  it('should render correctly', () => {\n    const props: VehicleProps = {\n      id: 10,\n      description: 'x',\n      capacity: 7,\n      removeHandler: jest.fn(),\n      capacityChangeHandler: jest.fn(),\n    };\n    const vehicle = shallow(<Vehicle {...props} />);\n    expect(toJson(vehicle)).toMatchSnapshot();\n  });\n\n  it('handlers', async () => {\n    const props: VehicleProps = {\n      id: 10,\n      description: 'x',\n      capacity: 7,\n      removeHandler: jest.fn(),\n      capacityChangeHandler: jest.fn(),\n    };\n    render(<Vehicle {...props} />);\n\n    const user = userEvent.setup();\n\n    await user.click(screen.getByTestId(`remove-${props.id}`));\n    expect(props.removeHandler).toHaveBeenCalledTimes(1);\n\n    await user.click(screen.getByTestId(`capacity-increase-${props.id}`));\n    const increasedCapacity: VehicleCapacity = {\n      vehicleId: props.id,\n      capacity: props.capacity + 1,\n    };\n    expect(props.capacityChangeHandler).toHaveBeenCalledWith(increasedCapacity);\n\n    await user.click(screen.getByTestId(`capacity-decrease-${props.id}`));\n    const decreasedCapacity: VehicleCapacity = {\n      vehicleId: props.id,\n      capacity: props.capacity - 1,\n    };\n    expect(props.capacityChangeHandler).toHaveBeenCalledWith(decreasedCapacity);\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/Vehicle.tsx",
    "content": "import {\n  Button,\n  ButtonVariant,\n  DataListCell,\n  DataListItem,\n  DataListItemRow,\n  InputGroup,\n  InputGroupText,\n} from '@patternfly/react-core';\nimport { MinusIcon, PlusIcon, TimesIcon } from '@patternfly/react-icons';\nimport * as React from 'react';\nimport { VehicleCapacity } from 'store/route/types';\n\nexport interface VehicleProps {\n  id: number;\n  description: string;\n  capacity: number;\n  removeHandler: (id: number) => void;\n  capacityChangeHandler: (vehicleCapacity: VehicleCapacity) => void;\n}\n\nconst Vehicle: React.FC<VehicleProps> = ({\n  id,\n  description,\n  capacity,\n  removeHandler,\n  capacityChangeHandler,\n}) => {\n  const [clicked, setClicked] = React.useState(false);\n\n  return (\n    <DataListItem\n      isExpanded={false}\n      aria-labelledby={`vehicle-${id}`}\n    >\n      <DataListItemRow>\n        <DataListCell isFilled>\n          <span id={`vehicle-${id}`}>{description}</span>\n        </DataListCell>\n        <DataListCell isFilled>\n          <InputGroup>\n            <Button\n              variant={ButtonVariant.primary}\n              isDisabled={capacity === 0}\n              data-testid={`capacity-decrease-${id}`}\n              onClick={() => capacityChangeHandler({ vehicleId: id, capacity: capacity - 1 })}\n            >\n              <MinusIcon />\n            </Button>\n            <InputGroupText readOnly>\n              {capacity}\n            </InputGroupText>\n            <Button\n              variant={ButtonVariant.primary}\n              data-testid={`capacity-increase-${id}`}\n              onClick={() => capacityChangeHandler({ vehicleId: id, capacity: capacity + 1 })}\n            >\n              <PlusIcon />\n            </Button>\n          </InputGroup>\n        </DataListCell>\n        <DataListCell isFilled={false}>\n          <Button\n            type=\"button\"\n            variant=\"link\"\n            data-testid={`remove-${id}`}\n            isDisabled={clicked}\n            onClick={() => {\n              setClicked(true);\n              removeHandler(id);\n            }}\n          >\n            <TimesIcon />\n          </Button>\n        </DataListCell>\n      </DataListItemRow>\n    </DataListItem>\n  );\n};\n\nexport default Vehicle;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/Alerts.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Alerts should call readMessage() when alert is closed 1`] = `\nObject {\n  \"asFragment\": [Function],\n  \"baseElement\": <body>\n    <div />\n    <div>\n      <ul\n        class=\"pf-c-alert-group pf-m-toast\"\n      >\n        <li>\n          <div\n            aria-atomic=\"false\"\n            aria-label=\"Danger Alert\"\n            aria-live=\"polite\"\n            class=\"pf-c-alert pf-m-danger\"\n            data-ouia-component-id=\"OUIA-Generated-Alert-danger-1\"\n            data-ouia-component-type=\"PF4/Alert\"\n            data-ouia-safe=\"true\"\n          >\n            <div\n              class=\"pf-c-alert__icon\"\n            >\n              <svg\n                aria-hidden=\"true\"\n                fill=\"currentColor\"\n                height=\"1em\"\n                role=\"img\"\n                style=\"vertical-align: -0.125em;\"\n                viewBox=\"0 0 512 512\"\n                width=\"1em\"\n              >\n                <path\n                  d=\"M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z\"\n                />\n              </svg>\n            </div>\n            <h4\n              class=\"pf-c-alert__title\"\n            >\n              <span\n                class=\"pf-u-screen-reader\"\n              >\n                Danger alert:\n              </span>\n              Error\n            </h4>\n            <div\n              class=\"pf-c-alert__action\"\n            >\n              <button\n                aria-disabled=\"false\"\n                aria-label=\"Close Danger alert: alert: Error\"\n                class=\"pf-c-button pf-m-plain\"\n                data-ouia-component-id=\"OUIA-Generated-Button-plain-1\"\n                data-ouia-component-type=\"PF4/Button\"\n                data-ouia-safe=\"true\"\n                title=\"Close alert\"\n                type=\"button\"\n              >\n                <svg\n                  aria-hidden=\"true\"\n                  fill=\"currentColor\"\n                  height=\"1em\"\n                  role=\"img\"\n                  style=\"vertical-align: -0.125em;\"\n                  viewBox=\"0 0 352 512\"\n                  width=\"1em\"\n                >\n                  <path\n                    d=\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\"\n                  />\n                </svg>\n              </button>\n            </div>\n            <div\n              class=\"pf-c-alert__description\"\n            >\n              msg 1\n            </div>\n          </div>\n        </li>\n        <li>\n          <div\n            aria-atomic=\"false\"\n            aria-label=\"Danger Alert\"\n            aria-live=\"polite\"\n            class=\"pf-c-alert pf-m-danger\"\n            data-ouia-component-id=\"OUIA-Generated-Alert-danger-2\"\n            data-ouia-component-type=\"PF4/Alert\"\n            data-ouia-safe=\"true\"\n          >\n            <div\n              class=\"pf-c-alert__icon\"\n            >\n              <svg\n                aria-hidden=\"true\"\n                fill=\"currentColor\"\n                height=\"1em\"\n                role=\"img\"\n                style=\"vertical-align: -0.125em;\"\n                viewBox=\"0 0 512 512\"\n                width=\"1em\"\n              >\n                <path\n                  d=\"M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z\"\n                />\n              </svg>\n            </div>\n            <h4\n              class=\"pf-c-alert__title\"\n            >\n              <span\n                class=\"pf-u-screen-reader\"\n              >\n                Danger alert:\n              </span>\n              Error\n            </h4>\n            <div\n              class=\"pf-c-alert__action\"\n            >\n              <button\n                aria-disabled=\"false\"\n                aria-label=\"Close Danger alert: alert: Error\"\n                class=\"pf-c-button pf-m-plain\"\n                data-ouia-component-id=\"OUIA-Generated-Button-plain-2\"\n                data-ouia-component-type=\"PF4/Button\"\n                data-ouia-safe=\"true\"\n                title=\"Close alert\"\n                type=\"button\"\n              >\n                <svg\n                  aria-hidden=\"true\"\n                  fill=\"currentColor\"\n                  height=\"1em\"\n                  role=\"img\"\n                  style=\"vertical-align: -0.125em;\"\n                  viewBox=\"0 0 352 512\"\n                  width=\"1em\"\n                >\n                  <path\n                    d=\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\"\n                  />\n                </svg>\n              </button>\n            </div>\n            <div\n              class=\"pf-c-alert__description\"\n            >\n              msg 2\n            </div>\n          </div>\n        </li>\n      </ul>\n    </div>\n  </body>,\n  \"container\": <div />,\n  \"debug\": [Function],\n  \"findAllByAltText\": [Function],\n  \"findAllByDisplayValue\": [Function],\n  \"findAllByLabelText\": [Function],\n  \"findAllByPlaceholderText\": [Function],\n  \"findAllByRole\": [Function],\n  \"findAllByTestId\": [Function],\n  \"findAllByText\": [Function],\n  \"findAllByTitle\": [Function],\n  \"findByAltText\": [Function],\n  \"findByDisplayValue\": [Function],\n  \"findByLabelText\": [Function],\n  \"findByPlaceholderText\": [Function],\n  \"findByRole\": [Function],\n  \"findByTestId\": [Function],\n  \"findByText\": [Function],\n  \"findByTitle\": [Function],\n  \"getAllByAltText\": [Function],\n  \"getAllByDisplayValue\": [Function],\n  \"getAllByLabelText\": [Function],\n  \"getAllByPlaceholderText\": [Function],\n  \"getAllByRole\": [Function],\n  \"getAllByTestId\": [Function],\n  \"getAllByText\": [Function],\n  \"getAllByTitle\": [Function],\n  \"getByAltText\": [Function],\n  \"getByDisplayValue\": [Function],\n  \"getByLabelText\": [Function],\n  \"getByPlaceholderText\": [Function],\n  \"getByRole\": [Function],\n  \"getByTestId\": [Function],\n  \"getByText\": [Function],\n  \"getByTitle\": [Function],\n  \"queryAllByAltText\": [Function],\n  \"queryAllByDisplayValue\": [Function],\n  \"queryAllByLabelText\": [Function],\n  \"queryAllByPlaceholderText\": [Function],\n  \"queryAllByRole\": [Function],\n  \"queryAllByTestId\": [Function],\n  \"queryAllByText\": [Function],\n  \"queryAllByTitle\": [Function],\n  \"queryByAltText\": [Function],\n  \"queryByDisplayValue\": [Function],\n  \"queryByLabelText\": [Function],\n  \"queryByPlaceholderText\": [Function],\n  \"queryByRole\": [Function],\n  \"queryByTestId\": [Function],\n  \"queryByText\": [Function],\n  \"queryByTitle\": [Function],\n  \"rerender\": [Function],\n  \"unmount\": [Function],\n}\n`;\n\nexports[`Alerts should not render if there are no messages 1`] = `null`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/DemoDropdown.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Demo dropdown button should be disabled with empty demos 1`] = `\n<Dropdown\n  dropdownItems={Array []}\n  isOpen={false}\n  onSelect={[Function]}\n  position=\"right\"\n  style={\n    Object {\n      \"marginBottom\": 16,\n      \"marginLeft\": 16,\n    }\n  }\n  toggle={\n    <DropdownToggle\n      disabled={true}\n      isPrimary={true}\n      onToggle={[Function]}\n    >\n      Load demo\n    </DropdownToggle>\n  }\n/>\n`;\n\nexports[`Demo dropdown button should render correctly with a couple of demos 1`] = `\n<Dropdown\n  dropdownItems={\n    Array [\n      <DropdownItem>\n        demo 1\n      </DropdownItem>,\n      <DropdownItem>\n        demo 2\n      </DropdownItem>,\n    ]\n  }\n  isOpen={false}\n  onSelect={[Function]}\n  position=\"right\"\n  style={\n    Object {\n      \"marginBottom\": 16,\n      \"marginLeft\": 16,\n    }\n  }\n  toggle={\n    <DropdownToggle\n      disabled={false}\n      isPrimary={true}\n      onToggle={[Function]}\n    >\n      Load demo\n    </DropdownToggle>\n  }\n/>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/Location.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Location Component should render correctly 1`] = `\n<DataListItem\n  aria-labelledby=\"location-10\"\n  className=\"\"\n  id=\"\"\n  isExpanded={false}\n  onMouseEnter={[Function]}\n  onMouseLeave={[Function]}\n>\n  <DataListItemRow>\n    <DataListCell\n      isFilled={true}\n    >\n      <Tooltip\n        content=\"x\"\n      >\n        <span\n          id=\"location-10\"\n        >\n          x\n        </span>\n      </Tooltip>\n    </DataListCell>\n    <DataListCell\n      isFilled={false}\n    >\n      <Button\n        isDisabled={false}\n        onClick={[Function]}\n        type=\"button\"\n        variant=\"link\"\n      >\n        <TimesIcon\n          color=\"currentColor\"\n          noVerticalAlign={false}\n          size=\"sm\"\n        />\n      </Button>\n    </DataListCell>\n  </DataListItemRow>\n</DataListItem>\n`;\n\nexports[`Location Component should render correctly when description is missing 1`] = `\n<DataListItem\n  aria-labelledby=\"location-11\"\n  className=\"\"\n  id=\"\"\n  isExpanded={false}\n  onMouseEnter={[Function]}\n  onMouseLeave={[Function]}\n>\n  <DataListItemRow>\n    <DataListCell\n      isFilled={true}\n    >\n      <span\n        id=\"location-11\"\n      >\n        Location 11\n      </span>\n    </DataListCell>\n    <DataListCell\n      isFilled={false}\n    >\n      <Button\n        isDisabled={false}\n        onClick={[Function]}\n        type=\"button\"\n        variant=\"link\"\n      >\n        <TimesIcon\n          color=\"currentColor\"\n          noVerticalAlign={false}\n          size=\"sm\"\n        />\n      </Button>\n    </DataListCell>\n  </DataListItemRow>\n</DataListItem>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/LocationList.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Location List Component should render correctly with a few routes 1`] = `\n<div\n  data-cy=\"location-list\"\n  style={\n    Object {\n      \"overflowY\": \"auto\",\n    }\n  }\n>\n  <DataList\n    aria-label=\"List of locations\"\n    className=\"\"\n    gridBreakpoint=\"md\"\n    isCompact={false}\n    selectedDataListItemId=\"\"\n    wrapModifier={null}\n  >\n    <Location\n      description=\"Depot\"\n      id={1}\n      removeDisabled={true}\n      removeHandler={[MockFunction]}\n      selectHandler={[MockFunction]}\n    />\n    <Location\n      description=\"Visit 1\"\n      id={2}\n      removeDisabled={false}\n      removeHandler={[MockFunction]}\n      selectHandler={[MockFunction]}\n    />\n    <Location\n      description=\"Visit 2\"\n      id={3}\n      removeDisabled={false}\n      removeHandler={[MockFunction]}\n      selectHandler={[MockFunction]}\n    />\n  </DataList>\n</div>\n`;\n\nexports[`Location List Component should render correctly with no routes 1`] = `\n<DataList\n  aria-label=\"Empty location list\"\n  className=\"\"\n  gridBreakpoint=\"md\"\n  isCompact={false}\n  selectedDataListItemId=\"\"\n  wrapModifier={null}\n>\n  <Bullseye>\n    No locations\n  </Bullseye>\n</DataList>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/LocationMarker.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Location Marker render depot 1`] = `\n<ForwardRef(Leaflet(Marker))\n  icon={\n    NewClass {\n      \"_initHooksCalled\": true,\n      \"options\": Object {\n        \"iconAnchor\": Array [\n          12,\n          12,\n        ],\n        \"iconSize\": Array [\n          24,\n          24,\n        ],\n        \"iconUrl\": \"if_big_house-home_2222740.png\",\n        \"popupAnchor\": Array [\n          0,\n          -10,\n        ],\n        \"shadowAnchor\": Array [\n          16,\n          2,\n        ],\n        \"shadowSize\": Array [\n          50,\n          16,\n        ],\n        \"shadowUrl\": \"if_big_house-home_2222740_shadow.png\",\n      },\n    }\n  }\n  onClick={[Function]}\n  position={\n    Object {\n      \"id\": 1,\n      \"lat\": 1.345678,\n      \"lng\": 1.345678,\n    }\n  }\n>\n  <ForwardRef(Leaflet(Tooltip))\n    permanent={false}\n  >\n    Location 1 [Lat=1.345678, Lng=1.345678]\n  </ForwardRef(Leaflet(Tooltip))>\n</ForwardRef(Leaflet(Marker))>\n`;\n\nexports[`Location Marker render visit 1`] = `\n<ForwardRef(Leaflet(Marker))\n  icon={\n    NewClass {\n      \"_initHooksCalled\": true,\n      \"options\": Object {},\n    }\n  }\n  onClick={[Function]}\n  position={\n    Object {\n      \"id\": 1,\n      \"lat\": 1.345678,\n      \"lng\": 1.345678,\n    }\n  }\n>\n  <ForwardRef(Leaflet(Tooltip))\n    permanent={false}\n  >\n    Location 1 [Lat=1.345678, Lng=1.345678]\n  </ForwardRef(Leaflet(Tooltip))>\n</ForwardRef(Leaflet(Marker))>\n`;\n\nexports[`Location Marker selected visit should show a tooltip 1`] = `\n<ForwardRef(Leaflet(Marker))\n  icon={\n    NewClass {\n      \"_initHooksCalled\": true,\n      \"options\": Object {},\n    }\n  }\n  onClick={[Function]}\n  position={\n    Object {\n      \"id\": 1,\n      \"lat\": 1.345678,\n      \"lng\": 1.345678,\n    }\n  }\n>\n  <ForwardRef(Leaflet(Tooltip))\n    permanent={true}\n  >\n    Location 1 [Lat=1.345678, Lng=1.345678]\n  </ForwardRef(Leaflet(Tooltip))>\n</ForwardRef(Leaflet(Marker))>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/RouteMap.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Route Map should ignore bounds if viewport is dirty 1`] = `\n<Map\n  onClick={[MockFunction]}\n  onViewportChanged={[MockFunction]}\n  style={\n    Object {\n      \"height\": \"calc(100vh - 176px)\",\n      \"width\": \"100%\",\n    }\n  }\n  viewport={\n    Object {\n      \"center\": Array [\n        1,\n        1,\n      ],\n      \"isDirty\": true,\n      \"zoom\": 4,\n    }\n  }\n  zoomControl={false}\n>\n  <ForwardRef(Leaflet(TileLayer))\n    attribution=\"© <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors\"\n    url=\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n  />\n  <ForwardRef(Leaflet(ZoomControl))\n    position=\"topright\"\n  />\n  <LocationMarker\n    isDepot={true}\n    isSelected={false}\n    location={\n      Object {\n        \"id\": 1,\n        \"lat\": 1.345678,\n        \"lng\": 1.345678,\n      }\n    }\n    removeHandler={[MockFunction]}\n  />\n  <ForwardRef(Leaflet(Rectangle))\n    bounds={\n      Object {\n        \"_northEast\": Object {\n          \"lat\": 10,\n          \"lng\": 20,\n        },\n        \"_southWest\": Object {\n          \"lat\": -1,\n          \"lng\": -2,\n        },\n      }\n    }\n    color=\"seagreen\"\n    dashArray=\"10,5\"\n    fill={false}\n    weight={1}\n  />\n</Map>\n`;\n\nexports[`Route Map should pan and zoom to show bounding box if viewport is not dirty 1`] = `\n<Map\n  bounds={\n    Object {\n      \"_northEast\": Object {\n        \"lat\": 10,\n        \"lng\": 20,\n      },\n      \"_southWest\": Object {\n        \"lat\": -1,\n        \"lng\": -2,\n      },\n    }\n  }\n  onClick={[MockFunction]}\n  onViewportChanged={[MockFunction]}\n  style={\n    Object {\n      \"height\": \"calc(100vh - 176px)\",\n      \"width\": \"100%\",\n    }\n  }\n  viewport={\n    Object {\n      \"center\": Array [\n        1,\n        1,\n      ],\n      \"isDirty\": false,\n      \"zoom\": 4,\n    }\n  }\n  zoomControl={false}\n>\n  <ForwardRef(Leaflet(TileLayer))\n    attribution=\"© <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors\"\n    url=\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n  />\n  <ForwardRef(Leaflet(ZoomControl))\n    position=\"topright\"\n  />\n  <LocationMarker\n    isDepot={true}\n    isSelected={true}\n    location={\n      Object {\n        \"id\": 1,\n        \"lat\": 1.345678,\n        \"lng\": 1.345678,\n      }\n    }\n    removeHandler={[MockFunction]}\n  />\n  <LocationMarker\n    isDepot={false}\n    isSelected={false}\n    location={\n      Object {\n        \"id\": 2,\n        \"lat\": 2.345678,\n        \"lng\": 2.345678,\n      }\n    }\n    removeHandler={[MockFunction]}\n  />\n  <LocationMarker\n    isDepot={false}\n    isSelected={false}\n    location={\n      Object {\n        \"id\": 3,\n        \"lat\": 3.676111,\n        \"lng\": 3.568333,\n      }\n    }\n    removeHandler={[MockFunction]}\n  />\n  <ForwardRef(Leaflet(Polyline))\n    color=\"deepskyblue\"\n    fill={false}\n    opacity={0.6666}\n    positions={\n      Array [\n        Array [\n          0.111222,\n          0.222333,\n        ],\n        Array [\n          0.444555,\n          0.555666,\n        ],\n      ]\n    }\n    smoothFactor={3}\n    weight={9}\n  />\n  <ForwardRef(Leaflet(Rectangle))\n    bounds={\n      Object {\n        \"_northEast\": Object {\n          \"lat\": 10,\n          \"lng\": 20,\n        },\n        \"_southWest\": Object {\n          \"lat\": -1,\n          \"lng\": -2,\n        },\n      }\n    }\n    color=\"seagreen\"\n    dashArray=\"10,5\"\n    fill={false}\n    weight={1}\n  />\n</Map>\n`;\n\nexports[`Route Map should show the whole world when bounding box is null 1`] = `\n<Map\n  onClick={[MockFunction]}\n  onViewportChanged={[Function]}\n  style={\n    Object {\n      \"height\": \"calc(100vh - 176px)\",\n      \"width\": \"100%\",\n    }\n  }\n  viewport={\n    Object {\n      \"center\": Array [\n        1,\n        1,\n      ],\n      \"isDirty\": false,\n      \"zoom\": 4,\n    }\n  }\n  zoomControl={false}\n>\n  <ForwardRef(Leaflet(TileLayer))\n    attribution=\"© <a href='http://osm.org/copyright'>OpenStreetMap</a> contributors\"\n    url=\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n  />\n  <ForwardRef(Leaflet(ZoomControl))\n    position=\"topright\"\n  />\n  <LocationMarker\n    isDepot={true}\n    isSelected={true}\n    location={\n      Object {\n        \"id\": 1,\n        \"lat\": 1.345678,\n        \"lng\": 1.345678,\n      }\n    }\n    removeHandler={[MockFunction]}\n  />\n  <ForwardRef(Leaflet(Polyline))\n    color=\"deepskyblue\"\n    fill={false}\n    opacity={0.6666}\n    positions={Array []}\n    smoothFactor={3}\n    weight={9}\n  />\n</Map>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/SearchBox.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Search box should render text input initially 1`] = `\n<React.Fragment>\n  <SearchInput\n    aria-label=\"geosearch text input\"\n    data-cy=\"geosearch-text-input\"\n    onChange={[Function]}\n    placeholder=\"Search to add a location...\"\n    style={\n      Object {\n        \"marginBottom\": 10,\n      }\n    }\n    value=\"\"\n  />\n</React.Fragment>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/components/__snapshots__/Vehicle.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Vehicle Component should render correctly 1`] = `\n<DataListItem\n  aria-labelledby=\"vehicle-10\"\n  className=\"\"\n  id=\"\"\n  isExpanded={false}\n>\n  <DataListItemRow>\n    <DataListCell\n      isFilled={true}\n    >\n      <span\n        id=\"vehicle-10\"\n      >\n        x\n      </span>\n    </DataListCell>\n    <DataListCell\n      isFilled={true}\n    >\n      <InputGroup>\n        <Button\n          data-testid=\"capacity-decrease-10\"\n          isDisabled={false}\n          onClick={[Function]}\n          variant=\"primary\"\n        >\n          <MinusIcon\n            color=\"currentColor\"\n            noVerticalAlign={false}\n            size=\"sm\"\n          />\n        </Button>\n        <InputGroupText\n          readOnly={true}\n        >\n          7\n        </InputGroupText>\n        <Button\n          data-testid=\"capacity-increase-10\"\n          onClick={[Function]}\n          variant=\"primary\"\n        >\n          <PlusIcon\n            color=\"currentColor\"\n            noVerticalAlign={false}\n            size=\"sm\"\n          />\n        </Button>\n      </InputGroup>\n    </DataListCell>\n    <DataListCell\n      isFilled={false}\n    >\n      <Button\n        data-testid=\"remove-10\"\n        isDisabled={false}\n        onClick={[Function]}\n        type=\"button\"\n        variant=\"link\"\n      >\n        <TimesIcon\n          color=\"currentColor\"\n          noVerticalAlign={false}\n          size=\"sm\"\n        />\n      </Button>\n    </DataListCell>\n  </DataListItemRow>\n</DataListItem>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/ConnectionError.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport ConnectionError, { ConnectionErrorProps } from './ConnectionError';\n\ndescribe('Connection Error Component', () => {\n  it('should render correctly', () => {\n    const props: ConnectionErrorProps = {\n      isOpen: true,\n    };\n\n    const connectionError = shallow(<ConnectionError {...props} />);\n    expect(toJson(connectionError)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/ConnectionError.tsx",
    "content": "import {\n  ExpandableSection,\n  List,\n  ListItem,\n  Modal,\n  Text,\n  TextContent,\n  TextVariants,\n} from '@patternfly/react-core';\nimport { backendUrl } from 'common';\nimport * as React from 'react';\n\nexport interface ConnectionErrorProps {\n  isOpen: boolean;\n}\n\nconst title = 'Connection error';\n\nconst ConnectionError: React.FC<ConnectionErrorProps> = ({ isOpen }) => (\n  <Modal\n    title={title}\n    titleIconVariant=\"danger\"\n    isOpen={isOpen}\n    variant=\"small\"\n    showClose={false}\n    aria-label={title}\n  >\n    <TextContent>\n      <Text component={TextVariants.p}>\n        The server is unreachable. Trying to reconnect.\n      </Text>\n      <ExpandableSection toggleText=\"Show more\">\n        <Text component={TextVariants.p}>\n          The server is expected to be running at\n          {' '}\n          <a href={backendUrl} target=\"_blank\" rel=\"noopener noreferrer\">{backendUrl}</a>\n          {' '}\n          but the connection failed.\n        </Text>\n        <Text component={TextVariants.p}>\n          Please check the following possible reasons and try to resolve them:\n        </Text>\n        <List>\n          <ListItem>You are offline. Check your network connection.</ListItem>\n          <ListItem>The server is running on a different URL. Check if the URL is incorrect.</ListItem>\n          <ListItem>The server is down. Restart the server.</ListItem>\n        </List>\n        <Text>\n          The application will reconnect as soon as the server is available again.\n        </Text>\n      </ExpandableSection>\n    </TextContent>\n  </Modal>\n);\n\nexport default ConnectionError;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/ConnectionManager.test.tsx",
    "content": "import { render } from '@testing-library/react';\nimport { WebSocketConnectionStatus } from 'store/websocket/types';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport { ConnectionManager, Props } from './ConnectionManager';\n\ndescribe('Connection Manager', () => {\n  it('should connect the WebSocket client when mounted', () => {\n    const props: Props = {\n      connectClient: jest.fn(),\n      connectionStatus: WebSocketConnectionStatus.CLOSED,\n    };\n\n    render(<ConnectionManager {...props} />);\n\n    // Connect WebSocket client when the component is mounted\n    expect(props.connectClient).toHaveBeenCalled();\n  });\n\n  it('should not display error when connection is closed', () => {\n    const props: Props = {\n      connectClient: jest.fn(),\n      connectionStatus: WebSocketConnectionStatus.CLOSED,\n    };\n\n    const connectionManager = shallow(<ConnectionManager {...props} />);\n    expect(toJson(connectionManager)).toMatchSnapshot();\n  });\n\n  it('should not display error when connection is open', () => {\n    const props: Props = {\n      connectClient: jest.fn(),\n      connectionStatus: WebSocketConnectionStatus.OPEN,\n    };\n\n    const connectionManager = shallow(<ConnectionManager {...props} />);\n    expect(toJson(connectionManager)).toMatchSnapshot();\n  });\n\n  it('should display error when connection fails', () => {\n    const props: Props = {\n      connectClient: jest.fn(),\n      connectionStatus: WebSocketConnectionStatus.ERROR,\n    };\n\n    const connectionManager = shallow(<ConnectionManager {...props} />);\n    expect(toJson(connectionManager)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/ConnectionManager.tsx",
    "content": "import * as React from 'react';\nimport { connect } from 'react-redux';\nimport { AppState } from 'store/types';\nimport { websocketOperations } from 'store/websocket';\nimport { WebSocketConnectionStatus } from 'store/websocket/types';\nimport ConnectionError from 'ui/connection/ConnectionError';\n\ninterface StateProps {\n  connectionStatus: WebSocketConnectionStatus;\n}\n\ninterface DispatchProps {\n  connectClient: typeof websocketOperations.connectClient;\n}\n\nexport type Props = StateProps & DispatchProps;\n\nconst mapStateToProps = ({ connectionStatus }: AppState): StateProps => ({\n  connectionStatus,\n});\n\nconst mapDispatchToProps: DispatchProps = {\n  connectClient: websocketOperations.connectClient,\n};\n\nexport class ConnectionManager extends React.Component<Props> {\n  componentDidMount() {\n    this.props.connectClient();\n  }\n\n  render() {\n    return (\n      <ConnectionError isOpen={this.props.connectionStatus === WebSocketConnectionStatus.ERROR} />\n    );\n  }\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps,\n)(ConnectionManager);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/__snapshots__/ConnectionError.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Connection Error Component should render correctly 1`] = `\n<Modal\n  actions={Array []}\n  appendTo={[Function]}\n  aria-describedby=\"\"\n  aria-label=\"Connection error\"\n  aria-labelledby=\"\"\n  className=\"\"\n  hasNoBodyWrapper={false}\n  isOpen={true}\n  onClose={[Function]}\n  ouiaSafe={true}\n  showClose={false}\n  title=\"Connection error\"\n  titleIconVariant=\"danger\"\n  titleLabel=\"\"\n  variant=\"small\"\n>\n  <TextContent>\n    <Text\n      component=\"p\"\n    >\n      The server is unreachable. Trying to reconnect.\n    </Text>\n    <ExpandableSection\n      className=\"\"\n      contentId=\"\"\n      displaySize=\"default\"\n      isActive={false}\n      isDetached={false}\n      isIndented={false}\n      isWidthLimited={false}\n      onToggle={[Function]}\n      toggleText=\"Show more\"\n      toggleTextCollapsed=\"\"\n      toggleTextExpanded=\"\"\n      variant=\"default\"\n    >\n      <Text\n        component=\"p\"\n      >\n        The server is expected to be running at\n         \n        <a\n          href=\"\"\n          rel=\"noopener noreferrer\"\n          target=\"_blank\"\n        >\n          \n        </a>\n         \n        but the connection failed.\n      </Text>\n      <Text\n        component=\"p\"\n      >\n        Please check the following possible reasons and try to resolve them:\n      </Text>\n      <List>\n        <ListItem>\n          You are offline. Check your network connection.\n        </ListItem>\n        <ListItem>\n          The server is running on a different URL. Check if the URL is incorrect.\n        </ListItem>\n        <ListItem>\n          The server is down. Restart the server.\n        </ListItem>\n      </List>\n      <Text>\n        The application will reconnect as soon as the server is available again.\n      </Text>\n    </ExpandableSection>\n  </TextContent>\n</Modal>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/__snapshots__/ConnectionManager.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Connection Manager should display error when connection fails 1`] = `\n<ConnectionError\n  isOpen={true}\n/>\n`;\n\nexports[`Connection Manager should not display error when connection is closed 1`] = `\n<ConnectionError\n  isOpen={false}\n/>\n`;\n\nexports[`Connection Manager should not display error when connection is open 1`] = `\n<ConnectionError\n  isOpen={false}\n/>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/connection/index.ts",
    "content": "import ConnectedConnectionManager from './ConnectionManager';\n\nexport { ConnectedConnectionManager as ConnectionManager };\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/Header.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport Header from './Header';\n\ndescribe('Header component', () => {\n  it('should match snapshot', () => {\n    const header = shallow(<Header />);\n    expect(toJson(header)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/Header.tsx",
    "content": "import { Brand, PageHeader } from '@patternfly/react-core';\nimport * as React from 'react';\nimport NavigationWithRouter from './Navigation';\n\nconst Header: React.FC = () => (\n  <PageHeader\n    logo={<Brand src=\"./assets/images/optaPlannerLogoDarkBackground200px.png\" alt=\"OptaPlanner Logo\" />}\n    topNav={<NavigationWithRouter />}\n  />\n);\n\nexport default Header;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/Navigation.test.tsx",
    "content": "import { Location } from 'history';\nimport { RouteComponentProps } from 'react-router';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport { Navigation } from './Navigation';\n\ndescribe('Navigation', () => {\n  it('should activate a navigation link matching the current path', () => {\n    const props: RouteComponentProps = {\n      location: {\n        pathname: '/visits',\n      } as Location<unknown>,\n    } as RouteComponentProps;\n\n    // const visitsId = '/visits';\n\n    const navigation = shallow(<Navigation {...props} />);\n    expect(toJson(navigation)).toMatchSnapshot();\n\n    // FIXME\n\n    // NavItem matching the path should be active\n    // const navItems = navigation.find(NavItem).filterWhere((navItem) => navItem.props().itemId === visitsId);\n    // expect(navItems).toHaveLength(1);\n    // expect(navItems.at(0).props().isActive).toEqual(true);\n\n    // Other NavItems should be inactive\n    // navigation.find(NavItem).filterWhere((navItem) => navItem.props().itemId !== visitsId).forEach(\n    //   (navItem) => expect(navItem.props().isActive).toEqual(false),\n    // );\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/Navigation.tsx",
    "content": "import { Nav, NavItem, NavList } from '@patternfly/react-core';\nimport * as React from 'react';\nimport { RouteComponentProps } from 'react-router';\nimport { Link, withRouter } from 'react-router-dom';\nimport { pagesByPath } from 'ui/App';\n\nexport const Navigation = ({ location }: RouteComponentProps) => (\n  <Nav variant=\"horizontal\" aria-label=\"Nav\">\n    <NavList>\n      {pagesByPath.map(({ path, label }) => (\n        <NavItem\n          key={path.canonical}\n          itemId={path.canonical}\n          isActive={[...path.aliases, path.canonical].includes(location.pathname)}\n        >\n          <Link to={path.canonical}>{label}</Link>\n        </NavItem>\n      ))}\n    </NavList>\n  </Nav>\n);\n\nexport default withRouter(Navigation);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/__snapshots__/Header.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Header component should match snapshot 1`] = `\n<PageHeader\n  logo={\n    <Brand\n      alt=\"OptaPlanner Logo\"\n      src=\"./assets/images/optaPlannerLogoDarkBackground200px.png\"\n    />\n  }\n  topNav={<withRouter(Navigation) />}\n/>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/header/__snapshots__/Navigation.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Navigation should activate a navigation link matching the current path 1`] = `\n<Nav\n  aria-label=\"Nav\"\n  onSelect={[Function]}\n  onToggle={[Function]}\n  ouiaSafe={true}\n  theme=\"dark\"\n  variant=\"horizontal\"\n>\n  <NavList\n    ariaLeftScroll=\"Scroll left\"\n    ariaRightScroll=\"Scroll right\"\n  >\n    <NavItem\n      isActive={false}\n      itemId=\"/demo\"\n    >\n      <Link\n        to=\"/demo\"\n      >\n        Demo\n      </Link>\n    </NavItem>\n    <NavItem\n      isActive={false}\n      itemId=\"/vehicles\"\n    >\n      <Link\n        to=\"/vehicles\"\n      >\n        Vehicles\n      </Link>\n    </NavItem>\n    <NavItem\n      isActive={true}\n      itemId=\"/visits\"\n    >\n      <Link\n        to=\"/visits\"\n      >\n        Visits\n      </Link>\n    </NavItem>\n    <NavItem\n      isActive={false}\n      itemId=\"/routes\"\n    >\n      <Link\n        to=\"/routes\"\n      >\n        Routes\n      </Link>\n    </NavItem>\n  </NavList>\n</Nav>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Demo.test.tsx",
    "content": "import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { UserViewport } from 'store/client/types';\nimport { shallow, toJson } from 'ui/shallow-test-util';\nimport { Demo, DemoProps } from './Demo';\n\ndescribe('Demo page', () => {\n  it('should render correctly with no routes', () => {\n    const demo = shallow(<Demo {...emptyRouteProps} />);\n    expect(toJson(demo)).toMatchSnapshot();\n  });\n\n  it('should render correctly with a few routes', () => {\n    const demo = shallow(<Demo {...threeLocationsProps} />);\n    expect(toJson(demo)).toMatchSnapshot();\n  });\n\n  it('clear and export buttons should be disabled when demo is loading', () => {\n    const props: DemoProps = {\n      ...threeLocationsProps,\n      isDemoLoading: true,\n    };\n    const demo = shallow(<Demo {...props} />);\n    expect(toJson(demo)).toMatchSnapshot();\n  });\n\n  // FIXME\n  xit('clear and export buttons should be disabled when demo is loading', async () => {\n    const props: DemoProps = {\n      ...threeLocationsProps,\n      isDemoLoading: true,\n    };\n    const user = userEvent.setup();\n    render(<Demo {...props} />);\n\n    const clearButton = screen.getByRole('button', { name: 'Clear' });\n    expect(clearButton).toBeDisabled();\n\n    await user.click(clearButton);\n    // Doesn't work, probably due to https://github.com/airbnb/enzyme/issues/386\n    expect(props.clearHandler).not.toHaveBeenCalled();\n\n    const exportButton = screen.getByRole('button', { name: 'Export' });\n    expect(exportButton).toBeDisabled();\n  });\n\n  it('clear button should replace demo dropdown as soon as there is a depot', () => {\n    const props: DemoProps = {\n      ...emptyRouteProps,\n      depot: {\n        id: 1,\n        lat: 1,\n        lng: 1,\n        description: '',\n      },\n    };\n    render(<Demo {...props} />);\n\n    const clearButton = screen.getByRole('button', { name: 'Clear' });\n    expect(clearButton).toBeEnabled();\n\n    const exportButton = screen.getByRole('button', { name: 'Export' });\n    expect(exportButton).toBeEnabled();\n\n    expect(screen.queryByRole('button', { name: 'Load demo' })).not.toBeInTheDocument();\n  });\n});\n\nconst userViewport: UserViewport = {\n  isDirty: false,\n  zoom: 1,\n  center: [0, 0],\n};\n\nconst emptyRouteProps: DemoProps = {\n  loadHandler: jest.fn(),\n  clearHandler: jest.fn(),\n  addVehicleHandler: jest.fn,\n  removeVehicleHandler: jest.fn,\n  addLocationHandler: jest.fn(),\n  removeLocationHandler: jest.fn(),\n  updateViewport: jest.fn(),\n\n  distance: '0',\n  vehicleCount: 0,\n  totalCapacity: 0,\n  totalDemand: 0,\n  demoNames: ['demo'],\n  isDemoLoading: false,\n  boundingBox: null,\n  userViewport,\n  countryCodeSearchFilter: [],\n\n  depot: null,\n  routes: [],\n  visits: [],\n};\n\nconst threeLocationsProps: DemoProps = {\n  loadHandler: jest.fn(),\n  clearHandler: jest.fn(),\n  addVehicleHandler: jest.fn,\n  removeVehicleHandler: jest.fn,\n  addLocationHandler: jest.fn(),\n  removeLocationHandler: jest.fn(),\n  updateViewport: jest.fn(),\n\n  distance: '10',\n  vehicleCount: 8,\n  totalCapacity: 5,\n  totalDemand: 2,\n  demoNames: ['demo'],\n  isDemoLoading: false,\n  boundingBox: null,\n  userViewport,\n  countryCodeSearchFilter: ['XY'],\n\n  depot: {\n    id: 1,\n    lat: 1.345678,\n    lng: 1.345678,\n  },\n\n  visits: [{\n    id: 2,\n    lat: 2.345678,\n    lng: 2.345678,\n  }, {\n    id: 3,\n    lat: 3.676111,\n    lng: 3.568333,\n  }],\n\n  routes: [{\n    vehicle: { id: 1, name: 'v1', capacity: 5 },\n    visits: [{\n      id: 1,\n      lat: 1.345678,\n      lng: 1.345678,\n    }, {\n      id: 2,\n      lat: 2.345678,\n      lng: 2.345678,\n    }, {\n      id: 3,\n      lat: 3.676111,\n      lng: 3.568333,\n    }],\n\n    track: [],\n\n  }],\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Demo.tsx",
    "content": "import {\n  Button,\n  ButtonVariant,\n  Flex,\n  FlexItem,\n  InputGroup,\n  InputGroupText,\n  Split,\n  SplitItem,\n  Title,\n} from '@patternfly/react-core';\nimport { MinusIcon, PlusIcon } from '@patternfly/react-icons';\nimport { backendUrl } from 'common';\nimport { LeafletMouseEvent } from 'leaflet';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { clientOperations } from 'store/client';\nimport { UserViewport } from 'store/client/types';\nimport { demoOperations } from 'store/demo';\nimport { routeOperations, routeSelectors } from 'store/route';\nimport { Location, RouteWithTrack } from 'store/route/types';\nimport { BoundingBox } from 'store/server/types';\nimport { AppState } from 'store/types';\nimport { DemoDropdown } from 'ui/components/DemoDropdown';\nimport LocationList from 'ui/components/LocationList';\nimport RouteMap from 'ui/components/RouteMap';\nimport SearchBox, { Result } from 'ui/components/SearchBox';\nimport { sideBarStyle } from 'ui/pages/common';\nimport { CapacityInfo, DistanceInfo, VehiclesInfo, VisitsInfo } from 'ui/pages/InfoBlock';\n\nexport interface StateProps {\n  distance: string;\n  depot: Location | null;\n  vehicleCount: number;\n  totalCapacity: number;\n  totalDemand: number;\n  visits: Location[];\n  routes: RouteWithTrack[];\n  isDemoLoading: boolean;\n  boundingBox: BoundingBox | null;\n  userViewport: UserViewport;\n  countryCodeSearchFilter: string[];\n  demoNames: string[];\n}\n\nexport interface DispatchProps {\n  loadHandler: typeof demoOperations.requestDemo;\n  clearHandler: typeof routeOperations.clearRoute;\n  addLocationHandler: typeof routeOperations.addLocation;\n  removeLocationHandler: typeof routeOperations.deleteLocation;\n  addVehicleHandler: typeof routeOperations.addVehicle;\n  removeVehicleHandler: typeof routeOperations.deleteAnyVehicle;\n  updateViewport: typeof clientOperations.updateViewport;\n}\n\nconst mapStateToProps = ({ plan, demo, serverInfo, userViewport }: AppState): StateProps => ({\n  distance: plan.distance,\n  vehicleCount: plan.vehicles.length,\n  totalCapacity: routeSelectors.totalCapacity(plan),\n  totalDemand: routeSelectors.totalDemand(plan),\n  depot: plan.depot,\n  visits: plan.visits,\n  routes: plan.routes,\n  isDemoLoading: demo.isLoading,\n  boundingBox: serverInfo.boundingBox,\n  countryCodeSearchFilter: serverInfo.countryCodes,\n  // TODO use selector\n  // TODO sort demos alphabetically?\n  demoNames: (serverInfo.demos && serverInfo.demos.map((value) => value.name)) || [],\n  userViewport,\n});\n\nconst mapDispatchToProps: DispatchProps = {\n  loadHandler: demoOperations.requestDemo,\n  clearHandler: routeOperations.clearRoute,\n  addLocationHandler: routeOperations.addLocation,\n  removeLocationHandler: routeOperations.deleteLocation,\n  addVehicleHandler: routeOperations.addVehicle,\n  removeVehicleHandler: routeOperations.deleteAnyVehicle,\n  updateViewport: clientOperations.updateViewport,\n};\n\nexport type DemoProps = DispatchProps & StateProps;\n\nexport interface DemoState {\n  selectedId: number;\n}\n\nexport class Demo extends React.Component<DemoProps, DemoState> {\n  constructor(props: DemoProps) {\n    super(props);\n\n    this.state = {\n      selectedId: NaN,\n    };\n    this.handleDemoLoadClick = this.handleDemoLoadClick.bind(this);\n    this.handleMapClick = this.handleMapClick.bind(this);\n    this.handleSearchResultClick = this.handleSearchResultClick.bind(this);\n    this.onSelectLocation = this.onSelectLocation.bind(this);\n  }\n\n  handleMapClick(e: LeafletMouseEvent) {\n    this.props.addLocationHandler({ ...e.latlng, description: '' }); // TODO use reverse geocoding to find address\n  }\n\n  handleSearchResultClick(result: Result) {\n    this.props.addLocationHandler({ ...result.latLng, description: result.address });\n  }\n\n  handleDemoLoadClick(demoName: string) {\n    this.props.loadHandler(demoName);\n  }\n\n  onSelectLocation(id: number) {\n    this.setState({ selectedId: id });\n  }\n\n  render() {\n    const { selectedId } = this.state;\n    const {\n      distance,\n      depot,\n      vehicleCount,\n      totalCapacity,\n      totalDemand,\n      visits,\n      routes,\n      demoNames,\n      isDemoLoading,\n      boundingBox,\n      userViewport,\n      countryCodeSearchFilter,\n      addVehicleHandler,\n      removeVehicleHandler,\n      removeLocationHandler,\n      clearHandler,\n      updateViewport,\n    } = this.props;\n\n    const exportDataSet = () => {\n      window.open(`${backendUrl}/api/dataset/export`);\n    };\n\n    return (\n      // FIXME find a way to avoid these style customizations\n      <Split hasGutter style={{ overflowY: 'auto' }}>\n        <SplitItem\n          isFilled={false}\n          style={sideBarStyle}\n        >\n          <Title headingLevel=\"h1\">Demo</Title>\n          <SearchBox\n            boundingBox={boundingBox}\n            countryCodeSearchFilter={countryCodeSearchFilter}\n            addHandler={this.handleSearchResultClick}\n          />\n          <LocationList\n            depot={depot}\n            visits={visits}\n            removeHandler={removeLocationHandler}\n            selectHandler={this.onSelectLocation}\n          />\n        </SplitItem>\n\n        <SplitItem\n          isFilled\n          style={{ display: 'flex', flexDirection: 'column' }}\n        >\n          <Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>\n            <FlexItem>\n              <VisitsInfo visitCount={visits.length} />\n            </FlexItem>\n            <FlexItem>\n              <CapacityInfo totalDemand={totalDemand} totalCapacity={totalCapacity} />\n            </FlexItem>\n            <FlexItem>\n              <Flex>\n                <FlexItem>\n                  <VehiclesInfo />\n                </FlexItem>\n                <FlexItem>\n                  <InputGroup>\n                    <Button\n                      variant={ButtonVariant.primary}\n                      isDisabled={vehicleCount === 0}\n                      onClick={removeVehicleHandler}\n                    >\n                      <MinusIcon />\n                    </Button>\n                    <InputGroupText readOnly style={{ minWidth: '2.5em' }}>\n                      {vehicleCount}\n                    </InputGroupText>\n                    <Button\n                      variant={ButtonVariant.primary}\n                      onClick={addVehicleHandler}\n                      data-cy=\"demo-add-vehicle\"\n                    >\n                      <PlusIcon />\n                    </Button>\n                  </InputGroup>\n                </FlexItem>\n              </Flex>\n            </FlexItem>\n            <FlexItem>\n              <DistanceInfo distance={distance} />\n            </FlexItem>\n            <FlexItem>\n              <Button\n                isDisabled={!depot || isDemoLoading}\n                style={{ marginBottom: 16, marginLeft: 16 }}\n                onClick={exportDataSet}\n              >\n                Export\n              </Button>\n              {(depot === null && (\n                <DemoDropdown\n                  demos={demoNames}\n                  onSelect={this.handleDemoLoadClick}\n                />\n              )) || (\n                <Button\n                  isDisabled={isDemoLoading}\n                  style={{ marginBottom: 16, marginLeft: 16 }}\n                  onClick={clearHandler}\n                  data-cy=\"demo-clear-button\"\n                >\n                  Clear\n                </Button>\n              )}\n            </FlexItem>\n          </Flex>\n          <RouteMap\n            boundingBox={boundingBox}\n            userViewport={userViewport}\n            updateViewport={updateViewport}\n            selectedId={selectedId}\n            clickHandler={this.handleMapClick}\n            removeHandler={removeLocationHandler}\n            depot={depot}\n            routes={routes}\n            visits={visits}\n          />\n        </SplitItem>\n      </Split>\n    );\n  }\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps,\n)(Demo);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/InfoBlock.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport { PlusIcon } from '@patternfly/react-icons';\nimport { CapacityInfo, DistanceInfo, InfoBlock, VehiclesInfo, VisitsInfo } from 'ui/pages/InfoBlock';\n\ndescribe('Info block snapshots:', () => {\n  it('generic', () => {\n    const infoBlock = shallow(\n      <InfoBlock\n        icon={PlusIcon}\n        content={{\n          data: 'test content', minWidth: '10',\n        }}\n        tooltip=\"test tooltip\"\n      />,\n    );\n    expect(toJson(infoBlock)).toMatchSnapshot();\n  });\n  it('capacity', () => {\n    const capacityInfoOK = shallow(<CapacityInfo totalDemand={20} totalCapacity={100} />);\n    expect(toJson(capacityInfoOK)).toMatchSnapshot();\n    const capacityInfoError = shallow(<CapacityInfo totalDemand={20} totalCapacity={10} />);\n    expect(toJson(capacityInfoError)).toMatchSnapshot();\n  });\n  it('distance', () => {\n    const distanceInfo = shallow(<DistanceInfo distance=\"3h 56m 11s\" />);\n    expect(toJson(distanceInfo)).toMatchSnapshot();\n  });\n  it('vehicles', () => {\n    const vehiclesInfo = shallow(<VehiclesInfo />);\n    expect(toJson(vehiclesInfo)).toMatchSnapshot();\n  });\n  it('visits', () => {\n    const visitsInfo = shallow(<VisitsInfo visitCount={300} />);\n    expect(toJson(visitsInfo)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/InfoBlock.tsx",
    "content": "import { Flex, FlexItem, Tooltip } from '@patternfly/react-core';\nimport {\n  ClockIcon as DistanceIcon,\n  IconSize,\n  MapMarkerIcon as VisitIcon,\n  TruckIcon,\n  WeightHangingIcon as CapacityIcon,\n} from '@patternfly/react-icons';\nimport { SVGIconProps } from '@patternfly/react-icons/dist/js/createIcon';\nimport * as React from 'react';\n\ninterface InfoBlockProps {\n  icon: React.ComponentClass<SVGIconProps>;\n  content?: {\n    data: string | number;\n    minWidth: string;\n  };\n  color?: string;\n  tooltip: string;\n}\n\nexport const InfoBlock = ({ icon, content, tooltip, color }: InfoBlockProps) => {\n  const Icon = icon;\n  return (\n    <Tooltip content={tooltip} position=\"bottom\">\n      <Flex spaceItems={{ default: 'spaceItemsSm' }}>\n        <FlexItem>\n          <Icon size={IconSize.md} color={color} />\n        </FlexItem>\n        {content && (\n          <FlexItem style={{ minWidth: content.minWidth, textAlign: 'right' }}>\n            {content.data}\n          </FlexItem>\n        )}\n      </Flex>\n    </Tooltip>\n  );\n};\n\ninterface CapacityInfoProps {\n  totalDemand: number;\n  totalCapacity: number;\n}\n\nexport const CapacityInfo = ({\n  totalDemand,\n  totalCapacity,\n}: CapacityInfoProps) => (\n  <InfoBlock\n    icon={CapacityIcon}\n    content={{ data: `${totalDemand}/${totalCapacity}`, minWidth: '4em' }}\n    color={totalDemand > totalCapacity ? 'var(--pf-global--danger-color--200)' : ''}\n    tooltip=\"Capacity usage: total demand / total capacity\"\n  />\n);\n\ninterface DistanceInfoProps {\n  distance: string;\n}\n\nexport const DistanceInfo = ({ distance }: DistanceInfoProps) => (\n  <InfoBlock\n    icon={DistanceIcon}\n    content={{ data: distance, minWidth: '6.8em' }}\n    tooltip=\"Total driving travel time spent by all vehicles\"\n  />\n);\n\nexport const VehiclesInfo = () => (\n  <InfoBlock icon={TruckIcon} tooltip=\"Vehicles\" />\n);\n\ninterface VisitInfoProps {\n  visitCount: number;\n}\n\nexport const VisitsInfo = ({ visitCount }: VisitInfoProps) => (\n  <InfoBlock\n    icon={VisitIcon}\n    content={{ data: visitCount, minWidth: '2em' }}\n    tooltip=\"Number of visits\"\n  />\n);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Route.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport { UserViewport } from 'store/client/types';\nimport { Route, RouteProps } from './Route';\n\ndescribe('Route page', () => {\n  it('should render correctly with no routes', () => {\n    const routes = shallow(<Route {...noRoutes} />);\n    expect(toJson(routes)).toMatchSnapshot();\n  });\n\n  it('should render correctly with a few routes', () => {\n    const routes = shallow(<Route {...twoRoutes} />);\n    expect(toJson(routes)).toMatchSnapshot();\n  });\n});\n\nconst userViewport: UserViewport = {\n  isDirty: false,\n  zoom: 1,\n  center: [0, 0],\n};\n\nconst noRoutes: RouteProps = {\n  addHandler: jest.fn(),\n  removeHandler: jest.fn(),\n  updateViewport: jest.fn(),\n\n  boundingBox: null,\n  userViewport,\n\n  depot: null,\n  visits: [],\n  routes: [],\n};\n\nconst depot = {\n  id: 1,\n  lat: 1.345678,\n  lng: 1.345678,\n};\nconst visit2 = {\n  id: 2,\n  lat: 2.345678,\n  lng: 2.345678,\n};\nconst visit3 = {\n  id: 3,\n  lat: 3.676111,\n  lng: 3.568333,\n};\nconst visit4 = {\n  id: 4,\n  lat: 4.345678,\n  lng: 4.345678,\n};\nconst visit5 = {\n  id: 5,\n  lat: 5.345678,\n  lng: 5.345678,\n};\n\nconst twoRoutes: RouteProps = {\n  addHandler: jest.fn(),\n  removeHandler: jest.fn(),\n  updateViewport: jest.fn(),\n\n  boundingBox: null,\n  userViewport,\n\n  depot,\n  visits: [visit2, visit3, visit4, visit5],\n\n  routes: [{\n    vehicle: { id: 1, name: 'v1', capacity: 5 },\n    visits: [depot, visit2, visit3],\n\n    track: [],\n\n  }, {\n    vehicle: { id: 2, name: 'v2', capacity: 5 },\n    visits: [depot, visit4, visit5],\n\n    track: [],\n\n  }],\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Route.tsx",
    "content": "import {\n  Form,\n  FormSelect,\n  FormSelectOption,\n  Split,\n  SplitItem,\n  Title,\n} from '@patternfly/react-core';\nimport { LeafletMouseEvent } from 'leaflet';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { clientOperations } from 'store/client';\nimport { UserViewport } from 'store/client/types';\nimport { routeOperations } from 'store/route';\nimport { Location, RouteWithTrack } from 'store/route/types';\nimport { BoundingBox } from 'store/server/types';\nimport { AppState } from 'store/types';\nimport LocationList from 'ui/components/LocationList';\nimport RouteMap from 'ui/components/RouteMap';\nimport { sideBarStyle } from 'ui/pages/common';\n\nexport interface StateProps {\n  depot: Location | null;\n  visits: Location[];\n  routes: RouteWithTrack[];\n  boundingBox: BoundingBox | null;\n  userViewport: UserViewport;\n}\n\nexport interface DispatchProps {\n  addHandler: typeof routeOperations.addLocation;\n  removeHandler: typeof routeOperations.deleteLocation;\n  updateViewport: typeof clientOperations.updateViewport;\n}\n\nconst mapStateToProps = ({ plan, serverInfo, userViewport }: AppState): StateProps => ({\n  depot: plan.depot,\n  visits: plan.visits,\n  routes: plan.routes,\n  boundingBox: serverInfo.boundingBox,\n  userViewport,\n});\n\nconst mapDispatchToProps: DispatchProps = {\n  addHandler: routeOperations.addLocation,\n  removeHandler: routeOperations.deleteLocation,\n  updateViewport: clientOperations.updateViewport,\n};\n\nexport type RouteProps = DispatchProps & StateProps;\n\nexport interface RouteState {\n  selectedId: number;\n  selectedRouteId: number;\n}\n\nexport class Route extends React.Component<RouteProps, RouteState> {\n  constructor(props: RouteProps) {\n    super(props);\n\n    this.state = {\n      selectedId: NaN,\n      selectedRouteId: 0,\n    };\n    this.onSelectLocation = this.onSelectLocation.bind(this);\n    this.handleMapClick = this.handleMapClick.bind(this);\n  }\n\n  handleMapClick(e: LeafletMouseEvent) {\n    this.props.addHandler({ ...e.latlng, description: '' });\n  }\n\n  onSelectLocation(id: number) {\n    this.setState({ selectedId: id });\n  }\n\n  render() {\n    const { selectedId, selectedRouteId } = this.state;\n    const {\n      boundingBox,\n      userViewport,\n      depot,\n      visits,\n      routes,\n      removeHandler,\n      updateViewport,\n    } = this.props;\n\n    // FIXME quick hack to preserve route color by keeping its index\n    const filteredRoutes = (\n      routes.map((value, index) => (index === selectedRouteId ? value : { visits: [], track: [] }))\n    );\n    const filteredVisits: Location[] = routes.length > 0 ? routes[selectedRouteId].visits : [];\n    return (\n      <>\n        <Title headingLevel=\"h1\">Route</Title>\n        <Split hasGutter>\n          <SplitItem\n            isFilled={false}\n            style={sideBarStyle}\n          >\n            <Form>\n              <FormSelect\n                style={{ backgroundColor: 'white', marginBottom: 10 }}\n                isDisabled={routes.length === 0}\n                value={selectedRouteId}\n                onChange={(e) => {\n                  this.setState({ selectedRouteId: parseInt(e as unknown as string, 10) });\n                }}\n                aria-label=\"FormSelect Input\"\n              >\n                {routes.map(\n                  (route, index) => (\n                    <FormSelectOption\n                      isDisabled={false}\n                      // eslint-disable-next-line react/no-array-index-key\n                      key={index}\n                      value={index}\n                      label={route.vehicle.name}\n                    />\n                  ),\n                )}\n              </FormSelect>\n            </Form>\n            <LocationList\n              depot={depot}\n              visits={filteredVisits}\n              removeHandler={removeHandler}\n              selectHandler={this.onSelectLocation}\n            />\n          </SplitItem>\n          <SplitItem isFilled>\n            <RouteMap\n              boundingBox={boundingBox}\n              userViewport={userViewport}\n              updateViewport={updateViewport}\n              selectedId={selectedId}\n              clickHandler={this.handleMapClick}\n              removeHandler={removeHandler}\n              depot={depot}\n              visits={visits}\n              routes={filteredRoutes}\n            />\n          </SplitItem>\n        </Split>\n      </>\n    );\n  }\n}\n\nexport default connect(\n  mapStateToProps,\n  mapDispatchToProps,\n)(Route);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Vehicles.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport { Props, Vehicles } from './Vehicles';\n\ndescribe('Vehicles page', () => {\n  it('should render correctly', () => {\n    const props: Props = {\n      addVehicleHandler: jest.fn(),\n      removeVehicleHandler: jest.fn(),\n      changeVehicleCapacityHandler: jest.fn,\n      vehicles: [\n        { id: 1, name: 'Vehicle 1', capacity: 5 },\n        { id: 2, name: 'Vehicle 2', capacity: 5 },\n      ],\n    };\n    const vehicles = shallow(<Vehicles {...props} />);\n    expect(toJson(vehicles)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Vehicles.tsx",
    "content": "import {\n  Button,\n  DataList,\n  Split,\n  SplitItem,\n  Title,\n} from '@patternfly/react-core';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { routeOperations } from 'store/route';\nimport { Vehicle } from 'store/route/types';\nimport { AppState } from 'store/types';\nimport VehicleItem from 'ui/components/Vehicle';\n\ninterface StateProps {\n  vehicles: Vehicle[];\n}\n\ninterface DispatchProps {\n  addVehicleHandler: typeof routeOperations.addVehicle;\n  removeVehicleHandler: typeof routeOperations.deleteVehicle;\n  changeVehicleCapacityHandler: typeof routeOperations.changeVehicleCapacity;\n}\n\nexport type Props = StateProps & DispatchProps;\n\nconst mapStateToProps = ({ plan }: AppState): StateProps => ({\n  vehicles: plan.vehicles,\n});\n\nconst mapDispatchToProps: DispatchProps = {\n  addVehicleHandler: routeOperations.addVehicle,\n  removeVehicleHandler: routeOperations.deleteVehicle,\n  changeVehicleCapacityHandler: routeOperations.changeVehicleCapacity,\n};\n\nexport const Vehicles: React.FC<Props> = ({\n  vehicles, addVehicleHandler, removeVehicleHandler, changeVehicleCapacityHandler,\n}) => (\n  <>\n    <Split hasGutter style={{ overflowY: 'auto' }}>\n      <SplitItem isFilled>\n        <Title headingLevel=\"h1\">{`Vehicles (${vehicles.length})`}</Title>\n      </SplitItem>\n      <SplitItem isFilled={false}>\n        <Button\n          style={{ marginBottom: 16, marginLeft: 16 }}\n          onClick={addVehicleHandler}\n        >\n          Add\n        </Button>\n      </SplitItem>\n    </Split>\n    <div style={{ overflowY: 'auto' }}>\n      <DataList\n        aria-label=\"List of vehicles\"\n      >\n        {vehicles\n          .slice(0) // clone the array because\n          // sort is done in place (that would affect the route)\n          .sort((a, b) => a.id - b.id)\n          .map((vehicle) => (\n            <VehicleItem\n              key={vehicle.id}\n              id={vehicle.id}\n              description={vehicle.name}\n              capacity={vehicle.capacity}\n              removeHandler={removeVehicleHandler}\n              capacityChangeHandler={changeVehicleCapacityHandler}\n            />\n          ))}\n      </DataList>\n    </div>\n  </>\n);\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Vehicles);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Visits.test.tsx",
    "content": "import { shallow, toJson } from 'ui/shallow-test-util';\nimport { Props, Visits } from './Visits';\n\ndescribe('Visits page', () => {\n  it('should render correctly with no visits', () => {\n    const visits = shallow(<Visits {...noVisits} />);\n    expect(toJson(visits)).toMatchSnapshot();\n  });\n\n  it('should render correctly with a few visits', () => {\n    const visits = shallow(<Visits {...twoVisits} />);\n    expect(toJson(visits)).toMatchSnapshot();\n  });\n});\n\nconst noVisits: Props = {\n  removeHandler: jest.fn(),\n\n  depot: null,\n  visits: [],\n};\n\nconst twoVisits: Props = {\n  removeHandler: jest.fn(),\n\n  depot: {\n    id: 1,\n    lat: 1.345678,\n    lng: 1.345678,\n  },\n\n  visits: [{\n    id: 2,\n    lat: 2.345678,\n    lng: 2.345678,\n  }, {\n    id: 3,\n    lat: 3.676111,\n    lng: 3.568333,\n  }],\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/Visits.tsx",
    "content": "import { Title } from '@patternfly/react-core';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { routeOperations } from 'store/route';\nimport { Location } from 'store/route/types';\nimport { AppState } from 'store/types';\nimport LocationList from 'ui/components/LocationList';\n\ninterface StateProps {\n  depot: Location | null;\n  visits: Location[];\n}\n\nconst mapStateToProps = ({ plan }: AppState): StateProps => ({\n  depot: plan.depot,\n  visits: plan.visits,\n});\n\nexport interface DispatchProps {\n  removeHandler: typeof routeOperations.deleteLocation;\n}\n\nconst mapDispatchToProps: DispatchProps = {\n  removeHandler: routeOperations.deleteLocation,\n};\n\nexport type Props = StateProps & DispatchProps;\n\nexport const Visits: React.FC<Props> = ({\n  depot,\n  visits,\n  removeHandler,\n}: Props) => (\n  <>\n    <Title headingLevel=\"h1\">{`Visits (${visits.length})`}</Title>\n    {/* TODO do not show depots */}\n    <LocationList\n      removeHandler={removeHandler}\n      selectHandler={() => undefined}\n      depot={depot}\n      visits={visits}\n    />\n  </>\n);\n\nexport default connect(mapStateToProps, mapDispatchToProps)(Visits);\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/__snapshots__/Demo.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Demo page clear and export buttons should be disabled when demo is loading 1`] = `\n<Split\n  hasGutter={true}\n  style={\n    Object {\n      \"overflowY\": \"auto\",\n    }\n  }\n>\n  <SplitItem\n    isFilled={false}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n        \"width\": \"320px\",\n      }\n    }\n  >\n    <Title\n      headingLevel=\"h1\"\n    >\n      Demo\n    </Title>\n    <SearchBox\n      addHandler={[Function]}\n      boundingBox={null}\n      countryCodeSearchFilter={\n        Array [\n          \"XY\",\n        ]\n      }\n      searchDelay={500}\n    />\n    <LocationList\n      depot={\n        Object {\n          \"id\": 1,\n          \"lat\": 1.345678,\n          \"lng\": 1.345678,\n        }\n      }\n      removeHandler={[MockFunction]}\n      selectHandler={[Function]}\n      visits={\n        Array [\n          Object {\n            \"id\": 2,\n            \"lat\": 2.345678,\n            \"lng\": 2.345678,\n          },\n          Object {\n            \"id\": 3,\n            \"lat\": 3.676111,\n            \"lng\": 3.568333,\n          },\n        ]\n      }\n    />\n  </SplitItem>\n  <SplitItem\n    isFilled={true}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n      }\n    }\n  >\n    <Flex\n      justifyContent={\n        Object {\n          \"default\": \"justifyContentSpaceBetween\",\n        }\n      }\n    >\n      <FlexItem>\n        <VisitsInfo\n          visitCount={2}\n        />\n      </FlexItem>\n      <FlexItem>\n        <CapacityInfo\n          totalCapacity={5}\n          totalDemand={2}\n        />\n      </FlexItem>\n      <FlexItem>\n        <Flex>\n          <FlexItem>\n            <VehiclesInfo />\n          </FlexItem>\n          <FlexItem>\n            <InputGroup>\n              <Button\n                isDisabled={false}\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <MinusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n              <InputGroupText\n                readOnly={true}\n                style={\n                  Object {\n                    \"minWidth\": \"2.5em\",\n                  }\n                }\n              >\n                8\n              </InputGroupText>\n              <Button\n                data-cy=\"demo-add-vehicle\"\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <PlusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n            </InputGroup>\n          </FlexItem>\n        </Flex>\n      </FlexItem>\n      <FlexItem>\n        <DistanceInfo\n          distance=\"10\"\n        />\n      </FlexItem>\n      <FlexItem>\n        <Button\n          isDisabled={true}\n          onClick={[Function]}\n          style={\n            Object {\n              \"marginBottom\": 16,\n              \"marginLeft\": 16,\n            }\n          }\n        >\n          Export\n        </Button>\n        <Button\n          data-cy=\"demo-clear-button\"\n          isDisabled={true}\n          onClick={[MockFunction]}\n          style={\n            Object {\n              \"marginBottom\": 16,\n              \"marginLeft\": 16,\n            }\n          }\n        >\n          Clear\n        </Button>\n      </FlexItem>\n    </Flex>\n    <RouteMap\n      boundingBox={null}\n      clickHandler={[Function]}\n      depot={\n        Object {\n          \"id\": 1,\n          \"lat\": 1.345678,\n          \"lng\": 1.345678,\n        }\n      }\n      removeHandler={[MockFunction]}\n      routes={\n        Array [\n          Object {\n            \"track\": Array [],\n            \"vehicle\": Object {\n              \"capacity\": 5,\n              \"id\": 1,\n              \"name\": \"v1\",\n            },\n            \"visits\": Array [\n              Object {\n                \"id\": 1,\n                \"lat\": 1.345678,\n                \"lng\": 1.345678,\n              },\n              Object {\n                \"id\": 2,\n                \"lat\": 2.345678,\n                \"lng\": 2.345678,\n              },\n              Object {\n                \"id\": 3,\n                \"lat\": 3.676111,\n                \"lng\": 3.568333,\n              },\n            ],\n          },\n        ]\n      }\n      selectedId={NaN}\n      updateViewport={[MockFunction]}\n      userViewport={\n        Object {\n          \"center\": Array [\n            0,\n            0,\n          ],\n          \"isDirty\": false,\n          \"zoom\": 1,\n        }\n      }\n      visits={\n        Array [\n          Object {\n            \"id\": 2,\n            \"lat\": 2.345678,\n            \"lng\": 2.345678,\n          },\n          Object {\n            \"id\": 3,\n            \"lat\": 3.676111,\n            \"lng\": 3.568333,\n          },\n        ]\n      }\n    />\n  </SplitItem>\n</Split>\n`;\n\nexports[`Demo page should render correctly with a few routes 1`] = `\n<Split\n  hasGutter={true}\n  style={\n    Object {\n      \"overflowY\": \"auto\",\n    }\n  }\n>\n  <SplitItem\n    isFilled={false}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n        \"width\": \"320px\",\n      }\n    }\n  >\n    <Title\n      headingLevel=\"h1\"\n    >\n      Demo\n    </Title>\n    <SearchBox\n      addHandler={[Function]}\n      boundingBox={null}\n      countryCodeSearchFilter={\n        Array [\n          \"XY\",\n        ]\n      }\n      searchDelay={500}\n    />\n    <LocationList\n      depot={\n        Object {\n          \"id\": 1,\n          \"lat\": 1.345678,\n          \"lng\": 1.345678,\n        }\n      }\n      removeHandler={[MockFunction]}\n      selectHandler={[Function]}\n      visits={\n        Array [\n          Object {\n            \"id\": 2,\n            \"lat\": 2.345678,\n            \"lng\": 2.345678,\n          },\n          Object {\n            \"id\": 3,\n            \"lat\": 3.676111,\n            \"lng\": 3.568333,\n          },\n        ]\n      }\n    />\n  </SplitItem>\n  <SplitItem\n    isFilled={true}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n      }\n    }\n  >\n    <Flex\n      justifyContent={\n        Object {\n          \"default\": \"justifyContentSpaceBetween\",\n        }\n      }\n    >\n      <FlexItem>\n        <VisitsInfo\n          visitCount={2}\n        />\n      </FlexItem>\n      <FlexItem>\n        <CapacityInfo\n          totalCapacity={5}\n          totalDemand={2}\n        />\n      </FlexItem>\n      <FlexItem>\n        <Flex>\n          <FlexItem>\n            <VehiclesInfo />\n          </FlexItem>\n          <FlexItem>\n            <InputGroup>\n              <Button\n                isDisabled={false}\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <MinusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n              <InputGroupText\n                readOnly={true}\n                style={\n                  Object {\n                    \"minWidth\": \"2.5em\",\n                  }\n                }\n              >\n                8\n              </InputGroupText>\n              <Button\n                data-cy=\"demo-add-vehicle\"\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <PlusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n            </InputGroup>\n          </FlexItem>\n        </Flex>\n      </FlexItem>\n      <FlexItem>\n        <DistanceInfo\n          distance=\"10\"\n        />\n      </FlexItem>\n      <FlexItem>\n        <Button\n          isDisabled={false}\n          onClick={[Function]}\n          style={\n            Object {\n              \"marginBottom\": 16,\n              \"marginLeft\": 16,\n            }\n          }\n        >\n          Export\n        </Button>\n        <Button\n          data-cy=\"demo-clear-button\"\n          isDisabled={false}\n          onClick={[MockFunction]}\n          style={\n            Object {\n              \"marginBottom\": 16,\n              \"marginLeft\": 16,\n            }\n          }\n        >\n          Clear\n        </Button>\n      </FlexItem>\n    </Flex>\n    <RouteMap\n      boundingBox={null}\n      clickHandler={[Function]}\n      depot={\n        Object {\n          \"id\": 1,\n          \"lat\": 1.345678,\n          \"lng\": 1.345678,\n        }\n      }\n      removeHandler={[MockFunction]}\n      routes={\n        Array [\n          Object {\n            \"track\": Array [],\n            \"vehicle\": Object {\n              \"capacity\": 5,\n              \"id\": 1,\n              \"name\": \"v1\",\n            },\n            \"visits\": Array [\n              Object {\n                \"id\": 1,\n                \"lat\": 1.345678,\n                \"lng\": 1.345678,\n              },\n              Object {\n                \"id\": 2,\n                \"lat\": 2.345678,\n                \"lng\": 2.345678,\n              },\n              Object {\n                \"id\": 3,\n                \"lat\": 3.676111,\n                \"lng\": 3.568333,\n              },\n            ],\n          },\n        ]\n      }\n      selectedId={NaN}\n      updateViewport={[MockFunction]}\n      userViewport={\n        Object {\n          \"center\": Array [\n            0,\n            0,\n          ],\n          \"isDirty\": false,\n          \"zoom\": 1,\n        }\n      }\n      visits={\n        Array [\n          Object {\n            \"id\": 2,\n            \"lat\": 2.345678,\n            \"lng\": 2.345678,\n          },\n          Object {\n            \"id\": 3,\n            \"lat\": 3.676111,\n            \"lng\": 3.568333,\n          },\n        ]\n      }\n    />\n  </SplitItem>\n</Split>\n`;\n\nexports[`Demo page should render correctly with no routes 1`] = `\n<Split\n  hasGutter={true}\n  style={\n    Object {\n      \"overflowY\": \"auto\",\n    }\n  }\n>\n  <SplitItem\n    isFilled={false}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n        \"width\": \"320px\",\n      }\n    }\n  >\n    <Title\n      headingLevel=\"h1\"\n    >\n      Demo\n    </Title>\n    <SearchBox\n      addHandler={[Function]}\n      boundingBox={null}\n      countryCodeSearchFilter={Array []}\n      searchDelay={500}\n    />\n    <LocationList\n      depot={null}\n      removeHandler={[MockFunction]}\n      selectHandler={[Function]}\n      visits={Array []}\n    />\n  </SplitItem>\n  <SplitItem\n    isFilled={true}\n    style={\n      Object {\n        \"display\": \"flex\",\n        \"flexDirection\": \"column\",\n      }\n    }\n  >\n    <Flex\n      justifyContent={\n        Object {\n          \"default\": \"justifyContentSpaceBetween\",\n        }\n      }\n    >\n      <FlexItem>\n        <VisitsInfo\n          visitCount={0}\n        />\n      </FlexItem>\n      <FlexItem>\n        <CapacityInfo\n          totalCapacity={0}\n          totalDemand={0}\n        />\n      </FlexItem>\n      <FlexItem>\n        <Flex>\n          <FlexItem>\n            <VehiclesInfo />\n          </FlexItem>\n          <FlexItem>\n            <InputGroup>\n              <Button\n                isDisabled={true}\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <MinusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n              <InputGroupText\n                readOnly={true}\n                style={\n                  Object {\n                    \"minWidth\": \"2.5em\",\n                  }\n                }\n              >\n                0\n              </InputGroupText>\n              <Button\n                data-cy=\"demo-add-vehicle\"\n                onClick={[Function]}\n                variant=\"primary\"\n              >\n                <PlusIcon\n                  color=\"currentColor\"\n                  noVerticalAlign={false}\n                  size=\"sm\"\n                />\n              </Button>\n            </InputGroup>\n          </FlexItem>\n        </Flex>\n      </FlexItem>\n      <FlexItem>\n        <DistanceInfo\n          distance=\"0\"\n        />\n      </FlexItem>\n      <FlexItem>\n        <Button\n          isDisabled={true}\n          onClick={[Function]}\n          style={\n            Object {\n              \"marginBottom\": 16,\n              \"marginLeft\": 16,\n            }\n          }\n        >\n          Export\n        </Button>\n        <DemoDropdown\n          demos={\n            Array [\n              \"demo\",\n            ]\n          }\n          onSelect={[Function]}\n        />\n      </FlexItem>\n    </Flex>\n    <RouteMap\n      boundingBox={null}\n      clickHandler={[Function]}\n      depot={null}\n      removeHandler={[MockFunction]}\n      routes={Array []}\n      selectedId={NaN}\n      updateViewport={[MockFunction]}\n      userViewport={\n        Object {\n          \"center\": Array [\n            0,\n            0,\n          ],\n          \"isDirty\": false,\n          \"zoom\": 1,\n        }\n      }\n      visits={Array []}\n    />\n  </SplitItem>\n</Split>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/__snapshots__/InfoBlock.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Info block snapshots: capacity 1`] = `\n<InfoBlock\n  color=\"\"\n  content={\n    Object {\n      \"data\": \"20/100\",\n      \"minWidth\": \"4em\",\n    }\n  }\n  icon={[Function]}\n  tooltip=\"Capacity usage: total demand / total capacity\"\n/>\n`;\n\nexports[`Info block snapshots: capacity 2`] = `\n<InfoBlock\n  color=\"var(--pf-global--danger-color--200)\"\n  content={\n    Object {\n      \"data\": \"20/10\",\n      \"minWidth\": \"4em\",\n    }\n  }\n  icon={[Function]}\n  tooltip=\"Capacity usage: total demand / total capacity\"\n/>\n`;\n\nexports[`Info block snapshots: distance 1`] = `\n<InfoBlock\n  content={\n    Object {\n      \"data\": \"3h 56m 11s\",\n      \"minWidth\": \"6.8em\",\n    }\n  }\n  icon={[Function]}\n  tooltip=\"Total driving travel time spent by all vehicles\"\n/>\n`;\n\nexports[`Info block snapshots: generic 1`] = `\n<Tooltip\n  content=\"test tooltip\"\n  position=\"bottom\"\n>\n  <Flex\n    spaceItems={\n      Object {\n        \"default\": \"spaceItemsSm\",\n      }\n    }\n  >\n    <FlexItem>\n      <PlusIcon\n        color=\"currentColor\"\n        noVerticalAlign={false}\n        size=\"md\"\n      />\n    </FlexItem>\n    <FlexItem\n      style={\n        Object {\n          \"minWidth\": \"10\",\n          \"textAlign\": \"right\",\n        }\n      }\n    >\n      test content\n    </FlexItem>\n  </Flex>\n</Tooltip>\n`;\n\nexports[`Info block snapshots: vehicles 1`] = `\n<InfoBlock\n  icon={[Function]}\n  tooltip=\"Vehicles\"\n/>\n`;\n\nexports[`Info block snapshots: visits 1`] = `\n<InfoBlock\n  content={\n    Object {\n      \"data\": 300,\n      \"minWidth\": \"2em\",\n    }\n  }\n  icon={[Function]}\n  tooltip=\"Number of visits\"\n/>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/__snapshots__/Route.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Route page should render correctly with a few routes 1`] = `\n<React.Fragment>\n  <Title\n    headingLevel=\"h1\"\n  >\n    Route\n  </Title>\n  <Split\n    hasGutter={true}\n  >\n    <SplitItem\n      isFilled={false}\n      style={\n        Object {\n          \"display\": \"flex\",\n          \"flexDirection\": \"column\",\n          \"width\": \"320px\",\n        }\n      }\n    >\n      <Form>\n        <FormSelect\n          aria-label=\"FormSelect Input\"\n          className=\"\"\n          isDisabled={false}\n          isIconSprite={false}\n          isRequired={false}\n          onBlur={[Function]}\n          onChange={[Function]}\n          onFocus={[Function]}\n          ouiaSafe={true}\n          style={\n            Object {\n              \"backgroundColor\": \"white\",\n              \"marginBottom\": 10,\n            }\n          }\n          validated=\"default\"\n          value={0}\n        >\n          <FormSelectOption\n            isDisabled={false}\n            label=\"v1\"\n            value={0}\n          />\n          <FormSelectOption\n            isDisabled={false}\n            label=\"v2\"\n            value={1}\n          />\n        </FormSelect>\n      </Form>\n      <LocationList\n        depot={\n          Object {\n            \"id\": 1,\n            \"lat\": 1.345678,\n            \"lng\": 1.345678,\n          }\n        }\n        removeHandler={[MockFunction]}\n        selectHandler={[Function]}\n        visits={\n          Array [\n            Object {\n              \"id\": 1,\n              \"lat\": 1.345678,\n              \"lng\": 1.345678,\n            },\n            Object {\n              \"id\": 2,\n              \"lat\": 2.345678,\n              \"lng\": 2.345678,\n            },\n            Object {\n              \"id\": 3,\n              \"lat\": 3.676111,\n              \"lng\": 3.568333,\n            },\n          ]\n        }\n      />\n    </SplitItem>\n    <SplitItem\n      isFilled={true}\n    >\n      <RouteMap\n        boundingBox={null}\n        clickHandler={[Function]}\n        depot={\n          Object {\n            \"id\": 1,\n            \"lat\": 1.345678,\n            \"lng\": 1.345678,\n          }\n        }\n        removeHandler={[MockFunction]}\n        routes={\n          Array [\n            Object {\n              \"track\": Array [],\n              \"vehicle\": Object {\n                \"capacity\": 5,\n                \"id\": 1,\n                \"name\": \"v1\",\n              },\n              \"visits\": Array [\n                Object {\n                  \"id\": 1,\n                  \"lat\": 1.345678,\n                  \"lng\": 1.345678,\n                },\n                Object {\n                  \"id\": 2,\n                  \"lat\": 2.345678,\n                  \"lng\": 2.345678,\n                },\n                Object {\n                  \"id\": 3,\n                  \"lat\": 3.676111,\n                  \"lng\": 3.568333,\n                },\n              ],\n            },\n            Object {\n              \"track\": Array [],\n              \"visits\": Array [],\n            },\n          ]\n        }\n        selectedId={NaN}\n        updateViewport={[MockFunction]}\n        userViewport={\n          Object {\n            \"center\": Array [\n              0,\n              0,\n            ],\n            \"isDirty\": false,\n            \"zoom\": 1,\n          }\n        }\n        visits={\n          Array [\n            Object {\n              \"id\": 2,\n              \"lat\": 2.345678,\n              \"lng\": 2.345678,\n            },\n            Object {\n              \"id\": 3,\n              \"lat\": 3.676111,\n              \"lng\": 3.568333,\n            },\n            Object {\n              \"id\": 4,\n              \"lat\": 4.345678,\n              \"lng\": 4.345678,\n            },\n            Object {\n              \"id\": 5,\n              \"lat\": 5.345678,\n              \"lng\": 5.345678,\n            },\n          ]\n        }\n      />\n    </SplitItem>\n  </Split>\n</React.Fragment>\n`;\n\nexports[`Route page should render correctly with no routes 1`] = `\n<React.Fragment>\n  <Title\n    headingLevel=\"h1\"\n  >\n    Route\n  </Title>\n  <Split\n    hasGutter={true}\n  >\n    <SplitItem\n      isFilled={false}\n      style={\n        Object {\n          \"display\": \"flex\",\n          \"flexDirection\": \"column\",\n          \"width\": \"320px\",\n        }\n      }\n    >\n      <Form>\n        <FormSelect\n          aria-label=\"FormSelect Input\"\n          className=\"\"\n          isDisabled={true}\n          isIconSprite={false}\n          isRequired={false}\n          onBlur={[Function]}\n          onChange={[Function]}\n          onFocus={[Function]}\n          ouiaSafe={true}\n          style={\n            Object {\n              \"backgroundColor\": \"white\",\n              \"marginBottom\": 10,\n            }\n          }\n          validated=\"default\"\n          value={0}\n        />\n      </Form>\n      <LocationList\n        depot={null}\n        removeHandler={[MockFunction]}\n        selectHandler={[Function]}\n        visits={Array []}\n      />\n    </SplitItem>\n    <SplitItem\n      isFilled={true}\n    >\n      <RouteMap\n        boundingBox={null}\n        clickHandler={[Function]}\n        depot={null}\n        removeHandler={[MockFunction]}\n        routes={Array []}\n        selectedId={NaN}\n        updateViewport={[MockFunction]}\n        userViewport={\n          Object {\n            \"center\": Array [\n              0,\n              0,\n            ],\n            \"isDirty\": false,\n            \"zoom\": 1,\n          }\n        }\n        visits={Array []}\n      />\n    </SplitItem>\n  </Split>\n</React.Fragment>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/__snapshots__/Vehicles.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Vehicles page should render correctly 1`] = `\n<React.Fragment>\n  <Split\n    hasGutter={true}\n    style={\n      Object {\n        \"overflowY\": \"auto\",\n      }\n    }\n  >\n    <SplitItem\n      isFilled={true}\n    >\n      <Title\n        headingLevel=\"h1\"\n      >\n        Vehicles (2)\n      </Title>\n    </SplitItem>\n    <SplitItem\n      isFilled={false}\n    >\n      <Button\n        onClick={[MockFunction]}\n        style={\n          Object {\n            \"marginBottom\": 16,\n            \"marginLeft\": 16,\n          }\n        }\n      >\n        Add\n      </Button>\n    </SplitItem>\n  </Split>\n  <div\n    style={\n      Object {\n        \"overflowY\": \"auto\",\n      }\n    }\n  >\n    <DataList\n      aria-label=\"List of vehicles\"\n      className=\"\"\n      gridBreakpoint=\"md\"\n      isCompact={false}\n      selectedDataListItemId=\"\"\n      wrapModifier={null}\n    >\n      <Vehicle\n        capacity={5}\n        capacityChangeHandler={[Function]}\n        description=\"Vehicle 1\"\n        id={1}\n        removeHandler={[MockFunction]}\n      />\n      <Vehicle\n        capacity={5}\n        capacityChangeHandler={[Function]}\n        description=\"Vehicle 2\"\n        id={2}\n        removeHandler={[MockFunction]}\n      />\n    </DataList>\n  </div>\n</React.Fragment>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/__snapshots__/Visits.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Visits page should render correctly with a few visits 1`] = `\n<React.Fragment>\n  <Title\n    headingLevel=\"h1\"\n  >\n    Visits (2)\n  </Title>\n  <LocationList\n    depot={\n      Object {\n        \"id\": 1,\n        \"lat\": 1.345678,\n        \"lng\": 1.345678,\n      }\n    }\n    removeHandler={[MockFunction]}\n    selectHandler={[Function]}\n    visits={\n      Array [\n        Object {\n          \"id\": 2,\n          \"lat\": 2.345678,\n          \"lng\": 2.345678,\n        },\n        Object {\n          \"id\": 3,\n          \"lat\": 3.676111,\n          \"lng\": 3.568333,\n        },\n      ]\n    }\n  />\n</React.Fragment>\n`;\n\nexports[`Visits page should render correctly with no visits 1`] = `\n<React.Fragment>\n  <Title\n    headingLevel=\"h1\"\n  >\n    Visits (0)\n  </Title>\n  <LocationList\n    depot={null}\n    removeHandler={[MockFunction]}\n    selectHandler={[Function]}\n    visits={Array []}\n  />\n</React.Fragment>\n`;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/common.ts",
    "content": "import * as React from 'react';\n\nexport const sideBarStyle: React.CSSProperties = {\n  display: 'flex',\n  flexDirection: 'column',\n  width: '320px',\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/pages/index.ts",
    "content": "import ConnectedDemo from './Demo';\nimport ConnectedRoute from './Route';\nimport ConnectedVehicles from './Vehicles';\nimport ConnectedVisits from './Visits';\n\nexport {\n  ConnectedDemo as Demo,\n  ConnectedVehicles as Vehicles,\n  ConnectedVisits as Visits,\n  ConnectedRoute as Route,\n};\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/ui/shallow-test-util.ts",
    "content": "import { ReactElement } from 'react';\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport { createRenderer } from 'react-test-renderer/shallow';\n\nexport const shallow = (e: ReactElement): ReactElement => {\n  const shallowRenderer = createRenderer();\n  shallowRenderer.render(e);\n  return shallowRenderer.getRenderOutput();\n};\n\nexport const toJson = (e: ReactElement): ReactElement => e;\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/websocket/WebSocketClient.test.ts",
    "content": "import { sources } from 'eventsourcemock';\nimport fetchMock from 'fetch-mock-jest';\nimport { LatLngWithDescription } from 'store/route/types';\nimport WebSocketClient from './WebSocketClient';\n\nbeforeEach(() => {\n  // Learn about fetch-mock: http://www.wheresrhys.co.uk/fetch-mock/.\n  fetchMock.reset();\n});\n\ndescribe('WebSocketClient', () => {\n  const url = 'http://test.url:123/my-endpoint';\n  const onSuccess = jest.fn();\n  const onError = jest.fn();\n\n  const connectClient = () => {\n    const client = new WebSocketClient(url);\n    client.connect(onSuccess, onError);\n    const source = sources[`${url}/events`];\n    source.emitOpen();\n    return { client, source };\n  };\n\n  it('Error callback should be called on EventSource error event', () => {\n    const { source } = connectClient();\n    source.onerror();\n    expect(onError).toBeCalled();\n    expect(source);\n  });\n\n  it('Success callback should be called on EventSource open event', () => {\n    const { source } = connectClient();\n    source.onopen();\n    expect(onSuccess).toBeCalled();\n  });\n\n  it('addLocation() should send location', () => {\n    const location: LatLngWithDescription = {\n      lat: 1,\n      lng: 2,\n      description: 'test',\n    };\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n\n    client.addLocation(location);\n\n    expect(fetchMock).toHaveLastFetched(`${url}/location`, { body: location });\n  });\n\n  it('deleteLocation() should send location ID', () => {\n    const locationId = 21;\n    fetchMock.deleteOnce('*', 200);\n    const { client } = connectClient();\n\n    client.deleteLocation(locationId);\n\n    expect(fetchMock).toHaveLastFetched(`${url}/location/${locationId}`);\n  });\n\n  it('addVehicle() should add vehicle', () => {\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n\n    client.addVehicle();\n\n    expect(fetchMock).toHaveLastFetched(`${url}/vehicle`);\n  });\n\n  it('deleteVehicle() should send vehicle ID', () => {\n    const vehicleId = 34;\n    fetchMock.deleteOnce('*', 200);\n    const { client } = connectClient();\n\n    client.deleteVehicle(vehicleId);\n\n    expect(fetchMock).toHaveLastFetched(`${url}/vehicle/${vehicleId}`);\n  });\n\n  it('deleteAnyVehicle() should send message to the correct destination', () => {\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n\n    client.deleteAnyVehicle();\n\n    expect(fetchMock).toHaveLastFetched(`${url}/vehicle/deleteAny`);\n  });\n\n  it('changeVehicleCapacity() should change capacity', () => {\n    const vehicleId = 7;\n    const capacity = 54;\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n\n    client.changeVehicleCapacity(vehicleId, capacity);\n\n    expect(fetchMock).toHaveLastFetched(`${url}/vehicle/${vehicleId}/capacity`, {\n      body: capacity as unknown as undefined,\n    });\n  });\n\n  it('loadDemo() should send demo name', () => {\n    const demo = 'Test demo';\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n\n    client.loadDemo(demo);\n\n    expect(fetchMock).toHaveLastFetched(`${url}/demo/${demo}`);\n  });\n\n  it('clear() should call clear endpoint', () => {\n    fetchMock.postOnce('*', 200);\n    const { client } = connectClient();\n    client.clear();\n    expect(fetchMock).toHaveLastFetched(`${url}/clear`);\n  });\n\n  it('subscribeToServerInfo() should subscribe with callback', async () => {\n    const callback = jest.fn();\n    const payload = { value: 'test' };\n    fetchMock.getOnce(`${url}/serverInfo`, {\n      status: 200,\n      body: JSON.stringify(payload),\n    });\n    const { client } = connectClient();\n\n    await client.subscribeToServerInfo(callback);\n\n    expect(fetchMock).toBeDone();\n    expect(callback).toHaveBeenCalledWith(payload);\n  });\n\n  it('subscribeToRoute() should subscribe with callback', () => {\n    const callback = jest.fn();\n    const payload = { msg: 'test' };\n    const messageEvent = new MessageEvent('route', {\n      data: JSON.stringify(payload),\n    });\n    const { client, source } = connectClient();\n\n    client.subscribeToRoute(callback);\n\n    source.emit(messageEvent.type, messageEvent);\n\n    expect(callback).toHaveBeenCalledWith(payload);\n  });\n\n  it('subscribeToErrorTopic() should subscribe with callback', () => {\n    const callback = jest.fn();\n    const payload = { msg: 'test' };\n    const messageEvent = new MessageEvent('errorMessage', {\n      data: JSON.stringify(payload),\n    });\n    const { client, source } = connectClient();\n\n    client.subscribeToErrorTopic(callback);\n\n    source.emit(messageEvent.type, messageEvent);\n\n    expect(callback).toHaveBeenCalledWith(payload);\n  });\n});\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/src/websocket/WebSocketClient.ts",
    "content": "import { MessagePayload } from 'store/message/types';\nimport { LatLngWithDescription, RoutingPlan } from 'store/route/types';\nimport { ServerInfo } from 'store/server/types';\n\nexport default class WebSocketClient {\n  readonly backendUrl: string;\n\n  eventSource: EventSource | null;\n\n  constructor(backendUrl: string) {\n    this.backendUrl = backendUrl;\n    this.eventSource = null;\n  }\n\n  connect(successCallback: () => void, errorCallback: (err: Event) => void): void {\n    if (this.eventSource === null) {\n      this.eventSource = new EventSource(`${this.backendUrl}/events`);\n      this.eventSource.onopen = successCallback;\n      this.eventSource.onerror = (event) => {\n        // Each time a connection error happens...\n        if (this.eventSource) {\n          // ...close the eventSource...\n          this.eventSource.close();\n          this.eventSource = null;\n        }\n        // ...and invoke the errorCallback, which dispatches a single connectClient thunk action.\n        // That forms an infinite loop, so the connection will be re-attempted until it succeeds.\n        errorCallback((event));\n      };\n    }\n  }\n\n  addLocation(latLng: LatLngWithDescription): Promise<Response> {\n    return fetch(`${this.backendUrl}/location`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(latLng),\n    });\n  }\n\n  addVehicle(): Promise<Response> {\n    return fetch(`${this.backendUrl}/vehicle`, { method: 'POST' });\n  }\n\n  loadDemo(name: string): Promise<Response> {\n    return fetch(`${this.backendUrl}/demo/${name}`, { method: 'POST' });\n  }\n\n  deleteLocation(locationId: number): Promise<Response> {\n    // TODO error callback\n    return fetch(`${this.backendUrl}/location/${locationId}`, { method: 'DELETE' });\n  }\n\n  deleteAnyVehicle(): Promise<Response> {\n    return fetch(`${this.backendUrl}/vehicle/deleteAny`, { method: 'POST' });\n  }\n\n  deleteVehicle(vehicleId: number): Promise<Response> {\n    return fetch(`${this.backendUrl}/vehicle/${vehicleId}`, { method: 'DELETE' });\n  }\n\n  changeVehicleCapacity(vehicleId: number, capacity: number): Promise<Response> {\n    return fetch(`${this.backendUrl}/vehicle/${vehicleId}/capacity`, {\n      method: 'POST',\n      body: JSON.stringify(capacity),\n    });\n  }\n\n  clear(): Promise<Response> {\n    return fetch(`${this.backendUrl}/clear`, { method: 'POST' });\n  }\n\n  subscribeToServerInfo(subscriptionCallback: (serverInfo: ServerInfo) => void): Promise<void> {\n    return fetch(`${this.backendUrl}/serverInfo`)\n      .then((response) => response.json())\n      .then((data) => subscriptionCallback(data));\n  }\n\n  subscribeToRoute(subscriptionCallback: (plan: RoutingPlan) => void): void {\n    if (this.eventSource !== null) {\n      this.eventSource.addEventListener('route', (event: MessageEvent) => {\n        subscriptionCallback(JSON.parse(event.data));\n      });\n    }\n  }\n\n  subscribeToErrorTopic(subscriptionCallback: (errorMessage: MessagePayload) => void): void {\n    if (this.eventSource !== null) {\n      this.eventSource.addEventListener('errorMessage', (event: MessageEvent) => {\n        subscriptionCallback(JSON.parse(event.data));\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-frontend/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"src\",\n    \"outDir\": \"build/dist\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"jsx\": \"react-jsx\",\n    \"allowJs\": true,\n    \"strict\": true,\n    \"strictFunctionTypes\": false,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"skipLibCheck\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\n    \"cypress\",\n    \"src\"\n  ],\n  \"exclude\": [\n    \"build\",\n    \"coverage\",\n    \"local\",\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/.gitignore",
    "content": "/target\n/local\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/data/openstreetmap/CREDITS.adoc",
    "content": "All `.osm.pbf` files in this folder contain data\nlink:https://www.openstreetmap.org/copyright[copyrighted by OpenStreetMap contributors]\nand licensed under\nlink:https://opendatacommons.org/licenses/odbl/[Open Database License (ODbL)].\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaweb.vehiclerouting</groupId>\n    <artifactId>optaweb-vehicle-routing</artifactId>\n    <version>8.35.0.Final</version>\n  </parent>\n\n  <artifactId>optaweb-vehicle-routing-standalone</artifactId>\n  <packaging>jar</packaging>\n\n  <name>OptaWeb Vehicle Routing Standalone</name>\n\n  <properties>\n    <application.host>localhost</application.host>\n    <application.port>8180</application.port>\n    <application.url>http://${application.host}:${application.port}</application.url>\n    <!-- Override to run with Podman. -->\n    <container.runtime>docker</container.runtime>\n    <frontend.project.name>optaweb-vehicle-routing-frontend</frontend.project.name>\n    <java.module.name>org.optaweb.vehiclerouting</java.module.name>\n    <version.cypress.docker>7.0.1</version.cypress.docker>\n  </properties>\n\n  <dependencies>\n    <!-- Only to make sure this module is built after backend and frontend. -->\n    <dependency>\n      <groupId>org.optaweb.vehiclerouting</groupId>\n      <artifactId>optaweb-vehicle-routing-backend</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.optaweb.vehiclerouting</groupId>\n      <artifactId>optaweb-vehicle-routing-frontend</artifactId>\n      <type>war</type>\n    </dependency>\n    <dependency>\n      <groupId>io.quarkus</groupId>\n      <artifactId>quarkus-undertow</artifactId>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>unpack-frontend</id>\n            <phase>prepare-package</phase>\n            <goals>\n              <goal>unpack</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>org.optaweb.vehiclerouting</groupId>\n                  <artifactId>optaweb-vehicle-routing-frontend</artifactId>\n                  <type>war</type>\n                  <outputDirectory>${project.build.outputDirectory}/META-INF/resources</outputDirectory>\n                </artifactItem>\n              </artifactItems>\n              <excludes>META-INF/**,WEB-INF/**</excludes>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>io.quarkus</groupId>\n        <artifactId>quarkus-maven-plugin</artifactId>\n        <version>${version.io.quarkus}</version>\n        <extensions>true</extensions>\n        <executions>\n          <execution>\n            <goals>\n              <goal>build</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>package-quarkus-app</id>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n            <configuration>\n              <descriptors>\n                <descriptor>src/main/assembly/assembly-quarkus-app.xml</descriptor>\n              </descriptors>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin> <!-- Start standalone JAR as a separate process. -->\n        <groupId>com.bazaarvoice.maven.plugins</groupId>\n        <artifactId>process-exec-maven-plugin</artifactId>\n        <version>0.9</version>\n        <executions>\n          <execution>\n            <id>start-application</id>\n            <phase>pre-integration-test</phase>\n            <goals>\n              <goal>start</goal>\n            </goals>\n            <configuration>\n              <name>Run application</name>\n              <healthcheckUrl>${application.url}</healthcheckUrl>\n              <workingDir>${project.basedir}</workingDir>\n              <processLogFile>${project.build.directory}/${project.artifactId}.log</processLogFile>\n              <arguments>\n                <argument>java</argument>\n                <argument>-Dquarkus.profile=cypress</argument>\n                <argument>-Dquarkus.http.host=${application.host}</argument>\n                <argument>-Dquarkus.http.port=${application.port}</argument>\n                <argument>-jar</argument>\n                <argument>target/quarkus-app/quarkus-run.jar</argument>\n              </arguments>\n            </configuration>\n          </execution>\n          <!-- Kill all running processes. -->\n          <execution>\n            <id>stop-running-processes</id>\n            <phase>post-integration-test</phase>\n            <goals>\n              <goal>stop-all</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <!--\n        Run Cypress tests in a docker container.\n        See https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command.\n      -->\n      <plugin>\n        <artifactId>exec-maven-plugin</artifactId>\n        <groupId>org.codehaus.mojo</groupId>\n        <executions>\n          <execution>\n            <id>run-cypress-tests</id>\n            <phase>integration-test</phase>\n            <goals>\n              <goal>exec</goal>\n            </goals>\n            <configuration>\n              <executable>${container.runtime}</executable>\n              <workingDirectory>${project.parent.basedir}/${frontend.project.name}</workingDirectory>\n              <arguments>\n                <argument>run</argument>\n                <argument>--network=host</argument> <!-- Cypress accesses UI running on the host. -->\n                <argument>--volume</argument>\n                <argument>${project.parent.basedir}/${frontend.project.name}:/e2e:Z</argument>\n                <argument>--workdir</argument>\n                <argument>/e2e</argument>\n                <argument>${user.flag}</argument>\n                <argument>${user.name.group}</argument>\n                <argument>--entrypoint</argument>\n                <argument>cypress</argument>\n                <argument>docker.io/cypress/included:${version.cypress.docker}</argument>\n                <argument>run</argument> <!-- Executing cypress:run. -->\n                <argument>--project</argument>\n                <argument>.</argument>\n                <argument>--config</argument>\n                <argument>baseUrl=${application.url}</argument>\n              </arguments>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <profiles>\n    <profile>\n      <!--\n        Turn off integration tests unless -Dintegration-tests=true.\n        Goal: do not run integration tests by default but be able to turn them on.\n      -->\n      <id>skip-integration-tests</id>\n      <activation>\n        <property>\n          <name>integration-tests</name>\n          <value>!true</value>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.bazaarvoice.maven.plugins</groupId>\n            <artifactId>process-exec-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>start-application</id>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <artifactId>exec-maven-plugin</artifactId>\n            <groupId>org.codehaus.mojo</groupId>\n            <executions>\n              <execution>\n                <id>run-cypress-tests</id>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <!--\n        Skip integration tests execution when the build runs on GitHub Actions and uses a Windows runner.\n        See https://github.community/t/github-actions-windows-runtime-linux-container-mode-docker/135874/4.\n        This profile allows to skip integration tests even if -Dintegration-tests=true.\n      -->\n      <id>no-docker</id>\n      <activation>\n        <property>\n          <name>env.GITHUB_ACTIONS</name>\n          <value>true</value>\n        </property>\n        <os>\n          <family>Windows</family>\n        </os>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.bazaarvoice.maven.plugins</groupId>\n            <artifactId>process-exec-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>start-application</id>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <artifactId>exec-maven-plugin</artifactId>\n            <groupId>org.codehaus.mojo</groupId>\n            <executions>\n              <execution>\n                <id>run-cypress-tests</id>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>docker</id>\n      <activation>\n        <property>\n          <name>container.runtime</name>\n          <value>docker</value>\n        </property>\n      </activation>\n      <properties>\n        <user.flag>--user</user.flag>\n        <user.name.group>1000:1000</user.name.group>\n      </properties>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/src/main/assembly/assembly-quarkus-app.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<assembly xmlns=\"http://maven.apache.org/ASSEMBLY/2.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xsi:schemaLocation=\"http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd\">\n\n  <id>quarkus-app</id>\n  <formats>\n    <format>zip</format>\n  </formats>\n\n  <includeBaseDirectory>true</includeBaseDirectory>\n\n  <fileSets>\n    <fileSet>\n      <directory>target/quarkus-app</directory>\n      <outputDirectory/>\n    </fileSet>\n  </fileSets>\n\n</assembly>\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/src/main/resources/META-INF/undertow-handlers.conf",
    "content": "path-prefix(/api/) -> done\nregex('/\\\\w+') -> rewrite(/)\n"
  },
  {
    "path": "optaweb-vehicle-routing-standalone/src/main/resources/application.properties",
    "content": "# App configuration\napp.demo.data-set-dir=local/dataset\napp.routing.gh-dir=local/graphhopper\napp.routing.osm-dir=local/openstreetmap\napp.routing.engine=GRAPHHOPPER\napp.routing.osm-file=belgium-latest.osm.pbf\napp.region.country-codes=BE\n\n# OptaPlanner\nquarkus.optaplanner.solver.daemon=true\nquarkus.optaplanner.solver.termination.spent-limit=30s\n\n# Datasource\nquarkus.datasource.db-kind=h2\nquarkus.datasource.jdbc.url=jdbc:h2:file:${app.persistence.h2-dir:../local/db}/${app.persistence.h2-filename:optaweb_vrp_database};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false\nquarkus.datasource.username=sa\nquarkus.datasource.password=\nquarkus.hibernate-orm.database.generation=update\n\n%postgresql.quarkus.datasource.db-kind=postgresql\n%postgresql.quarkus.datasource.jdbc.url=jdbc:postgresql://${DATABASE_HOST:postgresql}:5432/${DATABASE_NAME:optaweb_vrp_database}\n%postgresql.quarkus.datasource.username=${DATABASE_USER}\n%postgresql.quarkus.datasource.password=${DATABASE_PASSWORD}\n%postgresql.quarkus.hibernate-orm.database.generation=update\n\n%cypress.app.region.country-codes=DE\n%cypress.app.routing.gh-dir=target/graphhopper\n%cypress.app.routing.osm-dir=data/openstreetmap\n%cypress.app.routing.osm-file=planet_12.032,53.0171_12.1024,53.0491.osm.pbf\n%cypress.quarkus.datasource.jdbc.url=jdbc:h2:mem:vehicle-routing-test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE\n\n# Quarkus configuration\n# Use fast-jar packaging (https://quarkus.io/guides/maven-tooling#using-fast-jar).\nquarkus.package.type=fast-jar\n# Enable CORS filter (https://quarkus.io/guides/http-reference#cors-filter).\nquarkus.http.cors=true\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>org.optaplanner</groupId>\n    <artifactId>optaplanner-build-parent</artifactId>\n    <version>8.35.0.Final</version>\n    <relativePath/>\n  </parent>\n\n  <groupId>org.optaweb.vehiclerouting</groupId>\n  <artifactId>optaweb-vehicle-routing</artifactId>\n  <packaging>pom</packaging>\n\n  <name>OptaWeb Vehicle Routing</name>\n\n  <!-- Modules are sorted in preferred build order. -->\n  <modules>\n    <module>optaweb-vehicle-routing-backend</module>\n    <module>optaweb-vehicle-routing-frontend</module>\n    <module>optaweb-vehicle-routing-standalone</module>\n    <module>optaweb-vehicle-routing-docs</module>\n    <module>optaweb-vehicle-routing-distribution</module>\n  </modules>\n\n  <properties>\n    <version.com.graphhopper>6.0</version.com.graphhopper>\n    <version.com.neovisionaries>1.27</version.com.neovisionaries>\n    <version.frontend-maven-plugin>1.12.1</version.frontend-maven-plugin>\n    <version.node>v16.2.0</version.node>\n    <version.npm>7.15.1</version.npm>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <!-- Internal dependencies -->\n      <dependency>\n        <groupId>org.optaweb.vehiclerouting</groupId>\n        <artifactId>optaweb-vehicle-routing-backend</artifactId>\n        <version>${project.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.optaweb.vehiclerouting</groupId>\n        <artifactId>optaweb-vehicle-routing-docs</artifactId>\n        <version>${project.version}</version>\n        <type>zip</type>\n      </dependency>\n      <dependency>\n        <groupId>org.optaweb.vehiclerouting</groupId>\n        <artifactId>optaweb-vehicle-routing-frontend</artifactId>\n        <version>${project.version}</version>\n        <type>war</type>\n      </dependency>\n      <dependency>\n        <groupId>org.optaweb.vehiclerouting</groupId>\n        <artifactId>optaweb-vehicle-routing-standalone</artifactId>\n        <version>${project.version}</version>\n        <classifier>quarkus-app</classifier>\n        <type>zip</type>\n      </dependency>\n      <!-- BOM imports -->\n      <dependency>\n        <groupId>io.quarkus</groupId>\n        <artifactId>quarkus-bom</artifactId>\n        <version>${version.io.quarkus}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <!-- Dependencies not managed by optaplanner-build-parent and version overrides -->\n      <dependency>\n        <groupId>com.graphhopper</groupId>\n        <artifactId>graphhopper-core</artifactId>\n        <version>${version.com.graphhopper}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.neovisionaries</groupId>\n        <artifactId>nv-i18n</artifactId>\n        <version>${version.com.neovisionaries}</version>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-dependency-plugin</artifactId>\n          <executions>\n            <execution>\n              <id>analyze-only</id>\n              <phase>none</phase>\n            </execution>\n          </executions>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n\n  <repositories>\n    <!-- Bootstrap repository to locate the parent POM when it has not been built locally. -->\n    <repository>\n      <id>jboss-public-repository-group</id>\n      <name>JBoss Public Repository Group</name>\n      <url>https://repository.jboss.org/nexus/content/groups/public/</url>\n      <layout>default</layout>\n      <releases>\n        <enabled>true</enabled>\n        <updatePolicy>never</updatePolicy>\n      </releases>\n      <snapshots>\n        <enabled>true</enabled>\n        <updatePolicy>daily</updatePolicy>\n      </snapshots>\n    </repository>\n  </repositories>\n</project>\n"
  },
  {
    "path": "runLocally.sh",
    "content": "#!/usr/bin/env bash\n\n# Abort the script if any simple command outside an if, while, &&, ||, etc. exits with a non-zero status.\nset -e\n\n# If dir is empty, dir/* will expand to \"\" instead of \"dir/*\". This is useful when reading regions in interactive() and\n# either openstreetmap or graphhopper dir is empty.\nshopt -s nullglob\n\nfunction confirm() {\n  declare answer\n  read -r -p \"$1 [y/N]: \" answer\n  [[ \"$answer\" == \"y\" ]]\n}\n\nfunction abort() {\n  echo \"Aborted.\"\n  exit 0\n}\n\nfunction standalone_jar_or_maven() {\n  local -r standalone=optaweb-vehicle-routing-standalone\n\n  # BEGIN: Distribution use case\n  #\n  # We're running a copy of the script in the project root that has been moved to distribution's bin directory during\n  # distribution assembly. The only difference is that the standalone JAR is in the same directory as the script (bin)\n  # and project.version is set using resource filtering during assembly.\n\n  # shellcheck disable=SC2154 #(project.version variable is not declared)\n  if [[ ! -f pom.xml && -f ${standalone}-${project.version}/quarkus-run.jar ]]\n  then\n    readonly jar=${standalone}-${project.version}/quarkus-run.jar\n    return 0\n  fi\n  # END: Distribution use case\n\n  readonly jar=${standalone}/target/quarkus-app/quarkus-run.jar\n\n  if [[ ! -f ${jar} ]]\n  then\n    confirm \"Jarfile '$jar' does not exist. Run Maven build now?\" || abort\n    if ! ./mvnw clean install -DskipTests\n    then\n      echo >&2 \"Maven build failed. Aborting the script.\"\n      exit 1\n    fi\n  fi\n}\n\nfunction validate() {\n  local -r osm_file_path=${osm_dir}/${osm_file}\n  local -r gh_graph_path=${gh_dir}/${osm_file%.osm.pbf}\n  [[ -f \"$osm_file_path\" || -d \"$gh_graph_path\" ]]\n}\n\nfunction run_optaweb() {\n  declare -a args\n  args+=(\"-Dapp.demo.data-set-dir=$dataset_dir\")\n  args+=(\"-Dapp.persistence.h2-dir=$vrp_dir/db\")\n  args+=(\"-Dapp.persistence.h2-filename=${osm_file%.osm.pbf}\")\n  args+=(\"-Dapp.routing.engine=$routing_engine\")\n  if [[ ${routing_engine} == \"GRAPHHOPPER\" ]]\n  then\n    args+=(\"-Dapp.routing.osm-dir=$osm_dir\")\n    args+=(\"-Dapp.routing.gh-dir=$gh_dir\")\n    args+=(\"-Dapp.routing.osm-file=$osm_file\")\n  fi\n  [[ ${cc_list} != \"??\" ]] && args+=(\"-Dapp.region.country-codes=$cc_list\")\n  java \"${args[@]}\" \"$@\" -jar \"$jar\"\n}\n\nfunction download() {\n  echo \"Downloading $1...\"\n  curl -L \"$1\" -o \"$2\"\n  echo\n  echo \"Created $2.\"\n}\n\nfunction country_code() {\n  local -r region=${1%.osm.pbf}\n  local -r cc_file=${cc_dir}/${region}\n  local -r cc_tag=\"nv-i18n-1.27\"\n  local -r cc_java=\"$cache_dir/CountryCode-$cc_tag.java\"\n\n  # If an error has occurred in the list_downloads loop, mark this region's code as \"unknown\".\n  [[ $2 == \"ERROR\" ]] && echo \"??\" > \"$cc_file\"\n\n  if [[ (! -f ${cc_java} || -f ${cc_java}.err) && $2 != \"ERROR\" ]]\n  then\n    if curl 2>>\"$cc_java.err\" > \"$cc_java\" --silent --show-error \\\nhttps://raw.githubusercontent.com/TakahikoKawasaki/nv-i18n/${cc_tag}/src/main/java/com/neovisionaries/i18n/CountryCode.java\n    then\n      rm \"$cc_java.err\"\n    else\n      # mark this region's code as \"unknown\"\n      [[ ! -f ${cc_file} ]] && echo \"??\" > \"$cc_file\"\n      # and report error\n      return 1\n    fi\n  fi\n\n  # If this loop instance doesn't have an error and cc_file doesn't exist yet or its content is \"unknown\".\n  if [[ $2 != ERROR && ( (! -f ${cc_file}) || $(cat \"$cc_file\") == \"??\" ) ]]\n  then\n    local region_search=${region%-latest}\n    region_search=${region_search//-/ }\n\n    cc=$(grep -i \"$region_search.*OFFICIALLY_ASSIGNED\" \"$cc_java\" | sed 's/ *\\(..\\).*/\\1/')\n\n    echo \"$cc\" > \"$cc_file\"\n  fi\n}\n\nfunction download_menu() {\n  local -r url=$1\n  local -r url_parent=${url%/*} # remove shortest suffix matching \"/*\" => https://download.geofabrik.de/north-america/us\n  local -r region_filename=${url##*/} # index.html, europe.html, etc.\n  local -r region_file_html=\"$cache_geofabrik/$region_filename\"\n  local -r region_file_csv=${region_file_html/.html/.csv}\n  local -r region_osm_url=$2\n\n  # TODO refresh daily\n  if [[ ! -f ${region_file_html} || ! -s ${region_file_html} ]]\n  then\n    curl --silent --show-error 2>>\"$cache_geofabrik/error.log\" \"$url\" > \"$region_file_html\" || {\n      echo \"ERROR: Cannot download from Geofabrik. Are you offline?\"\n      exit 1\n    }\n  fi\n\n  # The following AWK program subregion information from a Geofabrik region HTML page.\n  #\n  # If Geofabrik offers subregions for the current region, the region HTML page contains a subregion table. The program\n  # goes over all subregion rows and extracts the following data:\n  # 1. subregion name (example: Europe),\n  # 2. subregion page link (example: europe.html),\n  # 3. subregion OSM link (example: europe-latest.osm.pbf),\n  # 4. subregion OSM size (example: 23.1 GB).\n  # Finally, the program prints the data in a format that makes it possible to read the data into a Bash array\n  # for further manipulation by the run script.\n  #\n  # Maintenance notes:\n  # An important requirement for the following implementation is that it works on macOS as well as on Linux.\n  # Use https://www.gnu.org/software/gawk/manual/gawk.html as a reference for the AWK language but note\n  # that it is a documentation for the GNU Awk (gawk) implementation that has some extra features (for example gensub())\n  # that are not available in awk found on macOS.\n  #\n  # DO NOT MODIFY OR SIMPLIFY THIS WITHOUT VERIFYING IT WORKS ON MACOS!\n  awk '\n    function href(element) {\n      match(element, /href=\"[^\"]*\"/)\n      return substr(element, RSTART+6, RLENGTH-7)\n    }\n    function text(element,    inner_text) {\n      match(element, />[^<]+</)\n      inner_text=substr(element, RSTART+1, RLENGTH-2)\n      sub(/&nbsp;/, \" \", inner_text)\n      return inner_text\n    }\n    function join(array, sep,    i, result) {\n      for (i = 0; i < NR; i++) {\n        if (array[i]) {\n          result = result ? result sep array[i] : array[i]\n        }\n      }\n      return result\n    }\n    BEGIN {\n      RS=\"<tr\" # Set record delimiter.\n      FS=\"<td\" # Set field delimiter.\n    }\n    /onMouseOver/ {\n      name[NR]=text($2)\n      sub_href[NR]=href($2)\n      osm_href[NR]=href($3)\n      size[NR]=text($4)\n      # Remove parentheses around the OSM size.\n      sub(/^\\(/, \"\", size[NR])\n      sub(/\\)$/, \"\", size[NR])\n    }\n    END {\n      print join(name, \";\")\n      print join(sub_href, \";\")\n      print join(osm_href, \";\")\n      print join(size, \";\")\n    }\n' \"$region_file_html\" > \"$region_file_csv\"\n\n  # read returns `false` here. Adding `|| true` allows the program to continue even with `set -e`.\n  IFS=$'\\n' read -d '' -r -a csv_lines < \"$region_file_csv\" || true\n  IFS=';' read -r -a region_names <<< \"${csv_lines[0]}\"\n  IFS=';' read -r -a region_sub_hrefs <<< \"${csv_lines[1]}\"\n  IFS=';' read -r -a region_osm_hrefs <<< \"${csv_lines[2]}\"\n  IFS=';' read -r -a region_sizes <<< \"${csv_lines[3]}\"\n\n  # Make the array empty if it contains just 1 empty element.\n  [[ ${#region_names[*]} == 1 && -z ${region_names[0]} ]] && region_names=()\n\n  local -r max=$((${#region_names[*]} - 1))\n\n  if [[ ${max} -lt 0 ]]\n  then\n    echo\n    echo \"This region has no subregions to choose from.\"\n    echo\n    confirm \"Do you want to download $region_osm_url?\" && download \"$region_osm_url\" \"$osm_dir/${region_osm_url##*/}\"\n    return 0\n  fi\n\n  declare answer_region_id\n  declare answer_action\n\n  local -r format=\" %2s %-30s %10s\\n\"\n  local -r width=46\n\n  while true\n  do\n    echo\n    # shellcheck disable=SC2059\n    printf \"$format\" \"#\" \"REGION\" \"SIZE\"\n    printf \"%.s=\" $(seq 1 \"$width\")\n    printf \"\\n\"\n\n    for i in \"${!region_names[@]}\"\n    do\n      # shellcheck disable=SC2059\n      printf \"$format\" \"$i\" \"${region_names[$i]}\" \"${region_sizes[$i]}\";\n    done\n\n    read -r -p \"Select a region (0-$max) or Enter to go back: \" answer_region_id\n\n    [[ -z ${answer_region_id} ]] && break\n\n    if [[ ${answer_region_id} != [0-9] && ${answer_region_id} != [1-9][0-9] || ${answer_region_id} -gt ${max} ]]\n    then\n      echo \"Wrong region ID '$answer_region_id'.\"\n      continue\n    fi\n\n    read -r -p \"Download (d) or enter (e): \" answer_action\n    if [[ ${answer_action} != [de] ]]\n    then\n      echo \"Wrong action '$answer_action'.\"\n      continue\n    fi\n\n    break\n  done\n\n  [[ -z ${answer_region_id} ]] && return 0\n\n  # osm_url is used either to download an OSM in the d) case or to pass it to next download_menu level\n  # to make it possible to download it if there are no subregions to choose from in the next step.\n  local -r osm_url=${url_parent}/${region_osm_hrefs[answer_region_id]}\n  local -r subregion_html_url=${url_parent}/${region_sub_hrefs[answer_region_id]}\n\n  case ${answer_action} in\n    e)\n      download_menu \"$subregion_html_url\" \"$osm_url\"\n    ;;\n    d)\n      # Remove region prefix (e.g. europe/) from href to get the OSM file name.\n      local -r osm_file=${osm_url##*/}\n      local -r osm_target=${osm_dir}/${osm_file}\n\n      if [[ -f ${osm_target} ]]\n      then\n        echo \"Already downloaded.\"\n      else\n        download \"$osm_url\" \"$osm_target\"\n        # Hack to set country code of any US state.\n        if [[ ${osm_url}/ == */north-america/us/* ]]\n        then\n          echo \"US\" > \"$cc_dir/${osm_file%.osm.pbf}\"\n        fi\n      fi\n    ;;\n    *)\n      echo \"ERROR: Not possible (region_id=$answer_region_id,action=$answer_action).\"\n      exit 1\n    ;;\n  esac\n}\n\nfunction interactive() {\n  while true\n  do\n    IFS=$'\\n' read -d '' -r -a regions <<< \"$(for r in \"$osm_dir\"/* \"$gh_dir\"/*; do basename \"$r\" | sed 's/.osm.pbf//'; done | sort | uniq)\" || true\n\n    # Make the array empty if it contains just 1 empty element.\n    [[ ${#regions[*]} == 1 && -z ${regions[0]} ]] && regions=()\n\n    local format=\" %2s %-24s %10s %10s %10s\\n\"\n    local width=62\n\n    echo\n    # shellcheck disable=SC2059\n    printf \"$format\" \"#\" \"REGION\" \"OSM\" \"GRAPH\" \"COUNTRY\"\n    printf \"%.s=\" $(seq 1 \"$width\")\n    printf \"\\n\"\n\n    local cc_status=\"OK\"\n\n    for i in \"${!regions[@]}\"\n    do\n      local region=${regions[$i]}\n      # pass cc_error to skip repeated curl in this loop\n      country_code \"$region\" \"$cc_status\" || cc_status=\"ERROR\"\n      # shellcheck disable=SC2059\n      printf \"$format\" \\\n        \"$i\" \\\n        \"$region\" \\\n        \"$(if [[ -f \"$osm_dir/$region.osm.pbf\" ]]; then echo \"[x]\"; else echo \"[ ]\"; fi)\" \\\n        \"$(if [[ -d \"$gh_dir/$region\" ]]; then echo \"[x]\"; else echo \"[ ]\"; fi)\" \\\n        \"$(cat \"$cc_dir/$region\")\"\n    done\n\n    if [[ ${cc_status} == \"ERROR\" ]]\n    then\n      echo\n      echo \"ERROR: Failed to download country codes. Are you offline?\"\n    fi\n\n    local max=$((${#regions[*]} - 1))\n\n    echo\n    echo \"Choose the next step:\"\n    echo \"d:    Download new region.\"\n    echo \"q:    Quit.\"\n    [[ ${max} -ge 0 ]] && echo \"0-$max: Select a region and run OptaWeb Vehicle Routing.\"\n\n    echo\n    local command\n    read -r -p \"Your choice: \" command\n    case \"$command\" in\n      q)\n        exit 0\n      ;;\n      d)\n        download_menu \"https://download.geofabrik.de/index.html\"\n        continue\n      ;;\n      [0-9] | [1-9][0-9])\n        if [[ ${command} -gt ${max} ]]\n        then\n          echo \"Wrong number: $command\"\n          continue\n        fi\n        osm_file=${regions[$command]}.osm.pbf\n        cc_list=$(cat \"$cc_dir/${regions[$command]}\")\n        break\n      ;;\n      *)\n        echo \"Wrong command.\"\n        continue\n      ;;\n    esac\n  done\n\n  echo \"Region: $osm_file\"\n  echo \"Country code list: $cc_list\"\n  echo\n  confirm \"Do you want to launch OptaWeb Vehicle Routing?\" || abort\n\n  standalone_jar_or_maven\n  run_optaweb \"$@\"\n}\n\nfunction quickstart() {\n  local url=https://download.geofabrik.de/europe/belgium-latest.osm.pbf\n  osm_file=\"belgium-latest.osm.pbf\"\n  cc_list=\"BE\"\n  local -r osm_target=${osm_dir}/${osm_file}\n  if [[ ! -f ${osm_target} ]]\n  then\n    echo \"OptaWeb Vehicle Routing needs an OSM file for distance calculation. \\\nIt contains a built-in dataset for $osm_file, which does not exist in $osm_dir. \\\nThis script can download it for you from Geofabrik.de.\"\n    confirm \"Download $osm_file from Geofabrik.de now?\" || abort\n    download \"$url\" \"$osm_target\"\n    echo \"$cc_list\" > \"$cc_dir/${osm_file%.osm.pbf}\"\n  fi\n  standalone_jar_or_maven\n  run_optaweb \"$@\"\n}\n\n# Change dir to the project root (where the script is located).\n# This is needed to correctly resolve .VRP_DIR_LAST, path to the standalone JAR, etc.\n# in case the script was called from a different location than the project root.\ncd -P \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &> /dev/null && pwd)\"\n\nreadonly last_vrp_dir_file=.DATA_DIR_LAST\n\nif [[ -f ${last_vrp_dir_file} ]]\nthen\n  readonly last_vrp_dir=$(cat ${last_vrp_dir_file})\nelse\n  readonly last_vrp_dir=\"\"\nfi\n\nif [[ -z ${last_vrp_dir} ]]\nthen\n  readonly vrp_dir=$HOME/.optaweb-vehicle-routing\n  echo \"There is no last used VRP dir. Using the default.\"\nelse\n  readonly vrp_dir=${last_vrp_dir}\nfi\n\necho \"VRP dir: $vrp_dir\"\n\nif [[ ! -d ${vrp_dir} ]]\nthen\n  confirm \"VRP dir '$vrp_dir' does not exist. Do you want to create it now?\" || abort\n  mkdir \"${vrp_dir}\" || {\n    echo >&2 \"Cannot create VRP directory '$vrp_dir'.\"\n    exit 1\n  }\nfi\n\n# Remember VRP dir\necho \"${vrp_dir}\" > ${last_vrp_dir_file}\n\nreadonly error_log=${vrp_dir}/error.log\nrm -f ${error_log}\n\nreadonly osm_dir=${vrp_dir}/openstreetmap\nreadonly gh_dir=${vrp_dir}/graphhopper\nreadonly cc_dir=${vrp_dir}/country_codes\nreadonly dataset_dir=${vrp_dir}/dataset\nreadonly cache_dir=${vrp_dir}/.cache\nreadonly cache_geofabrik=${cache_dir}/geofabrik\n\n[[ -d ${osm_dir} ]] || mkdir \"$osm_dir\"\n[[ -d ${gh_dir} ]] || mkdir \"$gh_dir\"\n[[ -d ${cc_dir} ]] || mkdir \"$cc_dir\"\n[[ -d ${dataset_dir} ]] || mkdir \"$dataset_dir\"\n[[ -d ${cache_geofabrik} ]] || mkdir -p ${cache_geofabrik}\n\ndeclare routing_engine=\"GRAPHHOPPER\"\n\n# Getting started (semi-interactive) - use OSM compatible with the built-in data set, download if not present.\nif [[ $# == 0 ]]\nthen\n  quickstart\n  exit 0\nfi\n\n# Use air mode (no OSM file, no country codes).\nif [[ $1 == \"--air\" ]]\nthen\n  routing_engine=\"AIR\"\n  shift\n  echo >&2 \"Air mode is currently not available. See https://github.com/kiegroup/optaweb-vehicle-routing/issues/455.\"\n  exit 1\n#  standalone_jar_or_maven\n#  run_optaweb \"$@\"\n#  exit 0\nfi\n\ncase $1 in\n  -i | --interactive)\n    shift\n    interactive \"$@\"\n  ;;\n  -*)\n    quickstart \"$@\"\n  ;;\n  # Demo use case (non-interactive) - start with existing data.\n  [a-z]*)\n    region=${1%.osm.pbf}\n    osm_file=${region}.osm.pbf\n    if ! validate\n    then\n      region=${region}-latest\n      osm_file=${region}.osm.pbf\n      validate || {\n        echo >&2 \"Wrong region '$1'. One of the following must exist:\"\n        echo >&2 \"- OSM file: $osm_dir/${region}.osm.pbf\"\n        echo >&2 \"- GraphHopper graph: $gh_dir/$region\"\n        exit 1\n      }\n    fi\n\n    cc_list=$(cat \"$cc_dir/$region\")\n    shift\n    standalone_jar_or_maven\n    run_optaweb \"$@\"\n  ;;\n  *)\n    echo >&2 \"Wrong argument.\"\n    # TODO display help\n  ;;\nesac\n"
  },
  {
    "path": "runOnOpenShift.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nreadonly script_name=\"./$(basename \"$0\")\"\n\nfunction print_help() {\n  echo \"Usage:\"\n  echo \"  $script_name [OSM_FILE_NAME COUNTRY_CODE_LIST OSM_FILE_DOWNLOAD_URL]\"\n  echo \"  $script_name --air\"\n  echo \"  $script_name --help\"\n  echo\n  echo \"First form configures the back end to use GraphHopper routing mode and downloads an OSM data file during startup. \\\nNote that download and processing of the OSM file can take some time depending on its size. \\\nDuring this period, the application informs about back end service being unreachable.\"\n  echo\n  echo \"Second form configures back end to use air routing mode. This is useful for development, debugging and \\\nhacking. Air distance routing is only an approximation. It is not useful for real vehicle routing.\"\n  echo\n  echo\n  echo \"OSM_FILE_NAME\"\n  echo \"  The file downloaded from OSM_FILE_DOWNLOAD_URL will be saved under this name.\"\n  echo\n  echo \"COUNTRY_CODE_LIST\"\n  echo \"  ISO_3166-1 country code used to filter geosearch results. You can provide multiple, comma-separated values.\"\n  echo\n  echo \"OSM_FILE_DOWNLOAD_URL\"\n  echo \"  Should point to an OSM data file in PBF format accessible from OpenShift. The file will be downloaded \\\nduring back end startup and saved as /deployments/local/OSM_FILE_NAME.\"\n  echo\n  echo\n  echo \"Example 1\"\n  echo \"  $script_name belgium-latest.osm.pbf BE https://download.geofabrik.de/europe/belgium-latest.osm.pbf\"\n  echo\n  echo \"  Configures the application to filter geosearch results to Belgium and download the latest Belgium \\\nOSM extract from Geofabrik.\"\n  echo\n  echo\n  echo \"Example 2\"\n  echo \"  $script_name my-city.osm.pbf FR https://download.bbbike.org/osm/extract/planet_12.032,53.0171_12.1024,53.0491.osm.pbf\"\n  echo\n  echo \"  Configures the application to download a custom region defined using the BBBike service and save it \\\nas my-city.osm.pbf.\"\n}\n\nfunction wrong_args() {\n    print_help\n    echo >&2\n    echo >&2 \"ERROR: Wrong arguments.\"\n    exit 1\n}\n\n[[ $1 == \"--help\" ]] && print_help && exit 0\n\n# Process arguments\ndeclare -a dc_backend_env\ncase $# in\n  0)\n    print_help\n    exit 0\n    ;;\n  1)\n    if [[ $1 == --air ]]\n    then\n      dc_backend_env+=(\"APP_ROUTING_ENGINE=AIR\")\n      summary=\"No routing config provided. The back end will start in air distance mode.\\n\\n\\\nWARNING: Air distance mode does not give accurate values. \\\nIt is only useful for evaluation, debugging, or incremental setup purpose. \\\nYou can run '$script_name --help' to see other options.\"\n    else\n      wrong_args\n    fi\n    ;;\n  2)\n    dc_backend_env+=(\"APP_ROUTING_ENGINE=AIR\")\n    dc_backend_env+=(\"APP_ROUTING_OSM_FILE=$1\")\n    dc_backend_env+=(\"APP_REGION_COUNTRY_CODES=$2\")\n    summary=\"The back end will start in air mode. Use the back end pod to upload a graph directory or an OSM file. \\\nThen change routing mode to graphhopper. Run '$script_name --help' for more info.\"\n    ;;\n  3)\n    dc_backend_env+=(\"APP_ROUTING_ENGINE=GRAPHHOPPER\")\n    dc_backend_env+=(\"APP_ROUTING_OSM_FILE=$1\")\n    dc_backend_env+=(\"APP_REGION_COUNTRY_CODES=$2\")\n    dc_backend_env+=(\"APP_ROUTING_OSM_DOWNLOAD_URL=$3\")\n    summary=\"The back end will download an OSM file on startup. \\\nIt may take several minutes to download and process the file before the application is fully available!\"\n    download=1\n    ;;\n  *)\n    wrong_args\nesac\n\n# Change dir to the project root (where the script is located) to correctly resolve module paths.\n# This is needed in case the script was called from a different location than the project root.\ncd -P \"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" &> /dev/null && pwd)\"\n\nreadonly dir_backend=optaweb-vehicle-routing-backend\nreadonly dir_frontend=optaweb-vehicle-routing-frontend\n\n# Fail fast if the project hasn't been built\nif ! stat -t ${dir_backend}/target/*.jar > /dev/null 2>&1\nthen\n  echo >&2 \"ERROR: The back end module is not built! Build the project before running this script.\"\n  exit 1\nfi\nif [[ ! -d ${dir_frontend}/docker/build ]]\nthen\n  echo >&2 \"ERROR: The front end module is not built! Build the project before running this script.\"\n  exit 1\nfi\n\ncommand -v oc > /dev/null 2>&1 || {\n  echo >&2 \"ERROR: The oc client tool needs to be installed to connect to OpenShift.\"\n  exit 1\n}\n\n[[ -x $(command -v oc) ]] || {\n  echo >&2 \"ERROR: The oc client tool is not executable. Please make it executable by running \\\n'chmod u+x \\$(command -v oc)'.\"\n  exit 1\n}\n\n# Print info about the current user and project\necho \"Current user: $(oc whoami)\"\n# Check that the current user has at least one project\n[[ -z \"$(oc projects -q)\" ]] && {\n  echo >&2 \"You have no projects. Use 'oc new-project <project-name>' to create one.\"\n  exit 1\n}\n# Display info about the current project\noc project\n\n# Check that the current project is empty\nget_all=$(oc get all -o name)\nif [[ -z \"$get_all\" ]]\nthen\n  echo \"The project appears to be empty.\"\nelse\n  echo >&2\n  echo >&2 \"Project content:\"\n  echo >&2\n  echo >&2 \"$get_all\"\n  echo >&2\n  echo >&2 \"ERROR: The project is not empty.\"\n  exit 1\nfi\n\necho\necho -e \"$summary\"\necho\n\ndeclare answer_continue\nread -r -p \"Do you want to continue? [y/N]: \" \"answer_continue\"\n[[ \"$answer_continue\" == \"y\" ]] || {\n  echo \"Aborted.\"\n  exit 0\n}\n\n# Set up PostgreSQL\noc new-app --name postgresql postgresql-persistent\n\n# Back end\n# -- binary build (upload local artifacts + Dockerfile)\noc new-build --name backend --strategy=docker --binary --build-arg QUARKUS_APP_BUILD_QUALIFIER=postgresql\noc patch bc backend -p '{\"spec\":{\"strategy\":{\"dockerStrategy\":{\"dockerfilePath\":\"src/main/docker/Dockerfile.jvm\"}}}}'\noc start-build backend --from-dir=${dir_backend} --follow\n# -- new app\noc new-app backend\n# -- use PostgreSQL secret\noc set env deployment/backend --from=secret/postgresql\n# -- set the rest of the configuration\noc set env deployment/backend \"${dc_backend_env[@]}\"\n# Add a PersistentVolumeClaim\noc set volumes deployment/backend --add \\\n    --type pvc \\\n    --claim-size 1Gi \\\n    --claim-mode ReadWriteOnce \\\n    --name data-local \\\n    --mount-path /deployments/local\n\n# Front end\n# -- binary build\noc new-build --name frontend --strategy=docker --binary\noc start-build frontend --from-dir=${dir_frontend}/docker --follow\n# -- new app\noc new-app frontend\n# -- expose the service\noc expose svc/frontend\n# -- change target port to 8080\noc patch route frontend -p '{\"spec\":{\"port\":{\"targetPort\":\"8080-tcp\"}}}'\n\necho\necho \"You can access the application at http://$(oc get route frontend -o custom-columns=:spec.host | tr -d '\\n') \\\nonce the deployment is done.\"\nif [[ -v download ]]\nthen\n  echo\n  echo \"The OSM file download and its processing can take some time depending on its size. \\\nFor large files (hundreds of MB) this can be several minutes. \\\nDuring this period, the application informs about the back end service being unreachable.\"\nfi\n"
  }
]