diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000..2201f84 --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,20 @@ +version: 2 +mergeable: + - when: pull_request.* + name: 'Check decoration' + validate: + - do: approvals + min: + count: 2 + limit: + users: ['Artem-Rzhankoff', 'ItIsMrLaG', 'RozhkovAleksandr'] + - do: description + no_empty: + enabled: true + message: Description matter and should not be empty. Provide detail with **what** was changed, **why** it was changed, and **how** it was changed. + - do: title + must_exclude: + regex: ^\[WIP\] + - do: label + must_exclude: + regex: 'wip' diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..cbb1d05 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,38 @@ +name: build project + +on: [push, pull_request] + +jobs: + run: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: Run Tests + run: ./gradlew clean test + + - if: matrix.os == 'ubuntu-latest' # Container action is only supported on Linux + name: Run Test Coverage + run: ./gradlew jacocoTestReport + + - if: matrix.os == 'ubuntu-latest' + name: Jacoco Code Coverage Report + uses: cicirello/jacoco-badge-generator@v2.8.0 + with: + generate-branches-badge: true + jacoco-csv-file: lib/build/jacoco/report.csv + + - if: ${{ github.event_name == 'pull_request' }} + name: Jacoco Test Coverage Verification + run: ./gradlew jacocoTestCoverageVerification diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68715a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/.gradle/ +/.idea/ +/build/ +/lib/build/ +/lib/TEST_TEST/ +/gradle/ +/neo4jDB/ +/saved-trees/ +/sqliteDB/ +/treeApp/build/ diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce95799 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +logo + + + +

badge +badge +badge +badge +badge

+ + + +## Tree Structure + + + +This project will help the user to build an RBT, AVL or binary tree. Add, delete, or search for elements in it. + + +## Features +With **trees-1** you can do the following: +- Create three kinds of binary search trees: **Binary search tree**, **AVL tree**, **Red-black tree** +- Using the GUI, perform the following operations (**insert**, **delete**, **search**) +- Save and load trees into databases (**json files** for binary, **SQLite** for AVL-tree, **neo4j** for RB-tree) +- Use an independent library with the implementation of these trees in your project + +## Get it! +Important: To build and run the application, you will need the following tools: Gradle, JDK (version 17 and latest) and Neo4j. +### Quick start +```bash +# Clone this repo +git clone https://github.com/spbu-coding-2022/trees-1.it + +# Build +./gradlew build + +# Build Docker (link) + +# Run +./gradlew run +``` +If you only want to use the library, you need to follow these steps: +```bash +# Find the "dependencies" block and add the dependency line to it: + +# If in your project build.gradle.kts: +implementation("com.github.spbu-coding-2022:trees-1:1.0.0") +# If in your project build.gradle: +implementation 'com.github.spbu-coding-2022:trees-1:1.0.0' + +# Save the changes in the build file +``` +## Neo4j setup +1. Install docker +2. Install docker compose (https://docs.docker.com/compose/install/) +3. Run in project dir: +```bash +docker compose -f "docker-compose.yml" up +``` +4. If you're having trouble getting started, check out this [GUIDE](https://www.baeldung.com/ops/docker-cannot-connect#:~:text=Due%20to%20Inactive%20Docker%20Service) + + +## See examples +logo +logo + + +## Contacts +For all questions: [Click](http://telegram.me/LesokSupportbot) + +## License +This project uses the **APACHE LICENSE, VERSION 2.0**. See the [LICENSE](LICENSE.md) for more info. + +
+ + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5fce390 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + neo4j: + image: neo4j:latest + volumes: + - ./neo4jDB:/data + container_name: neo4j-db + ports: + - "7474:7474" + - "7687:7687" + environment: + - NEO4J_AUTH=neo4j/password \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f1a74ac --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +kotlinVersion=1.8.10 +sqliteJdbcVersion=3.41.2.1 +exposedVersion=0.40.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..ccebba7 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..42defcc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..a9bd892 --- /dev/null +++ b/gradlew @@ -0,0 +1,243 @@ +#!/bin/sh +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts new file mode 100644 index 0000000..c44d77b --- /dev/null +++ b/lib/build.gradle.kts @@ -0,0 +1,127 @@ +plugins { + java + kotlin("jvm") version "1.8.10" + jacoco + `java-library` + `maven-publish` + kotlin("plugin.serialization") version "1.5.0" +} + +kotlin { + jvmToolchain(17) +} + +dependencies { + api("org.apache.commons:commons-math3:3.6.1") + implementation("com.google.guava:guava:31.1-jre") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + + implementation("com.google.code.gson:gson:2.10.1") + + val neo4jCore = "4.0.5" + implementation("org.neo4j", "neo4j-ogm-core", neo4jCore) + implementation("org.neo4j", "neo4j-ogm-bolt-driver", neo4jCore) + + // JDBC Sqlite + val sqliteJdbcVersion: String by project + implementation("org.xerial", "sqlite-jdbc", sqliteJdbcVersion) + + val exposedVersion: String by project + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.8.10") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.2") +} + +tasks.test { + finalizedBy(tasks.jacocoTestReport) + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + } + val failedTests = mutableListOf() + val skippedTests = mutableListOf() + + addTestListener (object: TestListener { + override fun beforeSuite(suite: TestDescriptor?) { } + override fun beforeTest(testDescriptor: TestDescriptor?) { } + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { + when (result.resultType) { + TestResult.ResultType.FAILURE -> failedTests.add(testDescriptor) + TestResult.ResultType.SKIPPED -> skippedTests.add(testDescriptor) + else -> {} + } + } + override fun afterSuite(suite: TestDescriptor, result: TestResult) { + if (suite.parent == null) { // root suite + logger.lifecycle("####################################################################################") + logger.lifecycle("Test result: ${result.resultType}") + logger.lifecycle( + "Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeed, " + + "${result.skippedTestCount} skipped, " + + "${result.failedTestCount} failed." + ) + } + } + }) + +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required.set(true) + xml.outputLocation.set(layout.buildDirectory.file("jacoco/report.xml")) + csv.required.set(true) + csv.outputLocation.set(layout.buildDirectory.file("jacoco/report.csv")) + html.required.set(true) + html.outputLocation.set(layout.buildDirectory.dir("jacocoHtml")) + } +} + +tasks.jacocoTestCoverageVerification { + classDirectories.setFrom( classDirectories.files.flatMap { fileTree(it) { + include("**/RBBalancer.class", "**/AVLBalancer.class", "**/BINStruct", "**/AVLStruct.class", "**/BINStruct.class") + exclude("**/singleObjects/**", "**/RBVertex.class", "**/AVLVertex.class", "**/BINVertex.class", "**/Vertex.class", "**/BINStruct.class", "**/AVLStruct.class", "**/RBStruct.class", "**/TreeStruct.class") + } }) + dependsOn(tasks.jacocoTestReport) + violationRules { + rule { + element = "CLASS" + limit { + counter = "BRANCH" + minimum = 0.5.toBigDecimal() + } + } + rule { + element = "CLASS" + limit { + counter = "LINE" + minimum = 0.6.toBigDecimal() + } + } + rule { + element = "CLASS" + limit { + counter = "METHOD" + minimum = 0.9.toBigDecimal() + } + } + } +} + +publishing { + publications { + create("maven") { + groupId = "Tree" + artifactId = "lib" + version = "1.1" + from(components["java"]) + } + } +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/Node.kt b/lib/src/main/kotlin/treelib/abstractTree/Node.kt new file mode 100644 index 0000000..849e46e --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/Node.kt @@ -0,0 +1,7 @@ +package treelib.abstractTree + +interface Node, SubNode : Node> { + var value: Pack + var left: SubNode? + var right: SubNode? +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/NodeParent.kt b/lib/src/main/kotlin/treelib/abstractTree/NodeParent.kt new file mode 100644 index 0000000..4d8c046 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/NodeParent.kt @@ -0,0 +1,7 @@ +package treelib.abstractTree + +interface NodeParent, SubNode : NodeParent> : Node { + var parent: SubNode? + override var right: SubNode? + override var left: SubNode? +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/StateContainer.kt b/lib/src/main/kotlin/treelib/abstractTree/StateContainer.kt new file mode 100644 index 0000000..b835879 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/StateContainer.kt @@ -0,0 +1,5 @@ +package treelib.abstractTree + +interface StateContainer, NodeType : Node> { + val contentNode: NodeType? +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/Tree.kt b/lib/src/main/kotlin/treelib/abstractTree/Tree.kt new file mode 100644 index 0000000..01bfc48 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/Tree.kt @@ -0,0 +1,52 @@ +package treelib.abstractTree + +import treelib.commonObjects.Container +import treelib.commonObjects.exceptions.VauleNotFound + +abstract class Tree< + K : Comparable, + V, + NodeType : Node, NodeType>, + State : StateContainer, NodeType>, + VertexType : Vertex> + > { + + protected abstract val treeStruct: TreeStruct, NodeType, State, VertexType> + + private fun wrapForFind(key: K) = Container(key to null) + + fun putItem(item: Pair) { + treeStruct.insert(Container(item)) + } + + fun putItem(vararg items: Pair) { + for (element in items) putItem(element) + } + + fun putItem(items: Iterable>) { + for (element in items) putItem(element) + } + + fun getItem(key: K): V? = treeStruct.find(wrapForFind(key))?.value + + open operator fun get(key: K): V? = treeStruct.find(wrapForFind(key))?.value + + fun deleteItem(key: K) { + if (treeStruct.find(wrapForFind(key)) == null) throw VauleNotFound() + treeStruct.delete(wrapForFind(key)) + } + + private fun createPoorList(info: List): List> { + val returnInfo = mutableListOf>() + for (element in info) { + returnInfo.add(element.value.pair) + } + return returnInfo + } + + fun inOrder(): List> = createPoorList(treeStruct.inOrder()) + + fun preOrder(): List> = createPoorList(treeStruct.preOrder()) + + fun postOrder(): List> = createPoorList(treeStruct.postOrder()) +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/TreeStruct.kt b/lib/src/main/kotlin/treelib/abstractTree/TreeStruct.kt new file mode 100644 index 0000000..272eed4 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/TreeStruct.kt @@ -0,0 +1,362 @@ +package treelib.abstractTree + +import treelib.commonObjects.exceptions.ImpossibleCaseException +import treelib.commonObjects.exceptions.VauleNotFound + + +abstract class TreeStruct< + Pack : Comparable, + NodeType : Node, + State : StateContainer, + VertexType : Vertex + > { + + protected abstract var root: NodeType? + + protected fun getLeafForInsert(item: Pack): NodeType? { + var currentNode: NodeType? = root ?: return null + + while (true) { + currentNode?.let { + when { + item > it.value -> { + if (it.right == null) return@getLeafForInsert currentNode + currentNode = it.right + } + + item < it.value -> { + if (it.left == null) return@getLeafForInsert currentNode + currentNode = it.left + } + + else -> throw InternalError("getLeafForInsert shouldn't be used with a value exists in Struct") + } + } ?: throw ImpossibleCaseException() + } + } + + private fun getParentByValue(item: Pack): NodeType? { + var currentNode = root + + if ((currentNode != null) && (currentNode.value == item)) return null + + while (true) { + currentNode?.let { + when { + item inRightOf it -> return@getParentByValue currentNode + item inLeftOf it -> return@getParentByValue currentNode + else -> { + if (item > it.value) currentNode = it.right + else currentNode = it.left + } + } + } + ?: throw InternalError("getParentByValue shouldn't be used with value doesn't exist in tree")// (1)l -> + } + } + + private infix fun Pack.inRightOf(node: NodeType?): Boolean { + if (node == null) return false + node.right?.let { + if (this == it.value) return@inRightOf true + } + return false + } + + private infix fun Pack.inLeftOf(node: NodeType?): Boolean { + if (node == null) return false + node.left?.let { + if (it.value == this) return@inLeftOf true + } + return false + } + + private fun getRightMinNode(localRoot: NodeType): NodeType { + var currentNode: NodeType? + + localRoot.right + ?: throw InternalError("Incorrect usage of the getRightMinNode: right node doesn't exist") + + currentNode = localRoot.right + + while (true) { + currentNode?.let { curNode -> + if (curNode.left == null) return@getRightMinNode curNode + else currentNode = curNode.left + } ?: throw ImpossibleCaseException() + } + } + + private fun unLink( + node: NodeType, + parent: NodeType?, + ): NodeType { + val unLinkedNode: NodeType = node + val childForLink: NodeType? + + when { + (node.right != null) && (node.left != null) -> throw InternalError("unLink - method Shouldn't be used with node with both children") + node.right != null -> childForLink = node.right + node.left != null -> childForLink = node.left + else -> childForLink = null + } + unLinkedNode.left = null + unLinkedNode.right = null + + if (parent == null) return unLinkedNode + connectUnlinkedSubTreeWithParent(node, parent, childForLink) + + return unLinkedNode + } + + private fun rebaseNode( + node: NodeType, + parent: NodeType?, + replaceNode: NodeType?, + ) { + when { + (parent == null) && (replaceNode == null) -> root = null + (parent != null) && (replaceNode == null) -> { + when { + node.value inLeftOf parent.left -> parent.left = null + node.value inRightOf parent.right -> parent.right = null + } + } + + replaceNode != null -> node.value = replaceNode.value + } + } + + protected abstract fun generateStateFind( + findNode: NodeType?, + contentNode: NodeType? = null, + ): State + + protected fun findItem(obj: Pack): State { + var currentNode = root + if (root == null) return generateStateFind(null, null) + + while (true) { + if (obj == currentNode?.value) return generateStateFind(currentNode, currentNode) + else { + currentNode?.let { + if (obj > it.value) currentNode = it.right + else currentNode = it.left + } + if (currentNode == null) return generateStateFind(null, null) + } + } + } + + protected abstract fun generateStateInsert( + insertNode: NodeType?, + contentNode: NodeType? = null, + ): State + + protected fun insertItem(item: Pack): State { + val parentNode: NodeType? + val currentNode: NodeType + val updateNode: NodeType? = findItem(item).contentNode + + if (updateNode == null) { + parentNode = getLeafForInsert(item) + currentNode = createNode(item) + + linkNewNode(currentNode, parentNode) + + if (parentNode != null) return generateStateInsert(currentNode, parentNode) + else return generateStateInsert(currentNode, currentNode) + } + + updateNode.value = item + return generateStateInsert(null, null) + } + + protected abstract fun generateStateDelete( + deletedNode: NodeType?, + contentNode: NodeType? = null, + ): State + + protected fun deleteItem(item: Pack): State { + val parentDeleteNode: NodeType? + val deleteNode: NodeType? + + if (findItem(item).contentNode == null) throw VauleNotFound() + + parentDeleteNode = getParentByValue(item) + if (parentDeleteNode != null) { + deleteNode = when { + item inRightOf parentDeleteNode -> parentDeleteNode.right + item inLeftOf parentDeleteNode -> parentDeleteNode.left + else -> throw ImpossibleCaseException() + } + } else deleteNode = root + + if (deleteNode == null) throw ImpossibleCaseException() + + val nodeForReplace: NodeType? + val parentNodeForReplace: NodeType? + val deletedNodeWithoutLinks = getNodeKernel(deleteNode) + + when { + (deleteNode.left != null) && (deleteNode.right != null) -> { + val rightMinNode = getRightMinNode(deleteNode) + + parentNodeForReplace = getParentByValue(rightMinNode.value) + nodeForReplace = unLink(rightMinNode, parentNodeForReplace) + + rebaseNode(deleteNode, parentDeleteNode, nodeForReplace) + + return generateStateDelete(deletedNodeWithoutLinks, parentNodeForReplace) + } + + (deleteNode.left == null) && (deleteNode.right == null) -> { + if (parentDeleteNode == null) { + root = null + return generateStateDelete(deletedNodeWithoutLinks, null) + } else { + when { + item inRightOf parentDeleteNode -> parentDeleteNode.right = null + item inLeftOf parentDeleteNode -> parentDeleteNode.left = null + } + return generateStateDelete(deletedNodeWithoutLinks, parentDeleteNode) + } + } + + else -> { + for (child in listOf(deleteNode.right, deleteNode.left)) + child?.let { + connectUnlinkedSubTreeWithParent(deleteNode, parentDeleteNode, it) + if (parentDeleteNode != null) { + return@deleteItem generateStateDelete(deletedNodeWithoutLinks, parentDeleteNode) + } else { + return@deleteItem generateStateDelete(deletedNodeWithoutLinks, root) + } + } + } + } + throw ImpossibleCaseException() + } + + protected abstract fun connectUnlinkedSubTreeWithParent( + node: NodeType, + parent: NodeType?, + childForLink: NodeType? + ) /* Behaviour: link rebased node */ + + /* Return node with fields: right == left == {parent} == null */ + protected abstract fun getNodeKernel(node: NodeType): NodeType + + protected abstract fun linkNewNode( + node: NodeType, + parent: NodeType?, + ): NodeType + + protected abstract fun createNode(item: Pack): NodeType + + abstract fun insert(item: Pack) + + /* Behaviour: null - means value not in tree; Pack - value was successfully deleted */ + abstract fun delete(item: Pack) + + fun find(obj: Pack): Pack? = findItem(obj).contentNode?.value + + fun inOrder(): List { + val arrayNodes = mutableListOf() + var flagVisited = 0 + var current = root + val parents = ArrayDeque() + + while (current != null) { + if (flagVisited == 0) { + while (true) { + current?.let { + if (it.left == null) return@let null + parents.add(it) + current = it.left + return@let current + } ?: break + } + } + current?.let { + arrayNodes.add(it) + if (it.right != null) { + flagVisited = 0 + current = it.right + } else { + if (parents.isEmpty()) + return@inOrder arrayNodes.map { toVertex(it) } + flagVisited = 1 + current = parents.removeLast() + } + } + } + return arrayNodes.map { toVertex(it) } + } + + abstract fun toVertex(node: NodeType): VertexType + + fun postOrder(): List { + val parents = ArrayDeque() + val arrayNodes = mutableListOf() + var flagVisited = 0 + var current = root + + while (current != null) { + if (flagVisited == 0) { + while (true) { + current?.let { + if (it.left == null) return@let null + parents.add(it) + current = it.left + return@let current + } ?: break + } + } + current?.let { + if (it.right != null && flagVisited != 2) { + parents.add(it) + current = it.right + flagVisited = 0 + } else { + arrayNodes.add(it) + if (parents.isEmpty()) + return@postOrder arrayNodes.map { toVertex(it) } + val parent = parents.removeLast() + if (parent.right == it) { + flagVisited = 2 + } + else { + flagVisited = 1 + } + current = parent + } + } ?: throw ImpossibleCaseException() + } + return arrayNodes.map { toVertex(it) } + } + + fun preOrder(): List { + val arrayNodes = mutableListOf() + var current: NodeType + val queue = ArrayDeque() + + root?.let { root -> + queue.add(root) + while (queue.isNotEmpty()) { + current = queue.removeLast() + arrayNodes.add(current) + if (current.right != null) + current.right?.let { + queue.add(it) + } ?: throw ImpossibleCaseException() + + if (current.left != null) + current.left?.let { + queue.add(it) + } ?: throw ImpossibleCaseException() + } + } + return arrayNodes.map { toVertex(it) } + } +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/Vertex.kt b/lib/src/main/kotlin/treelib/abstractTree/Vertex.kt new file mode 100644 index 0000000..68f3726 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/Vertex.kt @@ -0,0 +1,5 @@ +package treelib.abstractTree + +abstract class Vertex> { + abstract val value: Pack +} \ No newline at end of file diff --git a/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancedTreeStruct.kt b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancedTreeStruct.kt new file mode 100644 index 0000000..5172f71 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancedTreeStruct.kt @@ -0,0 +1,35 @@ +package treelib.abstractTree.balanced + +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer +import treelib.abstractTree.TreeStruct +import treelib.abstractTree.Vertex + +abstract class BalancedTreeStruct< + Pack : Comparable, + NodeType : Node, + State : StateContainer, + VertexType : Vertex, + BalancerType : Balancer, + > : TreeStruct() { + + protected abstract val balancer: BalancerType + + override fun insert(item: Pack) { + val currentState = insertItem(item) + if (currentState.contentNode != null) { + root = balancer.balance(currentState) + } + } + + override fun delete(item: Pack) { + val currentState = deleteItem(item) + if (root == null) { + return + } + if (currentState.contentNode != null) { + root = balancer.balance(currentState) + return + } + } +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/balanced/Balancer.kt b/lib/src/main/kotlin/treelib/abstractTree/balanced/Balancer.kt new file mode 100644 index 0000000..6931a5b --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/balanced/Balancer.kt @@ -0,0 +1,13 @@ +package treelib.abstractTree.balanced + +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer + +interface Balancer< + Pack : Comparable, + NodeType : Node, + State : StateContainer, + > { + + fun balance(state: State): NodeType +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerNoParent.kt b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerNoParent.kt new file mode 100644 index 0000000..c479e2b --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerNoParent.kt @@ -0,0 +1,21 @@ +package treelib.abstractTree.balanced + +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer + +abstract class BalancerNoParent, NodeType : Node, StateContainerType : StateContainer> : + Balancer { + fun rightRotate(currentNode: NodeType): NodeType { + val leftSon = currentNode.left ?: throw InternalError() + currentNode.left = leftSon.right + leftSon.right = currentNode + return leftSon + } + + fun leftRotate(currentNode: NodeType): NodeType { + val rightSon = currentNode.right ?: throw InternalError() + currentNode.right = rightSon.left + rightSon.left = currentNode + return rightSon + } +} diff --git a/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerParent.kt b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerParent.kt new file mode 100644 index 0000000..e72e763 --- /dev/null +++ b/lib/src/main/kotlin/treelib/abstractTree/balanced/BalancerParent.kt @@ -0,0 +1,41 @@ +package treelib.abstractTree.balanced + +import treelib.abstractTree.NodeParent +import treelib.abstractTree.StateContainer + +abstract class BalancerParent, NodeType : NodeParent, StateContainerType : StateContainer> : + Balancer { + fun rightRotate(currentNode: NodeType): NodeType { + val leftChild = currentNode.left ?: throw InternalError() + + val parent = currentNode.parent + leftChild.right?.parent = currentNode + currentNode.left = leftChild.right + leftChild.right = currentNode + + when { + parent?.left == currentNode -> parent.left = leftChild + parent?.right == currentNode -> parent.right = leftChild + } + currentNode.parent = leftChild + leftChild.parent = parent + return leftChild + } + + fun leftRotate(currentNode: NodeType): NodeType { + val rightChild = currentNode.right ?: throw InternalError() + val parent = currentNode.parent + + rightChild.left?.parent = currentNode + currentNode.right = rightChild.left + rightChild.left = currentNode + when { + parent?.left == currentNode -> parent.left = rightChild + parent?.right == currentNode -> parent.right = rightChild + } + currentNode.parent = rightChild + rightChild.parent = parent + return rightChild + } + +} diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLBalancer.kt b/lib/src/main/kotlin/treelib/avlTree/AVLBalancer.kt new file mode 100644 index 0000000..65157da --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLBalancer.kt @@ -0,0 +1,63 @@ +package treelib.avlTree + +import treelib.abstractTree.balanced.BalancerNoParent + +class AVLBalancer>(private var root: AVLNode?) : + BalancerNoParent, AVLStateContainer>() { + private fun updateBalance(node: AVLNode?): Int { + return (getHeight(node?.left) - getHeight(node?.right)).toInt() + } + + private fun getHeight(currentNode: AVLNode?): UInt { + return currentNode?.height ?: 0u + } + + private fun updateHeight(currentNode: AVLNode?) { + if (currentNode != null) + currentNode.height = maxOf(getHeight(currentNode.left), getHeight(currentNode.right)) + 1u + } + + override fun balance(state: AVLStateContainer): AVLNode { + val node = state.contentNode + ?: throw IllegalStateException("") // IllegalBaseNodeException("A non-existent node (null) was passed to the method") + root = state.root + return balance(root, node.value) + } + + /*** In the method we pass the parent of the removed/inserted node ***/ + private fun balance(currentNode: AVLNode?, value: Pack): AVLNode { + if (currentNode == null) { + throw NullPointerException() + } + when { + currentNode.value < value -> currentNode.right = balance(currentNode.right, value) + currentNode.value > value -> currentNode.left = balance(currentNode.left, value) + } + updateHeight(currentNode) + val balance = updateBalance(currentNode) + if (balance == -2) { + if (updateBalance(currentNode.right) == 1) { + currentNode.right = currentNode.right?.let { rightRotate(it) } + ?: throw NullPointerException() // IllegalNodeStateException() + updateHeight(currentNode.right?.right) + } + val balancedNode = leftRotate(currentNode) + updateHeight(balancedNode.left) + updateHeight(balancedNode) + return balancedNode + } + if (balance == 2) { + if (updateBalance(currentNode.left) == -1) { + currentNode.left = currentNode.left?.let { leftRotate(it) } + ?: throw NullPointerException() // IllegalNodeStateException("There is no node required by the condition of the algorithm") + updateHeight(currentNode.left?.left) + } + val balanceNode = rightRotate(currentNode) + updateHeight(balanceNode.right) + updateHeight(balanceNode) + return balanceNode + } + return currentNode + } + +} diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLNode.kt b/lib/src/main/kotlin/treelib/avlTree/AVLNode.kt new file mode 100644 index 0000000..bcbffc1 --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLNode.kt @@ -0,0 +1,10 @@ +package treelib.avlTree + +import treelib.abstractTree.Node + +class AVLNode>( + override var value: Pack, + override var left: AVLNode? = null, + override var right: AVLNode? = null, + var height: UInt = 1U, +) : Node> diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLStateContainer.kt b/lib/src/main/kotlin/treelib/avlTree/AVLStateContainer.kt new file mode 100644 index 0000000..121a614 --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLStateContainer.kt @@ -0,0 +1,8 @@ +package treelib.avlTree + +import treelib.abstractTree.StateContainer + +class AVLStateContainer>( + override val contentNode: AVLNode?, + val root: AVLNode?, +) : StateContainer> diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLStruct.kt b/lib/src/main/kotlin/treelib/avlTree/AVLStruct.kt new file mode 100644 index 0000000..d977ba0 --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLStruct.kt @@ -0,0 +1,69 @@ +package treelib.avlTree + +import treelib.abstractTree.balanced.BalancedTreeStruct +import treelib.commonObjects.exceptions.IncorrectUsage + +class AVLStruct> : + BalancedTreeStruct, AVLStateContainer, AVLVertex, AVLBalancer>() { + + override var root: AVLNode? = null + override val balancer = AVLBalancer(root) + + override fun generateStateDelete( + deletedNode: AVLNode?, + contentNode: AVLNode?, + ): AVLStateContainer = AVLStateContainer(contentNode, root) + + override fun generateStateInsert( + insertNode: AVLNode?, + contentNode: AVLNode? + ): AVLStateContainer = AVLStateContainer(contentNode, root) + + override fun generateStateFind( + findNode: AVLNode?, + contentNode: AVLNode?, + ): AVLStateContainer = AVLStateContainer(contentNode, root) + + override fun connectUnlinkedSubTreeWithParent( + node: AVLNode, + parent: AVLNode?, + childForLink: AVLNode? + ) { + if (root == null) return + + if (parent != null) { + when { + (node.value < parent.value) -> parent.left = childForLink + (node.value > parent.value) -> parent.right = childForLink + } + } else root?.let { + root = childForLink + } + } + + override fun toVertex(node: AVLNode): AVLVertex = AVLVertex(node.value, node.height) + + fun toNode(vertex: AVLVertex): AVLNode = AVLNode(value = vertex.value, height = vertex.height) + + override fun createNode(item: Pack): AVLNode = AVLNode(item) + + override fun getNodeKernel(node: AVLNode): AVLNode = AVLNode(node.value, height = node.height) + + override fun linkNewNode(node: AVLNode, parent: AVLNode?): AVLNode { + if (parent == null) root = node + else { + if (node.value > parent.value) parent.right = node + else parent.left = node + } + return node + } + + fun > restoreStruct(preOrder: MutableList) { + if (root != null) throw IncorrectUsage("The tree already exists") + for (vertex in preOrder) { + val currentNode = toNode(vertex) + val leaf = getLeafForInsert(currentNode.value) + linkNewNode(currentNode, leaf) + } + } +} diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLTree.kt b/lib/src/main/kotlin/treelib/avlTree/AVLTree.kt new file mode 100644 index 0000000..a5f1af3 --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLTree.kt @@ -0,0 +1,15 @@ +package treelib.avlTree + +import treelib.abstractTree.Tree + +import treelib.commonObjects.Container + +class AVLTree, V> : + Tree>, AVLStateContainer>, AVLVertex>>() { + + override val treeStruct = AVLStruct>() + + operator fun set(key: K, value: V) = putItem(key to value) + + override operator fun get(key: K) = getItem(key) +} diff --git a/lib/src/main/kotlin/treelib/avlTree/AVLVertex.kt b/lib/src/main/kotlin/treelib/avlTree/AVLVertex.kt new file mode 100644 index 0000000..774d064 --- /dev/null +++ b/lib/src/main/kotlin/treelib/avlTree/AVLVertex.kt @@ -0,0 +1,8 @@ +package treelib.avlTree + +import treelib.abstractTree.Vertex + +class AVLVertex>( + override val value: Pack, + val height: UInt, +) : Vertex() diff --git a/lib/src/main/kotlin/treelib/binTree/BINNode.kt b/lib/src/main/kotlin/treelib/binTree/BINNode.kt new file mode 100644 index 0000000..5b764ff --- /dev/null +++ b/lib/src/main/kotlin/treelib/binTree/BINNode.kt @@ -0,0 +1,9 @@ +package treelib.binTree + +import treelib.abstractTree.Node + +class BINNode>( + override var value: Pack, + override var left: BINNode? = null, + override var right: BINNode? = null, +) : Node> diff --git a/lib/src/main/kotlin/treelib/binTree/BINStateContainer.kt b/lib/src/main/kotlin/treelib/binTree/BINStateContainer.kt new file mode 100644 index 0000000..4a1266e --- /dev/null +++ b/lib/src/main/kotlin/treelib/binTree/BINStateContainer.kt @@ -0,0 +1,7 @@ +package treelib.binTree + +import treelib.abstractTree.StateContainer + +class BINStateContainer>( + override val contentNode: BINNode?, +) : StateContainer> diff --git a/lib/src/main/kotlin/treelib/binTree/BINStruct.kt b/lib/src/main/kotlin/treelib/binTree/BINStruct.kt new file mode 100644 index 0000000..b66ca85 --- /dev/null +++ b/lib/src/main/kotlin/treelib/binTree/BINStruct.kt @@ -0,0 +1,81 @@ +package treelib.binTree + +import treelib.abstractTree.TreeStruct +import treelib.commonObjects.exceptions.IncorrectUsage + +class BINStruct> : + TreeStruct, BINStateContainer, BINVertex>() { + + override var root: BINNode? = null + + override fun generateStateDelete( + deletedNode: BINNode?, + contentNode: BINNode? + ): BINStateContainer = BINStateContainer(deletedNode) + + override fun generateStateInsert( + insertNode: BINNode?, + contentNode: BINNode?, + ): BINStateContainer = BINStateContainer(insertNode) + + override fun generateStateFind( + findNode: BINNode?, + contentNode: BINNode?, + ): BINStateContainer = BINStateContainer(findNode) + + override fun getNodeKernel(node: BINNode) = BINNode(node.value) + + override fun connectUnlinkedSubTreeWithParent( + node: BINNode, + parent: BINNode?, + childForLink: BINNode?, + ) { + if (root == null) return + + if (parent != null) { + when { + (node.value < parent.value) -> parent.left = childForLink + (node.value > parent.value) -> parent.right = childForLink + } + } else root?.let { + root = childForLink + } + } + + override fun linkNewNode( + node: BINNode, + parent: BINNode?, + ): BINNode { + if (parent == null) root = node + else { + if (node.value > parent.value) parent.right = node + else parent.left = node + } + return node + } + + override fun toVertex(node: BINNode): BINVertex { + return BINVertex(node.value) + } + + override fun createNode(item: Pack) = BINNode(item) + + override fun delete(item: Pack) { + deleteItem(item).contentNode + } + + override fun insert(item: Pack) { + insertItem(item).contentNode + } + + private fun toNode(vertex: BINVertex): BINNode = BINNode(value = vertex.value) + + fun > restoreStruct(preOrder: List) { + if (root != null) throw IncorrectUsage("The tree already exists") + for (vertex in preOrder) { + val currentNode = toNode(vertex) + val leaf = getLeafForInsert(currentNode.value) + linkNewNode(currentNode, leaf) + } + } +} diff --git a/lib/src/main/kotlin/treelib/binTree/BINTree.kt b/lib/src/main/kotlin/treelib/binTree/BINTree.kt new file mode 100644 index 0000000..ebc792c --- /dev/null +++ b/lib/src/main/kotlin/treelib/binTree/BINTree.kt @@ -0,0 +1,14 @@ +package treelib.binTree + +import treelib.abstractTree.Tree +import treelib.commonObjects.Container + +class BINTree, V> + : Tree>, BINStateContainer>, BINVertex>>() { + + override val treeStruct = BINStruct>() + + operator fun set(key: K, value: V) = putItem(key to value) + + override operator fun get(key: K) = getItem(key) +} diff --git a/lib/src/main/kotlin/treelib/binTree/BINVertex.kt b/lib/src/main/kotlin/treelib/binTree/BINVertex.kt new file mode 100644 index 0000000..cb489a0 --- /dev/null +++ b/lib/src/main/kotlin/treelib/binTree/BINVertex.kt @@ -0,0 +1,5 @@ +package treelib.binTree + +import treelib.abstractTree.Vertex + +open class BINVertex>(override val value: Pack) : Vertex() \ No newline at end of file diff --git a/lib/src/main/kotlin/treelib/commonObjects/Container.kt b/lib/src/main/kotlin/treelib/commonObjects/Container.kt new file mode 100644 index 0000000..20530cf --- /dev/null +++ b/lib/src/main/kotlin/treelib/commonObjects/Container.kt @@ -0,0 +1,30 @@ +package treelib.commonObjects + +import kotlinx.serialization.Serializable + +@Serializable +data class Container, V>(val pair: Pair) : Comparable> { + + val key = pair.first + val value = pair.second + + override fun equals(other: Any?): Boolean { + if (this === other) + return true + + other as Container<*, *> + return this.pair.first == other.pair.first + } + + override fun hashCode(): Int { + return pair.hashCode() + } + + override fun compareTo(other: Container): Int { + return compareValuesBy(this, other) { it.pair.first } + } + + override fun toString(): String { + return "$key" + } +} diff --git a/lib/src/main/kotlin/treelib/commonObjects/exceptions/ImpossibleCaseException.kt b/lib/src/main/kotlin/treelib/commonObjects/exceptions/ImpossibleCaseException.kt new file mode 100644 index 0000000..568cf04 --- /dev/null +++ b/lib/src/main/kotlin/treelib/commonObjects/exceptions/ImpossibleCaseException.kt @@ -0,0 +1,18 @@ +package treelib.commonObjects.exceptions + +open class ImpossibleCaseException : Exception { + constructor() : super( + "This case is impossible, otherwise there is an error in the program code", + ) + + constructor(message: String) : super( + "This case is impossible, otherwise there is an error in the program code -> $message", + ) + + constructor(message: String, cause: Throwable) : super( + "This case is impossible, otherwise there is an error in the program code -> $message", + cause, + ) + + constructor(cause: Throwable) : super(cause) +} diff --git a/lib/src/main/kotlin/treelib/commonObjects/exceptions/IncorrectUsage.kt b/lib/src/main/kotlin/treelib/commonObjects/exceptions/IncorrectUsage.kt new file mode 100644 index 0000000..1bcd994 --- /dev/null +++ b/lib/src/main/kotlin/treelib/commonObjects/exceptions/IncorrectUsage.kt @@ -0,0 +1,18 @@ +package treelib.commonObjects.exceptions + +class IncorrectUsage : Exception { + constructor() : super( + "Incorrect use of the tree" + ) + + constructor(message: String) : super( + "$message", + ) + + constructor(message: String, cause: Throwable) : super( + "$message", + cause, + ) + + constructor(cause: Throwable) : super(cause) +} diff --git a/lib/src/main/kotlin/treelib/commonObjects/exceptions/VauleNotFound.kt b/lib/src/main/kotlin/treelib/commonObjects/exceptions/VauleNotFound.kt new file mode 100644 index 0000000..00efb04 --- /dev/null +++ b/lib/src/main/kotlin/treelib/commonObjects/exceptions/VauleNotFound.kt @@ -0,0 +1,18 @@ +package treelib.commonObjects.exceptions + +open class VauleNotFound : Exception { + constructor() : super( + "Value doesn't exist in the tree" + ) + + constructor(message: String) : super( + "Value doesn't exist in the tree $message", + ) + + constructor(message: String, cause: Throwable) : super( + "Value doesn't exist in the tree -> $message", + cause, + ) + + constructor(cause: Throwable) : super(cause) +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/DrawableVertex.kt b/lib/src/main/kotlin/treelib/databaseSave/DrawableVertex.kt new file mode 100644 index 0000000..28b2f52 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/DrawableVertex.kt @@ -0,0 +1,7 @@ +package treelib.databaseSave + +interface DrawableVertex> { + val value: Pack + val x: Double + val y: Double +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/DrawableBINVertex.kt b/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/DrawableBINVertex.kt new file mode 100644 index 0000000..1b4fb04 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/DrawableBINVertex.kt @@ -0,0 +1,11 @@ +package treelib.databaseSave.jsonFormat + +import treelib.databaseSave.DrawableVertex +import treelib.binTree.BINVertex + +class DrawableBINVertex>( + value: Pack, + override val x: Double = 0.0, + override val y: Double = 0.0 +) : BINVertex(value), DrawableVertex + diff --git a/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/JsonRepository.kt b/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/JsonRepository.kt new file mode 100644 index 0000000..c602da1 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/jsonFormat/JsonRepository.kt @@ -0,0 +1,50 @@ +package treelib.databaseSave.jsonFormat + +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import java.io.File + +class JsonRepository(private val dirPath: String) { + + init { + File(dirPath).mkdirs() + } + + fun > saveChanges( + preOrder: Array>, + fileName: String + ) { + + val gson = GsonBuilder().setPrettyPrinting().create() + val json = gson.toJson(preOrder) + + File(dirPath, fileName).run { + createNewFile() + writeText(json) + } + + } + + fun > exportTree( + fileName: String, + typeToken: TypeToken>> + ): Array> { + val gson = GsonBuilder().setPrettyPrinting().create() + val json = File(dirPath, fileName).readText() + + val preOrder = gson.fromJson>>(json, typeToken.type) + + return preOrder + } + + fun removeTree(treeName: String) { + File(dirPath, treeName).delete() + } + + fun clean() { + + File(dirPath).deleteRecursively() + } + + +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/neo4j/DrawableRBVertex.kt b/lib/src/main/kotlin/treelib/databaseSave/neo4j/DrawableRBVertex.kt new file mode 100644 index 0000000..285c858 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/neo4j/DrawableRBVertex.kt @@ -0,0 +1,12 @@ +package treelib.databaseSave.neo4j + +import treelib.databaseSave.DrawableVertex +import treelib.rbTree.Markers +import treelib.rbTree.RBVertex + +class DrawableRBVertex>( + value: Pack, + color: Markers, + override val x: Double = 0.0, + override val y: Double = 0.0 +) : RBVertex(value, color), DrawableVertex diff --git a/lib/src/main/kotlin/treelib/databaseSave/neo4j/Neo4jRepository.kt b/lib/src/main/kotlin/treelib/databaseSave/neo4j/Neo4jRepository.kt new file mode 100644 index 0000000..4770965 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/neo4j/Neo4jRepository.kt @@ -0,0 +1,265 @@ +package treelib.databaseSave.neo4j + + +import org.neo4j.driver.AuthTokens +import org.neo4j.driver.Driver +import org.neo4j.driver.GraphDatabase +import org.neo4j.driver.TransactionContext +import org.neo4j.driver.exceptions.SessionExpiredException +import treelib.commonObjects.Container +import treelib.rbTree.Markers +import java.io.Closeable +import java.io.IOException +import java.util.LinkedList +import kotlin.collections.HashSet + + +class Neo4jRepository : Closeable { + + private var driver: Driver? = null + + fun open(uri: String, username: String, password: String) { + try { + driver = GraphDatabase.driver(uri, AuthTokens.basic(username, password)) + } catch (ex: IllegalArgumentException) { + throw IOException() + } catch (ex: SessionExpiredException) { + throw IOException() + } + } + + fun > saveChanges( + preOrder: Array>, + inOrder: Array>, + treeName: String + ) { + + val session = driver?.session() ?: throw IOException() + + var inOrderIndex = 0 + var preOrderIndex = 0 + val set = HashSet>() + val stack = LinkedList>() + var id = 0 + + while (preOrderIndex in preOrder.indices) { + do { + val currentNode = preOrder[preOrderIndex] + if (preOrderIndex == 0) { + session.executeWrite { tx -> + //cleanDB(tx) + createRoot(tx, currentNode, id, treeName) + } + ++id + } + if (!stack.isEmpty()) { + if (set.contains(stack.peek())) { + set.remove(stack.peek()) + val parentNode = stack.pop() + session.executeWrite { tx -> + createRightSon(tx, parentNode, currentNode, id, treeName) + } + ++id + } else { + val parentNode = stack.peek() + parentNode.value as Container<*, *> + session.executeWrite { tx -> + createLeftSon(tx, parentNode, currentNode, id, treeName) + } + ++id + } + } + stack.push(currentNode) + } while (preOrder[preOrderIndex++].value != inOrder[inOrderIndex].value && preOrderIndex < preOrder.size) + + var currentNode: DrawableRBVertex? = null + + while (!stack.isEmpty() && inOrderIndex < inOrder.size && stack.peek().value == inOrder[inOrderIndex].value) { + currentNode = stack.pop() + ++inOrderIndex + } + + if (currentNode != null) { + set.add(currentNode) + stack.push(currentNode) + } + + } + + session.close() + } + + fun exportRBtree(treeName: String): Pair>>, List>>> { + + val session = driver?.session() ?: throw IOException() + var preOrder: List>> = listOf() + var inOrder: List>> = listOf() + + session.executeRead { tx -> + preOrder = tx.run( + "MATCH (node: Node {treeName: \$name}) " + + "RETURN node.value, node.key, node.color, node.x, node.y " + + "ORDER BY node.id", + mutableMapOf("name" to treeName) as Map? + ).list() + .map { + DrawableRBVertex( + value = Container(Pair(it.values()[1].toString().toInt(), it.values()[0].toString().replace("\"", ""))), + color = if (it.values()[2].toString().replace("\"", "") == "RED") Markers.RED else Markers.BLACK, + x = it.values()[3].toString().toDouble(), + y = it.values()[4].toString().toDouble() + ) + } + + inOrder = tx.run( + "MATCH (node: Node {treeName: \$name}) " + + "RETURN node.value, node.key, node.color, node.x, node.y " + + "ORDER BY node.key", + mutableMapOf("name" to treeName) as Map? + ).list() + .map { + DrawableRBVertex( + value = Container(Pair(it.values()[1].toString().toInt(), it.values()[0].toString())), + color = if (it.values()[2].toString() == """RED""") Markers.RED else Markers.BLACK, + x = it.values()[3].toString().toDouble(), + y = it.values()[4].toString().toDouble() + ) + } + } + + session.close() + + return Pair(preOrder, inOrder) + + } + + fun removeTree(treeName: String) { + + val session = driver?.session() ?: throw IOException() + + session.executeWrite { tx -> + tx.run( + "MATCH (n: Node {treeName: \$name}) DETACH DELETE n", + mutableMapOf("name" to treeName) as Map? + ) + } + + } + + fun findNamesTrees(): List { + val session = driver?.session() ?: throw IOException() + var treesNames: List = listOf() + session.executeRead { tx -> + treesNames = tx.run("MATCH (n: Node) WHERE NOT(:Node)-->(n) RETURN n.treeName") + .list().map { it.values()[0].toString().replace("\"", "") }.filter { it != "null" } + } + + return treesNames + } + + fun clean() { + val session = driver?.session() ?: throw IOException() + + session.executeWrite { tx -> + tx.run("MATCH (n: Node) DETACH DELETE n") + } + } + + private fun > createRoot( + tx: TransactionContext, + rootNode: DrawableRBVertex, + id: Int, + treeName: String + ) { + rootNode.value as Container<*, *> + tx.run( + "MERGE(:Node {value: \$nodeValue, key: \$nodeKey, color: \$nodeColor, x: \$nodeX, y: \$nodeY, id: \$nodeID, treeName: \$name})", + mutableMapOf( + "nodeValue" to rootNode.value.pair.second, + "nodeKey" to rootNode.value.pair.first, + "nodeColor" to rootNode.color.toString(), + "nodeX" to rootNode.x, + "nodeY" to rootNode.y, + "nodeID" to id, + "name" to treeName + ) + ) + } + + private fun > createRightSon( + tx: TransactionContext, parentNode: DrawableRBVertex, + currentNode: DrawableRBVertex, id: Int, treeName: String + ) { + parentNode.value as Container<*, *> + currentNode.value as Container<*, *> + tx.run( + "MERGE(parent:Node {value: \$parentNodeValue, key: \$parentNodeKey, " + + "color: \$parentNodeColor, x: \$parentNodeX, y: \$parentNodeY, treeName: \$name}) " + + "MERGE(son:Node {value: \$nodeValue, key: \$nodeKey, color: \$nodeColor, x: \$nodeX, y: \$nodeY, id: \$nodeID, treeName: \$name}) " + + "MERGE (parent)-[:RIGHT_SON]->(son)", + mutableMapOf( + "parentNodeValue" to parentNode.value.pair.second, + "parentNodeKey" to parentNode.value.pair.first, + "parentNodeColor" to parentNode.color.toString(), + "parentNodeX" to parentNode.x, + "parentNodeY" to parentNode.y, + "nodeValue" to currentNode.value.pair.second, + "nodeKey" to currentNode.value.pair.first, + "nodeColor" to currentNode.color.toString(), + "nodeX" to currentNode.x, + "nodeY" to currentNode.y, + "nodeID" to id, + "name" to treeName + ) + ) + } + + private fun > createLeftSon( + tx: TransactionContext, parentNode: DrawableRBVertex, + currentNode: DrawableRBVertex, id: Int, treeName: String + ) { + parentNode.value as Container<*, *> + currentNode.value as Container<*, *> + tx.run( + "MERGE(parent:Node {value: \$parentNodeValue, key: \$parentNodeKey, " + + "color: \$parentNodeColor, x: \$parentNodeX, y: \$parentNodeY, treeName: \$name}) " + + "MERGE(son:Node {value: \$nodeValue, key: \$nodeKey, color: \$nodeColor, x: \$nodeX, y: \$nodeY, id: \$nodeID, treeName: \$name}) " + + "MERGE (parent)-[:LEFT_SON]->(son)", + mutableMapOf( + "parentNodeValue" to parentNode.value.pair.second, + "parentNodeKey" to parentNode.value.pair.first, + "parentNodeColor" to parentNode.color.toString(), + "parentNodeX" to parentNode.x, + "parentNodeY" to parentNode.y, + "nodeValue" to currentNode.value.pair.second, + "nodeKey" to currentNode.value.pair.first, + "nodeColor" to currentNode.color.toString(), + "nodeX" to currentNode.x, + "nodeY" to currentNode.y, + "nodeID" to id, + "name" to treeName + ) + ) + } + + fun findTree(treeName: String): Boolean { + val session = driver?.session() ?: throw IOException() + var name = "" + + session.executeRead { tx -> + name = tx.run( + "MATCH (n: Node {treeName: \$treeName}) WHERE NOT (:Node)-->(n) RETURN n.treeName", + mutableMapOf( + "treeName" to treeName + ) as Map? + ).list().singleOrNull()?.values()?.get(0)?.toString()?.replace("\"", "") ?: "" + + } + return name == treeName + + } + + override fun close() { + driver?.close() + } +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/DrawableAVLVertex.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/DrawableAVLVertex.kt new file mode 100644 index 0000000..ef62b62 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/DrawableAVLVertex.kt @@ -0,0 +1,10 @@ +package treelib.databaseSave.sqlite + +import treelib.databaseSave.DrawableVertex + +class DrawableAVLVertex>( + override val value: Pack, + override val x: Double = 0.0, + override val y: Double = 0.0, + val height: UInt, +) : DrawableVertex diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryExposed.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryExposed.kt new file mode 100644 index 0000000..ad2c32b --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryExposed.kt @@ -0,0 +1,121 @@ +package treelib.databaseSave.sqlite + +import treelib.databaseSave.sqlite.treeEntities.TreeTableEntity +import treelib.databaseSave.sqlite.treeEntities.TreesTable +import treelib.databaseSave.sqlite.vertexEntities.VertexTable +import treelib.databaseSave.sqlite.vertexEntities.VertexTableEntity +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction +import java.io.File +import java.sql.SQLException + +class SQLiteRepositoryExposed { + private var db: Database? = null + var dbName: String? = null + private set + + fun initDataBase(name: String) { + if ((dbName != null) && (dbName == name)) return + + File(System.getProperty("user.dir") + "/sqliteDB").mkdirs() + + db = Database.connect("jdbc:sqlite:sqliteDB/$name", driver = "org.sqlite.JDBC") + + if (!isEmptyDB()) createTables() + } + + + private fun createTables() { + transaction(db) { + when { + !TreesTable.exists() && !VertexTable.exists() -> { + SchemaUtils.create(TreesTable) + SchemaUtils.create(VertexTable) + } + + !TreesTable.exists() -> SchemaUtils.create(TreesTable) + !VertexTable.exists() -> SchemaUtils.create(VertexTable) + else -> {} + } + } + } + + private fun interIsTreeExist(name: String): Int? { + val id = TreeTableEntity.find(TreesTable.name eq name).firstOrNull()?.id ?: return null + return id.value + } + + private fun interDelete(name: String) { + val treeId = TreeTableEntity.find(TreesTable.name eq name).firstOrNull()?.id ?: return + TreeTableEntity.find(TreesTable.name eq name).firstOrNull()?.delete() + + VertexTableEntity.find(VertexTable.tree eq treeId.value).firstOrNull() ?: return + VertexTable.deleteWhere { tree eq treeId.value } + return + } + + fun > saveTree( + treeName: String, + vertexes: MutableList>, + serializeData: (input: Pack) -> String, + ): Unit = transaction(db) { + interDelete(treeName) + + val id = TreeTableEntity.new { + name = treeName + } + + for (index in vertexes.indices) VertexTableEntity.new { + height = vertexes[index].height.toInt() + value = serializeData(vertexes[index].value) + order = index + x = vertexes[index].x + y = vertexes[index].y + tree = id.id.value + } + } + + fun getAllSavedTrees(): List = transaction(db) { + return@transaction TreeTableEntity.all().map { el -> el.name } + } + + fun > getTree( + name: String, + deSerializeData: (input: String) -> Pack, + ): MutableList> = transaction(db) { + val ans = mutableListOf>() + val treeId = interIsTreeExist(name) ?: throw SQLException("Tree doesn't exist") + + for (el in VertexTableEntity.find(VertexTable.tree eq treeId).orderBy(VertexTable.order to SortOrder.ASC)){ + ans.add( + DrawableAVLVertex( + value = deSerializeData(el.value), + height = el.height.toUInt(), + x = el.x, + y = el.y + ) + ) + } + return@transaction ans + } + + fun deleteTree(name: String): Boolean = + transaction(db) { + val treeId = TreeTableEntity.find(TreesTable.name eq name).firstOrNull()?.id ?: return@transaction false + TreeTableEntity.find(TreesTable.name eq name).firstOrNull()?.delete() + + VertexTableEntity.find(VertexTable.tree eq treeId.value).firstOrNull() ?: return@transaction false + VertexTable.deleteWhere { tree eq treeId.value } + return@transaction true + } + + fun isEmptyDB(): Boolean = transaction(db) { + if (TreesTable.exists() && VertexTable.exists()) return@transaction true + else false + } + + fun isTreeExist(name: String): Boolean = transaction(db) { + return@transaction TreeTableEntity.find(TreesTable.name eq name).firstOrNull() != null + } +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryJDBC.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryJDBC.kt new file mode 100644 index 0000000..29d5c9d --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/SQLiteRepositoryJDBC.kt @@ -0,0 +1,227 @@ +package treelib.databaseSave.sqlite + +import java.io.Closeable +import java.sql.DriverManager +import java.sql.SQLException + +class SQLiteRepositoryJDBC>( + private val dbPath: String, + private val serializeData: (input: Pack) -> String, + private val deSerializeData: (input: String) -> Pack, + private val logErrorMethod: (input: Exception) -> Unit = { throw it }, + private val logInfoMethod: (input: String) -> Unit = { /* Nothing to do */ }, +) : Closeable { + + private val treeTable = "AVLTreesTable" + private val avlTreeName = "name" + + private val value = "value" + private val height = "height" + private val xCord = "x" + private val yCord = "y" + + private val dbDriver = "jdbc:sqlite" + private val connection = DriverManager.getConnection("$dbDriver:$dbPath") + ?: throw SQLException("Cannot connect to database") + + init { + createTreeTable() + } + + fun createTreeTable() { + connection.createStatement().also { stmt -> + try { + stmt.execute("CREATE TABLE if not exists $treeTable(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name text);") + logInfoMethod("Table with trees created or already exists") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + } + } + + fun clearTreeTable() { + connection.createStatement().also { stmt -> + try { + stmt.execute("DELETE FROM $treeTable;") + logInfoMethod("TreeTable: $treeTable has been deleted") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + } + } + + fun addTree(treeName: String) { + val isInDB = isNameInDB(treeName, avlTreeName, treeTable) + + if (isInDB) { + logInfoMethod("Tree - $treeName, have been exist yet in treeTable - $treeTable") + return + } + + connection.createStatement().also { stmt -> + try { + stmt.execute("CREATE TABLE if not exists $treeName(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, $value text, $height INT, $xCord DOUBLE, $yCord DOUBLE);") + stmt.execute("INSERT INTO $treeTable ($avlTreeName) VALUES ('$treeName');") + logInfoMethod("Was created Tree: $treeName in table: $treeTable") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + } + } + + fun getTreeNames(): MutableList { + val info = mutableListOf() + connection.createStatement().also { stmt -> + try { + val result = stmt.executeQuery("SELECT $treeTable.$avlTreeName as $avlTreeName FROM $treeTable;") + while (result.next()) { + info.add(result.getString(avlTreeName)) + } + logInfoMethod("Available tree is given") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + return info + } + } + + fun deleteTree(treeName: String) { + val deleteId = getTreeId(treeName) + if (deleteId == 0) return + + connection.createStatement().also { stmt -> + try { + stmt.execute("DROP TABLE $treeName;") + stmt.execute("DELETE FROM $treeTable WHERE id=$deleteId;") + logInfoMethod("Tree: $treeName has been deleted") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + } + } + + fun getTreeId(treeName: String): Int { + var id: Int? = null + try { + val statement = connection.prepareStatement("SELECT id FROM $treeTable WHERE name=?;") + statement.setString(1, treeName) + id = statement.executeQuery().getInt(1) + statement.close() + } catch (ex: SQLException) { + logErrorMethod(ex) + } + if (id != null) return id + else throw SQLException("Impossible case") + } + + fun addVertex(avlDVertex: DrawableAVLVertex, treeName: String) { + val isInDB = getVertexId(avlDVertex, treeName) + if (isInDB != 0) { + deleteVertex(isInDB, treeName) + logInfoMethod("Attempt write duplicate of the vertex: value = ${avlDVertex.value}; hieght = ${avlDVertex.height}; x = ${avlDVertex.x}; y = ${avlDVertex.y}") + } + + try { + val stmt = connection.prepareStatement("INSERT INTO $treeName (value, height, x, y) VALUES (?, ?, ?, ?);") + val info = serializeData(avlDVertex.value) + stmt.setString(1, info) + stmt.setInt(2, avlDVertex.height.toInt()) + stmt.setDouble(3, avlDVertex.x) + stmt.setDouble(4, avlDVertex.y) + stmt.execute() + stmt.close() + logInfoMethod("Vertex: value = $info has been saved") + } catch (ex: SQLException) { + logErrorMethod(ex) + } + + } + + fun addVertexes(list: MutableList>, treeName: String) { + for (el in list) addVertex(el, treeName) + } + + fun deleteVertex(id: Int, treeName: String) { + try { + val stmt = connection.prepareStatement("DELETE FROM $treeName WHERE id=?;") + stmt.setInt(1, id) + stmt.execute() + stmt.close() + logInfoMethod("Element: id = $id has been deleted in table: $treeName") + } catch (ex: SQLException) { + logErrorMethod(ex) + } + } + + fun getAllVertexes(treeName: String): MutableList> { + val info = mutableListOf>() + connection.createStatement().also { stmt -> + try { + val result = + stmt.executeQuery("SELECT $treeName.$value as $value, $treeName.$height as $height, $treeName.$xCord as $xCord, $treeName.$yCord as $yCord FROM $treeName;") + while (result.next()) { + info.add( + DrawableAVLVertex( + value = deSerializeData(result.getString(value)), + height = result.getInt(height).toUInt(), + x = result.getDouble(xCord), + y = result.getDouble(yCord), + ) + ) + } + logInfoMethod("Vertexes from $treeName have been received") + } catch (ex: SQLException) { + logErrorMethod(ex) + } finally { + stmt.close() + } + return info + } + } + + private fun getVertexId(vertex: DrawableAVLVertex, tableName: String): Int { + var id: Int? = null + try { + val stmt = + connection.prepareStatement("SELECT id FROM $tableName WHERE value=? AND height=? AND x=? AND y=?;") + stmt.setString(1, serializeData(vertex.value)) + stmt.setInt(2, vertex.height.toInt()) + stmt.setDouble(3, vertex.x) + stmt.setDouble(4, vertex.y) + id = stmt.executeQuery().getInt(1) + stmt.close() + } catch (ex: SQLException) { + logErrorMethod(ex) + } + if (id != null) return id + else throw SQLException("Impossible case") + } + + private fun isNameInDB(rowName: String, columnName: String, tableName: String): Boolean { + var isInDB: Boolean? = null + try { + val statement = connection.prepareStatement("SELECT EXISTS(SELECT 1 FROM $tableName WHERE $columnName=?);") + statement.setString(1, rowName) + isInDB = statement.executeQuery().getBoolean(1) + statement.close() + } catch (ex: SQLException) { + logErrorMethod(ex) + } + if (isInDB != null) return isInDB + else throw SQLException("Impossible case") + } + + override fun close() { + connection.close() + } +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreeTableEntity.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreeTableEntity.kt new file mode 100644 index 0000000..dcb96b2 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreeTableEntity.kt @@ -0,0 +1,11 @@ +package treelib.databaseSave.sqlite.treeEntities + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class TreeTableEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(TreesTable) + + var name by TreesTable.name +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreesTable.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreesTable.kt new file mode 100644 index 0000000..32daf7d --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/treeEntities/TreesTable.kt @@ -0,0 +1,13 @@ +package treelib.databaseSave.sqlite.treeEntities + +import org.jetbrains.exposed.dao.id.IntIdTable + + +object TreesTable : IntIdTable("trees") { + + var name = text("name") + + init { + uniqueIndex(name) + } +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTable.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTable.kt new file mode 100644 index 0000000..2607412 --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTable.kt @@ -0,0 +1,13 @@ +package treelib.databaseSave.sqlite.vertexEntities + +import org.jetbrains.exposed.dao.id.IntIdTable + + +object VertexTable : IntIdTable("vertex") { + var height = integer("height") + var value = text("data") + var order = integer("orderId") + var x = double("xCord") + var y = double("yCord") + var tree = integer("treeId") +} diff --git a/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTableEntity.kt b/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTableEntity.kt new file mode 100644 index 0000000..3b8461f --- /dev/null +++ b/lib/src/main/kotlin/treelib/databaseSave/sqlite/vertexEntities/VertexTableEntity.kt @@ -0,0 +1,16 @@ +package treelib.databaseSave.sqlite.vertexEntities + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class VertexTableEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(VertexTable) + + var height by VertexTable.height + var value by VertexTable.value + var order by VertexTable.order + var x by VertexTable.x + var y by VertexTable.y + var tree by VertexTable.tree +} diff --git a/lib/src/main/kotlin/treelib/rbTree/Markers.kt b/lib/src/main/kotlin/treelib/rbTree/Markers.kt new file mode 100644 index 0000000..7ae37fc --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/Markers.kt @@ -0,0 +1,5 @@ +package treelib.rbTree + +enum class Markers { + RED, BLACK +} diff --git a/lib/src/main/kotlin/treelib/rbTree/RBBalancer.kt b/lib/src/main/kotlin/treelib/rbTree/RBBalancer.kt new file mode 100644 index 0000000..6c20f2f --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBBalancer.kt @@ -0,0 +1,344 @@ +package treelib.rbTree + +import treelib.abstractTree.balanced.BalancerParent + + +class RBBalancer>(var root: RBNode?) : + BalancerParent, RBStateContainer>() { + + init { + root?.color = Markers.BLACK + } + + private fun getUncle(node: RBNode): RBNode? { + val parent = node.parent + return when (parent?.parent?.left) { + parent -> parent?.parent?.right + else -> parent?.parent?.left + } + } + + private fun getBrother(parent: RBNode?, node: RBNode?): RBNode? { + return if (parent?.right == node) + parent?.left + else + parent?.right + } + + private fun getRoot(node: RBNode): RBNode { + var currentNode = node + while (currentNode.parent != null) + currentNode = currentNode.parent ?: throw NullPointerException() + root = currentNode + root?.color = Markers.BLACK + return currentNode + } + + private fun nodeIsLeaf(node: RBNode?): Boolean { + if (node == null) + throw NullPointerException() + return node.right == null && node.left == null + } + + override fun balance(state: RBStateContainer): RBNode { + val node = state.contentNode + ?: throw InternalError() + val uncle = getUncle(node) + when { + /** node insertion case **/ + node.color == Markers.RED && node.right == null && node.left == null -> { + var currentNode = node + + if (currentNode.parent?.color == Markers.RED && uncle?.color == Markers.RED) { + currentNode = afterInsert(node) + } + if (currentNode.parent?.color != Markers.RED) { + return getRoot(currentNode) + } + + var parent = + currentNode.parent ?: throw IllegalStateException() + when (parent) { + parent.parent?.left -> { + if (currentNode == parent.right) { + leftRotate(parent) + currentNode = parent + } + parent = + currentNode.parent?.parent ?: throw InternalError() + currentNode = rightRotate(parent) + currentNode.color = Markers.BLACK + currentNode.right?.color = Markers.RED + currentNode.left?.color = Markers.RED + } + + parent.parent?.right -> { + if (currentNode == parent.left) { + rightRotate(parent) + currentNode = parent + } + parent = + currentNode.parent?.parent ?: throw InternalError() + currentNode = leftRotate(parent) + currentNode.color = Markers.BLACK + currentNode.right?.color = Markers.RED + currentNode.left?.color = Markers.RED + } + + else -> throw IllegalStateException() + } + if (currentNode.parent == null) + root = currentNode + return root ?: throw NullPointerException() + } + /** node removal cases **/ + node.color == Markers.RED && (node.right != null || node.left != null) -> { + when { + /** black leaf removal case **/ + node.right?.color == Markers.BLACK -> { + return firstCase(node, node.left) + } + + node.left?.color == Markers.BLACK -> { + return firstCase(node, node.right) + } + } + + } + + node.color == Markers.BLACK -> { + return when { + /** red leaf removal case **/ + (node.left == null && node.right == null) || + (node.left == null && node.right?.color == Markers.RED && nodeIsLeaf(node.right)) || + (node.right == null && node.left?.color == Markers.RED && nodeIsLeaf(node.left)) -> { + getRoot(node) + } + /** black leaf removal case **/ + node.left == null || node.right == null -> { + firstCase(node, null) + + } + + node.left?.color == Markers.RED && node.right?.color == Markers.RED -> { + if (nodeIsLeaf(node.right)) { + node.right?.color = Markers.BLACK + } + else { + node.left?.color = Markers.BLACK + } + getRoot(node) + } + + node.left?.color == Markers.RED -> { + if (node.right?.right?.color != Markers.BLACK && node.right?.left?.color != Markers.BLACK) { + node.left?.color = Markers.BLACK + } + getRoot(node) + } + + node.right?.color == Markers.RED -> { + if (node.left?.right?.color != Markers.BLACK && node.left?.left?.color != Markers.BLACK) { + node.right?.color = Markers.BLACK + } + getRoot(node) + } + + else -> throw IllegalStateException() + } + } + } + throw IllegalStateException() + } + + private fun afterInsert(node: RBNode): RBNode { + var currentNode = node + while (currentNode.parent?.color == Markers.RED) { + val uncle = getUncle(currentNode) + if (uncle?.color == Markers.RED) { + currentNode.parent?.color = Markers.BLACK + currentNode = currentNode.parent?.parent ?: throw InternalError() + currentNode.color = Markers.RED + uncle.color = Markers.BLACK + } else if (uncle != null) { + return currentNode + } + } + root?.color = Markers.BLACK + return currentNode + } + + /** black node removal case **/ + private fun firstCase(parent: RBNode?, node: RBNode?): RBNode { + return when { + parent == null && node == null -> throw NullPointerException() + parent != null -> { + when (parent.color) { + Markers.RED -> secondCase(parent, node) + Markers.BLACK -> thirdCase(parent, node) + } + getRoot(parent) + } + + else -> getRoot(node ?: throw InternalError()) + } + } + + /** parent is red **/ + private fun secondCase(parent: RBNode, node: RBNode?) { + var brother = getBrother(parent, node) ?: throw InternalError() + if (brother.color == Markers.RED) + throw NullPointerException() + + if (brother.right?.color != Markers.RED && brother.left?.color != Markers.RED) { + brother.color = Markers.RED + parent.color = Markers.BLACK + return + } + + when (node) { + parent.left -> { + if (brother.right?.color == Markers.RED) { + leftRotate(parent) + brother.left?.color = Markers.RED + brother.right?.color = Markers.RED + brother.color = Markers.BLACK + } else if (brother.left?.color == Markers.RED) { + brother = rightRotate(brother) + leftRotate(parent) + brother.left?.color = Markers.BLACK + brother.left?.color = Markers.BLACK + brother.color = Markers.RED + } else { + throw IllegalStateException() + } + } + + parent.right -> { + if (brother.left?.color == Markers.RED) { + rightRotate(parent) + brother.color = Markers.BLACK + brother.left?.color = Markers.RED + brother.right?.color = Markers.RED + } else if (brother.right?.color == Markers.RED) { + brother = leftRotate(brother) + rightRotate(parent) + brother.color = Markers.RED + brother.left?.color = Markers.BLACK + brother.right?.color = Markers.BLACK + } else { + throw IllegalStateException() + } + + } + + else -> throw IllegalStateException() + } + } + + /** parent is black **/ + private fun thirdCase(parent: RBNode, node: RBNode?) { + val brother = getBrother(parent, node) ?: throw InternalError() + when (brother.color) { + Markers.RED -> thirdCaseSubFirst(brother, parent) + Markers.BLACK -> thirdCaseSubSecond(brother, parent) + } + } + + /** black parent and red brother **/ + private fun thirdCaseSubFirst(brother: RBNode, parent: RBNode) { + when (brother) { + brother.parent?.left -> { + var rightBrotherSon = brother.right ?: throw InternalError() + + if (rightBrotherSon.right?.color != Markers.RED && rightBrotherSon.left?.color != Markers.RED) { + rightBrotherSon.color = Markers.RED + brother.color = Markers.BLACK + rightRotate(parent) + return + } + /** if the right son of the right son of the brother is red, then we make it the right son + * of the brother, and the right son of the brother is the left son of the new right son + * of the brother and repaint it red so that the following condition works **/ + + if (rightBrotherSon.right?.color == Markers.RED) { + rightBrotherSon.color = Markers.RED + leftRotate(rightBrotherSon) + + rightBrotherSon = + rightBrotherSon.parent ?: throw InternalError() + rightBrotherSon.color = Markers.BLACK + } + + if (rightBrotherSon.left?.color == Markers.RED) { + rightBrotherSon.left?.color = Markers.BLACK + leftRotate(brother) + rightRotate(parent) + } + } + + brother.parent?.right -> { + var leftBrotherSon = brother.left ?: throw NullPointerException() + if (leftBrotherSon.right?.color != Markers.RED && leftBrotherSon.left?.color != Markers.RED) { + leftBrotherSon.color = Markers.RED + brother.color = Markers.BLACK + leftRotate(brother.parent ?: throw InternalError()) + return + } + + if (leftBrotherSon.left?.color == Markers.RED) { + rightRotate(leftBrotherSon) + leftBrotherSon.color = Markers.RED + leftBrotherSon = + leftBrotherSon.parent ?: throw InternalError() + leftBrotherSon.color = Markers.BLACK + } + + if (leftBrotherSon.right?.color == Markers.RED) { + leftBrotherSon.right?.color = Markers.BLACK + rightRotate(brother) + leftRotate(parent) + } + } + + else -> throw IllegalStateException() + } + } + + /** black parent and black brother **/ + private fun thirdCaseSubSecond(brother: RBNode, parent: RBNode) { + /** if the brother hasn't read children, restart + * from the parent (the height in the subtree has decreased by 1) **/ + + if (brother.left?.color != Markers.RED && brother.right?.color != Markers.RED) { + brother.color = Markers.RED + firstCase(parent.parent, parent) + return + } + when { + brother.left?.color == Markers.RED -> { + brother.left?.color = Markers.BLACK + if (brother == parent.left) { + rightRotate(parent) + } else { + rightRotate(brother) + leftRotate(parent) + } + } + + brother.right?.color == Markers.RED -> { + brother.right?.color = Markers.BLACK + if (brother == parent.right) { + leftRotate(parent) + } else { + leftRotate(brother) + rightRotate(parent) + } + + } + + else -> throw IllegalStateException() + } + } + +} diff --git a/lib/src/main/kotlin/treelib/rbTree/RBNode.kt b/lib/src/main/kotlin/treelib/rbTree/RBNode.kt new file mode 100644 index 0000000..f62abe2 --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBNode.kt @@ -0,0 +1,11 @@ +package treelib.rbTree + +import treelib.abstractTree.NodeParent + +class RBNode>( + override var value: Pack, + override var left: RBNode? = null, + override var right: RBNode? = null, + override var parent: RBNode? = null, + var color: Markers = Markers.RED, +) : NodeParent> diff --git a/lib/src/main/kotlin/treelib/rbTree/RBStateContainer.kt b/lib/src/main/kotlin/treelib/rbTree/RBStateContainer.kt new file mode 100644 index 0000000..18e01e4 --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBStateContainer.kt @@ -0,0 +1,7 @@ +package treelib.rbTree + +import treelib.abstractTree.StateContainer + +class RBStateContainer>( + override val contentNode: RBNode?, +) : StateContainer> diff --git a/lib/src/main/kotlin/treelib/rbTree/RBStruct.kt b/lib/src/main/kotlin/treelib/rbTree/RBStruct.kt new file mode 100644 index 0000000..090b5d1 --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBStruct.kt @@ -0,0 +1,126 @@ +package treelib.rbTree + +import treelib.abstractTree.balanced.BalancedTreeStruct +import treelib.commonObjects.exceptions.ImpossibleCaseException +import java.util.* + +class RBStruct> : + BalancedTreeStruct, RBStateContainer, RBVertex, RBBalancer>() { + + override var root: RBNode? = null + + override val balancer = RBBalancer(root) + + override fun generateStateDelete( + deletedNode: RBNode?, + contentNode: RBNode?, + ): RBStateContainer = RBStateContainer(contentNode) + + override fun generateStateInsert( + insertNode: RBNode?, + contentNode: RBNode?, + ): RBStateContainer = RBStateContainer(insertNode) + + override fun generateStateFind( + findNode: RBNode?, + contentNode: RBNode?, + ): RBStateContainer = RBStateContainer(findNode) + + override fun connectUnlinkedSubTreeWithParent( + node: RBNode, + parent: RBNode?, + childForLink: RBNode? + ) { + if (root == null) return + + if (parent != null) { + when { + (node.value < parent.value) -> { + parent.left = childForLink + } + + (node.value > parent.value) -> { + parent.right = childForLink + } + } + if (childForLink != null) { + childForLink.parent = parent + } + } else root?.let { + root = childForLink + if (childForLink != null) childForLink.parent = null + } + } + + override fun getNodeKernel(node: RBNode): RBNode = RBNode(node.value, color = node.color) + + override fun toVertex(node: RBNode): RBVertex { + return RBVertex(node.value, node.color) + } + + override fun createNode(item: Pack): RBNode = RBNode(item) + + override fun linkNewNode(node: RBNode, parent: RBNode?): RBNode { + if (parent == null) { + root = node + root?.let { + it.color = Markers.BLACK + } ?: throw ImpossibleCaseException() + } else { + if (node.value > parent.value) parent.right = node + else parent.left = node + node.parent = parent + } + return node + } + + fun > restoreStruct(preOrder: List, inOrder: List) { + var inOrderIndex = 0 + var preOrderIndex = 0 + val set = HashSet>() + val stack = LinkedList>() + + while (preOrderIndex in preOrder.indices) { + var currentNode: RBNode? + var drawNode: RBVertexType + + do { + drawNode = preOrder[preOrderIndex] + currentNode = createRBNode(drawNode) + if (root == null) { + root = currentNode + } + if (!stack.isEmpty()) { + if (set.contains(stack.peek())) { + set.remove(stack.peek()) + currentNode.parent = stack.peek() + stack.pop().right = currentNode + } else { + stack.peek().left = currentNode + currentNode.parent = stack.peek() + } + } + stack.push(currentNode) + } while (preOrder[preOrderIndex++].value != inOrder[inOrderIndex].value && preOrderIndex < preOrder.size) + + currentNode = null + while (!stack.isEmpty() && inOrderIndex < inOrder.size && + stack.peek().value == inOrder[inOrderIndex].value + ) { + currentNode = stack.pop() + ++inOrderIndex + } + + if (currentNode != null) { + set.add(currentNode) + stack.push(currentNode) + } + } + balancer.root = root + } + + private fun > createRBNode(drawNode: RBVertexType): RBNode { + val node = RBNode(value = drawNode.value, color = drawNode.color) + return node + } +} diff --git a/lib/src/main/kotlin/treelib/rbTree/RBTree.kt b/lib/src/main/kotlin/treelib/rbTree/RBTree.kt new file mode 100644 index 0000000..829b42f --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBTree.kt @@ -0,0 +1,15 @@ +package treelib.rbTree + +import treelib.abstractTree.Tree + +import treelib.commonObjects.Container + +class RBTree, V> : + Tree>, RBStateContainer>, RBVertex>>() { + + override val treeStruct = RBStruct>() + + operator fun set(key: K, value: V) = putItem(key to value) + + override operator fun get(key: K) = getItem(key) +} diff --git a/lib/src/main/kotlin/treelib/rbTree/RBVertex.kt b/lib/src/main/kotlin/treelib/rbTree/RBVertex.kt new file mode 100644 index 0000000..f2e4609 --- /dev/null +++ b/lib/src/main/kotlin/treelib/rbTree/RBVertex.kt @@ -0,0 +1,8 @@ +package treelib.rbTree + +import treelib.abstractTree.Vertex + +open class RBVertex>( + override val value: Pack, + val color: Markers, +) : Vertex() diff --git a/lib/src/main/resources/appIcon.png b/lib/src/main/resources/appIcon.png new file mode 100644 index 0000000..47bdd13 Binary files /dev/null and b/lib/src/main/resources/appIcon.png differ diff --git a/lib/src/main/resources/avl_im.png b/lib/src/main/resources/avl_im.png new file mode 100644 index 0000000..fa30eb8 Binary files /dev/null and b/lib/src/main/resources/avl_im.png differ diff --git a/lib/src/main/resources/drawable/dir.png b/lib/src/main/resources/drawable/dir.png new file mode 100644 index 0000000..f6f65e7 Binary files /dev/null and b/lib/src/main/resources/drawable/dir.png differ diff --git a/lib/src/main/resources/drawable/go-back-arrow.png b/lib/src/main/resources/drawable/go-back-arrow.png new file mode 100644 index 0000000..97d8c5b Binary files /dev/null and b/lib/src/main/resources/drawable/go-back-arrow.png differ diff --git a/lib/src/main/resources/drawable/jsonFormat.png b/lib/src/main/resources/drawable/jsonFormat.png new file mode 100644 index 0000000..71582ae Binary files /dev/null and b/lib/src/main/resources/drawable/jsonFormat.png differ diff --git a/lib/src/main/resources/drawable/maximize.png b/lib/src/main/resources/drawable/maximize.png new file mode 100644 index 0000000..906ffe3 Binary files /dev/null and b/lib/src/main/resources/drawable/maximize.png differ diff --git a/lib/src/main/resources/drawable/minimize.png b/lib/src/main/resources/drawable/minimize.png new file mode 100644 index 0000000..c4abda9 Binary files /dev/null and b/lib/src/main/resources/drawable/minimize.png differ diff --git a/lib/src/main/resources/drawable/neo4jFormat.png b/lib/src/main/resources/drawable/neo4jFormat.png new file mode 100644 index 0000000..26063a8 Binary files /dev/null and b/lib/src/main/resources/drawable/neo4jFormat.png differ diff --git a/lib/src/main/resources/drawable/save.png b/lib/src/main/resources/drawable/save.png new file mode 100644 index 0000000..aba7b4e Binary files /dev/null and b/lib/src/main/resources/drawable/save.png differ diff --git a/lib/src/main/resources/drawable/sqlFormat.png b/lib/src/main/resources/drawable/sqlFormat.png new file mode 100644 index 0000000..625793a Binary files /dev/null and b/lib/src/main/resources/drawable/sqlFormat.png differ diff --git a/lib/src/main/resources/font/HelveticaMedium.ttf b/lib/src/main/resources/font/HelveticaMedium.ttf new file mode 100644 index 0000000..cf97ecc Binary files /dev/null and b/lib/src/main/resources/font/HelveticaMedium.ttf differ diff --git a/lib/src/main/resources/img_1.png b/lib/src/main/resources/img_1.png new file mode 100644 index 0000000..76f7a1b Binary files /dev/null and b/lib/src/main/resources/img_1.png differ diff --git a/lib/src/main/resources/rb_im.png b/lib/src/main/resources/rb_im.png new file mode 100644 index 0000000..e680402 Binary files /dev/null and b/lib/src/main/resources/rb_im.png differ diff --git a/lib/src/main/resources/readmeIcon.png b/lib/src/main/resources/readmeIcon.png new file mode 100644 index 0000000..69b77b7 Binary files /dev/null and b/lib/src/main/resources/readmeIcon.png differ diff --git a/lib/src/test/kotlin/treelib/AVLBalancerTest.kt b/lib/src/test/kotlin/treelib/AVLBalancerTest.kt new file mode 100644 index 0000000..43b6978 --- /dev/null +++ b/lib/src/test/kotlin/treelib/AVLBalancerTest.kt @@ -0,0 +1,236 @@ +package treelib + +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import treelib.avlTree.AVLBalancer +import treelib.avlTree.AVLNode +import treelib.avlTree.AVLStateContainer + +class AVLBalancerTest { + + val testModel = TestModelAVL() + + @DisplayName("Tests to check the operation of the balancer after removal") + @Nested + inner class RemovalTests { + + @Test + fun `first remove (RightHeight-LeftHeight=2)`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.thirdTreeValues) + root.right?.right?.left = null + root = balancer.balance(AVLStateContainer(root.right?.right!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(3, root.left?.left?.height?.toInt()) }, + { assertEquals(5, root.height.toInt()) }, + { assertEquals(3, root.right?.right?.height?.toInt()) }, + { assertEquals(1, root.right?.right?.right?.height?.toInt()) }, + { assertEquals(3, root.right?.left?.height?.toInt()) } + ) + + assertAll( + "Grouped Assertions of values", + { assertEquals(104, root.value) }, + { assertEquals(74, root.left?.right?.value) }, + { assertEquals(163, root.right?.left?.value) }, + { assertEquals(370, root.right?.right?.right?.value) }, + { assertEquals(293, root.right?.right?.left?.right?.value) }, + { assertEquals(null, root.right?.right?.left?.left?.value) } + ) + } + + @Test + fun `second remove`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.thirdTreeValuesCase2) + root.right?.right?.right = null + root = balancer.balance(AVLStateContainer(root.right?.right!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(3, root.left?.left?.height?.toInt()) }, + { assertEquals(5, root.height.toInt()) }, + { assertEquals(2, root.right?.right?.height?.toInt()) }, + { assertEquals(1, root.right?.right?.right?.height?.toInt()) }, + { assertEquals(1, root.right?.right?.left?.height?.toInt()) }, + { assertEquals(3, root.right?.left?.height?.toInt()) } + ) + + assertAll( + "Grouped Assertions of values", + { assertEquals(104, root.value) }, + { assertEquals(74, root.left?.right?.value) }, + { assertEquals(163, root.right?.left?.value) }, + { assertEquals(351, root.right?.right?.right?.value) }, + { assertEquals(293, root.right?.right?.value) }, + { assertEquals(174, root.right?.left?.right?.left?.value) }, + { assertEquals(null, root.right?.right?.left?.left?.value) } + ) + } + + @Test + fun `third remove`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.thirdTreeValuesCase3) + root.right?.left?.left = null + root = balancer.balance(AVLStateContainer(root.right?.left!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(3, root.left?.left?.height?.toInt()) }, + { assertEquals(5, root.height.toInt()) }, + { assertEquals(2, root.right?.right?.height?.toInt(), "2") }, + { assertEquals(1, root.right?.left?.left?.height?.toInt()) }, + { assertEquals(2, root.right?.left?.height?.toInt()) } + ) + + assertAll( + "Grouped Assertions of values", + { assertEquals(104, root.value) }, + { assertEquals(74, root.left?.right?.value) }, + { assertEquals(174, root.right?.left?.value) }, + { assertEquals(180, root.right?.left?.right?.value) }, + { assertEquals(163, root.right?.left?.left?.value) }, + { assertEquals(293, root.right?.right?.value) }, + { assertEquals(null, root.right?.left?.left?.left?.value) }, + { assertEquals(null, root.right?.left?.right?.left?.value) } + ) + } + + @Test + fun `fourth remove (without the need for balancing)`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.thirdTreeValuesCase4) + root.right?.right?.right = null + root = balancer.balance(AVLStateContainer(root.right?.right!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(3, root.left?.left?.height?.toInt()) }, + { assertEquals(5, root.height.toInt()) }, + { assertEquals(2, root.right?.right?.height?.toInt()) }, + { assertEquals(1, root.right?.left?.right?.height?.toInt()) }, + { assertEquals(1, root.right?.left?.left?.height?.toInt()) }, + { assertEquals(2, root.right?.left?.height?.toInt()) } + ) + + assertAll( + "Grouped Assertions of values", + { assertEquals(104, root.value) }, + { assertEquals(74, root.left?.right?.value) }, + { assertEquals(174, root.right?.left?.value) }, + { assertEquals(180, root.right?.left?.right?.value) }, + { assertEquals(163, root.right?.left?.left?.value) }, + { assertEquals(293, root.right?.right?.value) }, + { assertEquals(285, root.right?.right?.left?.value) }, + { assertEquals(null, root.right?.right?.right?.value) } + ) + } + + + } + + @DisplayName("Tests to check the operation of the balancer after insertion") + @Nested + inner class InsertionTests { + @Test + fun `insert case 1(LeftHeight - RightHeight = 2, delta(leftSon)=1)`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.firstTreeValues) + root = balancer.balance(AVLStateContainer(root.left!!, root)) + assertAll( + { assertEquals(14, root.value, "Value of the root") }, + { assertEquals(1, root.left?.value, "Value of the left son of the root") }, + { assertEquals(23, root.right?.value, "Value of the right son of the root") } + ) + } + + @Test + fun `insert case 2(LeftHeight- RightHeight =-2, delta(rightSon)=-1`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.secondTreeValues) + root.left?.left?.right?.right = AVLNode(53, AVLNode(49, null, null), null) + root = balancer.balance(AVLStateContainer(root.left?.left?.right?.right!!, root)) + assertAll( + { assertEquals(101, root.value) }, + { assertEquals(230, root.right?.value) }, + { assertEquals(47, root.left?.left?.value) }, + { assertEquals(44, root.left?.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.left?.value) }, + { assertEquals(53, root.left?.left?.right?.value) }, + { assertEquals(49, root.left?.left?.right?.left?.value) } + ) + } + + @Test + fun `multiple insertions`() { + val balancer = AVLBalancer(null) + var root = testModel.getTree(testModel.secondTreeValues) + /** First insert **/ + root.left?.left?.right?.left?.right = AVLNode(46, null, null) + root = balancer.balance(AVLStateContainer(root.left?.left?.right?.left!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(5, root.height.toInt()) }, + { assertEquals(2, root.right?.left?.height?.toInt()) }, + { assertEquals(3, root.left?.left?.height?.toInt()) }, + { assertEquals(2, root.left?.left?.right?.height?.toInt()) }, + { assertEquals(1, root.left?.left?.right?.right?.height?.toInt()) }, + { assertEquals(1, root.left?.left?.left?.height?.toInt()) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(101, root.value) }, + { assertEquals(205, root.right?.left?.value) }, + { assertEquals(44, root.left?.left?.value) }, + { assertEquals(46, root.left?.left?.right?.value) }, + { assertEquals(47, root.left?.left?.right?.right?.value) }, + { assertEquals(null, root.left?.left?.right?.left?.left?.value) } + ) + + /** Second insert **/ + root.left?.left?.right?.right?.right = AVLNode(50, null, null) + root = balancer.balance(AVLStateContainer(root.left?.left?.right?.right!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(1, root.left?.left?.right?.right?.height?.toInt()) }, + { assertEquals(1, root.left?.left?.left?.left?.height?.toInt()) }, + { assertEquals(2, root.right?.right?.height?.toInt()) }, + { assertEquals(2, root.left?.left?.right?.height?.toInt()) }, + { assertEquals(3, root.left?.left?.height?.toInt()) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(101, root.value) }, + { assertEquals(205, root.right?.left?.value) }, + { assertEquals(46, root.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.left?.value) }, + { assertEquals(null, root.left?.left?.right?.left?.value) }, + { assertEquals(null, root.left?.left?.right?.right?.left?.value) } + ) + + /** Third insert **/ + root.left?.left?.right?.right?.left = AVLNode(49, null, null) + root = balancer.balance(AVLStateContainer(root.left?.left?.right?.right!!, root)) + assertAll( + "Grouped Assertions of height", + { assertEquals(1, root.left?.left?.right?.right?.height?.toInt()) }, + { assertEquals(1, root.left?.left?.left?.left?.height?.toInt()) }, + { assertEquals(1, root.left?.left?.right?.left?.height?.toInt()) }, + { assertEquals(2, root.right?.right?.height?.toInt()) }, + { assertEquals(2, root.left?.left?.right?.height?.toInt()) }, + { assertEquals(3, root.left?.left?.height?.toInt()) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(101, root.value) }, + { assertEquals(205, root.right?.left?.value) }, + { assertEquals(46, root.left?.left?.value) }, + { assertEquals(49, root.left?.left?.right?.value) }, + { assertEquals(1, root.left?.left?.left?.left?.value) }, + { assertEquals(47, root.left?.left?.right?.left?.value) }, + { assertEquals(null, root.left?.left?.right?.right?.left?.value) }, + { assertEquals(null, root.left?.left?.right?.right?.right?.value) } + ) + } + } + +} diff --git a/lib/src/test/kotlin/treelib/AVLStructTest.kt b/lib/src/test/kotlin/treelib/AVLStructTest.kt new file mode 100644 index 0000000..b7d1f4b --- /dev/null +++ b/lib/src/test/kotlin/treelib/AVLStructTest.kt @@ -0,0 +1,108 @@ +package treelib + +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.fail +import treelib.avlTree.AVLNode +import treelib.avlTree.AVLStateContainer +import treelib.avlTree.AVLStruct +import treelib.avlTree.AVLVertex +import utils.AVLAnalyzer +import utils.TreeStructWrapper + +@DisplayName("Test: AVL Struct") +class AVLStructTest { + private val treeW = TreeStructWrapper, AVLVertex, AVLStateContainer, AVLStruct>() + private val treeH = AVLAnalyzer(::testAssert) + private var treeStruct = AVLStruct() + + private fun testAssert(msg: String): Nothing = fail(msg) + + @BeforeEach + fun reInitClassUnderTest() { + treeStruct = AVLStruct() + } + + @Test + fun `test one root`() { + val num = mutableListOf(1) + for (i in num) { + treeStruct.insert(i) + } + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test height 3`() { + val num = mutableListOf(3, 2, 1, 4) + for (i in num) { + treeStruct.insert(i) + } + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test height 3 with delete root`() { + val num = mutableListOf(3, 2, 1, 4) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(2) + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test 100000 arguments`() { + for (i in 1..100000) { + treeStruct.insert(i) + } + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test 100000 arguments and delete`() { + for (i in 1..100000) { + treeStruct.insert(i) + } + treeStruct.delete(5000) + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test two arguments`() { + treeStruct.insert(2) + treeStruct.insert(3) + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } + + @Test + fun `test many arguments`() { + val num = mutableListOf(3, 2, 1, 4, 2343, 123213, 3213, 657, 534, 12432, 5676756, 321, 5436546, 5435) + for (i in num) { + treeStruct.insert(i) + } + val node = treeW.getPrivateNode(treeStruct) + if (node != null) { + treeW.getPrivateNode(treeStruct)?.let { treeH.checkTree(it) } + } + } +} diff --git a/lib/src/test/kotlin/treelib/AVLTreeTest.kt b/lib/src/test/kotlin/treelib/AVLTreeTest.kt new file mode 100644 index 0000000..cb5b7fe --- /dev/null +++ b/lib/src/test/kotlin/treelib/AVLTreeTest.kt @@ -0,0 +1,95 @@ +package treelib + +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import treelib.avlTree.* +import treelib.commonObjects.Container +import utils.TreeStructWrapper +import utils.TreeWrapper +import kotlin.test.assertEquals + + +class AVLTreeTest { + private val tree = AVLTree() + private val treeW = + TreeWrapper>, AVLVertex>, AVLStateContainer>, AVLStruct>, AVLTree>() + private val treeSW = + TreeStructWrapper, AVLNode>, AVLVertex>, AVLStateContainer>, AVLStruct>>() + + + @ParameterizedTest + @ValueSource(strings = ["5 3 8 9", "1 2 3 4", "4 3 5 2", "4 3 2 1", "2 3 1 4"]) + fun `test check root`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, 1)) + } + tree.putItem(num) + + numbers.sort() + val root = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key + if (root == numbers[1]) { + assertEquals(expected = root, actual = numbers[1]) + } else { + assertEquals(expected = root, actual = numbers[2]) + } + } + + @ParameterizedTest + @ValueSource(strings = ["1 1000", "1 10000", "1 100000", "1 1000000"]) + fun `test add many args and delete root`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + for (i in numbers[0]..numbers[1]) { + tree.putItem(Pair(i, i)) + } + val root = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key + if (root != null) { + treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.let { tree.deleteItem(it.key) } + } + + when (numbers[1]) { + 1000 -> assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, actual = 513) + 10000 -> assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, + actual = 4097 + ) + + 100000 -> assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, + actual = 65537 + ) + + else -> assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, + actual = 524289 + ) + } + } + + @ParameterizedTest + @ValueSource(strings = ["5", "0"]) + fun `test delete root one arg`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, 1)) + } + tree.putItem(num) + tree.deleteItem(numbers[0]) + + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, actual = null) + } + + @Test + fun `test get set methods`() { + for (i in 1..3) { + tree.putItem(Pair(i, i)) + } + + assertEquals(expected = tree[1], actual = 1) + assertEquals(expected = tree[2], actual = 2) + assertEquals(expected = tree[3], actual = 3) + } +} \ No newline at end of file diff --git a/lib/src/test/kotlin/treelib/BINStructTest.kt b/lib/src/test/kotlin/treelib/BINStructTest.kt new file mode 100644 index 0000000..2213b19 --- /dev/null +++ b/lib/src/test/kotlin/treelib/BINStructTest.kt @@ -0,0 +1,526 @@ +package treelib + +import org.junit.jupiter.api.* +import treelib.binTree.BINNode +import treelib.binTree.BINStateContainer +import treelib.binTree.BINStruct +import treelib.binTree.BINVertex +import treelib.commonObjects.exceptions.VauleNotFound +import utils.BINAnalyzer +import utils.TreeStructWrapper +import kotlin.test.assertEquals + +@DisplayName("Test: Binary Search Tree Struct") +class BINStructTest { + private val treeW = TreeStructWrapper, BINVertex, BINStateContainer, BINStruct>() + private var treeStruct = BINStruct() + + private fun testAssert(msg: String): Nothing = fail(msg) + + private val analyzer = BINAnalyzer(::testAssert) + + @BeforeEach + fun reInitClassUnderTest() { + treeStruct = BINStruct() + } + + @Test + fun `test delete root`() { + val num = mutableListOf(5, 3, 7, 1, 9, -1, 4, 2, 0, 6) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(5) + val root = treeW.getPrivateNode(treeStruct)?.value + + assertEquals(expected = 6, actual = root) + } + + @Test + fun `test insert`() { + val num = mutableListOf(1, 2, 3, 4, 5, 8) + for (i in num) { + treeStruct.insert(i) + } + + val additionalNum = mutableListOf(1, 2, 3, 5, 7, 8, 11) + for (i in additionalNum) { + treeStruct.insert(i) + } + + val root = treeW.getPrivateNode(treeStruct)?.value + val node2 = treeW.getPrivateNode(treeStruct)?.right?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.left?.value + val node3 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val nodeNull1 = treeW.getPrivateNode(treeStruct)?.right?.left?.value + val node4 = treeW.getPrivateNode(treeStruct)?.right?.right?.right?.value + + assertEquals(expected = root, actual = 1) + assertEquals(expected = nodeNull, actual = null) + assertEquals(expected = node4, actual = 4) + assertEquals(expected = node2, actual = 2) + assertEquals(expected = node3, actual = 3) + assertEquals(expected = nodeNull1, actual = null) + } + + @Test + fun `test find ordinary`() { + val num = mutableListOf(2, 3, 1, 4, 5, 10) + + assertEquals(expected = treeStruct.find(2), actual = null) + + for (i in num) { + treeStruct.insert(i) + } + + assertEquals(expected = treeStruct.find(2), actual = 2) + } + + @Test + fun `test find null`() { + val num = mutableListOf(1) + treeStruct.insert(num[0]) + + assertEquals(treeStruct.find(2), null) + + } + + @Test + fun `test find root`() { + val num = mutableListOf(1) + treeStruct.insert(num[0]) + + assertEquals(treeStruct.find(1), 1) + } + + @Test + fun `test insert and delete root`() { + val num = mutableListOf(1, 2) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(1) + + val additionalNum = mutableListOf(1, 2, 11) + for (i in additionalNum) { + treeStruct.insert(i) + } + + val root = treeW.getPrivateNode(treeStruct)?.value + val node1 = treeW.getPrivateNode(treeStruct)?.left?.value + val node11 = treeW.getPrivateNode(treeStruct)?.right?.value + + + assertEquals(expected = root, actual = 2) + assertEquals(expected = node1, actual = 1) + assertEquals(expected = node11, actual = 11) + } + + @Test + fun `test delete nonexistent value right`() { + val num = mutableListOf(5, 6) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(6) + + val root = treeW.getPrivateNode(treeStruct)?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.value + + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 5, actual = root) + } + + @Test + fun `test delete nonexistent value left`() { + val num = mutableListOf(6, 5) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(5) + + val root = treeW.getPrivateNode(treeStruct)?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.value + + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 6, actual = root) + } + + @Test + fun `test delete no child root`() { + val num = mutableListOf(3) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(3) + + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = null, actual = root) + } + + @Test + fun `test delete no child right`() { + val num = mutableListOf(3, 10, 15) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(15) + + val node10 = treeW.getPrivateNode(treeStruct)?.right?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.right?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + + assertEquals(expected = 10, actual = node10) + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 3, actual = root) + } + + @Test + fun `test delete no child left`() { + val num = mutableListOf(15, 10, 3) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(3) + + val node10 = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.left?.right?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + + assertEquals(expected = 10, actual = node10) + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 15, actual = root) + } + + @Test + fun `test delete one child left`() { + val num = mutableListOf(3, 2, 1, 5) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(2) + + val node1 = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.left?.right?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + assertEquals(expected = 1, actual = node1) + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 3, actual = root) + } + + @Test + fun `test delete one child right`() { + val num = mutableListOf(3, 1, 5, 6) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(5) + + val node6 = treeW.getPrivateNode(treeStruct)?.right?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.right?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + assertEquals(expected = 6, actual = node6) + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 3, actual = root) + } + + @Test + fun `test delete one child root`() { + val num = mutableListOf(3, 6) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(3) + + val node6 = treeW.getPrivateNode(treeStruct)?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.right?.value + + assertEquals(expected = 6, actual = node6) + assertEquals(expected = null, actual = nodeNullLeft) + assertEquals(expected = null, actual = nodeNullRight) + } + + @Test + fun `test delete one child with family`() { + val num = mutableListOf(10, 7, 13, 6, 3, 1, 5, 2, 4, 15) + for (value in num) { + treeStruct.insert(value) + } + treeStruct.delete(7) + val node6 = treeW.getPrivateNode(treeStruct)?.left?.value + val node3 = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val nodeNullRight = treeW.getPrivateNode(treeStruct)?.left?.right?.value + val node1 = treeW.getPrivateNode(treeStruct)?.left?.left?.left?.value + val node2 = treeW.getPrivateNode(treeStruct)?.left?.left?.left?.right?.value + val node5 = treeW.getPrivateNode(treeStruct)?.left?.left?.right?.value + val node4 = treeW.getPrivateNode(treeStruct)?.left?.left?.right?.left?.value + val root = treeW.getPrivateNode(treeStruct)?.value + + assertEquals(expected = 6, actual = node6) + assertEquals(expected = 3, actual = node3) + assertEquals(expected = null, actual = nodeNullRight) + assertEquals(expected = 1, actual = node1) + assertEquals(expected = 2, actual = node2) + assertEquals(expected = 5, actual = node5) + assertEquals(expected = 4, actual = node4) + assertEquals(expected = 10, actual = root) + } + + @Test + fun `test delete two child only three element`() { + val num = mutableListOf(2, 1, 3) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(2) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node1 = treeW.getPrivateNode(treeStruct)?.left?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val nodeNullRight1 = treeW.getPrivateNode(treeStruct)?.left?.right?.value + val nodeNullRightRoot = treeW.getPrivateNode(treeStruct)?.right?.value + + assertEquals(expected = root, actual = 3) + assertEquals(expected = node1, actual = 1) + assertEquals(expected = nodeNullLeft, actual = null) + assertEquals(expected = nodeNullRight1, actual = null) + assertEquals(expected = nodeNullRightRoot, actual = null) + } + + @Test + fun `test delete two child without family`() { + val num = mutableListOf(10, 7, 5, 4, 6) + for (i in num) { + treeStruct.insert(i) + } + treeStruct.delete(7) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node5 = treeW.getPrivateNode(treeStruct)?.left?.value + val node4 = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val node6 = treeW.getPrivateNode(treeStruct)?.left?.right?.value + val nodeNullLeft4 = treeW.getPrivateNode(treeStruct)?.left?.left?.left?.value + val nodeNullRight4 = treeW.getPrivateNode(treeStruct)?.left?.left?.right?.value + val nodeNullLeft6 = treeW.getPrivateNode(treeStruct)?.left?.right?.left?.value + val nodeNullRight6 = treeW.getPrivateNode(treeStruct)?.left?.right?.left?.value + + assertEquals(expected = root, actual = 10) + assertEquals(expected = node5, actual = 5) + assertEquals(expected = node4, actual = 4) + assertEquals(expected = node6, actual = 6) + assertEquals(expected = nodeNullLeft4, actual = null) + assertEquals(expected = nodeNullRight4, actual = null) + assertEquals(expected = nodeNullLeft6, actual = null) + assertEquals(expected = nodeNullRight6, actual = null) + } + + @Test + fun `test two child double delete and delete root`() { + val num = mutableListOf(6, 8, 10, 7) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(6) + treeStruct.delete(7) + + val root = treeW.getPrivateNode(treeStruct)?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val node10 = treeW.getPrivateNode(treeStruct)?.right?.value + val nodeNullRight10 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val nodeNullLeft10 = treeW.getPrivateNode(treeStruct)?.right?.left?.value + + assertEquals(expected = root, actual = 8) + assertEquals(expected = node10, actual = 10) + assertEquals(expected = nodeNullLeft10, actual = null) + assertEquals(expected = nodeNullRight10, actual = null) + assertEquals(expected = nodeNullLeft, actual = null) + } + + @Test + fun `test two child delete min element in right tree`() { + val num = mutableListOf(6, 8, 10, 7, 12, 9) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(8) + + val root = treeW.getPrivateNode(treeStruct)?.value + val nodeNullLeft = treeW.getPrivateNode(treeStruct)?.left?.value + val node9 = treeW.getPrivateNode(treeStruct)?.right?.value + val node7 = treeW.getPrivateNode(treeStruct)?.right?.left?.value + val node10 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val node12 = treeW.getPrivateNode(treeStruct)?.right?.right?.right?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.right?.right?.left?.value + + assertEquals(expected = root, actual = 6) + assertEquals(expected = node9, actual = 9) + assertEquals(expected = nodeNull, actual = null) + assertEquals(expected = nodeNullLeft, actual = null) + assertEquals(expected = node7, actual = 7) + assertEquals(expected = node10, actual = 10) + assertEquals(expected = node12, actual = 12) + } + + @Test + fun `test two child delete min element in right tree for root`() { + val num = mutableListOf(8, 10, 7, 12, 9) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(8) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node7 = treeW.getPrivateNode(treeStruct)?.left?.value + val node10 = treeW.getPrivateNode(treeStruct)?.right?.value + val node12 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.right?.left?.value + + assertEquals(expected = root, actual = 9) + assertEquals(expected = nodeNull, actual = null) + assertEquals(expected = node7, actual = 7) + assertEquals(expected = node10, actual = 10) + assertEquals(expected = node12, actual = 12) + } + + @Test + fun `test two child delete min element in right tree for rightmost element`() { + val num = mutableListOf(8, 10, 7, 12, 13, 14) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(8) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node7 = treeW.getPrivateNode(treeStruct)?.left?.value + val node12 = treeW.getPrivateNode(treeStruct)?.right?.value + val node13 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val node14 = treeW.getPrivateNode(treeStruct)?.right?.right?.right?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.right?.left?.value + + assertEquals(expected = root, actual = 10) + assertEquals(expected = nodeNull, actual = null) + assertEquals(expected = node7, actual = 7) + assertEquals(expected = node13, actual = 13) + assertEquals(expected = node14, actual = 14) + assertEquals(expected = node12, actual = 12) + } + + @Test + fun `test two child delete min element in right tree but in Tree`() { + val num = mutableListOf(8, 12, 15, 13, 10, 11, 9) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(12) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node13 = treeW.getPrivateNode(treeStruct)?.right?.value + val node15 = treeW.getPrivateNode(treeStruct)?.right?.right?.value + val node10 = treeW.getPrivateNode(treeStruct)?.right?.left?.value + val node11 = treeW.getPrivateNode(treeStruct)?.right?.left?.right?.value + val node9 = treeW.getPrivateNode(treeStruct)?.right?.left?.left?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.right?.right?.left?.value + + assertEquals(expected = root, actual = 8) + assertEquals(expected = node10, actual = 10) + assertEquals(expected = node11, actual = 11) + assertEquals(expected = node13, actual = 13) + assertEquals(expected = node9, actual = 9) + assertEquals(expected = node15, actual = 15) + assertEquals(expected = nodeNull, actual = null) + } + + @Test + fun `test two child delete min element in right tree for leftmost element`() { + val num = mutableListOf(8, 10, 7, 6) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(8) + + val root = treeW.getPrivateNode(treeStruct)?.value + val node7 = treeW.getPrivateNode(treeStruct)?.left?.value + val node6 = treeW.getPrivateNode(treeStruct)?.left?.left?.value + val nodeNull = treeW.getPrivateNode(treeStruct)?.right?.value + + assertEquals(expected = root, actual = 10) + assertEquals(expected = nodeNull, actual = null) + assertEquals(expected = node7, actual = 7) + assertEquals(expected = node6, actual = 6) + } + + @Test + fun `test analyzer`() { + val num = mutableListOf(6, 8, 10, 7, 12, 9) + for (i in num) { + treeStruct.insert(i) + } + + treeStruct.delete(8) + + val root = treeW.getPrivateNode(treeStruct) + root?.let { analyzer.checkTree(root) } ?: Exception("CHzh") + } + + @Test + fun `test delete a node that is not in tree`() { + Assertions.assertThrows(VauleNotFound::class.java) { + for (i in (0..10000).shuffled()) { + treeStruct.insert(i) + } + treeStruct.delete(100001) + } + } + + @Test + fun `test add and delete 10000 arg`() { + for (i in (0..10000).shuffled()) { + treeStruct.insert(i) + } + for (i in (0..10000).shuffled()) { + treeStruct.delete(i) + } + + val root = treeW.getPrivateNode(treeStruct)?.value + assertEquals(expected = root, actual = null) + } + + @Test + fun `test delete a node that is not in tree with message`() { + Assertions.assertThrows(VauleNotFound(". Repeat again.")::class.java) { + for (i in (0..10000).shuffled()) { + treeStruct.insert(i) + } + treeStruct.delete(100001) + } + } +} diff --git a/lib/src/test/kotlin/treelib/BINTreeTest.kt b/lib/src/test/kotlin/treelib/BINTreeTest.kt new file mode 100644 index 0000000..d3e03e3 --- /dev/null +++ b/lib/src/test/kotlin/treelib/BINTreeTest.kt @@ -0,0 +1,101 @@ +package treelib + +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import treelib.binTree.* +import treelib.commonObjects.Container +import utils.TreeStructWrapper +import utils.TreeWrapper +import kotlin.test.assertEquals + +class BINTreeTest { + private val tree = BINTree() + private val treeW = + TreeWrapper>, BINVertex>, BINStateContainer>, BINStruct>, BINTree>() + private val treeSW = + TreeStructWrapper, BINNode>, BINVertex>, BINStateContainer>, BINStruct>>() + + + // line - 0.6, branch - 0.5, methods = 0.9 + @ParameterizedTest + @ValueSource(ints = [1, 2, 0, 6, 4]) + fun `test putItem`(str: Int) { + tree.putItem((Pair(str, 1))) + } + + @ParameterizedTest + @ValueSource(strings = ["1 2 3 4", "5 6 7 8 9"]) + fun `test putItems`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, 1)) + } + tree.putItem(num) + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, actual = numbers[0]) + assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.right?.value?.key, + actual = numbers[1] + ) + } + + @Test + fun `test getItem`() { + val num = mutableListOf(Pair(1, 1), Pair(2, 1), Pair(5, 1), Pair(4, 1), Pair(3, 5)) + tree.putItem(num) + val temp = tree.getItem(3) + + assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.right?.right?.left?.left?.value?.value, + actual = temp + ) + } + + @ParameterizedTest + @ValueSource(strings = ["5 2 3", "5 6 7", "5 6 13", "5 6 1", "192 5 6"]) + fun `test putItems and delete`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, 1)) + } + tree.putItem(num) + tree.deleteItem(numbers[2]) + + assertEquals(expected = tree.getItem(numbers[2]), actual = null) + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, actual = numbers[0]) + } + + @ParameterizedTest + @ValueSource(strings = ["5 2 3 9", "5 6 7 1", "5 6 13 4", "5 6 1", "192 5 6 222"]) + fun `test putItems and delete root`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, 1)) + } + tree.putItem(num) + val root = numbers[0] + numbers.sort() + val index = numbers.indexOf(root) + tree.deleteItem(root) + + assertEquals( + expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key, + actual = numbers[index + 1] + ) + } + + @Test + fun `test get set methods`() { + for (i in 1..3) { + tree.putItem(Pair(i, i)) + } + + assertEquals(expected = tree[1], actual = 1) + assertEquals(expected = tree[2], actual = 2) + assertEquals(expected = tree[3], actual = 3) + + } +} \ No newline at end of file diff --git a/lib/src/test/kotlin/treelib/RBBalancerTest.kt b/lib/src/test/kotlin/treelib/RBBalancerTest.kt new file mode 100644 index 0000000..d0cea6d --- /dev/null +++ b/lib/src/test/kotlin/treelib/RBBalancerTest.kt @@ -0,0 +1,488 @@ +package treelib + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll +import treelib.rbTree.RBBalancer +import treelib.rbTree.RBNode +import treelib.rbTree.RBStateContainer +import treelib.rbTree.Markers + +class RBBalancerTest { + + val testModel = TestModelRBT() + fun > countBlackNodes(node: RBNode) = testModel.countBlackNodes(node) + + @DisplayName("Tests to check the operation of the balancer after removal") + @Nested + inner class RemovalTests { + + @Test + fun `init test`() { + val firstBalancer = RBBalancer(null) + assertEquals(15, firstBalancer.balance(RBStateContainer(RBNode(15, null, null, null, Markers.BLACK))).value) + val secondBalancer = RBBalancer(null) + assertEquals( + "Test", + secondBalancer.balance(RBStateContainer(RBNode("Test", null, null, null, Markers.BLACK))).value + ) + } + + @Test + fun `remove red leaf`() { + val nodes = testModel.getFirstTree() + val root = nodes[30] + val balancer = RBBalancer(root) + nodes[18]!!.right = null + assertEquals(root, balancer.balance(RBStateContainer(nodes[18]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[4]!!)) }, + { assertEquals(3, countBlackNodes(nodes[5]!!)) }, + { assertEquals(3, countBlackNodes(nodes[18]!!)) }, + { assertEquals(3, countBlackNodes(nodes[19]!!)) }, + ) + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(19, root?.left?.right?.value) }, + { assertEquals(24, root?.left?.right?.right?.value) }, + { assertEquals(18, root?.left?.right?.left?.value) }, + { assertEquals(16, root?.left?.right?.left?.left?.value) }, + { assertEquals(null, root?.left?.right?.left?.right?.value) }, + ) + + } + + @Test + fun `remove black leaf (black parent)`() { + val nodes = testModel.getFirstTree() + val root = nodes[30] + val balancer = RBBalancer(root) + nodes[27]?.right = null + assertEquals(root, balancer.balance(RBStateContainer(nodes[27]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[12]!!)) }, + { assertEquals(3, countBlackNodes(nodes[13]!!)) }, + { assertEquals(3, countBlackNodes(nodes[27]!!)) }, + { assertEquals(2, countBlackNodes(nodes[22]!!)) }, + { assertEquals(3, countBlackNodes(nodes[11]!!)) }, + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(50, root?.right?.value) }, + { assertEquals(63, root?.right?.right?.value) }, + { assertEquals(71, root?.right?.right?.right?.value) }, + { assertEquals(52, root?.right?.right?.left?.value) }, + { assertEquals(nodes[13]?.value, nodes[27]?.left?.value) }, + { assertEquals(null, nodes[27]?.right) }, + ) + } + + @Test + fun `remove black leaf(red parent)`() { + val nodes = testModel.getFirstTree() + val root = nodes[30] + val balancer = RBBalancer(root) + nodes[17]?.left = null + assertEquals(root, balancer.balance(RBStateContainer(nodes[17]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(3, countBlackNodes(nodes[1]!!)) }, + { assertEquals(3, countBlackNodes(nodes[3]!!)) }, + { assertEquals(3, countBlackNodes(nodes[17]!!)) }, + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(50, root?.right?.value) }, + { assertEquals(15, root?.left?.value) }, + { assertEquals(19, root?.left?.right?.value) }, + { assertEquals(10, root?.left?.left?.value) }, + { assertEquals(12, root?.left?.left?.right?.value) }, + { assertEquals(14, nodes[17]?.right?.value) }, + { assertEquals(null, nodes[17]?.left) } + ) + } + + @Test + fun `remove root (Case 1)`() { + val nodes = testModel.getFirstTree() + var root = nodes[30] + val balancer = RBBalancer(root) + root = testModel.deleteNode(nodes[30]!!, nodes[8]!!) + + root = balancer.balance(RBStateContainer(nodes[20]!!)) + assertAll( + "Assertions of root", + { assertEquals(nodes[8]?.value, root.value) }, + { assertEquals(Markers.BLACK, root.color) }, + { assertEquals(null, root.parent) }, + { assertEquals(nodes[28]?.value, root.left?.value) }, + { assertEquals(nodes[29]?.value, root.right?.value) } + ) + + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(3, countBlackNodes(nodes[9]!!)) }, + { assertEquals(1, countBlackNodes(nodes[29]!!)) }, + { assertEquals(3, countBlackNodes(nodes[23]!!)) }, + { assertEquals(3, countBlackNodes(nodes[20]!!)) } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(19, root.left?.right?.value) }, + { assertEquals(40, root.right?.left?.value) }, + { assertEquals(42, root.right?.left?.right?.value) }, + { assertEquals(37, root.right?.left?.left?.value) }, + { assertEquals(38, nodes[20]?.right?.value) }, + { assertEquals(null, nodes[20]?.left) } + ) + } + + @Test + fun `remove root (Case 2)`() { + var root = RBNode(100, RBNode(29, null, null, null, Markers.RED), null, null, Markers.BLACK) + root.left?.parent = root + val balance = RBBalancer(root) + root = testModel.deleteNode(root, root.left!!) + + root = balance.balance(RBStateContainer(root)) + + assertAll( + "Assertions of root", + { assertEquals(Markers.BLACK, root.color) }, + { assertEquals(29, root.value) }, + { assertEquals(null, root.parent) }, + { assertEquals(null, root.left) }, + { assertEquals(null, root.right) } + ) + } + + @Test + fun `remove black leaf(red parent, black brother with two red child)`() { + val nodes = testModel.getFirstTree() + val root = nodes[30]!! + nodes[13]?.value = 68 + val rightBrotherSon = RBNode(70, null, null, nodes[13], Markers.RED) + val leftBrotherSon = RBNode(65, null, null, nodes[13], Markers.RED) + nodes[13]?.right = rightBrotherSon + nodes[13]?.left = leftBrotherSon + val balancer = RBBalancer(nodes[30]) + nodes[22]?.left = null + + assertEquals(root, balancer.balance(RBStateContainer(nodes[22]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(rightBrotherSon), "1") }, + { assertEquals(3, countBlackNodes(leftBrotherSon), "2") }, + { assertEquals(3, countBlackNodes(nodes[22]!!)) }, + { assertEquals(3, countBlackNodes(nodes[13]!!)) }, + { assertEquals(2, countBlackNodes(root.right?.right!!)) }, + { assertEquals(2, countBlackNodes(nodes[13]?.parent!!)) } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(50, root.right?.value) }, + { assertEquals(71, root.right?.right?.value) }, + { assertEquals(68, root.right?.right?.left?.value) }, + { assertEquals(70, root.right?.right?.left?.right?.value) }, + { assertEquals(63, root.right?.right?.left?.left?.value) }, + { assertEquals(65, nodes[22]?.right?.value) }, + { assertEquals(null, nodes[22]?.left) }, + ) + + + } + + @Test + fun `multiple deletions`() { + val nodes = testModel.getFirstTree() + val root = nodes[30] + val balancer = RBBalancer(root) + + /** first delete **/ + testModel.deleteNode(nodes[24]!!, nodes[2]!!) + + assertEquals(root!!.value, balancer.balance(RBStateContainer(nodes[17]!!)).value) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[3]!!)) }, + { assertEquals(3, countBlackNodes(nodes[17]!!)) }, + { assertEquals(2, countBlackNodes(nodes[26]!!)) }, + { assertEquals(2, countBlackNodes(nodes[24]!!)) } + ) + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(12, nodes[17]?.value) }, + { assertEquals(14, nodes[3]?.value) }, + { assertEquals(null, nodes[17]?.left) } + ) + + /** second delete **/ + testModel.deleteNode(nodes[16]!!, nodes[1]!!) + + assertEquals(root, balancer.balance(RBStateContainer(nodes[1]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(3, countBlackNodes(root.left?.left?.left!!)) }, + { assertEquals(2, countBlackNodes(nodes[24]!!), "2") }, + { assertEquals(1, countBlackNodes(nodes[28]!!)) }, + { assertEquals(1, countBlackNodes(nodes[30]!!)) }, + { assertEquals(3, countBlackNodes(nodes[17]!!), "1") } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(15, root.left?.value) }, + { assertEquals(11, root.left?.left?.value) }, + { assertEquals(12, root.left?.left?.right?.value) }, + { assertEquals(7, root.left?.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.left?.value) }, + { assertEquals(null, root.left?.left?.left?.right) }, + ) + + /** third delete **/ + nodes[17]?.right = null + assertEquals(root, balancer.balance(RBStateContainer(nodes[17]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!), "1") }, + { assertEquals(3, countBlackNodes(nodes[1]!!), "2") }, + { assertEquals(3, countBlackNodes(nodes[17]!!), "3") }, + { assertEquals(2, countBlackNodes(nodes[2]!!)) }, + { assertEquals(1, countBlackNodes(nodes[28]!!)) }, + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(15, root.left?.value) }, + { assertEquals(11, root.left?.left?.value) }, + { assertEquals(7, root.left?.left?.left?.value) }, + { assertEquals(12, nodes[17]?.value) }, + { assertEquals(null, nodes[17]?.right) }, + { assertEquals(null, nodes[17]?.left) }, + ) + + /** fourth delete **/ + nodes[2]?.right = null + + assertEquals(root, balancer.balance(RBStateContainer(nodes[2]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(2, countBlackNodes(nodes[1]!!)) }, + { assertEquals(3, countBlackNodes(nodes[2]!!)) }, + { assertEquals(1, countBlackNodes(nodes[28]!!)) }, + { assertEquals(1, countBlackNodes(nodes[30]!!)) }, + { assertEquals(2, countBlackNodes(nodes[25]!!)) } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(15, root.left?.value) }, + { assertEquals(7, root.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.value) }, + { assertEquals(11, root.left?.left?.right?.value) }, + { assertEquals(19, root.left?.right?.value) }, + { assertEquals(null, nodes[2]?.right) }, + { assertEquals(null, nodes[2]?.left) }, + ) + + /** fourth delete **/ + nodes[2]?.right = null + + assertEquals(root, balancer.balance(RBStateContainer(nodes[2]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(2, countBlackNodes(nodes[1]!!)) }, + { assertEquals(3, countBlackNodes(nodes[2]!!)) }, + { assertEquals(1, countBlackNodes(nodes[28]!!)) }, + { assertEquals(1, countBlackNodes(nodes[30]!!)) }, + { assertEquals(2, countBlackNodes(nodes[25]!!)) } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(15, root.left?.value) }, + { assertEquals(7, root.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.value) }, + { assertEquals(11, root.left?.left?.right?.value) }, + { assertEquals(19, root.left?.right?.value) }, + { assertEquals(null, nodes[2]?.right) }, + { assertEquals(null, nodes[2]?.left) }, + ) + + /** fifth delete **/ + nodes[1]?.right = null + + assertEquals(root, balancer.balance(RBStateContainer(nodes[1]!!))) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!)) }, + { assertEquals(3, countBlackNodes(nodes[1]!!)) }, + { assertEquals(2, countBlackNodes(nodes[28]!!)) }, + { assertEquals(2, countBlackNodes(nodes[25]!!)) }, + { assertEquals(3, countBlackNodes(nodes[18]!!)) }, + { assertEquals(3, countBlackNodes(nodes[19]!!)) } + ) + + assertAll( + "Grouped Assertions of nodes values", + { assertEquals(15, root.left?.value) }, + { assertEquals(7, root.left?.left?.value) }, + { assertEquals(1, root.left?.left?.left?.value) }, + { assertEquals(19, root.left?.right?.value) }, + { assertEquals(24, root.left?.right?.right?.value) }, + { assertEquals(null, root.left?.left?.right) }, + ) + } + } + + @DisplayName("Tests to check the operation of the balancer after insertion") + @Nested + inner class InsertionTests { + + @Test + fun `insertion root`() { + val root = RBNode(18, null, null, null, Markers.RED) + RBBalancer(root) + assertAll( + { assertEquals(Markers.BLACK, root.color) }, + { assertEquals(18, root.value) } + ) + } + + @Test + fun `insertion with black parent`() { + var root = RBNode(18, null, null, null, Markers.BLACK) + val balancer = RBBalancer(root) + root.right = RBNode(39, null, null, root, Markers.RED) + root = balancer.balance(RBStateContainer(root.right!!)) + assertAll( + { assertEquals(18, root.value) }, + { assertEquals(Markers.BLACK, root.color) }, + { assertEquals(39, root.right?.value) }, + { assertEquals(null, root.left) }, + { assertEquals(null, root.right?.right) }, + { assertEquals(null, root.right?.left) } + ) + } + + @Test + fun `insertion with red uncle (Case 1)`() { + val nodes = testModel.getSecondTree() + var root = nodes[30] + val balancer = RBBalancer(root) + root = balancer.balance(RBStateContainer(nodes[0]!!)) + assertAll( + "Assertions of root", + { assertEquals(25, root.value) }, + { assertEquals(Markers.BLACK, root.color) } + ) + assertAll( + "Grouped Assertions of black height", + { assertEquals(3, countBlackNodes(nodes[0]!!), "1") }, + { assertEquals(3, countBlackNodes(nodes[16]!!), "2") }, + { assertEquals(3, countBlackNodes(nodes[17]!!)) }, + { assertEquals(2, countBlackNodes(nodes[24]!!)) }, + { assertEquals(2, countBlackNodes(nodes[29]!!)) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(1, root.left?.left?.left?.left?.value) }, + { assertEquals(4, root.left?.left?.left?.value) }, + { assertEquals(12, root.left?.left?.right?.value) }, + { assertEquals(15, root.left?.value) }, + { assertEquals(null, root.left?.left?.left?.left?.left) }, + { assertEquals(null, root.left?.left?.right?.left) }, + { assertEquals(null, root.left?.left?.left?.right) }, + ) + } + + @Test + fun `insertion with red uncle (Case 2)`() { + val nodes = testModel.getThirdTree() + var root = nodes[30] + val balancer = RBBalancer(root) + nodes[22]?.right = RBNode(65, null, null, nodes[22], Markers.RED) + root = balancer.balance(RBStateContainer(nodes[22]!!.right!!)) + assertAll( + "Assertions of root", + { assertEquals(50, root.value) }, + { assertEquals(Markers.BLACK, root.color) } + ) + assertAll( + "Grouped Assertions of black height", + { assertEquals(2, countBlackNodes(nodes[23]!!)) }, + { assertEquals(2, countBlackNodes(nodes[22]!!)) }, + { assertEquals(2, countBlackNodes(nodes[26]!!)) }, + { assertEquals(2, countBlackNodes(nodes[28]!!)) }, + { assertEquals(2, countBlackNodes(nodes[22]!!.right!!)) }, + { assertEquals(1, countBlackNodes(nodes[30]!!)) }, + { assertEquals(1, countBlackNodes(nodes[27]!!)) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(10, root.left?.left?.left?.value) }, + { assertEquals(19, root.left?.left?.right?.value) }, + { assertEquals(40, root.left?.right?.value) }, + { assertEquals(63, root.right?.left?.value) }, + { assertEquals(65, root.right?.left?.right?.value) }, + { assertEquals(90, root.right?.right?.value) }, + { assertEquals(71, root.right?.value) }, + { assertEquals(42, root.left?.right?.right?.value) }, + { assertEquals(null, root.right?.left?.left) }, + ) + + } + + @Test + fun `insertion with red uncle (Case 3)`() { + val nodes = testModel.getFourthTree() + var root = nodes[30] + val balancer = RBBalancer(root) + nodes[19]!!.left = RBNode(21, null, null, nodes[19], Markers.RED) + root = balancer.balance(RBStateContainer(nodes[19]!!.left!!)) + + assertAll( + "Assertions of root", + { assertEquals(19, root.value) }, + { assertEquals(Markers.BLACK, root.color) } + ) + assertAll( + "Grouped Assertions of black height", + { assertEquals(2, countBlackNodes(nodes[24]!!)) }, + { assertEquals(1, countBlackNodes(nodes[28]!!)) }, + { assertEquals(2, countBlackNodes(nodes[18]!!)) }, + { assertEquals(2, countBlackNodes(nodes[19]!!)) }, + { assertEquals(2, countBlackNodes(nodes[19]!!.left!!)) }, + { assertEquals(2, countBlackNodes(nodes[27]!!)) }, + { assertEquals(2, countBlackNodes(nodes[26]!!)) } + ) + assertAll( + "Grouped Assertions of values", + { assertEquals(10, root.left?.left?.value) }, + { assertEquals(15, root.left?.value) }, + { assertEquals(18, root.left?.right?.value) }, + { assertEquals(25, root.right?.value) }, + { assertEquals(50, root.right?.right?.value) }, + { assertEquals(24, root.right?.left?.value) }, + { assertEquals(71, root.right?.right?.right?.value) }, + { assertEquals(21, root.right?.left?.left?.value) }, + { assertEquals(null, root.right?.left?.right) }, + { assertEquals(null, root.left?.left?.right) }, + { assertEquals(null, root.left?.right?.left) }, + ) + } + } +} diff --git a/lib/src/test/kotlin/treelib/RBStructTest.kt b/lib/src/test/kotlin/treelib/RBStructTest.kt new file mode 100644 index 0000000..6a941de --- /dev/null +++ b/lib/src/test/kotlin/treelib/RBStructTest.kt @@ -0,0 +1,152 @@ +package treelib + +import org.junit.jupiter.api.* +import treelib.rbTree.RBNode +import treelib.rbTree.RBStateContainer +import treelib.rbTree.RBStruct +import treelib.rbTree.RBVertex +import treelib.rbTree.Markers +import utils.RBAnalyzer +import utils.TreeStructWrapper +import utils.fuzzers.RBStructFuzzer +import kotlin.test.assertEquals + +@DisplayName("Test: Red-Black Tree Struct") +class RBStructTest { + private val treeW = TreeStructWrapper, RBVertex, RBStateContainer, RBStruct>() + private var treeStruct = RBStruct() + + private fun testAssert(msg: String): Nothing = fail(msg) + + private val analyzer = RBAnalyzer(::testAssert) + + @BeforeEach + fun reInitClassUnderTest() { + treeStruct = RBStruct() + } + + @Test + fun `base test on creation root`() { + treeStruct.insert(6) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(root?.value, 6) }, + { assertEquals(root?.color, Markers.BLACK) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test on creation root with left`() { + treeStruct.insert(6) + treeStruct.insert(3) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(root?.left?.value, 3) }, + { assertEquals(root?.left?.color, Markers.RED) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test on creation root with right`() { + treeStruct.insert(6) + treeStruct.insert(8) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(root?.right?.value, 8) }, + { assertEquals(root?.right?.color, Markers.RED) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test on creation children`() { + treeStruct.insert(6) + treeStruct.insert(8) + treeStruct.insert(3) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(root?.right?.value, 8) }, + { assertEquals(root?.left?.value, 3) }, + { assertEquals(root?.right?.color, Markers.RED) }, + { assertEquals(root?.left?.color, Markers.RED) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test delete root (left & right children)`() { + treeStruct.insert(6) + treeStruct.insert(8) + treeStruct.insert(3) + treeStruct.delete(6) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(root?.value, 8) }, + { assertEquals(root?.color, Markers.BLACK) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test delete root (right child)`() { + treeStruct.insert(6) + treeStruct.insert(8) + treeStruct.delete(6) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(8, root?.value) }, + { assertEquals(Markers.BLACK, root?.color) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `base test delete root (left child)`() { + treeStruct.insert(6) + treeStruct.insert(3) + treeStruct.delete(6) + val root = treeW.getPrivateNode(treeStruct) + assertAll( + { assertEquals(3, root?.value) }, + { assertEquals(Markers.BLACK, root?.color) }, + { analyzer.checkTree(root!!) } + ) + } + + @Test + fun `fuzzer test`() { + val fuzzer = RBStructFuzzer( + arrayOf( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 20, + 100, + 123, + 234, + 556, + 345677, + 88765, + 43, + 364, + 23456, + 2754 + ), ::testAssert + ) + fuzzer.saveNextTestSets("TEST_TEST") + + assertAll( + { + fuzzer.fuzzInvariantInsert(15, 10) + } + ) + } +} diff --git a/lib/src/test/kotlin/treelib/RBTreeTest.kt b/lib/src/test/kotlin/treelib/RBTreeTest.kt new file mode 100644 index 0000000..3490f63 --- /dev/null +++ b/lib/src/test/kotlin/treelib/RBTreeTest.kt @@ -0,0 +1,67 @@ +package treelib + +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import treelib.rbTree.* +import treelib.commonObjects.Container +import treelib.rbTree.Markers +import utils.TreeStructWrapper +import utils.TreeWrapper +import kotlin.test.assertEquals + +class RBTreeTest { + private val tree = RBTree() + private val treeW = + TreeWrapper>, RBVertex>, RBStateContainer>, RBStruct>, RBTree>() + private val treeSW = + TreeStructWrapper, RBNode>, RBVertex>, RBStateContainer>, RBStruct>>() + + + @ParameterizedTest + @ValueSource(strings = ["1 2 3 4 6 5", "5 3 8 6 9 11 13", "10 11 15 12 17 18"]) + fun `test change color delete root`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, i)) + } + + tree.putItem(num) + + val rootR = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.right?.color + assertEquals(expected = rootR, actual = Markers.RED) + val root = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.key + if (root != null) { + treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.value?.let { tree.deleteItem(it.key) } + } + assertEquals(expected = rootR, actual = Markers.RED) + } + + @ParameterizedTest + @ValueSource(strings = ["1 2 5", "1 11 13", "10 11 18"]) + fun `test check color`(str: String) { + val numbers = str.split(" ").map { it.toInt() }.toMutableList() + val num = mutableListOf>() + for (i in numbers) { + num.add(Pair(i, i)) + } + + tree.putItem(num) + + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.right?.color, actual = Markers.RED) + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.left?.color, actual = Markers.RED) + assertEquals(expected = treeSW.getPrivateNode(treeW.getPrivateNode(tree))?.color, actual = Markers.BLACK) + } + + @Test + fun `test set method`() { + for (i in (1..3)) { + tree.putItem(Pair(i, i)) + } + + assertEquals(expected = tree[3], actual = 3) + assertEquals(expected = tree[2], actual = 2) + assertEquals(expected = tree[1], actual = 1) + } +} \ No newline at end of file diff --git a/lib/src/test/kotlin/treelib/TestModelAVL.kt b/lib/src/test/kotlin/treelib/TestModelAVL.kt new file mode 100644 index 0000000..9b4823a --- /dev/null +++ b/lib/src/test/kotlin/treelib/TestModelAVL.kt @@ -0,0 +1,86 @@ +package treelib + +import treelib.avlTree.AVLNode + +class TestModelAVL { + val firstTreeValues = + listOf( + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + 1, null, null, null, + 14, null, + 23 + ) + val secondTreeValues = + listOf( + null, null, 45, null, null, null, null, null, null, null, null, null, null, null, null, null, + 1, 47, 63, 90, 163, 208, 315, 999, + 44, 72, 205, 507, + 57, 230, + 101 + ) + val thirdTreeValues = + listOf( + null, 33, null, null, 69, 72, null, null, null, null, 174, null, null, null, 293, 370, + 10, 63, 70, 93, 139, 180, 240, 351, + 59, 74, 163, 285, + 67, 192, + 104 + ) + val thirdTreeValuesCase2 = + listOf( + null, 33, null, null, 69, 72, null, null, null, null, 174, null, null, 293, null, null, + 10, 63, 70, 93, 139, 180, 285, 370, + 59, 74, 163, 351, + 67, 192, + 104 + ) + val thirdTreeValuesCase3 = + listOf( + null, 33, null, null, 69, 72, null, null, null, null, 174, null, null, null, null, null, + 10, 63, 70, 93, 139, 180, 285, 351, + 59, 74, 163, 293, + 67, 192, + 104 + ) + val thirdTreeValuesCase4 = + listOf( + null, 33, null, null, 69, 72, null, null, null, null, null, null, null, null, null, null, + 10, 63, 70, 93, 163, 180, 285, 351, + 59, 74, 174, 293, + 67, 192, + 104 + ) + + fun > getTree(values: List): AVLNode { + val nodes = mutableListOf?>() + for (i in 0..15) { + if (values[i] != null) { + nodes.add(AVLNode(values[i]!!, null, null)) + } else + nodes.add(null) + } + for (i in 16..29) { + if (values[i] != null) { + nodes.add(AVLNode(values[i]!!, nodes[2 * (i - 16)], nodes[2 * (i - 16) + 1])) + updateHeight(nodes[i]) + } else + nodes.add(null) + } + nodes.add(AVLNode(values[30]!!, nodes[28], nodes[29])) + + return nodes[30]!! + } + + private fun > updateHeight(currentNode: AVLNode?) { + if (currentNode != null) + currentNode.height = (maxOf(getHeight(currentNode.left), getHeight(currentNode.right)) + 1u) + + } + + private fun > getHeight(currentNode: AVLNode?): UInt { + return currentNode?.height ?: 0u + } + + +} \ No newline at end of file diff --git a/lib/src/test/kotlin/treelib/TestModelRBT.kt b/lib/src/test/kotlin/treelib/TestModelRBT.kt new file mode 100644 index 0000000..b58fea3 --- /dev/null +++ b/lib/src/test/kotlin/treelib/TestModelRBT.kt @@ -0,0 +1,196 @@ +package treelib + +import treelib.rbTree.RBNode +import treelib.rbTree.Markers + +class TestModelRBT { + fun > countBlackNodes(node: RBNode): Int { + var count = 0 + var currentNode: RBNode? = node + while (currentNode != null) { + count = if (currentNode.color == Markers.BLACK) count + 1 else count + currentNode = currentNode.parent + } + return count + } + + fun > deleteNode(remoteNode: RBNode, newNode: RBNode): RBNode { + remoteNode.left?.parent = newNode + remoteNode.right?.parent = newNode + newNode.left = remoteNode.left + newNode.right = remoteNode.right + newNode.color = remoteNode.color + when (newNode) { + newNode.parent?.left -> newNode.parent?.left = null + else -> newNode.parent?.right = null + } + + if (remoteNode.parent != null) { + val parent = remoteNode.parent!! + when (remoteNode) { + parent.left -> parent.left = newNode + else -> parent.right = newNode + } + newNode.parent = parent + } else { + newNode.parent = null + } + + return newNode + } + + fun getFourthTree(): MutableList?> { + val nodes = getSecondTree() + for (i in 24..27) { + nodes[i]!!.right = null + nodes[i]!!.left = null + } + nodes[25]!!.right = nodes[19] + nodes[25]!!.left = nodes[18] + nodes[25]!!.color = Markers.RED + nodes[29]!!.color = Markers.BLACK + nodes[26]!!.color = Markers.RED + nodes[27]!!.color = Markers.RED + + return nodes + } + + fun getThirdTree(): MutableList?> { + val nodes = getSecondTree() + nodes[28]!!.color = Markers.BLACK + nodes[24]!!.color = Markers.RED + nodes[24]?.right = null + nodes[24]?.left = null + nodes[25]!!.color = Markers.RED + nodes[25]?.right = null + nodes[25]?.left = null + return nodes + + } + + fun getSecondTree(): MutableList?> { + val nodes = getFirstTree() + for (i in 16..23) { + nodes[i]!!.color = Markers.RED + nodes[i]!!.right = null + nodes[i]!!.left = null + } + nodes[0]!!.color = Markers.RED + nodes[16]!!.left = nodes[0] + + return nodes + } + + + fun getFirstTree(): MutableList?> { + val values = listOf( + 1, 7, 11, 14, 16, 20, 21, null, 29, 38, null, 45, 52, 70, null, null, + 4, 12, 18, 24, 37, 42, 63, 90, + 10, 19, 40, 71, + 15, 50, + 25 + ) + val markers = listOf( + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.RED, + Markers.RED, + Markers.RED, + null, + Markers.RED, + Markers.RED, + null, + Markers.RED, + Markers.BLACK, + Markers.BLACK, + Markers.RED, + Markers.RED, + Markers.RED, + Markers.RED, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.RED, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.BLACK, + Markers.RED, + Markers.RED, + Markers.BLACK + + ) + val nodes = mutableListOf?>() + for (i in 1..31) { + when (i) { + in 1..16 -> { + if (values[i - 1] != null) + nodes.add(RBNode(values[i - 1]!!, null, null, null, markers[i - 1]!!)) + else + nodes.add(null) + + } + + in 17..24 -> { + val delta = i - 17 + if (values[i - 1] != null) + nodes.add( + RBNode( + values[i - 1]!!, + nodes[i - 17 + delta], + nodes[i - 17 + delta + 1], + null, + markers[i - 1]!! + ) + ) + else + nodes.add(null) + nodes[i - 17 + delta]?.parent = nodes[i - 1] + nodes[i - 17 + delta + 1]?.parent = nodes[i - 1] + + } + + in 25..28 -> { + val delta = i - 25 + nodes.add( + RBNode( + values[i - 1]!!, + nodes[i - 9 + delta], + nodes[i - 9 + delta + 1], + null, + markers[i - 1]!! + ) + ) + nodes[i - 9 + delta]?.parent = nodes[i - 1] + nodes[i - 9 + delta + 1]?.parent = nodes[i - 1] + } + + in 29..30 -> { + val delta = i - 29 + nodes.add( + RBNode( + values[i - 1]!!, + nodes[i - 5 + delta], + nodes[i - 5 + delta + 1], + null, + markers[i - 1]!! + ) + ) + nodes[i - 5 + delta]?.parent = nodes[i - 1] + nodes[i - 5 + delta + 1]?.parent = nodes[i - 1] + } + + else -> { + nodes.add(RBNode(values[i - 1]!!, nodes[28], nodes[29], null, markers[i - 1]!!)) + nodes[28]?.parent = nodes[i - 1] + nodes[29]?.parent = nodes[i - 1] + } + } + } + return nodes + } +} diff --git a/lib/src/test/kotlin/utils/AVLAnalyzer.kt b/lib/src/test/kotlin/utils/AVLAnalyzer.kt new file mode 100644 index 0000000..3d1a6fd --- /dev/null +++ b/lib/src/test/kotlin/utils/AVLAnalyzer.kt @@ -0,0 +1,40 @@ +package utils + +import treelib.avlTree.AVLNode +import kotlin.math.abs +import kotlin.math.max + +class AVLAnalyzer>(override val assertMethod: (input: String) -> Unit) : Analyzer>() { + private var heightL = 0 + private var heightR = 0 + private var heightMax = 0 + private var mainRight: AVLNode? = null + + override fun checkTree(root: AVLNode) { + mainRight = root.right + descent(root, 0) + heightR = heightMax + if (abs(heightR - heightL) > 1) { + wrappedAssertMethod("the invariant is not observed") + } + } + + private fun descent(node: AVLNode, height: Int) { + heightMax = max(heightMax, height) + val left = node.left + val right = node.right + + if (left != null) { + descent(left, height + 1) + } + + if (right == mainRight && heightR == 0) { + heightL = heightMax + heightMax = 0 + } + + if (right != null) { + descent(right, height + 1) + } + } +} diff --git a/lib/src/test/kotlin/utils/Analyzer.kt b/lib/src/test/kotlin/utils/Analyzer.kt new file mode 100644 index 0000000..6131d02 --- /dev/null +++ b/lib/src/test/kotlin/utils/Analyzer.kt @@ -0,0 +1,17 @@ +package utils + +import treelib.abstractTree.Node + +abstract class Analyzer< + Pack : Comparable, + NodeType : Node, + > { + + var message: String = "" + + protected abstract val assertMethod: (input: String) -> Unit + + protected fun wrappedAssertMethod(input: String) = assertMethod("$message$input") + + abstract fun checkTree(root: NodeType) +} \ No newline at end of file diff --git a/lib/src/test/kotlin/utils/BINAnalyzer.kt b/lib/src/test/kotlin/utils/BINAnalyzer.kt new file mode 100644 index 0000000..8ad0b8f --- /dev/null +++ b/lib/src/test/kotlin/utils/BINAnalyzer.kt @@ -0,0 +1,51 @@ +package utils + +import treelib.binTree.BINNode + +class BINAnalyzer>( + override val assertMethod: (input: String) -> Unit = { + throw InternalError(it) + } +) : Analyzer>() { + + override fun checkTree(root: BINNode) { + checkInvariant(root) + } + + private fun checkInvariant(node: BINNode) { + if ((node.left == null) && (node.right == null)) return + + node.right?.let { + when { + it.value == node.value -> { + wrappedAssertMethod("parent.value == RightChild.value => [${node.value} == ${it.value}]") + return@checkInvariant + } + + it.value < node.value -> { + wrappedAssertMethod("parent.value > RightChild.value => [${node.value} > ${it.value}]") + return@checkInvariant + } + + else -> {} + } + } + + node.left?.let { + when { + it.value == node.value -> { + wrappedAssertMethod("parent.value == LeftChild.value => [${node.value} == ${it.value}]") + return@checkInvariant + } + + it.value > node.value -> { + wrappedAssertMethod("parent.value < LeftChild.value => [${node.value} < ${it.value}]") + return@checkInvariant + } + + else -> {} + } + } + } + +} diff --git a/lib/src/test/kotlin/utils/RBAnalyzer.kt b/lib/src/test/kotlin/utils/RBAnalyzer.kt new file mode 100644 index 0000000..1b5d9b5 --- /dev/null +++ b/lib/src/test/kotlin/utils/RBAnalyzer.kt @@ -0,0 +1,84 @@ +package utils + +import treelib.rbTree.RBNode +import treelib.rbTree.Markers + +class RBAnalyzer>( + override val assertMethod: (input: String) -> Unit = { + throw InternalError(it) + } +) : Analyzer>() { + /** Magic number for error := -9999999 -> just an impossible value **/ + private val errorMagicNumber = -9999999 + + override fun checkTree(root: RBNode) { + if (root.color != Markers.BLACK) wrappedAssertMethod("The root isn't black!!!") + checkInvariant(root) + } + + private fun checkInvariant(node: RBNode): Int { + var leftBlackCount = 0 + var rightBlackCount = 0 + + if ((node.right == null) && (node.left == null)) { + if (node.color == Markers.RED) return 0 + else return 1 + } + node.right?.let { + when { + it.value == node.value -> { + wrappedAssertMethod("parent.value == RightChild.value => [${node.value} == ${it.value}]") + return@checkInvariant errorMagicNumber + } + + it.value < node.value -> { + wrappedAssertMethod("parent.value > RightChild.value => [${node.value} > ${it.value}]") + return@checkInvariant errorMagicNumber + } + + (it.color == Markers.RED) && (node.color == Markers.RED) -> { + wrappedAssertMethod("parent.color == RED == RightChild.color => [parent - ${node.value} RightChild - ${it.value}]") + return@checkInvariant errorMagicNumber + } + + else -> {} + } + } + + node.left?.let { + when { + it.value == node.value -> { + wrappedAssertMethod("parent.value == LeftChild.value => [${node.value} == ${it.value}]") + return@checkInvariant errorMagicNumber + } + + it.value > node.value -> { + wrappedAssertMethod("parent.value < LeftChild.value => [${node.value} < ${it.value}]") + return@checkInvariant errorMagicNumber + } + + (it.color == Markers.RED) && (node.color == Markers.RED) -> { + wrappedAssertMethod("parent.color == RED == LeftChild.color => [parent - ${node.value} LeftChild - ${it.value}]") + return@checkInvariant errorMagicNumber + } + + else -> {} + } + } + + leftBlackCount = node.left?.let { return@let checkInvariant(it) } ?: 0 + rightBlackCount = node.right?.let { return@let checkInvariant(it) } ?: 0 + + if (leftBlackCount < 0 || rightBlackCount < 0) return errorMagicNumber + + if (leftBlackCount != rightBlackCount) { + wrappedAssertMethod( + "Number of black nodes does not match in children: parent.value - ${node.value} =>[left - $leftBlackCount] != [right - $rightBlackCount]" + ) + return errorMagicNumber + } + + if (node.color == Markers.BLACK) return leftBlackCount + 1 + else return rightBlackCount + } +} diff --git a/lib/src/test/kotlin/utils/TreeStructWrapper.kt b/lib/src/test/kotlin/utils/TreeStructWrapper.kt new file mode 100644 index 0000000..c0e05bb --- /dev/null +++ b/lib/src/test/kotlin/utils/TreeStructWrapper.kt @@ -0,0 +1,30 @@ +package utils + +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer +import treelib.abstractTree.TreeStruct +import treelib.abstractTree.Vertex + +@Suppress("UNCHECKED_CAST") +class TreeStructWrapper, NodeType : Node, VertexType : Vertex, State : StateContainer, TStruct : TreeStruct> { + + fun getPrivateNode(tree: TStruct, name: String = "root"): NodeType? { + val field = tree.javaClass.getDeclaredField(name) + field.isAccessible = true + val root = field.get(tree) + return if (root == null) null + else root as? NodeType + } + + fun executePrivateMethod( + tree: TStruct, + name: String, + parameterValues: Array? = null, + vararg parameterTypes: Class<*> + ): Any? { + val method = tree.javaClass.getDeclaredMethod(name, *parameterTypes) + method.isAccessible = true + return if (parameterValues != null) method.invoke(tree, *parameterValues) + else method.invoke(tree) + } +} diff --git a/lib/src/test/kotlin/utils/TreeWrapper.kt b/lib/src/test/kotlin/utils/TreeWrapper.kt new file mode 100644 index 0000000..e8f8285 --- /dev/null +++ b/lib/src/test/kotlin/utils/TreeWrapper.kt @@ -0,0 +1,23 @@ +package utils + +import treelib.abstractTree.* +import treelib.commonObjects.Container + + +@Suppress("UNCHECKED_CAST") +class TreeWrapper< + V : Comparable, + Value, + NodeType : Node, NodeType>, + VertexType : Vertex>, + State : StateContainer, NodeType>, + TStruct : TreeStruct, NodeType, State, VertexType>, + Wood : Tree> { + fun getPrivateNode(tree: Wood, name: String = "treeStruct"): TStruct { + val treeS = tree.javaClass.getDeclaredField(name) + treeS.isAccessible = true + val treeStruct = treeS.get(tree) + + return treeStruct as TStruct + } +} diff --git a/lib/src/test/kotlin/utils/fuzzers/AVLStructFuzzer.kt b/lib/src/test/kotlin/utils/fuzzers/AVLStructFuzzer.kt new file mode 100644 index 0000000..17d52a2 --- /dev/null +++ b/lib/src/test/kotlin/utils/fuzzers/AVLStructFuzzer.kt @@ -0,0 +1,17 @@ +package utils.fuzzers + +import treelib.avlTree.AVLNode +import treelib.avlTree.AVLStateContainer +import treelib.avlTree.AVLStruct +import treelib.avlTree.AVLVertex +import utils.AVLAnalyzer + +class AVLStructFuzzer>( + override val baseInput: Array, + override val assertMethod: (input: String) -> Unit +) : TreeStructFuzzer, AVLVertex, AVLAnalyzer, AVLStateContainer, AVLStruct>() { + + override fun createTreeStruct(): AVLStruct = AVLStruct() + + override fun createAnalyzer(): AVLAnalyzer = AVLAnalyzer(assertMethod) +} \ No newline at end of file diff --git a/lib/src/test/kotlin/utils/fuzzers/BINStructFuzzer.kt b/lib/src/test/kotlin/utils/fuzzers/BINStructFuzzer.kt new file mode 100644 index 0000000..720789c --- /dev/null +++ b/lib/src/test/kotlin/utils/fuzzers/BINStructFuzzer.kt @@ -0,0 +1,17 @@ +package utils.fuzzers + +import treelib.binTree.BINNode +import treelib.binTree.BINStateContainer +import treelib.binTree.BINStruct +import treelib.binTree.BINVertex +import utils.BINAnalyzer + +class BINStructFuzzer>( + override val baseInput: Array, + override val assertMethod: (input: String) -> Unit +) : TreeStructFuzzer, BINVertex, BINAnalyzer, BINStateContainer, BINStruct>() { + + override fun createTreeStruct(): BINStruct = BINStruct() + + override fun createAnalyzer(): BINAnalyzer = BINAnalyzer(assertMethod) +} \ No newline at end of file diff --git a/lib/src/test/kotlin/utils/fuzzers/RBStructFuzzer.kt b/lib/src/test/kotlin/utils/fuzzers/RBStructFuzzer.kt new file mode 100644 index 0000000..81d5278 --- /dev/null +++ b/lib/src/test/kotlin/utils/fuzzers/RBStructFuzzer.kt @@ -0,0 +1,17 @@ +package utils.fuzzers + +import treelib.rbTree.RBNode +import treelib.rbTree.RBStateContainer +import treelib.rbTree.RBStruct +import treelib.rbTree.RBVertex +import utils.RBAnalyzer + +class RBStructFuzzer>( + override val baseInput: Array, + override val assertMethod: (input: String) -> Unit +) : TreeStructFuzzer, RBVertex, RBAnalyzer, RBStateContainer, RBStruct>() { + + override fun createTreeStruct(): RBStruct = RBStruct() + + override fun createAnalyzer(): RBAnalyzer = RBAnalyzer(assertMethod) +} \ No newline at end of file diff --git a/lib/src/test/kotlin/utils/fuzzers/TreeStructFuzzer.kt b/lib/src/test/kotlin/utils/fuzzers/TreeStructFuzzer.kt new file mode 100644 index 0000000..5ae952e --- /dev/null +++ b/lib/src/test/kotlin/utils/fuzzers/TreeStructFuzzer.kt @@ -0,0 +1,120 @@ +package utils.fuzzers + +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer +import treelib.abstractTree.TreeStruct +import treelib.abstractTree.Vertex +import treelib.commonObjects.exceptions.* +import utils.Analyzer +import utils.TreeStructWrapper +import java.io.File +import java.time.Instant +import kotlin.random.Random +import kotlin.random.nextInt + +abstract class TreeStructFuzzer< + Pack : Comparable, + NodeType : Node, + VertexType: Vertex, + AnalyzerType : Analyzer, + State : StateContainer, + TreeStructType : TreeStruct, + > { + abstract val baseInput: Array + + protected abstract val assertMethod: (input: String) -> Unit + + private var saveFlag: Boolean = false + + private var dirPath: String? = null + + protected val treeWrapper = TreeStructWrapper() + + protected abstract fun createTreeStruct(): TreeStructType + + protected abstract fun createAnalyzer(): AnalyzerType + + /** + * testNumbers - How many times fuzzer would be run + * inputSize - How many elements from baseInput would be used + * **/ + fun fuzzInvariantInsert(testNumbers: Int, inputSize: Int? = null) { + val dataSize: Int = checkCorrectInput(testNumbers, inputSize) + + for (iterations in 0 until testNumbers) { + val treeStruct = createTreeStruct() + val testSetIndexes = getInputSetIndexes(dataSize) + val testID = "${Instant.now().toEpochMilli() + iterations}-insert" + val analyzer = createAnalyzer() + + analyzer.message = "$testID: " + + if (saveFlag) saveCurrentTestSet(testID, testSetIndexes) + + try { + for (index in testSetIndexes) { + treeStruct.insert(baseInput[index]) + } + } catch (ex: Exception) { + exceptionsCatch(ex) + } + + val root = treeWrapper.getPrivateNode(treeStruct, "root") + // todo: probably won't work with junit.fail() + root?.let { analyzer.checkTree(it) } ?: assertMethod("The root was not created in the test $testID") + } + } + + fun fuzzInvariantDelete(testNumbers: Int, inputSize: Int? = null) { + val dataSize: Int = checkCorrectInput(testNumbers, inputSize) + TODO("DON'T IMPLEMENTED YET") + } + + fun fuzzInvariant(testNumbers: Int, inputSize: Int? = null) { + val dataSize: Int = checkCorrectInput(testNumbers, inputSize) + TODO("DON'T IMPLEMENTED YET") + } + + private fun checkCorrectInput(testNumbers: Int, inputSize: Int?): Int { + val dataSize: Int + + if (inputSize == null) dataSize = baseInput.size + else dataSize = inputSize + + if (dataSize > baseInput.size) throw InternalError("inputSize > size of the baseInput") + return dataSize + } + + private fun getInputSetIndexes(inputSize: Int): List { + return generateSequence { Random.nextInt(baseInput.indices) }.distinct().take(inputSize).toList() + } + + private fun exceptionsCatch(ex: Exception) { + when (ex) { + is VauleNotFound, + -> {/*TODO: Implement */ + } + + else -> throw ex + } + } + + fun saveNextTestSets(dirName: String) { + dirPath = dirName + val file = File("./$dirPath") + file.mkdir() + if (file.isDirectory) saveFlag = true + } + + fun dontSaveNextTestSets() { + saveFlag = false + } + + private fun saveCurrentTestSet(testId: String, testSet: List) { + val file = File("./$dirPath/$testId.txt") + file.createNewFile() + for (index in testSet) { + file.appendText("${baseInput[index]} \n") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..9e01550 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,28 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0/userguide/multi_project_builds.html + */ + +rootProject.name = "treelib" +include("lib") +include("treeApp") + + +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() + } +} diff --git a/treeApp/build.gradle.kts b/treeApp/build.gradle.kts new file mode 100644 index 0000000..a44b44d --- /dev/null +++ b/treeApp/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + java + kotlin("jvm") version "1.8.10" + id("org.jetbrains.compose") version "1.4.0" +} + +kotlin { + jvmToolchain(17) +} + +dependencies { + api("org.apache.commons:commons-math3:3.6.1") + implementation(project(":lib")) + implementation("com.google.guava:guava:31.1-jre") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") + + implementation("com.google.code.gson:gson:2.10.1") + + implementation(compose.desktop.currentOs) + implementation(compose.material3) +} + +compose.desktop { + application { + mainClass = "treeApp.MainKt" + } +} diff --git a/treeApp/src/main/kotlin/treeApp/Main.kt b/treeApp/src/main/kotlin/treeApp/Main.kt new file mode 100644 index 0000000..654a8a5 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/Main.kt @@ -0,0 +1,127 @@ +package treeApp +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.onDrag +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.window.WindowDraggableArea +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.* +import androidx.compose.ui.window.* +import treeApp.ui.AppIcon +import treeApp.ui.ControlFields +import treeApp.ui.MyTopAppBar +import java.awt.Dimension + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +fun main() = application { + + + val deleteButton: MutableState = remember { mutableStateOf(false) } + val saveButton: MutableState = remember { mutableStateOf(false) } + val openButton: MutableState = remember { mutableStateOf(false) } + val createButton: MutableState = remember { mutableStateOf(false) } + val minimizeButton: MutableState = remember { mutableStateOf(false) } + val maximizeButton: MutableState = remember { mutableStateOf(false) } + val closeButton: MutableState = remember { mutableStateOf(false) } + + val controller = treeApp.controller.Controller() + val activeTree = remember { mutableStateOf(false) } + val windowState = rememberWindowState(placement = WindowPlacement.Maximized) + val deleteTreeState = remember { mutableStateOf(false) } + val openTreeState = remember { mutableStateOf(false) } + val createTreeState = remember { mutableStateOf(false) } + + val dragState = remember { mutableStateOf(false) } + + if (!closeButton.value) { + Window( + onCloseRequest = ::exitApplication, + undecorated = true, + state = windowState, + icon = AppIcon() + ) { + + this.window.minimumSize = Dimension(800, 600) + Scaffold( + topBar = { + WindowDraggableArea { + MyTopAppBar( + clickDeleteButton = deleteButton, + clickSaveButton = saveButton, + clickOpenButton = openButton, + clickCreateButton = createButton, + clickMinimizeButton = minimizeButton, + clickMaximizeButton = maximizeButton, + clickCloseButton = closeButton, + windowState = windowState, + controller = controller, + activeTree = activeTree, + deleteTreeState = deleteTreeState, + openTreeState = openTreeState, + createTreeState = createTreeState + ) + } + }, + content = { + MaterialTheme( + colorScheme = lightColors, + typography = myTypography + ) { + + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .offset(0.dp, 50.dp) + .onDrag { + dragState.value = true + } + + ) { + ControlFields( + controller = controller, + activeTree = activeTree, + deleteTreeState = deleteTreeState, + openTreeState = openTreeState, + createTreeState = createTreeState, + dragState = dragState, + windowState = windowState + ) + } + + } + + } + ) + + + } + } + +} + + +val lightColors = lightColorScheme( + background = Color(255, 255, 255), + primary = Color(34, 35, 41), + secondary = Color(47, 105, 215), + onSecondary = Color(148, 150, 166), + tertiary = Color(208, 223, 252), + onTertiary = Color(51, 51, 51), + onPrimary = Color(53, 55, 63) + +) + +val myTypography = Typography( + headlineMedium = TextStyle( + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 96.sp + ) +) diff --git a/treeApp/src/main/kotlin/treeApp/controller/Controller.kt b/treeApp/src/main/kotlin/treeApp/controller/Controller.kt new file mode 100644 index 0000000..4c6e39a --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/Controller.kt @@ -0,0 +1,138 @@ +package treeApp.controller + +import androidx.compose.ui.window.WindowState +import treeApp.controller.databaseManage.AVLTreeManager +import treeApp.controller.databaseManage.BINTreeManager +import treeApp.controller.databaseManage.RBTreeManager +import treelib.commonObjects.Container +import treeApp.controller.viewPart.drawableAVL.AVLDrawableTree +import treeApp.controller.viewPart.drawableBIN.BINDrawableTree +import treeApp.controller.viewPart.drawableRB.RBDrawableTree +import treeApp.controller.viewPart.drawableTree.DrawTree +import java.io.File +import java.lang.Integer.min + + +class Controller { + + private val avlManager = AVLTreeManager() + private val rbManager = RBTreeManager() + private val binManager = BINTreeManager() + + + var tree: DrawTree? = null + + fun getSavedTreesNames(): List> { + val avlTrees = avlManager.getSavedTreesNames() + val rbTrees = rbManager.getSavedTreesNames() + + createDummyFiles(avlTrees, rbTrees) + + return listOf( + rbTrees.subList(0, min(3, rbTrees.size)), + avlTrees.subList(0, min(3, avlTrees.size)), + binManager.getSavedTreesNames() + ) + } + + private fun createDummyFiles(avlTrees: List, rbTrees: List) { + val avlPath = System.getProperty("user.dir") + "/saved-trees/AVL-trees" + val rbPath = System.getProperty("user.dir") + "/saved-trees/RB-trees" + + File(avlPath).mkdirs() + File(rbPath).mkdirs() + + if (avlTrees.isNotEmpty()) { + for (name in avlTrees) { + File(avlPath, name).run { + createNewFile() + } + } + } + + if (rbTrees.isNotEmpty()) { + for (name in rbTrees) { + File(rbPath, name).run { + createNewFile() + } + } + } + + } + + fun createTree(treeName: String, id: Int): DrawTree { + when (id) { + 0 -> tree = RBDrawableTree(treeName, rbManager) + 1 -> tree = AVLDrawableTree(treeName, avlManager) + 2 -> tree = BINDrawableTree(treeName, binManager) + } + + tree?.initTree() + + return tree ?: throw NullPointerException() + } + + fun insert(value: String): DrawTree { + val key = if (isInt(value.trim())) value.toInt() else value.hashCode() + + tree?.insert(Container(Pair(key, value))) ?: throw NullPointerException() + tree?.updateTree() ?: throw NullPointerException() + tree?.repositionTree(800f, 10f) ?: throw NullPointerException() + + return tree ?: throw NullPointerException() + } + + fun find(value: String, windowState: WindowState): DrawTree { + val key = if (isInt(value.trim())) value.toInt() else value.hashCode() + + val offset1 = tree?.find(key) ?: throw NullPointerException() + if (offset1.first >= 0){ + tree?.addOffset( + windowState.size.width.value / 2 - offset1.first, + windowState.size.height.value / 2 - offset1.second, + ) + } + + + return tree ?: throw NullPointerException() + + } + + fun delete(value: String): DrawTree { + val key = if (isInt(value.trim())) value.toInt() else value.hashCode() + + tree?.delete(Container(Pair(key, ""))) ?: throw NullPointerException() + tree?.updateTree() ?: throw NullPointerException() + tree?.repositionTree(800f, 10f) ?: throw NullPointerException() + + return tree ?: throw NullPointerException() + } + + + private fun isInt(value: String): Boolean { + return try { + Integer.parseInt(value.trim()) + true + } catch (ex: NumberFormatException) { + false + } + } + + fun deleteTree(): DrawTree { + tree?.deleteTree() + return tree ?: throw Exception("Tree not initialized") + } + + fun saveTree(fileName: String) { + if (tree == null) + throw Exception("Tree not initialized") + + tree?.name = fileName + getSavedTreesNames() + tree?.saveTreeToDB() ?: throw Exception() + + } + + +} + diff --git a/treeApp/src/main/kotlin/treeApp/controller/databaseManage/AVLTreeManager.kt b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/AVLTreeManager.kt new file mode 100644 index 0000000..1d7902f --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/AVLTreeManager.kt @@ -0,0 +1,116 @@ +package treeApp.controller.databaseManage + +import treelib.databaseSave.sqlite.DrawableAVLVertex +import treelib.databaseSave.sqlite.SQLiteRepositoryExposed +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import treelib.avlTree.AVLNode +import treelib.avlTree.AVLStateContainer +import treelib.avlTree.AVLStruct +import treelib.avlTree.AVLVertex +import treelib.commonObjects.Container + + +class AVLTreeManager : TreeManager< + Container, + DrawableAVLVertex>, + AVLNode>, + AVLStateContainer>, + AVLVertex>, + AVLStruct>, + > { + private val db = SQLiteRepositoryExposed() + + init { + db.initDataBase(AVL_DB_DEFAULT_NAME) + } + + private fun drawVertexToVertex(drawVertex: MutableList>>): MutableList>> { + val ans = mutableListOf>>() + for (el in drawVertex) ans.add(AVLVertex(value = el.value, height = el.height.toUInt())) + return ans + } + + private fun vertexToDrawVertex(drawVertex: List>>): MutableList>> { + val ans = mutableListOf>>() + for (el in drawVertex) ans.add( + DrawableAVLVertex( + value = el.value, + height = el.height, + x = -0.0, + y = -0.0 + ) + ) + return ans + } + + private fun serialization(data: Container): String = Json.encodeToString(data) + + private fun deserialization(data: String): Container = + Json.decodeFromString>(data) + + override fun initTree(name: String, tree: AVLStruct>): MutableList>> { + if (db.isTreeExist(name)) { + val vertexes = db.getTree(name, ::deserialization) + tree.restoreStruct(drawVertexToVertex(vertexes)) + return vertexes + } + return mutableListOf() + } + + override fun getVertexesForDrawFromTree(tree: AVLStruct>): MutableList>> { + return vertexToDrawVertex(tree.preOrder()) + } + + override fun getVertexesForDrawFromDB(name: String): MutableList>> { + return db.getTree(name, ::deserialization) + } + + override fun saveTreeToDB( + name: String, + preOrder: List>>, + inOrder: List>> + ) { + db.deleteTree(name) + db.saveTree(name, preOrder.toMutableList(), ::serialization) + } + + override fun saveTreeToDB(name: String, tree: AVLStruct>) { + val info = vertexToDrawVertex(tree.preOrder()) + db.deleteTree(name) + db.saveTree(name, info.toMutableList(), ::serialization) + } + + override fun deleteTreeFromDB(name: String) { + if (db.isTreeExist(name)) db.deleteTree(name) + } + + override fun getSavedTreesNames(): List = db.getAllSavedTrees() +/* { + val treesNames = db.getAllSavedTrees() + +// val dirPath = System.getProperty("user.dir") + "/saved-trees/AVL-trees" +// File(dirPath).mkdirs() +// if (treesNames.isNotEmpty()) { +// for (name in treesNames) { +// File(dirPath, name).run { +// createNewFile() +// } +// } +// } + + return treesNames.subList(0, treesNames.size) + } + + override fun insert(item: Container) = avlTree.insert(item) + + override fun delete(item: Container) { + if (avlTree.find(item) != null) avlTree.delete(item) + } + */ + + companion object { + const val AVL_DB_DEFAULT_NAME = "avlDB.db" + } +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/databaseManage/BINTreeManager.kt b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/BINTreeManager.kt new file mode 100644 index 0000000..583cf14 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/BINTreeManager.kt @@ -0,0 +1,96 @@ +package treeApp.controller.databaseManage + +import com.google.gson.reflect.TypeToken +import treelib.databaseSave.jsonFormat.DrawableBINVertex +import treelib.databaseSave.jsonFormat.JsonRepository +import treelib.binTree.BINNode +import treelib.binTree.BINStateContainer +import treelib.binTree.BINStruct +import treelib.binTree.BINVertex +import treelib.commonObjects.Container +import java.io.File + +class BINTreeManager : TreeManager< + Container, + DrawableBINVertex>, + BINNode>, + BINStateContainer>, + BINVertex>, + BINStruct> + > { + + /*** using json format files ***/ + + private val dirPath = System.getProperty("user.dir") + "/saved-trees/BIN-trees" + + private val jsonRep = JsonRepository(dirPath) + + override fun initTree( + name: String, + tree: BINStruct> + ): List>> { + if (this.isTreeExist(name)) { + val typeToken = object : TypeToken>>>() {} + val preOrder = jsonRep.exportTree("$name.json", typeToken).toList() + + tree.restoreStruct(preOrder.toList()) + + return preOrder + } + + return listOf() + } + + override fun saveTreeToDB( + name: String, + preOrder: List>>, + inOrder: List>> + ) { + jsonRep.saveChanges(preOrder.toTypedArray(), "$name.json") + } + + override fun saveTreeToDB(name: String, tree: BINStruct>) { + val info = vertexToDrawVertex(tree.preOrder()) + jsonRep.saveChanges(info.toTypedArray(), "$name.json") + } + + private fun drawVertexToVertex(drawVertex: MutableList>>): MutableList>> { + val ans = mutableListOf>>() + for (el in drawVertex) ans.add(BINVertex(value = el.value)) + return ans + } + + private fun vertexToDrawVertex(drawVertex: List>>): MutableList>> { + val ans = mutableListOf>>() + for (el in drawVertex) ans.add( + DrawableBINVertex( + value = el.value, + x = -0.0, + y = -0.0 + ) + ) + return ans + } + + override fun deleteTreeFromDB(name: String) = jsonRep.removeTree(name) + + override fun getSavedTreesNames(): List { + val filesNames = File(dirPath).list()?.map { it.replace(".json", "") } + + return filesNames?.subList(0, filesNames.size) ?: listOf() + } + + private fun isTreeExist(treeName: String): Boolean { + return File(dirPath, "${treeName}.json").exists() + } + + override fun getVertexesForDrawFromDB(name: String): List>> { + TODO("Not yet implemented") + } + + override fun getVertexesForDrawFromTree(tree: BINStruct>): List>> = + tree.preOrder().map { DrawableBINVertex(it.value) } + + fun cleanDB() = jsonRep.clean() + +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/databaseManage/RBTreeManager.kt b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/RBTreeManager.kt new file mode 100644 index 0000000..1087744 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/RBTreeManager.kt @@ -0,0 +1,78 @@ +package treeApp.controller.databaseManage + +import treelib.databaseSave.neo4j.DrawableRBVertex +import treelib.databaseSave.neo4j.Neo4jRepository +import treelib.commonObjects.Container +import treelib.rbTree.RBNode +import treelib.rbTree.RBStateContainer +import treelib.rbTree.RBStruct +import treelib.rbTree.RBVertex + +class RBTreeManager : TreeManager< + Container, + DrawableRBVertex>, + RBNode>, + RBStateContainer>, + RBVertex>, + RBStruct>, + > { + + private val neo4jDB = Neo4jRepository() + + init { + neo4jDB.open("bolt://localhost:7687", "neo4j", "password") + } + + override fun initTree(name: String, tree: RBStruct>): List>> { + /*** orders.first = preOrder, orders.second = inOrder ***/ + if (this.isTreeExist(name)) { + val orders: Pair>>, List>>> = + neo4jDB.exportRBtree(name) + tree.restoreStruct(orders.first, orders.second) + //neo4jDB.close() + return orders.first + } + + return listOf() + } + + override fun saveTreeToDB( + name: String, + preOrder: List>>, + inOrder: List>> + ) { + neo4jDB.saveChanges(preOrder.toTypedArray(), inOrder.toTypedArray(), name) + //neo4jDB.close() + } + + override fun saveTreeToDB(name: String, tree: RBStruct>) { + TODO() + } + + + override fun deleteTreeFromDB(name: String) { + + neo4jDB.removeTree(name) + //neo4jDB.close() + + } + + override fun getSavedTreesNames(): List = neo4jDB.findNamesTrees() + + override fun getVertexesForDrawFromDB(name: String): List>> { + return neo4jDB.exportRBtree(name).first.map { DrawableRBVertex(it.value, it.color) } + } + + override fun getVertexesForDrawFromTree(tree: RBStruct>): List>> { + return tree.preOrder().map { DrawableRBVertex(it.value, it.color) } + } + + private fun isTreeExist(treeName: String): Boolean { + return neo4jDB.findTree(treeName) + } + + fun cleanDB() { + neo4jDB.clean() + neo4jDB.close() + } +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/databaseManage/TreeManager.kt b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/TreeManager.kt new file mode 100644 index 0000000..f598fd7 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/databaseManage/TreeManager.kt @@ -0,0 +1,33 @@ +package treeApp.controller.databaseManage + +import treelib.databaseSave.DrawableVertex +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer +import treelib.abstractTree.TreeStruct +import treelib.abstractTree.Vertex +import treelib.commonObjects.Container + +interface TreeManager< + Pack: Container, + DVertexType : DrawableVertex, + NodeType : Node, + State : StateContainer, + VertexType : Vertex, + StructType: TreeStruct + > { + + fun initTree(name: String, tree: StructType): List + + fun getVertexesForDrawFromTree(tree: StructType): List + + fun getVertexesForDrawFromDB(name: String): List + + fun saveTreeToDB(name: String, preOrder: List, inOrder: List) + + fun saveTreeToDB(name: String, tree: StructType) + + fun deleteTreeFromDB(name: String) + + fun getSavedTreesNames(): List + +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableNode.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableNode.kt new file mode 100644 index 0000000..e15eb48 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableNode.kt @@ -0,0 +1,17 @@ +package treeApp.controller.viewPart.drawableAVL + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import treeApp.controller.viewPart.drawableTree.DrawableNode +import treeApp.ui.nodeDesign.AVLNodeDesign + +class AVLDrawableNode( + override val value: Pack, + override var leftChild: AVLDrawableNode? = null, + override var rightChild: AVLDrawableNode? = null, + val height: Int, + xState: MutableState, + yState: MutableState, +) : DrawableNode>(AVLNodeDesign, AVLNodeDesign.colorNode, xState, yState) { + override val clickState = mutableStateOf(false) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableTree.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableTree.kt new file mode 100644 index 0000000..3b9da5d --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableAVL/AVLDrawableTree.kt @@ -0,0 +1,48 @@ +package treeApp.controller.viewPart.drawableAVL + +import androidx.compose.runtime.mutableStateOf +import treeApp.controller.databaseManage.TreeManager +import treelib.databaseSave.sqlite.DrawableAVLVertex +import treelib.avlTree.AVLNode +import treelib.avlTree.AVLStateContainer +import treelib.avlTree.AVLStruct +import treelib.avlTree.AVLVertex +import treelib.commonObjects.Container +import treeApp.controller.viewPart.drawableTree.DrawableTree +import treeApp.ui.nodeDesign.AVLNodeDesign + +class AVLDrawableTree( + override var name: String, + override val treeManager: TreeManager, DrawableAVLVertex>, AVLNode>, AVLStateContainer>, AVLVertex>, AVLStruct>>, +) : DrawableTree>, DrawableAVLVertex>, AVLNode>, AVLStateContainer>, AVLVertex>, AVLStruct>>() { + override var root: AVLDrawableNode>? = null + override var drawablePreOrder: List>>? = null + override var treeStruct = AVLStruct>() + override val designNode = AVLNodeDesign + + override fun deleteTree() { + root = null + treeStruct = AVLStruct() + } + + override fun drawableVertexToNode(vertex: DrawableAVLVertex>) = AVLDrawableNode( + value = vertex.value, + xState = mutableStateOf(vertex.x.toFloat()), + yState = mutableStateOf(vertex.y.toFloat()), + height = vertex.height.toInt(), + ) + + override fun vertexToNode(vertex: AVLVertex>) = AVLDrawableNode( + value = vertex.value, + xState = mutableStateOf(0f), + yState = mutableStateOf(0f), + height = vertex.height.toInt(), + ) + + override fun nodeToDrawableVertex(node: AVLDrawableNode>) = DrawableAVLVertex( + value = node.value, + x = node.xState.value.toDouble(), + y = node.yState.value.toDouble(), + height = node.height.toUInt(), + ) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableNode.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableNode.kt new file mode 100644 index 0000000..9c1ac9b --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableNode.kt @@ -0,0 +1,18 @@ +package treeApp.controller.viewPart.drawableBIN + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import treeApp.controller.viewPart.drawableTree.DrawableNode +import treeApp.ui.nodeDesign.BINNodeDesign + +class BINDrawableNode( + override val value: Pack, + override var leftChild: BINDrawableNode? = null, + override var rightChild: BINDrawableNode? = null, + xState: MutableState, + yState: MutableState, + + ) : DrawableNode>(BINNodeDesign, BINNodeDesign.colorNode, xState, yState) { + override val clickState = mutableStateOf(false) + +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableTree.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableTree.kt new file mode 100644 index 0000000..a2f1142 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableBIN/BINDrawableTree.kt @@ -0,0 +1,62 @@ +package treeApp.controller.viewPart.drawableBIN + +import androidx.compose.runtime.mutableStateOf +import treeApp.controller.databaseManage.BINTreeManager +import treelib.databaseSave.jsonFormat.DrawableBINVertex +import treelib.binTree.BINNode +import treelib.binTree.BINStateContainer +import treelib.binTree.BINStruct +import treelib.binTree.BINVertex +import treelib.commonObjects.Container +import treeApp.controller.viewPart.drawableTree.DrawableTree +import treeApp.ui.nodeDesign.BINNodeDesign + +class BINDrawableTree( + override var name: String, + override val treeManager: BINTreeManager, +) : + DrawableTree< + BINDrawableNode>, + DrawableBINVertex>, + BINNode>, + BINStateContainer>, + BINVertex>, + BINStruct> + >() { + + override var root: BINDrawableNode>? = null + override var treeStruct = BINStruct>() + override var drawablePreOrder: List>>? = null + override val designNode = BINNodeDesign + + override fun deleteTree() { + root = null + treeStruct = BINStruct() + } + + override fun saveTreeToDB() { + if (root != null) { + treeManager.saveTreeToDB(name, preOrder().map { nodeToDrawableVertex(it) }.toList(), listOf()) + } else { + treeManager.saveTreeToDB(name, treeStruct) + } + } + + override fun drawableVertexToNode(vertex: DrawableBINVertex>) = BINDrawableNode( + value = vertex.value, + xState = mutableStateOf(vertex.x.toFloat()), + yState = mutableStateOf(vertex.y.toFloat()), + ) + + override fun vertexToNode(vertex: BINVertex>) = BINDrawableNode( + value = vertex.value, + xState = mutableStateOf(0f), + yState = mutableStateOf(0f), + ) + + override fun nodeToDrawableVertex(node: BINDrawableNode>) = DrawableBINVertex( + value = node.value, + x = node.xState.value.toDouble(), + y = node.yState.value.toDouble(), + ) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableNode.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableNode.kt new file mode 100644 index 0000000..57e0298 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableNode.kt @@ -0,0 +1,18 @@ +package treeApp.controller.viewPart.drawableRB + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import treelib.rbTree.Markers +import treeApp.controller.viewPart.drawableTree.DrawableNode +import treeApp.ui.nodeDesign.RBNodeDesign + +class RBDrawableNode( + override val value: Pack, + override var leftChild: RBDrawableNode? = null, + override var rightChild: RBDrawableNode? = null, + val color: Markers, + xState: MutableState, + yState: MutableState, +) : DrawableNode>(RBNodeDesign, if (color == Markers.BLACK) RBNodeDesign.blackMarker else RBNodeDesign.redMarker, xState, yState) { + override val clickState = mutableStateOf(false) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableTree.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableTree.kt new file mode 100644 index 0000000..739444d --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableRB/RBDrawableTree.kt @@ -0,0 +1,68 @@ +package treeApp.controller.viewPart.drawableRB + +import androidx.compose.runtime.mutableStateOf +import treeApp.controller.databaseManage.TreeManager +import treelib.databaseSave.neo4j.DrawableRBVertex +import treelib.commonObjects.Container +import treelib.rbTree.RBNode +import treelib.rbTree.RBStateContainer +import treelib.rbTree.RBStruct +import treelib.rbTree.RBVertex +import treeApp.controller.viewPart.drawableTree.DrawableTree +import treeApp.ui.nodeDesign.RBNodeDesign + +class RBDrawableTree( + override var name: String, + override val treeManager: TreeManager, DrawableRBVertex>, RBNode>, RBStateContainer>, RBVertex>, RBStruct>>, +) : DrawableTree< + RBDrawableNode>, + DrawableRBVertex>, + RBNode>, + RBStateContainer>, + RBVertex>, + RBStruct> + >() { + override var root: RBDrawableNode>? = null + override var drawablePreOrder: List>>? = null + override var treeStruct = RBStruct>() + override val designNode = RBNodeDesign + + override fun deleteTree() { + this.deleteTreeFromBD() + root = null + treeStruct = RBStruct() + } + + override fun saveTreeToDB() { + if (root != null) { + treeManager.saveTreeToDB( + name, + preOrder().map { nodeToDrawableVertex(it) }.toList(), + inOrder().map { nodeToDrawableVertex(it) }.toList() + ) + } else { + treeManager.saveTreeToDB(name, treeStruct) + } + } + + override fun drawableVertexToNode(vertex: DrawableRBVertex>) = RBDrawableNode( + value = vertex.value, + xState = mutableStateOf(vertex.x.toFloat()), + yState = mutableStateOf(vertex.y.toFloat()), + color = vertex.color, + ) + + override fun vertexToNode(vertex: RBVertex>) = RBDrawableNode( + value = vertex.value, + xState = mutableStateOf(0f), + yState = mutableStateOf(0f), + color = vertex.color, + ) + + override fun nodeToDrawableVertex(node: RBDrawableNode>) = DrawableRBVertex( + value = node.value, + x = node.xState.value.toDouble(), + y = node.yState.value.toDouble(), + color = node.color, + ) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawTree.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawTree.kt new file mode 100644 index 0000000..b7dba82 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawTree.kt @@ -0,0 +1,44 @@ +package treeApp.controller.viewPart.drawableTree + +import treeApp.ui.nodeDesign.NodeDesign +import treelib.commonObjects.Container + +interface DrawTree { + /**How you should it use: + * + * + * + * OUTSIDE A COMPOSABLE CONTEXT { + * * val manager = TreeManager() + * * val tree = DrawableTree("name", manager) + * * tree.insert + * * tree.delete + * * ... + * * tree.update <- (means moving information: TreeStruct.root -> DrawableTree.root) + * * tree.repositisonTree <- (set "beautiful" coordinates without display) + * + * After tree.initTree() you should not write tree.updateTree() or tree.repositisonTree(...), otherwise coordinates from db would be missed + * + * } + * + * IN A COMPOSABLE CONTEXT { + * * tree.displayTree() + * + * } + * **/ + + var name: String + val designNode: NodeDesign + var yShiftBetweenNodes: Float + + fun initTree() + fun updateTree() + fun deleteTree() + fun deleteTreeFromBD() + fun saveTreeToDB() + fun insert(item: Container) + fun delete(item: Container) + fun find(item: Int): Pair + fun repositionTree(xBase: Float, yBase: Float) + fun addOffset(xOffset: Float, yOffset: Float) +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableNode.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableNode.kt new file mode 100644 index 0000000..c2dfd75 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableNode.kt @@ -0,0 +1,48 @@ +package treeApp.controller.viewPart.drawableTree + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.onClick +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import treeApp.ui.nodeDesign.NodeDesign +import kotlin.math.roundToInt + +abstract class DrawableNode>( + design : NodeDesign, + color : Color, + val xState: MutableState, + val yState: MutableState, +) { + abstract val value: Pack + abstract var leftChild: NodeType? + abstract var rightChild: NodeType? + abstract val clickState: MutableState + @OptIn(ExperimentalFoundationApi::class) + var modifier = Modifier + .offset { + IntOffset( + x = xState.value.roundToInt(), + y = yState.value.roundToInt() + ) + } + .pointerInput(xState, yState) { + detectDragGestures { _, dragAmount -> + xState.value += dragAmount.x + yState.value += dragAmount.y + } + } + .size(design.nodeSize.dp) + .clip(design.shape) + .background(color) + .onClick { clickState.value = !clickState.value } + +} diff --git a/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableTree.kt b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableTree.kt new file mode 100644 index 0000000..e744132 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/controller/viewPart/drawableTree/DrawableTree.kt @@ -0,0 +1,235 @@ +package treeApp.controller.viewPart.drawableTree + +import treeApp.controller.databaseManage.TreeManager +import treelib.databaseSave.DrawableVertex +import treelib.abstractTree.Node +import treelib.abstractTree.StateContainer +import treelib.abstractTree.TreeStruct +import treelib.abstractTree.Vertex +import treelib.commonObjects.Container +import treelib.commonObjects.exceptions.ImpossibleCaseException + +abstract class DrawableTree< + DNodeType : DrawableNode, DNodeType>, + DVertexType : DrawableVertex>, + NodeType : Node, NodeType>, + State : StateContainer, NodeType>, + VertexType : Vertex>, + StructType : TreeStruct, NodeType, State, VertexType> + > : DrawTree { + + protected abstract var drawablePreOrder: List? + protected abstract val treeManager: TreeManager, DVertexType, NodeType, State, VertexType, StructType> + protected abstract var treeStruct: StructType + abstract var root: DNodeType? + + override var yShiftBetweenNodes = 10f + + override fun addOffset(xOffset: Float, yOffset: Float) { + root?.let { addSet(it, xOffset, yOffset) } + } + + private fun addSet(root: DNodeType, xOffset: Float, yOffset: Float){ + root.xState.value += xOffset + root.yState.value += yOffset + root.leftChild?.let { addSet(it, xOffset, yOffset) } + root.rightChild?.let { addSet(it, xOffset, yOffset) } + } + + override fun initTree() { + val binVertexes = treeManager.initTree(name, treeStruct) + drawablePreOrder = binVertexes.map { drawableVertexToNode(it) } + + drawablePreOrder?.let { + if (it.isNotEmpty()) { + restoreTree(it) + } + } + } + + override fun updateTree() { + root = null + val ded = treeStruct.preOrder() + for (el in vertexesToNodes(ded)) { + restoreInsert(el) + } + } + + override fun deleteTreeFromBD() = treeManager.deleteTreeFromDB(name) + + override fun saveTreeToDB() { + if (root != null) { + treeManager.saveTreeToDB(name, preOrder().map { nodeToDrawableVertex(it) }.toList(), listOf()) + } else { + treeManager.saveTreeToDB(name, treeStruct) + } + } + + override fun insert(item: Container) = treeStruct.insert(item) + + override fun delete(item: Container) { + if (treeStruct.find(item) != null) treeStruct.delete(item) + } + + override fun find(item: Int): Pair { + var currentNode = root + if (root == null) + return Pair(-1f, -1f) + while(true) { + if (item == currentNode?.value?.key) { + currentNode.clickState.value = true + return Pair(currentNode.xState.value, currentNode.yState.value) + } + currentNode?.let { + currentNode = if (item > it.value.key) it.rightChild + else it.leftChild + } + if (currentNode == null) return Pair(-1f, -1f) + } + } + + override fun repositionTree(xBase: Float, yBase: Float) { + /*xBase: Float = 0f, yBase: Float = 0f*/ + root?.let { + createCordsState1(it, xBase, yBase) + createCordsState2(it) + } + } + + protected abstract fun vertexToNode(vertex: VertexType): DNodeType + + protected abstract fun nodeToDrawableVertex(node: DNodeType): DVertexType + + protected abstract fun drawableVertexToNode(vertex: DVertexType): DNodeType + + private fun restoreTree(preOrder: List) { + root = null + for (el in preOrder) { + restoreInsert(el) + } + } + + private fun restoreInsert(preOrderNode: DNodeType) { + if (root == null) { + root = preOrderNode + return + } + var currentParent = root + while (currentParent != null) { + currentParent.let { + when { + it.value < preOrderNode.value -> { + if (it.rightChild == null) { + it.rightChild = preOrderNode + return@restoreInsert + } else currentParent = it.rightChild + } + + it.value > preOrderNode.value -> { + if (it.leftChild == null) { + it.leftChild = preOrderNode + return@restoreInsert + } else currentParent = it.leftChild + } + + else -> { + throw InternalError("Can't restore tree from preOrder :(") + } + } + } + } + } + + private fun createCordsState1(node: DNodeType, x: Float, y: Float): Float { + if (node.leftChild == null && node.rightChild == null) { + node.xState.value = x + node.yState.value = y + return x + designNode.nodeSize + } + + var xInfo = node.leftChild?.let { + return@let createCordsState1(it, x, y + designNode.nodeSize + yShiftBetweenNodes) + } ?: x + + node.xState.value = xInfo + node.yState.value = y + xInfo += designNode.nodeSize + + node.rightChild?.let { + return@createCordsState1 createCordsState1(it, xInfo, y + designNode.nodeSize + yShiftBetweenNodes) + } + return xInfo + } + + private fun createCordsState2(node: DNodeType): Float { + if (node.leftChild == null && node.rightChild == null) return node.xState.value + val xLeft = node.leftChild?.let { return@let createCordsState2(it) } ?: -1f + val xRight = node.rightChild?.let { return@let createCordsState2(it) } ?: -1f + + if ((xLeft == -1f) || (xRight == -1f)) return node.xState.value + + val n = xLeft + (xRight - xLeft) / 2 + node.xState.value = n + return n + } + + protected fun preOrder() = sequence { + var current: DNodeType + val queue = ArrayDeque() + + root?.let { root -> + queue.add(root) + while (queue.isNotEmpty()) { + current = queue.removeLast() + yield(current) + if (current.rightChild != null) + current.rightChild?.let { + queue.add(it) + } ?: throw ImpossibleCaseException() + + if (current.leftChild != null) + current.leftChild?.let { + queue.add(it) + } ?: throw ImpossibleCaseException() + } + } + } + + protected fun inOrder() = sequence { + var flagVisited = 0 + var current = root + val parents = ArrayDeque() + + while (current != null) { + if (flagVisited == 0) { + while (true) { + current?.let { + if (it.leftChild == null) return@let null + parents.add(it) + current = it.leftChild + return@let current + } ?: break + } + } + current?.let { + yield(it) + if (it.rightChild != null) { + flagVisited = 0 + current = it.rightChild + } else { + if (parents.isEmpty()) return@sequence + flagVisited = 1 + current = parents.removeLast() + } + } + } + } + + private fun vertexesToNodes(preOrder: List) = sequence { + for (el in preOrder) { + yield( + vertexToNode(el) + ) + } + } +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/ControlFields.kt b/treeApp/src/main/kotlin/treeApp/ui/ControlFields.kt new file mode 100644 index 0000000..ebafe11 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/ControlFields.kt @@ -0,0 +1,212 @@ +package treeApp.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.input.key.* +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.WindowState +import treeApp.controller.Controller +import treeApp.controller.viewPart.drawableTree.DrawTree + +@Composable +fun ControlFields( + controller: treeApp.controller.Controller, + activeTree: MutableState, + deleteTreeState: MutableState, + openTreeState: MutableState, + createTreeState: MutableState, + dragState: MutableState, + windowState: WindowState, +) { + + val addFieldState = remember { mutableStateOf(false) } + val findFieldState = remember { mutableStateOf(false) } + val deleteFieldState = remember { mutableStateOf(false) } + val returnButtonClickState = remember { mutableStateOf(false) } + val returnButtonHoverState = remember { mutableStateOf(false) } + + val value = remember { mutableStateOf("") } + + Column( + modifier = Modifier.offset(0.dp, 0.dp).padding(horizontal = 10.dp), + horizontalAlignment = Alignment.Start + ) { + ControlField("Add", addFieldState, value, activeTree) + Spacer(modifier = Modifier.height(2.dp)) + + ControlField("Find", findFieldState, value, activeTree) + Spacer(modifier = Modifier.height(2.dp)) + + ControlField("Delete", deleteFieldState, value, activeTree) + Spacer(modifier = Modifier.height(5.dp)) + + ReturnButton( + returnButtonHoverState, + returnButtonClickState, + painterResource("drawable/go-back-arrow.png"), + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.background, + MaterialTheme.colorScheme.primary, + 50.dp, 50.dp, + CircleShape + ) + } + + var tree by remember { mutableStateOf(null) } + + if (!addFieldState.value && !findFieldState.value && !deleteFieldState.value) { + if (deleteTreeState.value) { + tree = controller.deleteTree() + deleteTreeState.value = false + } + if (openTreeState.value) { + tree = controller.tree + openTreeState.value = false + } + if (createTreeState.value) { + tree = controller.tree + createTreeState.value = false + } + if (returnButtonClickState.value) { + LaunchedEffect(returnButtonClickState){ + tree?.updateTree() + tree?.repositionTree(800f, 10f) + returnButtonClickState.value = false + } + dragState.value = false + } + tree?.let { displayTree(it, dragState) } + + addFieldState.value = false + } + + if (addFieldState.value) { + tree = controller.insert(value.value) + addFieldState.value = false + } + if (findFieldState.value) { + tree = controller.find(value.value, windowState) + findFieldState.value = false + } + if (deleteFieldState.value) { + tree = controller.delete(value.value) + deleteFieldState.value = false + } + +} + + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun ControlField( + buttonName: String, + buttonState: MutableState, + value: MutableState, + activeTree: MutableState +) { + + var userInput by remember { mutableStateOf("") } + + var fieldBackgroundColor by remember { mutableStateOf(Color(206, 211, 216)) } + + OutlinedTextField( + value = userInput, + onValueChange = { userInput = it }, + singleLine = true, + label = { Text(buttonName) }, + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = fieldBackgroundColor, + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedBorderColor = MaterialTheme.colorScheme.onTertiary, + focusedLabelColor = Color(99, 95, 95), + unfocusedLabelColor = MaterialTheme.colorScheme.onTertiary, + ), + enabled = activeTree.value, + modifier = Modifier + .padding(start = 3.dp) + .width(150.dp) + .onFocusChanged { + fieldBackgroundColor = if (it.isFocused) { + Color(255, 255, 255) + } else { + Color(206, 211, 216) + } + }.onPreviewKeyEvent { + when { + (!it.isShiftPressed && it.key == Key.Enter && it.type == KeyEventType.KeyUp) -> { + value.value = userInput // change !!! + buttonState.value = true + userInput = "" + true + } + + else -> { + false + } + } + + }, + trailingIcon = { }, + shape = RoundedCornerShape(8.dp) + ) + +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun ReturnButton( + hoverButtonState: MutableState, + clickButtonState: MutableState, + painter: Painter, + hoverColor: Color, + color: Color, + iconColor: Color, + height: Dp, + width: Dp, + shape: Shape, +) { + val interactionSource = remember { MutableInteractionSource() } + + Row() { + IconButton( + onClick = { clickButtonState.value = !clickButtonState.value }, + modifier = Modifier.height(height).width(width) + .onPointerEvent(PointerEventType.Enter) { hoverButtonState.value = true } + .onPointerEvent(PointerEventType.Exit) { hoverButtonState.value = false } + .clickable(indication = null, interactionSource = interactionSource) {} + .clip(shape), + colors = IconButtonDefaults.iconButtonColors( + contentColor = if (hoverButtonState.value) iconColor else MaterialTheme.colorScheme.background, + containerColor = if (hoverButtonState.value) color else hoverColor + ) + ) { + Icon(painter, contentDescription = null) + } + + if (hoverButtonState.value) { + Popup(offset = IntOffset(x = 60, y = 50)) { + Text(text = "Back to root") + } + } + } +} + diff --git a/treeApp/src/main/kotlin/treeApp/ui/DeleteAlertDialog.kt b/treeApp/src/main/kotlin/treeApp/ui/DeleteAlertDialog.kt new file mode 100644 index 0000000..b2cd8e6 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/DeleteAlertDialog.kt @@ -0,0 +1,82 @@ +package treeApp.ui + + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.AlertDialog +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import treeApp.controller.Controller + + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun DeleteDialog( + hoverButtonState: MutableState, + clickButtonsState: MutableState, + controller: treeApp.controller.Controller, + activeTree: MutableState, + deleteTreeState: MutableState +) { + + val deleteDialogState = remember { mutableStateOf(true) } + + if (clickButtonsState.value) { + AlertDialog( + backgroundColor = MaterialTheme.colorScheme.background, + onDismissRequest = { clickButtonsState.value = false }, + text = { + Text( + text = DeleteDialogText(activeTree, controller.tree?.name ?: ""), + color = MaterialTheme.colorScheme.primary + ) + }, + buttons = { + + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.Bottom + ) { + BottomButtons(deleteDialogState, clickButtonsState, "Cancel", "Delete") + } + } + ) + } + + if (!deleteDialogState.value && activeTree.value) { + deleteTreeState.value = true + deleteDialogState.value = true + activeTree.value = false + } + + TopAppIconButton( + rememberVectorPainter(Icons.Outlined.Delete), + hoverButtonState, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickButtonsState + ) + +} + +@Composable +fun DeleteDialogText(activeTree: MutableState, treeName: String): String { + return if (activeTree.value) { + "Are you sure you want to delete $treeName ?" + } else { + "Nothing to delete" + } + +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/Design.kt b/treeApp/src/main/kotlin/treeApp/ui/Design.kt new file mode 100644 index 0000000..c73f18c --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/Design.kt @@ -0,0 +1,73 @@ +package treeApp.ui + +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.KeyboardArrowRight +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp + +@Composable +fun SearchIcon() = Icon( + imageVector = Icons.Outlined.Search, + contentDescription = null +) + +@Composable +fun ArrowIcon() = Icon( + imageVector = Icons.Outlined.KeyboardArrowRight, + contentDescription = null, + modifier = Modifier.size(16.dp) +) + +@Composable +fun MenuItemBackgroundColor(state: MutableState): Color { + return if (state.value) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.background +} + +@Composable +fun AppIcon(): Painter { + return painterResource("appIcon.png") +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun TopAppIconButton( + painter: Painter, + hoverButtonState: MutableState, + color: Color, + disabledColor: Color, + iconColor: Color, + clickButtonState: MutableState, +) { + + IconButton(onClick = { + clickButtonState.value = !clickButtonState.value + }, modifier = Modifier.fillMaxHeight().width(50.dp).clip(RectangleShape) + .onPointerEvent(PointerEventType.Enter) { hoverButtonState.value = true } + .onPointerEvent(PointerEventType.Exit) { hoverButtonState.value = false }, + colors = IconButtonDefaults.iconButtonColors( + contentColor = if (hoverButtonState.value) MaterialTheme.colorScheme.background else iconColor, + containerColor = if (hoverButtonState.value) color else disabledColor + ) + ) { + Icon(painter, contentDescription = null) + } + +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/FileDialog.kt b/treeApp/src/main/kotlin/treeApp/ui/FileDialog.kt new file mode 100644 index 0000000..65becda --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/FileDialog.kt @@ -0,0 +1,340 @@ +package treeApp.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.window.WindowDraggableArea +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.rounded.KeyboardArrowDown +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberDialogState +import treeApp.lightColors +import java.awt.Dimension +import java.io.File + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SearchItem( + dirPath: String, + expandedNested: MutableState, + selectedTree: MutableState, + expandedOpenNested: MutableState +) { + + val dialogState = remember { mutableStateOf(false) } + val minWidth = remember { mutableStateOf(200) } + var pressedState by remember { mutableStateOf(false) } + val backgroundColorState = remember { mutableStateOf(false) } + val enabledButtonIndex = remember { mutableStateOf(-1) } + + val files = findFiles(dirPath) + + if (dialogState.value) { + MaterialTheme(colorScheme = lightColors) { + Dialog( + onCloseRequest = { + dialogState.value = false + expandedOpenNested.value = false + selectedTree.value = + if (enabledButtonIndex.value >= 0) files[enabledButtonIndex.value] else "" + }, visible = dialogState.value, + content = { + WindowDraggableArea { + this.window.minimumSize = Dimension(minWidth.value, 150) + CompleteDialogContent("Open file", dialogState, dirPath, minWidth, files, enabledButtonIndex) + } + }, + undecorated = true, + state = rememberDialogState( + position = WindowPosition(Alignment.Center), + size = DpSize(450.dp, 450.dp) + ) + ) + + } + } else if (pressedState){ + expandedNested.value = false + expandedOpenNested.value = false + selectedTree.value = + if (enabledButtonIndex.value >= 0) files[enabledButtonIndex.value] else "" + + } + + DropdownMenuItem( + leadingIcon = { SearchIcon() }, + text = { Text("Search") }, + onClick = { + dialogState.value = true + pressedState = true + }, + modifier = Modifier.onPointerEvent(PointerEventType.Enter) { backgroundColorState.value = true } + .onPointerEvent(PointerEventType.Exit) { backgroundColorState.value = false } + .background(color = MenuItemBackgroundColor(backgroundColorState)) + ) + +} + +fun findFiles(dirPath: String): List { + + val listFiles = File(dirPath).list() ?: throw IllegalStateException() + + return listFiles.map { it.split(".")[0] } +} + +@Composable +fun CompleteDialogContent( + title: String, + dialogState: MutableState, + dirPath: String, + minWidth: MutableState, + listFiles: List, + enabledButtonIndex: MutableState +) { + Card( + modifier = Modifier.fillMaxHeight(1f).fillMaxWidth(1f).shadow(elevation = 10.dp, ambientColor = Color.Gray), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.primary + ), + shape = RectangleShape, + border = BorderStroke(0.5.dp, Color.LightGray) + ) { + Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween) { + + Column( + modifier = Modifier + .height(50.dp) + ) { + TittleAndButton(title, dialogState) + } + + + Column(modifier = Modifier.fillMaxWidth().weight(1f)) { + mainBody(dirPath, listFiles, minWidth, enabledButtonIndex) + } + + + Row( + modifier = Modifier + .height(50.dp) + .fillMaxWidth(1f) + .padding(horizontal = 10.dp, vertical = 5.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.Bottom + ) { + BottomButtons(dialogState, dialogState,"Ok", "Cancel") + } + + + } + } + +} + +@Composable +fun TittleAndButton(title: String, dialogState: MutableState) { + + val hoverButtonState = remember { mutableStateOf(false) } + + val clickButtonState = remember { mutableStateOf(false) } + + Row( + modifier = Modifier + .fillMaxWidth(1f) + .padding(start = 20.dp, top = 0.dp, end = 0.dp, bottom = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = title, fontSize = 20.sp, color = MaterialTheme.colorScheme.primary) + + TopAppIconButton( + rememberVectorPainter(Icons.Outlined.Close), + hoverButtonState, + Color(233, 8, 28), + MaterialTheme.colorScheme.background, + MaterialTheme.colorScheme.primary, + clickButtonState + ) + dialogState.value = !clickButtonState.value + } + Divider(modifier = Modifier.height(1.dp), color = Color.LightGray) + +} + +@Composable +fun BottomButtons(dialogState: MutableState, firstButtonState: MutableState, firstButtonText: String, secondButtonText: String) { + Button( + modifier = Modifier.width(90.dp), + shape = RoundedCornerShape(3.dp), + onClick = { + firstButtonState.value = !firstButtonState.value + }, + contentPadding = PaddingValues(start = 2.dp, end = 2.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.background + ) + ) { + Text(text = firstButtonText, fontSize = 12.sp, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, maxLines = 1) + } + Spacer(modifier = Modifier.width(5.dp)) + Button( + modifier = Modifier.width(90.dp), + shape = RoundedCornerShape(3.dp), + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.primary, + containerColor = MaterialTheme.colorScheme.background + ), + border = BorderStroke(1.dp, Color(208, 210, 219)), + onClick = { dialogState.value = false }, + contentPadding = PaddingValues(start = 2.dp, end = 2.dp) + ) { + Text(text = secondButtonText, fontSize = 12.sp, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, maxLines = 1) + } +} + +@Composable +fun mainBody( + dirPath: String, + listFiles: List, + minWidth: MutableState, + enabledButtonIndex: MutableState +) { + + val dirName = dirPath.split("/").last() + + Card( + shape = RoundedCornerShape(3.dp), + border = BorderStroke(1.dp, Color(47, 105, 215)), + modifier = Modifier.fillMaxWidth().height(50.dp).padding(horizontal = 10.dp), // для него + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + Text( + maxLines = 1, + text = dirPath, + modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp) + .onGloballyPositioned { coordinates -> minWidth.value = coordinates.size.width }, + fontFamily = FontFamily.Monospace, + fontSize = 15.sp, + fontWeight = FontWeight.Bold + ) + } + + Spacer(modifier = Modifier.height(1.dp).fillMaxWidth()) + + LazyColumn( + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 10.dp).fillMaxHeight() + .border(0.5.dp, Color.LightGray), + contentPadding = PaddingValues(0.dp) + ) { + item { + Row(verticalAlignment = Alignment.Bottom) { + Icon( + Icons.Rounded.KeyboardArrowDown, + contentDescription = null, + tint = Color.Gray, + modifier = Modifier.size(20.dp) + ) + Icon( + painterResource("/drawable/dir.png"), + contentDescription = null, + tint = Color.Gray, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(5.dp)) + Text( + "${dirName}:", + fontFamily = FontFamily.Monospace, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary + ) + } + } + + item { + Spacer(modifier = Modifier.height(4.dp)) + } + + items(listFiles.size) { index -> + val checked = remember { mutableStateOf(false) } + Button( + onClick = { + checked.value = !checked.value + enabledButtonIndex.value = index + }, + colors = ButtonDefaults.buttonColors( + containerColor = + if (checked.value && enabledButtonIndex.value == index) MaterialTheme.colorScheme.tertiary + else MaterialTheme.colorScheme.background + ), + modifier = Modifier + .fillParentMaxWidth() + .clickable(interactionSource = MutableInteractionSource(), indication = null, onClick = {}), // ?? + shape = RectangleShape + ) { + + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillParentMaxWidth().align(Alignment.CenterStart) + ) { + Spacer(modifier = Modifier.width(25.dp)) + Icon(FileIcon(dirName), contentDescription = null, tint = Color.Gray) + Spacer(modifier = Modifier.width(5.dp)) + Text( + text = listFiles[index], fontSize = 13.sp, fontFamily = FontFamily.Monospace, + color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Normal, maxLines = 1 + ) + } + + } + } + } + + } + +} + +@Composable +fun FileIcon(dirName: String): Painter { + + return when (dirName) { + "RB-trees" -> painterResource("/drawable/neo4jFormat.png") + "AVL-trees" -> painterResource("/drawable/sqlFormat.png") + "BIN-trees" -> painterResource("/drawable/jsonFormat.png") + else -> throw IllegalStateException() + } + +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/SaveDialog.kt b/treeApp/src/main/kotlin/treeApp/ui/SaveDialog.kt new file mode 100644 index 0000000..874017d --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/SaveDialog.kt @@ -0,0 +1,167 @@ +package treeApp.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.input.key.* +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.rememberDialogState +import treeApp.controller.Controller + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun SaveDialog( + hoverButtonState: MutableState, + clickButtonState: MutableState, + selectFileName: MutableState, + controller: treeApp.controller.Controller, + activeTree: MutableState +) { + + var validateInputState by remember { mutableStateOf(true) } + var successDialogState by remember { mutableStateOf(false) } + var warningDialogState by remember { mutableStateOf(false) } + val saveButtonState = remember { mutableStateOf(false) } + + if (clickButtonState.value) { + Dialog( + onCloseRequest = { clickButtonState.value = false }, undecorated = true, + state = rememberDialogState( + position = WindowPosition(Alignment.Center), + size = DpSize(300.dp, 180.dp) + ), + resizable = false + ) + { + Card( + modifier = Modifier.fillMaxHeight(1f).fillMaxWidth(1f) + .shadow(elevation = 10.dp, ambientColor = Color.Gray), + shape = RectangleShape, + border = BorderStroke(0.5.dp, Color.LightGray), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + + Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween) { + + Column(modifier = Modifier.height(50.dp)) { + TittleAndButton("Save tree", clickButtonState) + Divider(modifier = Modifier.height(1.dp).fillMaxWidth()) + } + + Column( + modifier = Modifier.fillMaxWidth().padding(start = 5.dp, end = 5.dp, top = 5.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Bottom + ) { + TextField( + enabled = activeTree.value, + value = selectFileName.value, + singleLine = true, + shape = RoundedCornerShape(4.dp), + onValueChange = { selectFileName.value = it }, + modifier = Modifier + .onPreviewKeyEvent { + when { + (!it.isShiftPressed && it.key == Key.Enter && it.type == KeyEventType.KeyUp) -> { + validateInputState = validate(selectFileName.value) + if (validateInputState) { + successDialogState = true + } else { + warningDialogState = true + } + true + } + + else -> { + warningDialogState = false + successDialogState = false + saveButtonState.value = false + false + } + } + } + .align(Alignment.CenterHorizontally).border(2.dp, Color(47, 105, 215)), + colors = TextFieldDefaults.textFieldColors( + containerColor = if (!warningDialogState) MaterialTheme.colorScheme.background + else Color(232, 169, 169) + ), + isError = !validateInputState, + placeholder = { + Text( + text = if (activeTree.value) "Enter a filename" else "Nothing to save", + color = Color.Gray + ) + }, + ) + Text( + text = when { + successDialogState -> "Tree successfully saved to ${selectFileName.value}" + warningDialogState -> "Invalid tree name for saving" + else -> "" + }, + color = if (successDialogState) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, + fontFamily = FontFamily.Monospace, + fontSize = 12.sp + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.Bottom + ) { + BottomButtons( + clickButtonState, + saveButtonState, + "Save", + "Cancel" + ) + } + + if (saveButtonState.value && activeTree.value) { + validateInputState = validate(selectFileName.value) + if (validateInputState) { + successDialogState = true + } else { + warningDialogState = true + } + } + + if (successDialogState) { + controller.saveTree(selectFileName.value) + } + + } + + } + + } + } + } + + TopAppIconButton( + painterResource("/drawable/save.png"), + hoverButtonState, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickButtonState + ) +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/Submenu.kt b/treeApp/src/main/kotlin/treeApp/ui/Submenu.kt new file mode 100644 index 0000000..5b5a39a --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/Submenu.kt @@ -0,0 +1,171 @@ +package treeApp.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import treeApp.controller.Controller + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun CreateMenu( + expandedNested: MutableState, + controller: treeApp.controller.Controller, + activeTree: MutableState, + createTreeState: MutableState +) { + + val trees = listOf("Red black tree", "AVL tree", "Binary tree") + val defaultTreesNames = listOf("rbTree", "avlTree", "binTree") + + val backgroundColorState = List(3) { remember { mutableStateOf(false) } } + + DropdownMenu( + expanded = expandedNested.value, + onDismissRequest = { expandedNested.value = false }, + offset = DpOffset(150.dp, 0.dp), + modifier = Modifier.background(MaterialTheme.colorScheme.background) + ) { + + repeat(3) { index -> + DropdownMenuItem(text = { Text(trees[index]) }, + onClick = { + controller.createTree(defaultTreesNames[index], index) + createTreeState.value = true + activeTree.value = true + expandedNested.value = false + }, + modifier = Modifier + .onPointerEvent(PointerEventType.Enter) { backgroundColorState[index].value = true } + .onPointerEvent(PointerEventType.Exit) { backgroundColorState[index].value = false } + .background(color = MenuItemBackgroundColor(backgroundColorState[index]))) + } + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun OpenMenu( + expandedNested: MutableState, + treesNames: List>, + activeTree: MutableState, + openTreeState: MutableState, + controller: treeApp.controller.Controller +) { + val trees = listOf("Red black tree", "AVL tree", "Binary tree") + + val expandedTreesNames = List(3) { remember { mutableStateOf(false) } } // !! + val backgroundColorState = List(3) { remember { mutableStateOf(false) } } // !! + + val selectedTreeName = remember { mutableStateOf("") } + val selectedTreeID = remember { mutableStateOf(0) } + + DropdownMenu( + expanded = expandedNested.value, + onDismissRequest = { + expandedNested.value = !expandedNested.value + }, + offset = DpOffset(100.dp, 0.dp), + modifier = Modifier.width(150.dp).background(MaterialTheme.colorScheme.background) + ) { + repeat(3) { index -> + DropdownMenuItem( + text = { + Text( + trees[index], + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.height(50.dp), + maxLines = 1 + ) + }, + onClick = { + expandedTreesNames[index].value = !expandedTreesNames[index].value + selectedTreeID.value = index + }, + trailingIcon = { ArrowIcon() }, + modifier = Modifier.onPointerEvent(PointerEventType.Enter) { backgroundColorState[index].value = true } + .onPointerEvent(PointerEventType.Exit) { backgroundColorState[index].value = false } + .background(color = MenuItemBackgroundColor(backgroundColorState[index])), + colors = MenuDefaults.itemColors(textColor = MaterialTheme.colorScheme.primary)) + + SelectTree( + expandedTreesNames[index], + expandedNested, + treesNames[index], + selectedTreeID, + 150.dp, + selectedTreeName, + activeTree, + openTreeState, + controller + ) + } + } + + +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun SelectTree( + expandedNested: MutableState, + expandedOpenNested: MutableState, + treesNames: List, + selectedTreeID: MutableState, + offset: Dp, + selectedTreeName: MutableState, + activeTree: MutableState, + openTreeState: MutableState, + controller: treeApp.controller.Controller +) { + + val dirPath = System.getProperty("user.dir") + "/saved-trees" // !! + val dirFiles = listOf("$dirPath/RB-trees", "$dirPath/AVL-trees", "$dirPath/BIN-trees") // !! + + val backgroundColorState = List(treesNames.size) { remember { mutableStateOf(false) } } // !! + + AnimatedVisibility(visible = expandedNested.value) { + DropdownMenu( + expanded = expandedNested.value, + onDismissRequest = { expandedNested.value = false }, + offset = DpOffset(offset, (-50).dp), + modifier = Modifier.background(MaterialTheme.colorScheme.background) + ) { + SearchItem(dirFiles[selectedTreeID.value], expandedNested, selectedTreeName, expandedOpenNested) + + repeat(treesNames.size) { index -> + DropdownMenuItem(onClick = { + selectedTreeName.value = treesNames[index] + expandedNested.value = false + expandedOpenNested.value = false + }, + text = { Text(treesNames[index]) }, + modifier = Modifier + .onPointerEvent(PointerEventType.Enter) { backgroundColorState[index].value = true } + .onPointerEvent(PointerEventType.Exit) { backgroundColorState[index].value = false } + .background(color = MenuItemBackgroundColor(backgroundColorState[index])), + colors = MenuDefaults.itemColors(textColor = MaterialTheme.colorScheme.primary)) + } + } + } + + if (selectedTreeName.value.isNotEmpty()) { + controller.createTree(selectedTreeName.value, selectedTreeID.value) + openTreeState.value = true + activeTree.value = true + selectedTreeName.value = "" + } + +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/TopAppBar.kt b/treeApp/src/main/kotlin/treeApp/ui/TopAppBar.kt new file mode 100644 index 0000000..57015e6 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/TopAppBar.kt @@ -0,0 +1,158 @@ +package treeApp.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.WindowState +import treeApp.controller.Controller +import treeApp.lightColors +import treeApp.myTypography + +@Composable +fun MyTopAppBar( + clickDeleteButton: MutableState, + clickSaveButton: MutableState, + clickOpenButton: MutableState, + clickCreateButton: MutableState, + clickMinimizeButton: MutableState, + clickMaximizeButton: MutableState, + clickCloseButton: MutableState, + windowState: WindowState, + controller: treeApp.controller.Controller, + activeTree: MutableState, + deleteTreeState: MutableState, + openTreeState: MutableState, + createTreeState: MutableState +) { + + val treesNames = controller.getSavedTreesNames() + + val selectFileName = remember { mutableStateOf("") } + + MaterialTheme( + colorScheme = lightColors, + typography = myTypography + ) { + + val hoverDeleteButton: MutableState = remember { mutableStateOf(false) } + val hoverSaveButton: MutableState = remember { mutableStateOf(false) } + val hoverOpenButton: MutableState = remember { mutableStateOf(false) } + val hoverCreateButton: MutableState = remember { mutableStateOf(false) } + val hoverMinimizeButton: MutableState = remember { mutableStateOf(false) } + val hoverMaximizeButton: MutableState = remember { mutableStateOf(false) } + val hoverCloseButton: MutableState = remember { mutableStateOf(false) } + + TopAppBar( + backgroundColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.background, + modifier = Modifier.height(50.dp), + contentPadding = PaddingValues(0.dp) + ) { + + Row( + modifier = Modifier.fillMaxWidth().fillMaxHeight(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row { + DeleteDialog( + hoverDeleteButton, + clickDeleteButton, + controller, + activeTree, + deleteTreeState + ) + + SaveDialog(hoverSaveButton, clickSaveButton, selectFileName, controller, activeTree) + + TopAppIconButton( + painterResource("/drawable/dir.png"), + hoverOpenButton, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickOpenButton + ) + OpenMenu(clickOpenButton, treesNames, activeTree, openTreeState, controller) + + TopAppIconButton( + rememberVectorPainter(Icons.Outlined.Add), + hoverCreateButton, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickCreateButton + ) + CreateMenu(clickCreateButton, controller, activeTree, createTreeState) + } + + Row(horizontalArrangement = Arrangement.End) { + + TopAppIconButton( + painterResource("/drawable/minimize.png"), + hoverMinimizeButton, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickMinimizeButton + ) + if (clickMinimizeButton.value) { + MinimizeWindow(windowState) + clickMinimizeButton.value = false + } + + TopAppIconButton( + painterResource("/drawable/maximize.png"), + hoverMaximizeButton, + MaterialTheme.colorScheme.onPrimary, + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickMaximizeButton + ) + if (clickMaximizeButton.value) { + MaximizeWindow(windowState) + clickMaximizeButton.value = false + } + + TopAppIconButton( + rememberVectorPainter(Icons.Outlined.Close), + hoverCloseButton, + Color(233, 8, 28), + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.background, + clickCloseButton + ) + + } + + } + + } + } +} + +@Composable +private fun MinimizeWindow(state: WindowState) { + state.isMinimized = !state.isMinimized +} + +@Composable +private fun MaximizeWindow(state: WindowState) { + if (state.placement == WindowPlacement.Maximized) { + state.placement = WindowPlacement.Floating + } else { + state.placement = WindowPlacement.Maximized + } +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/TreeDrawingUtils.kt b/treeApp/src/main/kotlin/treeApp/ui/TreeDrawingUtils.kt new file mode 100644 index 0000000..8272297 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/TreeDrawingUtils.kt @@ -0,0 +1,145 @@ +package treeApp.ui + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import treelib.commonObjects.Container +import treeApp.controller.viewPart.drawableAVL.AVLDrawableNode +import treeApp.controller.viewPart.drawableAVL.AVLDrawableTree +import treeApp.ui.nodeDesign.AVLNodeDesign +import treeApp.controller.viewPart.drawableBIN.BINDrawableNode +import treeApp.controller.viewPart.drawableBIN.BINDrawableTree +import treeApp.ui.nodeDesign.BINNodeDesign +import treeApp.controller.viewPart.drawableRB.RBDrawableNode +import treeApp.controller.viewPart.drawableRB.RBDrawableTree +import treeApp.ui.nodeDesign.RBNodeDesign +import treeApp.controller.viewPart.drawableTree.DrawTree +import treeApp.controller.viewPart.drawableTree.DrawableNode +import treeApp.ui.nodeDesign.NodeDesign +import java.util.Locale +import kotlin.math.roundToInt + +@Composable +fun displayTree(tree: DrawTree, state: MutableState){ + Box(modifier = Modifier.fillMaxSize() + .pointerInput(state, tree) { + detectDragGestures { change, dragAmount -> + change.consume() + tree.addOffset(dragAmount.x, dragAmount.y) + state.value = false + } + }.zIndex(-10000f) + ){ + when (tree) { + is BINDrawableTree -> displayBIN(tree) + is RBDrawableTree -> displayRB(tree) + is AVLDrawableTree -> displayAVL(tree) + else -> throw NullPointerException("Wrong DrawableTree type") + } + } +} + +@Composable +fun displayRB(tree: RBDrawableTree) { + val root = tree.root + root?.let { + displayNode(it, RBNodeDesign) + } +} + +@Composable +fun displayAVL(tree: AVLDrawableTree) { + val root = tree.root + root?.let { + displayNode(it, AVLNodeDesign) + } +} + +@Composable +fun displayBIN(tree: BINDrawableTree) { + val root = tree.root + root?.let { + displayNode(it, BINNodeDesign) + } +} + + +@Composable +fun , DNode>, NodeD : NodeDesign> displayNode( + node: DNode, + design: NodeD, +) { + + if (node.clickState.value) { + Box(modifier = Modifier + .height(if (node is BINDrawableNode<*>) 60.dp else 90.dp).width(100.dp) + .offset { + IntOffset( + node.xState.value.roundToInt() + 71, + node.yState.value.roundToInt(), + ) + } + .background(color = Color(206, 211, 216), shape = AbsoluteRoundedCornerShape(5.dp)) + .border(2.dp, MaterialTheme.colorScheme.primary, AbsoluteRoundedCornerShape(5.dp)) + .padding(horizontal = 4.dp, vertical = 2.dp) + .zIndex(1f) + ) { + Column(modifier = Modifier.zIndex(1f)) { + Text(text = "key: ${node.value.key}") + Text(text = "value: ${node.value.value}") + when (node) { + is AVLDrawableNode<*> -> Text(text = "height: ${node.height}") + is RBDrawableNode<*> -> Text(text = "color: ${node.color.toString().lowercase(Locale.getDefault())}") + } + } + } + } + + design.infoView(node.modifier, node.value.key.toString()) + + node.leftChild?.let { + edgeView(node, it, design) + displayNode(it, design) + } + + node.rightChild?.let { + edgeView(node, it, design) + displayNode(it, design) + } +} + +@Composable +fun , NodeD : NodeDesign> edgeView( + node: DNode, + child: DNode, + design: NodeD, +) { + Canvas(modifier = Modifier.fillMaxSize().zIndex(-1f)) { + drawLine( + color = design.lineColor, + start = Offset( + node.xState.value + design.nodeSize / 2, + node.yState.value + design.nodeSize / 2, + ), + end = Offset( + child.xState.value + design.nodeSize / 2, + child.yState.value + design.nodeSize / 2, + ), + strokeWidth = design.lineStrokeWidth, + ) + } +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/UserInputValidation.kt b/treeApp/src/main/kotlin/treeApp/ui/UserInputValidation.kt new file mode 100644 index 0000000..358e379 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/UserInputValidation.kt @@ -0,0 +1,25 @@ +package treeApp.ui + +import java.util.* + +private val INVALID_WINDOWS_SPECIFIC_CHAR = arrayOf('"', '*', ':', '<', '>', '/', '\"', '|', '?') +private val INVALID_UNIX_SPECIFIC_CHAR = arrayOf('/') // add '\000' // single . + +fun validate(fileName: String): Boolean { + if (fileName == "" || fileName.length > 255) { + return false + } + val os = System.getProperty("os.name").lowercase(Locale.getDefault()) + return when { + os.contains("win") -> + Arrays.stream(INVALID_WINDOWS_SPECIFIC_CHAR) + .noneMatch { ch -> fileName.contains(ch.toString()) } + + os.contains("unix") || os.contains("linux") || os.contains("mac") -> + Arrays.stream(INVALID_UNIX_SPECIFIC_CHAR) + .noneMatch { ch -> fileName.contains(ch.toString()) } + + else -> true + } + +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/AVLNodeDesign.kt b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/AVLNodeDesign.kt new file mode 100644 index 0000000..420f682 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/AVLNodeDesign.kt @@ -0,0 +1,28 @@ +package treeApp.ui.nodeDesign + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.style.TextAlign +import treeApp.ui.nodeDesign.NodeDesign + +object AVLNodeDesign : NodeDesign { + override var colorNode = Color(208, 223, 252) + override var lineColor = Color(34, 35, 41) + override var nodeSize = 60f + override var shape: Shape = CircleShape + override var lineStrokeWidth = 10f + + @Composable + override fun infoView(modifier: Modifier, information: String) = Box(modifier = modifier, contentAlignment = Alignment.Center,) { + Text( + textAlign = TextAlign.Center, + text = if (information.length <=3) information else "..." + ) + } +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/BINNodeDesign.kt b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/BINNodeDesign.kt new file mode 100644 index 0000000..cebe63b --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/BINNodeDesign.kt @@ -0,0 +1,28 @@ +package treeApp.ui.nodeDesign + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.style.TextAlign +import treeApp.ui.nodeDesign.NodeDesign + +object BINNodeDesign : NodeDesign { + override var colorNode = Color(37, 146, 110) + override var lineColor = Color(34, 35, 41) + override var nodeSize = 60f + override var shape: Shape = CircleShape + override var lineStrokeWidth = 10f + + @Composable + override fun infoView(modifier: Modifier, information: String) = Box(modifier = modifier, contentAlignment = Alignment.Center,) { + Text( + textAlign = TextAlign.Center, + text = if (information.length <=3) information else "..." + ) + } +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/NodeDesign.kt b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/NodeDesign.kt new file mode 100644 index 0000000..8008443 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/NodeDesign.kt @@ -0,0 +1,17 @@ +package treeApp.ui.nodeDesign + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape + +interface NodeDesign { + var colorNode: Color + var nodeSize: Float + var shape: Shape + var lineStrokeWidth: Float + var lineColor: Color + + @Composable + fun infoView(modifier: Modifier, information: String) +} diff --git a/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/RBNodeDesign.kt b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/RBNodeDesign.kt new file mode 100644 index 0000000..cc40291 --- /dev/null +++ b/treeApp/src/main/kotlin/treeApp/ui/nodeDesign/RBNodeDesign.kt @@ -0,0 +1,31 @@ +package treeApp.ui.nodeDesign + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.style.TextAlign +import treeApp.ui.nodeDesign.NodeDesign + +object RBNodeDesign: NodeDesign { + val redMarker = Color(233, 8, 28) + val blackMarker = Color(51, 51, 51) + override var colorNode: Color = Color(34, 35, 41) + override var nodeSize = 60f + override var shape: Shape = CircleShape + override var lineStrokeWidth = 10f + override var lineColor = Color(34, 35, 41) + + @Composable + override fun infoView(modifier: Modifier, information: String) = Box(modifier = modifier, contentAlignment = Alignment.Center,){ + Text( + textAlign = TextAlign.Center, + text = if (information.length <=3) information else "...", + color = Color.White + ) + } +}