diff --git a/.editorconfig b/.editorconfig index 2ff3afa4..033f440f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,20 @@ root = true -[*] +[*.{java,gradle,groovy}] charset = utf-8 indent_style = tab indent_size = 4 tab_width = 4 +trim_trailing_whitespace = true +insert_final_newline = true + [*.{java,gradle,groovy,kt,kts}] trim_trailing_whitespace = true insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 424b19a5..8e26b458 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, macos-latest ] - java-version: [ 17, 21, 23 ] + java-version: [ 21, 24 ] steps: - uses: actions/checkout@v2 @@ -27,4 +27,4 @@ jobs: java-version: ${{ matrix.java-version }} distribution: 'zulu' - name: Build with Gradle - run: ./gradlew build --stacktrace --info \ No newline at end of file + run: ./gradlew build --stacktrace --info diff --git a/build.gradle.kts b/build.gradle.kts index 235b2e7f..7d79ba60 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import io.jenetics.gradle.dsl.isModule import io.jenetics.gradle.dsl.moduleName +import org.apache.tools.ant.filters.ReplaceTokens /* * Java GPX Library (@__identifier__@). @@ -9,7 +10,7 @@ import io.jenetics.gradle.dsl.moduleName * 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 + * 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, @@ -18,7 +19,7 @@ import io.jenetics.gradle.dsl.moduleName * limitations under the License. * * Author: - * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) */ /** @@ -28,13 +29,13 @@ import io.jenetics.gradle.dsl.moduleName */ plugins { base - id("me.champeau.jmh") version "0.7.2" apply false + alias(libs.plugins.version.catalog.update) } rootProject.version = JPX.VERSION tasks.named("wrapper") { - version = "8.11" + version = "9.0.0" distributionType = Wrapper.DistributionType.ALL } @@ -71,8 +72,8 @@ gradle.projectsEvaluated { plugins.withType { configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } configure { @@ -169,13 +170,12 @@ fun setupJavadoc(project: Project) { doclet.charSet = "UTF-8" doclet.linkSource(true) doclet.linksOffline( - "https://docs.oracle.com/en/java/javase/17/docs/api/", + "https://docs.oracle.com/en/java/javase/21/docs/api/", "${project.rootDir}/buildSrc/resources/javadoc/java.se" ) doclet.windowTitle = "JPX ${project.version}" doclet.docTitle = "

JPX ${project.version}

" doclet.bottom = "© ${Env.COPYRIGHT_YEAR} Franz Wilhelmstötter  (${Env.BUILD_DATE})" - doclet.stylesheetFile = project.file("${project.rootDir}/buildSrc/resources/javadoc/stylesheet.css") doclet.tags = listOf( "apiNote:a:API Note:", @@ -193,38 +193,6 @@ fun setupJavadoc(project: Project) { } } } - - val javadoc = project.tasks.findByName("javadoc") as Javadoc? - if (javadoc != null) { - project.tasks.register("colorizer") { - directory = javadoc.destinationDir!! - } - - project.tasks.register("java2html") { - doLast { - providers.javaexec { - mainClass.set("de.java2html.Java2Html") - args = listOf( - "-srcdir", "src/main/java", - "-targetdir", "${javadoc.destinationDir}/src-html/${project.extra["moduleName"]}" - ) - classpath = files("${project.rootDir}/buildSrc/lib/java2html.jar") - } - } - } - - javadoc.doLast { - val colorizer = project.tasks.findByName("colorizer") - colorizer?.actions?.forEach { - it.execute(colorizer) - } - - val java2html = project.tasks.findByName("java2html") - java2html?.actions?.forEach { - it.execute(java2html) - } - } - } } /** @@ -242,13 +210,18 @@ fun xlint(): String { "empty", "exports", "finally", + "lossy-conversions", "module", "opens", "overrides", "rawtypes", "removal", - "serial", + // "serial", "static", + "strictfp", + "synchronization", + "text-blocks", + "this-escape", "try", "unchecked", "varargs" @@ -257,6 +230,9 @@ fun xlint(): String { val identifier = "${JPX.ID}-${JPX.VERSION}" +/** + * Setup of the Maven publishing. + */ /** * Setup of the Maven publishing. */ @@ -268,26 +244,29 @@ fun setupPublishing(project: Project) { project.tasks.named("sourcesJar") { filter( - org.apache.tools.ant.filters.ReplaceTokens::class, "tokens" to mapOf( - "__identifier__" to identifier, - "__year__" to Env.COPYRIGHT_YEAR - ) + ReplaceTokens::class, "tokens" to mapOf( + "__identifier__" to identifier, + "__year__" to Env.COPYRIGHT_YEAR + ) ) } project.tasks.named("javadocJar") { filter( - org.apache.tools.ant.filters.ReplaceTokens::class, "tokens" to mapOf( - "__identifier__" to identifier, - "__year__" to Env.COPYRIGHT_YEAR - ) + ReplaceTokens::class, "tokens" to mapOf( + "__identifier__" to identifier, + "__year__" to Env.COPYRIGHT_YEAR + ) ) } project.configure { publications { create("mavenJava") { - artifactId = JPX.ID + suppressPomMetadataWarningsFor("testFixturesApiElements") + suppressPomMetadataWarningsFor("testFixturesRuntimeElements") + + artifactId = project.name from(project.components["java"]) versionMapping { usage("java-api") { @@ -327,24 +306,23 @@ fun setupPublishing(project: Project) { } repositories { maven { - url = if (version.toString().endsWith("SNAPSHOT")) { - uri(Maven.SNAPSHOT_URL) - } else { - uri(Maven.RELEASE_URL) - } + url = if (version.toString().endsWith("SNAPSHOT")) + uri(layout.buildDirectory.dir("repos/snapshots")) + else + uri(layout.buildDirectory.dir("repos/releases")) + } + } - credentials { - username = if (extra.properties["nexus_username"] != null) { - extra.properties["nexus_username"] as String - } else { - "nexus_username" - } - password = if (extra.properties["nexus_password"] != null) { - extra.properties["nexus_password"] as String - } else { - "nexus_password" - } - } + // Exclude test fixtures from publication, as we use them only internally + plugins.withId("org.gradle.java-test-fixtures") { + val component = components["java"] as AdhocComponentWithVariants + component.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } + component.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } + + // Workaround to not publish test fixtures sources added by com.vanniktech.maven.publish plugin + // TODO: Remove as soon as https://github.com/vanniktech/gradle-maven-publish-plugin/issues/779 closed + afterEvaluate { + component.withVariantsFromConfiguration(configurations["testFixturesSourcesElements"]) { skip() } } } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 372fd0d8..f6766eb2 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,6 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - /* * Java GPX Library (@__identifier__@). * Copyright (c) @__year__@ Franz Wilhelmstötter @@ -35,14 +32,3 @@ repositories { mavenLocal() gradlePluginPortal() } - -tasks.withType { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } -} - -configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} diff --git a/buildSrc/resources/javadoc/java.se/element-list b/buildSrc/resources/javadoc/java.se/element-list index 9cf9ae2d..4d9e2fef 100644 --- a/buildSrc/resources/javadoc/java.se/element-list +++ b/buildSrc/resources/javadoc/java.se/element-list @@ -3,6 +3,7 @@ java.io java.lang java.lang.annotation java.lang.constant +java.lang.foreign java.lang.invoke java.lang.module java.lang.ref @@ -221,12 +222,9 @@ module:jdk.hotspot.agent module:jdk.httpserver com.sun.net.httpserver com.sun.net.httpserver.spi -module:jdk.incubator.foreign -jdk.incubator.foreign module:jdk.incubator.vector jdk.incubator.vector module:jdk.jartool -com.sun.jarsigner jdk.security.jarsigner module:jdk.javadoc jdk.javadoc.doclet diff --git a/buildSrc/resources/javadoc/stylesheet.css b/buildSrc/resources/javadoc/stylesheet.css deleted file mode 100644 index d14736d0..00000000 --- a/buildSrc/resources/javadoc/stylesheet.css +++ /dev/null @@ -1,881 +0,0 @@ -/* - * Javadoc style sheet - */ - -@import url('resources/fonts/dejavu.css'); - -/* - * Styles for individual HTML elements. - * - * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular - * HTML element throughout the page. - */ - -div.code { - background-color:#F9F9F9; - margin-top:8px; - margin-bottom:8px; - margin-left:8px; - margin-right:25px; - border:1px solid #9EADC0; -} - -code[lang="java"] { - white-space:pre; - margin-top:5px; - font-family:'DejaVu Sans Mono', monospace; - font-size:1.0em; -} - -body { - background-color:#F5F5F5; - color:#353833; - font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; - font-size:14px; - margin:0; - padding:0; - height:100%; - width:100%; -} -iframe { - margin:0; - padding:0; - height:100%; - width:100%; - overflow-y:scroll; - border:none; -} -a:link, a:visited { - text-decoration:none; - color:#4A6782; -} -a[href]:hover, a[href]:focus { - text-decoration:none; - color:#bb7a2a; -} -a[name] { - color:#353833; -} -pre { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; -} -h1 { - font-size:20px; -} -h2 { - font-size:18px; -} -h3 { - font-size:16px; -} -h4 { - font-size:15px; -} -h5 { - font-size:14px; -} -h6 { - font-size:13px; -} -ul { - list-style-type:disc; -} -code, tt { - font-family:'DejaVu Sans Mono', monospace; -} -:not(h1, h2, h3, h4, h5, h6) > code, -:not(h1, h2, h3, h4, h5, h6) > tt { - font-size:14px; - padding-top:4px; - margin-top:8px; - line-height:1.4em; -} -dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - padding-top:4px; -} -.summary-table dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - vertical-align:top; - padding-top:4px; -} -sup { - font-size:8px; -} -button { - font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; - font-size: 14px; -} -/* - * Styles for HTML generated by javadoc. - * - * These are style classes that are used by the standard doclet to generate HTML documentation. - */ - -/* - * Styles for document title and copyright. - */ -.clear { - clear:both; - height:0; - overflow:hidden; -} -.about-language { - float:right; - padding:0 21px 8px 8px; - font-size:11px; - margin-top:-9px; - height:2.9em; -} -.legal-copy { - margin-left:.5em; -} -.tab { - background-color:#0066FF; - color:#ffffff; - padding:8px; - width:5em; - font-weight:bold; -} -/* - * Styles for navigation bar. - */ -@media screen { - .flex-box { - position:fixed; - display:flex; - flex-direction:column; - height: 100%; - width: 100%; - } - .flex-header { - flex: 0 0 auto; - } - .flex-content { - flex: 1 1 auto; - overflow-y: auto; - } -} -.top-nav { - background-color:#4D7A97; - color:#FFFFFF; - float:left; - padding:0; - width:100%; - clear:right; - min-height:2.8em; - padding-top:10px; - overflow:hidden; - font-size:12px; -} -.sub-nav { - background-color:#dee3e9; - float:left; - width:100%; - overflow:hidden; - font-size:12px; -} -.sub-nav div { - clear:left; - float:left; - padding:0 0 5px 6px; - text-transform:uppercase; -} -.sub-nav .nav-list { - padding-top:5px; -} -ul.nav-list { - display:block; - margin:0 25px 0 0; - padding:0; -} -ul.sub-nav-list { - float:left; - margin:0 25px 0 0; - padding:0; -} -ul.nav-list li { - list-style:none; - float:left; - padding: 5px 6px; - text-transform:uppercase; -} -.sub-nav .nav-list-search { - float:right; - margin:0 0 0 0; - padding:5px 6px; - clear:none; -} -.nav-list-search label { - position:relative; - right:-16px; -} -ul.sub-nav-list li { - list-style:none; - float:left; - padding-top:10px; -} -.top-nav a:link, .top-nav a:active, .top-nav a:visited { - color:#FFFFFF; - text-decoration:none; - text-transform:uppercase; -} -.top-nav a:hover { - text-decoration:none; - color:#bb7a2a; - text-transform:uppercase; -} -.nav-bar-cell1-rev { - background-color:#F8981D; - color:#253441; - margin: auto 5px; -} -.skip-nav { - position:absolute; - top:auto; - left:-9999px; - overflow:hidden; -} -/* - * Hide navigation links and search box in print layout - */ -@media print { - ul.nav-list, div.sub-nav { - display:none; - } -} -/* - * Styles for page header and footer. - */ -.title { - color:#2c4557; - margin:10px 0; -} -.sub-title { - margin:5px 0 0 0; -} -.header ul { - margin:0 0 15px 0; - padding:0; -} -.header ul li, .footer ul li { - list-style:none; - font-size:13px; -} -/* - * Styles for headings. - */ -body.class-declaration-page .summary h2, -body.class-declaration-page .details h2, -body.class-use-page h2, -body.module-declaration-page .block-list h2 { - font-style: italic; - padding:0; - margin:15px 0; -} -body.class-declaration-page .summary h3, -body.class-declaration-page .details h3, -body.class-declaration-page .summary .inherited-list h2 { - background-color:#dee3e9; - border:1px solid #d0d9e0; - margin:0 0 6px -8px; - padding:7px 5px; -} -/* - * Styles for page layout containers. - */ -main { - clear:both; - padding:10px 20px; - position:relative; -} -dl.notes > dt { - font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; - font-size:12px; - font-weight:bold; - margin:10px 0 0 0; - color:#4E4E4E; -} -dl.notes > dd { - margin:5px 10px 10px 0; - font-size:14px; - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; -} -dl.name-value > dt { - margin-left:1px; - font-size:1.1em; - display:inline; - font-weight:bold; -} -dl.name-value > dd { - margin:0 0 0 1px; - font-size:1.1em; - display:inline; -} -/* - * Styles for lists. - */ -li.circle { - list-style:circle; -} -ul.horizontal li { - display:inline; - font-size:0.9em; -} -div.inheritance { - margin:0; - padding:0; -} -div.inheritance div.inheritance { - margin-left:2em; -} -ul.block-list, -ul.details-list, -ul.member-list, -ul.summary-list { - margin:10px 0 10px 0; - padding:0; -} -ul.block-list > li, -ul.details-list > li, -ul.member-list > li, -ul.summary-list > li { - list-style:none; - margin-bottom:15px; - line-height:1.4; -} -.summary-table dl, .summary-table dl dt, .summary-table dl dd { - margin-top:0; - margin-bottom:1px; -} -ul.see-list, ul.see-list-long { - padding-left: 0; - list-style: none; -} -ul.see-list li { - display: inline; -} -ul.see-list li:not(:last-child):after, -ul.see-list-long li:not(:last-child):after { - content: ", "; - white-space: pre-wrap; -} -/* - * Styles for tables. - */ -.summary-table, .details-table { - width:100%; - border-spacing:0; - border-left:1px solid #EEE; - border-right:1px solid #EEE; - border-bottom:1px solid #EEE; - padding:0; -} -.caption { - position:relative; - text-align:left; - background-repeat:no-repeat; - color:#253441; - font-weight:bold; - clear:none; - overflow:hidden; - padding:0; - padding-top:10px; - padding-left:1px; - margin:0; - white-space:pre; -} -.caption a:link, .caption a:visited { - color:#1f389c; -} -.caption a:hover, -.caption a:active { - color:#FFFFFF; -} -.caption span { - white-space:nowrap; - padding-top:5px; - padding-left:12px; - padding-right:12px; - padding-bottom:7px; - display:inline-block; - float:left; - background-color:#F8981D; - border: none; - height:16px; -} -div.table-tabs { - padding:10px 0 0 1px; - margin:0; -} -div.table-tabs > button { - border: none; - cursor: pointer; - padding: 5px 12px 7px 12px; - font-weight: bold; - margin-right: 3px; -} -div.table-tabs > button.active-table-tab { - background: #F8981D; - color: #253441; -} -div.table-tabs > button.table-tab { - background: #4D7A97; - color: #FFFFFF; -} -.two-column-summary { - display: grid; - grid-template-columns: minmax(15%, max-content) minmax(15%, auto); -} -.three-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto); -} -.four-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto); -} -@media screen and (max-width: 600px) { - .two-column-summary { - display: grid; - grid-template-columns: 1fr; - } -} -@media screen and (max-width: 800px) { - .three-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(25%, auto); - } - .three-column-summary .col-last { - grid-column-end: span 2; - } -} -@media screen and (max-width: 1000px) { - .four-column-summary { - display: grid; - grid-template-columns: minmax(15%, max-content) minmax(15%, auto); - } -} -.summary-table > div, .details-table > div { - text-align:left; - padding: 8px 3px 3px 7px; -} -.col-first, .col-second, .col-last, .col-constructor-name, .col-summary-item-name { - vertical-align:top; - padding-right:0; - padding-top:8px; - padding-bottom:3px; -} -.table-header { - background:#dee3e9; - font-weight: bold; -} -.col-first, .col-first { - font-size:13px; -} -.col-second, .col-second, .col-last, .col-constructor-name, .col-summary-item-name, .col-last { - font-size:13px; -} -.col-first, .col-second, .col-constructor-name { - vertical-align:top; - overflow: auto; -} -.col-last { - white-space:normal; -} -.col-first a:link, .col-first a:visited, -.col-second a:link, .col-second a:visited, -.col-first a:link, .col-first a:visited, -.col-second a:link, .col-second a:visited, -.col-constructor-name a:link, .col-constructor-name a:visited, -.col-summary-item-name a:link, .col-summary-item-name a:visited, -.constant-values-container a:link, .constant-values-container a:visited, -.all-classes-container a:link, .all-classes-container a:visited, -.all-packages-container a:link, .all-packages-container a:visited { - font-weight:bold; -} -.table-sub-heading-color { - background-color:#EEEEFF; -} -.even-row-color, .even-row-color .table-header { - background-color:#FFFFFF; -} -.odd-row-color, .odd-row-color .table-header { - background-color:#EEEEEF; -} -/* - * Styles for contents. - */ -.deprecated-content { - margin:0; - padding:10px 0; -} -div.block { - font-size:14px; - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; -} -.col-last div { - padding-top:0; -} -.col-last a { - padding-bottom:3px; -} -.module-signature, -.package-signature, -.type-signature, -.member-signature { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - margin:14px 0; - white-space: pre-wrap; -} -.module-signature, -.package-signature, -.type-signature { - margin-top: 0; -} -.member-signature .type-parameters-long, -.member-signature .parameters, -.member-signature .exceptions { - display: inline-block; - vertical-align: top; - white-space: pre; -} -.member-signature .type-parameters { - white-space: normal; -} -/* - * Styles for formatting effect. - */ -.source-line-no { - color:green; - padding:0 30px 0 0; -} -h1.hidden { - visibility:hidden; - overflow:hidden; - font-size:10px; -} -.block { - display:block; - margin:0 10px 5px 0; - color:#474747; -} -.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link, -.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type, -.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label { - font-weight:bold; -} -.deprecation-comment, .help-footnote, .preview-comment { - font-style:italic; -} -.deprecation-block { - font-size:14px; - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; - border-style:solid; - border-width:thin; - border-radius:10px; - padding:10px; - margin-bottom:10px; - margin-right:10px; - display:inline-block; -} -.preview-block { - font-size:14px; - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; - border-style:solid; - border-width:thin; - border-radius:10px; - padding:10px; - margin-bottom:10px; - margin-right:10px; - display:inline-block; -} -div.block div.deprecation-comment { - font-style:normal; -} -/* - * Styles specific to HTML5 elements. - */ -main, nav, header, footer, section { - display:block; -} -/* - * Styles for javadoc search. - */ -.ui-autocomplete-category { - font-weight:bold; - font-size:15px; - padding:7px 0 7px 3px; - background-color:#4D7A97; - color:#FFFFFF; -} -.result-item { - font-size:13px; -} -.ui-autocomplete { - max-height:85%; - max-width:65%; - overflow-y:scroll; - overflow-x:scroll; - white-space:nowrap; - box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); -} -ul.ui-autocomplete { - position:fixed; - z-index:999999; -} -ul.ui-autocomplete li { - float:left; - clear:both; - width:100%; -} -.result-highlight { - font-weight:bold; -} -#search-input { - background-image:url('resources/glass.png'); - background-size:13px; - background-repeat:no-repeat; - background-position:2px 3px; - padding-left:20px; - position:relative; - right:-18px; - width:400px; -} -#reset-button { - background-color: rgb(255,255,255); - background-image:url('resources/x.png'); - background-position:center; - background-repeat:no-repeat; - background-size:12px; - border:0 none; - width:16px; - height:16px; - position:relative; - left:-4px; - top:-4px; - font-size:0px; -} -.watermark { - color:#545454; -} -.search-tag-desc-result { - font-style:italic; - font-size:11px; -} -.search-tag-holder-result { - font-style:italic; - font-size:12px; -} -.search-tag-result:target { - background-color:yellow; -} -.module-graph span { - display:none; - position:absolute; -} -.module-graph:hover span { - display:block; - margin: -100px 0 0 100px; - z-index: 1; -} -.inherited-list { - margin: 10px 0 10px 0; -} -section.class-description { - line-height: 1.4; -} -.summary section[class$="-summary"], .details section[class$="-details"], -.class-uses .detail, .serialized-class-details { - padding: 0px 20px 5px 10px; - border: 1px solid #ededed; - background-color: #f8f8f8; -} -.inherited-list, section[class$="-details"] .detail { - padding:0 0 5px 8px; - background-color:#ffffff; - border:none; -} -.vertical-separator { - padding: 0 5px; -} -ul.help-section-list { - margin: 0; -} -ul.help-subtoc > li { - display: inline-block; - padding-right: 5px; - font-size: smaller; -} -ul.help-subtoc > li::before { - content: "\2022" ; - padding-right:2px; -} -span.help-note { - font-style: italic; -} -/* - * Indicator icon for external links. - */ -main a[href*="://"]::after { - content:""; - display:inline-block; - background-image:url('data:image/svg+xml; utf8, \ - \ - \ - '); - background-size:100% 100%; - width:7px; - height:7px; - margin-left:2px; - margin-bottom:4px; -} -main a[href*="://"]:hover::after, -main a[href*="://"]:focus::after { - background-image:url('data:image/svg+xml; utf8, \ - \ - \ - '); -} - -/* - * Styles for user-provided tables. - * - * borderless: - * No borders, vertical margins, styled caption. - * This style is provided for use with existing doc comments. - * In general, borderless tables should not be used for layout purposes. - * - * plain: - * Plain borders around table and cells, vertical margins, styled caption. - * Best for small tables or for complex tables for tables with cells that span - * rows and columns, when the "striped" style does not work well. - * - * striped: - * Borders around the table and vertical borders between cells, striped rows, - * vertical margins, styled caption. - * Best for tables that have a header row, and a body containing a series of simple rows. - */ - -table.borderless, -table.plain, -table.striped { - margin-top: 10px; - margin-bottom: 10px; -} -table.borderless > caption, -table.plain > caption, -table.striped > caption { - font-weight: bold; - font-size: smaller; -} -table.borderless th, table.borderless td, -table.plain th, table.plain td, -table.striped th, table.striped td { - padding: 2px 5px; -} -table.borderless, -table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, -table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { - border: none; -} -table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { - background-color: transparent; -} -table.plain { - border-collapse: collapse; - border: 1px solid black; -} -table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { - background-color: transparent; -} -table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, -table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { - border: 1px solid black; -} -table.striped { - border-collapse: collapse; - border: 1px solid black; -} -table.striped > thead { - background-color: #E3E3E3; -} -table.striped > thead > tr > th, table.striped > thead > tr > td { - border: 1px solid black; -} -table.striped > tbody > tr:nth-child(even) { - background-color: #EEE -} -table.striped > tbody > tr:nth-child(odd) { - background-color: #FFF -} -table.striped > tbody > tr > th, table.striped > tbody > tr > td { - border-left: 1px solid black; - border-right: 1px solid black; -} -table.striped > tbody > tr > th { - font-weight: normal; -} -/** - * Tweak font sizes and paddings for small screens. - */ -@media screen and (max-width: 1050px) { - #search-input { - width: 300px; - } -} -@media screen and (max-width: 800px) { - #search-input { - width: 200px; - } - .top-nav, - .bottom-nav { - font-size: 11px; - padding-top: 6px; - } - .sub-nav { - font-size: 11px; - } - .about-language { - padding-right: 16px; - } - ul.nav-list li, - .sub-nav .nav-list-search { - padding: 6px; - } - ul.sub-nav-list li { - padding-top: 5px; - } - main { - padding: 10px; - } - .summary section[class$="-summary"], .details section[class$="-details"], - .class-uses .detail, .serialized-class-details { - padding: 0 8px 5px 8px; - } - body { - -webkit-text-size-adjust: none; - } -} -@media screen and (max-width: 500px) { - #search-input { - width: 150px; - } - .top-nav, - .bottom-nav { - font-size: 10px; - } - .sub-nav { - font-size: 10px; - } - .about-language { - font-size: 10px; - padding-right: 12px; - } -} diff --git a/buildSrc/src/main/java/io/jenetics/gradle/Colorizer.java b/buildSrc/src/main/java/io/jenetics/gradle/Colorizer.java deleted file mode 100644 index c360c86b..00000000 --- a/buildSrc/src/main/java/io/jenetics/gradle/Colorizer.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Java GPX Library (@__identifier__@). - * Copyright (c) @__year__@ Franz Wilhelmstötter - * - * 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. - * - * Author: - * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) - */ -package io.jenetics.gradle; - -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.requireNonNull; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Set; - -/** - * @author Franz Wilhelmstötter - * @since 1.0 - * @version 6.1 - */ -public final class Colorizer extends SimpleFileVisitor { - - private static final Charset CHARSET = UTF_8; - - // Original start tag:
{@code
-	private static final String START_TAG = "
";
-
-	// Original end tag: }
- private static final String END_TAG = "
"; - - private File _baseDir; - - private int _processed = 0; - private int _modified = 0; - - public Colorizer(final File baseDir) { - _baseDir = requireNonNull(baseDir, "Base dir must not be null."); - } - - public Colorizer() { - this(new File(".")); - } - - public void setBaseDir(final File baseDir) { - _baseDir = requireNonNull(baseDir, "Base dir must not be null."); - } - - public File getBaseDir() { - return _baseDir; - } - - public int getProcessed() { - return _processed; - } - - public int getModified() { - return _modified; - } - - public void colorize() throws IOException { - Files.walkFileTree(_baseDir.toPath(), this); - } - - @Override - public FileVisitResult visitFile( - final Path file, - final BasicFileAttributes attrs - ) { - if (file.toString().endsWith(".html")) { - try { - colorize(file); - } catch (IOException e) { - System.out.println("Error while processing file: " + file); - return FileVisitResult.TERMINATE; - } - } - - return FileVisitResult.CONTINUE; - } - - private void colorize(final Path file) throws IOException { - ++_processed; - - try (FileInputStream fis = new FileInputStream(file.toFile()); - InputStreamReader isr = new InputStreamReader(fis, CHARSET); - BufferedReader in = new BufferedReader(isr)) - { - final StringBuilder out = new StringBuilder(10000); - State state = State.DATA; - boolean modified = false; - - for (int ch = in.read(); ch != -1; ch = in.read()) { - out.append((char)ch); - - if (state == State.CODE_TAG) { - modified = true; - } - - state = state.apply((char)ch, out); - } - - if (modified) { - ++_modified; - try (FileOutputStream fout = new FileOutputStream(file.toFile()); - OutputStreamWriter writer = new OutputStreamWriter(fout, CHARSET)) - { - writer.write(out.toString()); - } - } - } - } - - /** - * Represents the current 'Colorizer' state. - * - * @author Franz Wilhelmstötter - * @since 1.0 - * @version 1.4 - */ - private enum State { - - DATA { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if ((ch == '>') && - (out.length() >= START_TAG.length()) && - out.substring(out.length() - START_TAG.length()) - .equalsIgnoreCase(START_TAG)) - { - out.setLength(out.length() - START_TAG.length()); - out.append("
"); - state = SKIP_NEWLINE; - } - - return state; - } - }, - - SKIP_NEWLINE { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if (ch == '\n') { - out.setLength(out.length() - 1); - state = CODE_TAG; - } - return state; - } - }, - - CODE_TAG { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if (Character.isJavaIdentifierPart(ch)) { - state = IDENTIFIER; - state._start = out.length() - 1; - } else if (ch == '"') { - state = STRING_LITERAL; - out.insert( - out.length() - 1, - "" - ); - } else if ((ch == '/') && (out.charAt(out.length() - 2) == '/')) { - state = COMMENT; - out.insert( - out.length() - 2, - "" - ); - } else if ((ch == '@') && - (out.charAt(out.length() - 2) == '\\') && - (out.charAt(out.length() - 3) != '\\')) - { - state = ANNOTATION; - out.deleteCharAt(out.length() - 2); - out.insert( - out.length() - 1, - "" - ); - } - - return state; - } - }, - - IDENTIFIER { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if ((ch == '>') && - out.substring(out.length() - END_TAG.length()) - .equalsIgnoreCase(END_TAG)) - { - int index = out.lastIndexOf("\n"); - out.setLength(index); - out.append("
"); - state = DATA; - } else if (!Character.isJavaIdentifierPart(ch)) { - final String name = out.substring(_start, out.length() - 1); - if (IDENTIFIERS.contains(name)) { - out.insert(_start + name.length(), ""); - out.insert( - _start, - "" - ); - } - state = CODE_TAG; - } - - return state; - } - }, - - ANNOTATION { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if (!Character.isJavaIdentifierPart(ch)) { - out.insert(out.length() - 1, ""); - state = CODE_TAG; - } - return state; - } - }, - - STRING_LITERAL { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if ((ch == '"') && (out.charAt(out.length() - 2) != '\\')) { - out.append(""); - state = CODE_TAG; - } - return state; - } - }, - - COMMENT { - @Override - public State apply(final char ch, final StringBuilder out) { - State state = this; - if ((ch == '\n') || (ch == '\r')) { - out.insert(out.length() - 1, ""); - state = CODE_TAG; - } - return state; - } - }; - - int _start = -1; - - public abstract State apply(final char read, final StringBuilder doc); - - private static final String ANNOTATION_COLOR = "#808080"; - private static final String KEYWORD_COLOR = "#7F0055"; - private static final String COMMENT_COLOR = "#3F7F5F"; - private static final String STRING_COLOR = "#0000FF"; - - private static final Set IDENTIFIERS = Set.of( - "abstract", - "assert", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - "class", - "const", - "continue", - "default", - "do", - "double", - "else", - "enum", - "extends", - "final", - "finally", - "float", - "for", - "goto", - "if", - "implements", - "import", - "instanceof", - "int", - "interface", - "long", - "native", - "null", - "new", - "package", - "private", - "protected", - "public", - "return", - "short", - "static", - "strictfp", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "try", - "void", - "volatile", - "while" - ); - } - - - public static void main(final String[] args) { - final File dir = new File(args[0]); - if (!dir.isDirectory()) { - System.err.println(args[0] + " is not a directory."); - System.exit(1); - } - - try { - final Colorizer colorizer = new Colorizer(dir); - colorizer.colorize(); - - System.out.println(format( - "Colorizer processed %d files and modified %d.", - colorizer.getProcessed(), - colorizer.getModified() - )); - } catch (IOException e) { - System.err.println("Error while processing files: " + e); - System.exit(1); - } - } - -} diff --git a/buildSrc/src/main/kotlin/Env.kt b/buildSrc/src/main/kotlin/Env.kt index 24b4e7c3..78f339f9 100644 --- a/buildSrc/src/main/kotlin/Env.kt +++ b/buildSrc/src/main/kotlin/Env.kt @@ -52,7 +52,7 @@ object Env { * Information about the library and author. */ object JPX { - const val VERSION = "3.2.1" + const val VERSION = "4.0.0-SNAPSHOT" const val ID = "jpx" const val NAME = "jpx" const val GROUP = "io.jenetics" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..53454cf7 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,84 @@ +[versions] +assertj = "3.27.4" +codemodel = "4.0.5" +commons-csv = "1.14.1" +commons-math4-legacy = "4.0-beta1" +commons-numbers-combinatorics = "1.2" +commons-numbers-core = "1.2" +commons-numbers-gamma = "1.2" +commons-numbers-rootfinder = "1.2" +commons-rng-sampling = "1.6" +commons-rng-simple = "1.6" +commons-statistics-descriptive = "1.1" +commons-statistics-distribution = "1.1" +equalsverifier = "4.0.9" +facilejdbc = "2.1.1" +guava = "33.4.8-jre" +h2 = "2.3.232" +jackson = "2.19.2" +jackson-databind-nullable = "0.2.7" +jackson-datatype-jsr310 = "2.19.2" +jacoco-agent = "0.8.13" +jakarta-annotation-api = "3.0.0" +jakarta-validation-api = "3.1.1" +javacsv = "2.0" +jexl = "3.5.0" +jmh = "0.7.3" +jpx = "3.2.1" +lombok = "8.14.2" +mvel = "2.5.2.Final" +nashorn = "15.7" +openapi-generator = "7.14.0" +opencsv = "5.12.0" +prngine = "2.0.0" +reactor-core = "3.7.9" +rxjava = "2.2.21" +supercsv = "2.4.0" +swagger-models = "2.2.36" +swagger-parser = "2.1.32" +testng = "7.11.0" +version-catalog-update = "1.0.0" + +[plugins] +jmh = { id = "me.champeau.jmh", version.ref = "jmh" } +lombok = { id = "io.freefair.lombok", version.ref = "lombok" } +openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } +version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "version-catalog-update" } + +[libraries] +assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" } +codemodel = { module = "org.glassfish.jaxb:codemodel", version.ref = "codemodel" } +commons-csv = { module = "org.apache.commons:commons-csv", version.ref = "commons-csv" } +commons-math4-legacy = { module = "org.apache.commons:commons-math4-legacy", version.ref = "commons-math4-legacy" } +commons-numbers-combinatorics = { module = "org.apache.commons:commons-numbers-combinatorics", version.ref = "commons-numbers-combinatorics" } +commons-numbers-core = { module = "org.apache.commons:commons-numbers-core", version.ref = "commons-numbers-core" } +commons-numbers-gamma = { module = "org.apache.commons:commons-numbers-gamma", version.ref = "commons-numbers-gamma" } +commons-numbers-rootfinder = { module = "org.apache.commons:commons-numbers-rootfinder", version.ref = "commons-numbers-rootfinder" } +commons-rng-sampling = { module = "org.apache.commons:commons-rng-sampling", version.ref = "commons-rng-sampling" } +commons-rng-simple = { module = "org.apache.commons:commons-rng-simple", version.ref = "commons-rng-simple" } +commons-statistics-descriptive = { module = "org.apache.commons:commons-statistics-descriptive", version.ref = "commons-statistics-descriptive" } +commons-statistics-distribution = { module = "org.apache.commons:commons-statistics-distribution", version.ref = "commons-statistics-distribution" } +equalsverifier = { module = "nl.jqno.equalsverifier:equalsverifier", version.ref = "equalsverifier" } +facilejdbc = { module = "io.jenetics:facilejdbc", version.ref = "facilejdbc" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +h2 = { module = "com.h2database:h2", version.ref = "h2" } +jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-databind-nullable = { module = "org.openapitools:jackson-databind-nullable", version.ref = "jackson-databind-nullable" } +jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson-datatype-jsr310" } +jacoco-agent = { module = "org.jacoco:org.jacoco.agent", version.ref = "jacoco-agent" } +jakarta-annotation-api = { module = "jakarta.annotation:jakarta.annotation-api", version.ref = "jakarta-annotation-api" } +jakarta-validation-api = { module = "jakarta.validation:jakarta.validation-api", version.ref = "jakarta-validation-api" } +javacsv = { module = "net.sourceforge.javacsv:javacsv", version.ref = "javacsv" } +jexl = { module = "org.apache.commons:commons-jexl3", version.ref = "jexl" } +jpx = { module = "io.jenetics:jpx", version.ref = "jpx" } +mvel = { module = "org.mvel:mvel2", version.ref = "mvel" } +nashorn-core = { module = "org.openjdk.nashorn:nashorn-core", version.ref = "nashorn" } +opencsv = { module = "com.opencsv:opencsv", version.ref = "opencsv" } +prngine = { module = "io.jenetics:prngine", version.ref = "prngine" } +reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor-core" } +rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" } +supercsv = { module = "net.sf.supercsv:super-csv", version.ref = "supercsv" } +swagger-models = { module = "io.swagger.core.v3:swagger-models", version.ref = "swagger-models" } +swagger-parser = { module = "io.swagger.parser.v3:swagger-parser", version.ref = "swagger-parser" } +testng = { module = "org.testng:testng", version.ref = "testng" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b95..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7cf748e7..48b43d35 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/jpx.jdbc/build.gradle.kts b/jpx.jdbc/build.gradle.kts new file mode 100644 index 00000000..ae8b7a1b --- /dev/null +++ b/jpx.jdbc/build.gradle.kts @@ -0,0 +1,46 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ + +/** + * @author Franz Wilhelmstötter + * @since 1.0 + * @version 1.0 + */ + +plugins { + `java-library` + `maven-publish` +} + +description = "JPX - Java GPX (GPS) Library" + +extra["moduleName"] = "io.jenetics.jpx.jdbc" + +dependencies { + api(project(":jpx")) + api(libs.facilejdbc) + + testImplementation(libs.assertj.core) + testImplementation(libs.equalsverifier) + testImplementation(libs.h2) + testImplementation(libs.prngine) + testImplementation(libs.testng) +} + diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/BoundsAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/BoundsAccess.java new file mode 100644 index 00000000..f6a547c5 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/BoundsAccess.java @@ -0,0 +1,91 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; + +import java.sql.Connection; +import java.sql.SQLException; + +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Bounds; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class BoundsAccess { + private BoundsAccess() { + } + + static final RowParser PARSER = (row, conn) -> Bounds.of( + row.getDouble("minlat"), + row.getDouble("minlon"), + row.getDouble("maxlat"), + row.getDouble("maxlon") + ); + + static final Dctor DCTOR = Dctor.of( + field("minlat", Bounds::getMinLatitude), + field("minlon", Bounds::getMinLongitude), + field("maxlat", Bounds::getMaxLatitude), + field("maxlon", Bounds::getMaxLongitude) + ); + + private static final Query SELECT = Query.of(""" + SELECT minlat, minlon, maxlat, maxlon + FROM bounds + WHERE id = :id + """ + ); + + private static final Query INSERT = Query.of(""" + INSERT INTO bounds(minlat, minlon, maxlat, maxlon) + VALUES(:minlat, :minlon, :maxlat, :maxlon) + """ + ); + + public static Bounds selectById(final Long id, final Connection conn) + throws SQLException + { + return id != null + ? SELECT + .on(value("id", id)) + .as(PARSER.singleNull(), conn) + : null; + } + + public static Long insert(final Bounds bounds, final Connection conn) + throws SQLException + { + return bounds != null + ? INSERT + .on(bounds, DCTOR) + .executeInsert(conn) + .orElseThrow() + : null; + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/CopyrightAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/CopyrightAccess.java new file mode 100644 index 00000000..b38b55de --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/CopyrightAccess.java @@ -0,0 +1,92 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; +import static io.jenetics.facilejdbc.Row.map; + +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Year; + +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Copyright; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class CopyrightAccess { + private CopyrightAccess() { + } + + static final RowParser PARSER = (row, conn) -> Copyright.of( + row.getString("author"), + map(row.getInt("year"), Year::of), + map(row.getString("license"), URI::create) + ); + + static final Dctor DCTOR = Dctor.of( + field("author", Copyright::getAuthor), + field("year", c -> c.getYear().map(Year::getValue)), + field("license", Copyright::getLicense) + ); + + private static final Query SELECT = Query.of(""" + SELECT id, author, year, license + FROM copyright + WHERE id = :id + """ + ); + + private static final Query INSERT = Query.of(""" + INSERT INTO copyright(author, year, license) + VALUES(:author, :year, :license) + """ + ); + + public static Copyright selectById(final Long id, final Connection conn) + throws SQLException + { + return id != null + ? SELECT + .on(value("id", id)) + .as(PARSER.singleNull(), conn) + : null; + } + + public static Long insert(final Copyright copyright, final Connection conn) + throws SQLException + { + return copyright != null + ? INSERT + .on(copyright, DCTOR) + .executeInsert(conn) + .orElseThrow() + : null; + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GPXAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GPXAccess.java new file mode 100644 index 00000000..2b058bb0 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GPXAccess.java @@ -0,0 +1,149 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; + +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.Route; +import io.jenetics.jpx.Track; +import io.jenetics.jpx.WayPoint; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class GPXAccess { + private GPXAccess() {} + + private static final Query INSERT_QUERY = Query.of(""" + INSERT INTO gpx(version, creator, metadata_id) + VALUES(:version, :creator, :metadata_id) + """ + ); + + private static final Dctor DCTOR = Dctor.of( + field("version", GPX::getVersion), + field("creator", GPX::getCreator), + field( + "metadata_id", + (gpx, conn) -> MetadataAccess.insert(gpx.getMetadata().orElse(null), conn) + ) + ); + + + public static Long insert(final GPX gpx, final Connection conn) + throws SQLException + { + if (gpx == null) return null; + + final Long id = INSERT_QUERY + .on(gpx, DCTOR) + .executeInsert(conn) + .orElseThrow(); + + insertWayPoints(id, gpx.getWayPoints(), conn); + insertRoutes(id, gpx.getRoutes(), conn); + insertTracks(id, gpx.getTracks(), conn); + return id; + } + + private static final Query WAY_POINT_INSERT_QUERY = Query.of(""" + INSERT INTO gpx_way_point(gpx_id, way_point_id) + VALUES(:gpx_id, :way_point_id) + """ + ); + + private static void insertWayPoints( + final Long id, + final List points, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + points, + Dctor.of( + field("gpx_id", r -> id), + field("way_point_id", WayPointAccess::insert) + ) + ); + + WAY_POINT_INSERT_QUERY.executeUpdate(batch, conn); + } + + private static final Query ROUTE_INSERT_QUERY = Query.of(""" + INSERT INTO gpx_route(gpx_id, route_id) + VALUES(:gpx_id, :route_id) + """ + ); + + private static void insertRoutes( + final Long id, + final List routes, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + routes, + Dctor.of( + field("gpx_id", r -> id), + field("route_id", RouteAccess::insert) + ) + ); + + ROUTE_INSERT_QUERY.executeUpdate(batch, conn); + } + + private static final Query TRACK_INSERT_QUERY = Query.of(""" + INSERT INTO gpx_track(gpx_id, track_id) + VALUES(:gpx_id, :track_id) + """ + ); + + private static void insertTracks( + final Long id, + final List tracks, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + tracks, + Dctor.of( + field("gpx_id", r ->id), + field("track_id", TrackAccess::insert) + ) + ); + + TRACK_INSERT_QUERY.executeUpdate(batch, conn); + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GpxTypeMapper.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GpxTypeMapper.java new file mode 100644 index 00000000..bde1ce3b --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/GpxTypeMapper.java @@ -0,0 +1,60 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import java.net.URI; +import java.net.URL; +import java.time.Duration; +import java.time.ZonedDateTime; + +import io.jenetics.facilejdbc.spi.SqlTypeMapper; + +import io.jenetics.jpx.DGPSStation; +import io.jenetics.jpx.Degrees; +import io.jenetics.jpx.Fix; +import io.jenetics.jpx.Latitude; +import io.jenetics.jpx.Length; +import io.jenetics.jpx.Longitude; +import io.jenetics.jpx.Speed; +import io.jenetics.jpx.UInt; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public class GpxTypeMapper extends SqlTypeMapper { + @Override + public Object convert(final Object value) { + if (value instanceof Latitude) return ((Latitude)value).doubleValue(); + if (value instanceof Longitude) return ((Longitude)value).doubleValue(); + if (value instanceof Length) return ((Length)value).doubleValue(); + if (value instanceof Speed) return ((Speed)value).doubleValue(); + if (value instanceof Degrees) return ((Degrees)value).doubleValue(); + if (value instanceof Fix) return ((Fix)value).getValue(); + if (value instanceof UInt) return ((UInt)value).getValue(); + if (value instanceof DGPSStation) return ((DGPSStation)value).intValue(); + if (value instanceof ZonedDateTime) return ((ZonedDateTime)value).toOffsetDateTime(); + if (value instanceof Duration) return ((Duration)value).getSeconds(); + if (value instanceof URI) return value.toString(); + if (value instanceof URL) return value.toString(); + return value; + } +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/LinkAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/LinkAccess.java new file mode 100644 index 00000000..01a3d57d --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/LinkAccess.java @@ -0,0 +1,110 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; +import static io.jenetics.facilejdbc.Row.map; + +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; + +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Link; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class LinkAccess { + private LinkAccess() { + } + + static final RowParser PARSER = (row, conn) -> Link.of( + map(row.getString("href"), URI::create), + row.getString("text"), + row.getString("type") + ); + + static final Dctor DCTOR = Dctor.of( + field("href", Link::getHref), + field("text", Link::getText), + field("type", Link::getType) + ); + + static final Query SELECT_BY_ID = Query.of(""" + SELECT id, href, text, type + FROM link + WHERE id = :id + """ + ); + + static final Query INSERT = Query.of(""" + INSERT INTO link(href, text, type) + VALUES(:href, :text, :type) + """ + ); + + public static Link selectById(final Long id, final Connection conn) + throws SQLException + { + return id != null + ? SELECT_BY_ID + .on(value("id", id)) + .as(PARSER.singleNull(), conn) + : null; + } + + public static Long insertIfMissing(final Link link, final Connection conn) + throws SQLException + { + final var select = Query.of(""" + SELECT id FROM link + WHERE href = :href AND text = :text AND type = :type + """ + ); + + final var id = select + .on( + value("href", link.getHref()), + value("text", link.getText()), + value("type", link.getType())) + .as(RowParser.int64(1).singleNull(), conn); + + return 1L; + } + + public static Long insert(final Link link, final Connection conn) + throws SQLException + { + return link != null + ? INSERT + .on(link, DCTOR) + .executeInsert(conn) + .orElseThrow() + : null; + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/MetadataAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/MetadataAccess.java new file mode 100644 index 00000000..1046c929 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/MetadataAccess.java @@ -0,0 +1,159 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Bounds; +import io.jenetics.jpx.Copyright; +import io.jenetics.jpx.Link; +import io.jenetics.jpx.Metadata; +import io.jenetics.jpx.Person; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class MetadataAccess { + private MetadataAccess() { + } + + record Row( + String name, + String desc, + Timestamp time, + String keywords, + Long personId, + Long copyrightId, + Long boundsId + ) {} + + private static final RowParser ROW_PARSER = RowParser.record(Row.class); + + private static final Dctor DCTOR = Dctor.of( + field("name", Metadata::getName), + field("dscr", Metadata::getDescription), + field("time", Metadata::getTime), + field("keywords", Metadata::getKeywords), + field( + "person_id", + (md, conn) -> PersonAccess.insert(md.getAuthor().orElse(null), conn) + ), + field( + "copyright_id", + (md, conn) -> CopyrightAccess.insert(md.getCopyright().orElse(null), conn) + ), + field( + "bounds_id", + (md, conn) -> BoundsAccess.insert(md.getBounds().orElse(null), conn) + ) + ); + + private static final Query SELECT = Query.of(""" + SELECT name, dscr, time, keywords, person_id, copyright_id, bounds_id + FROM metadata + WHERE id = :id + """ + ); + + private static final Query INSERT = Query.of(""" + INSERT INTO metadata(name, dscr, time, keywords, person_id, copyright_id, bounds_id) + VALUES(:name, :dscr, :time, :keywords, :person_id, :copyright_id, :bounds_id) + """ + ); + + public static Metadata selectById(final Long id, final Connection conn) + throws SQLException + { + if (id == null) return null; + + final Row row = SELECT + .on(value("id", id)) + .as(ROW_PARSER.singleNull(), conn); + + if (row == null) return null; + + final Person author = PersonAccess.selectById(row.personId(), conn); + final Copyright copyright = CopyrightAccess.selectById(row.copyrightId(), conn); + final Bounds bounds = BoundsAccess.selectById(row.boundsId(), conn); + + return Metadata.builder() + .name(row.name()) + .desc(row.desc()) + .time(row.time().toInstant()) + .keywords(row.keywords()) + .author(author) + .copyright(copyright) + .bounds(bounds) + .build(); + } + + public static Long insert(final Metadata metadata, final Connection conn) + throws SQLException + { + if (metadata == null || metadata.isEmpty()) return null; + + final Long id = INSERT + .on(metadata, DCTOR) + .executeInsert(conn) + .orElseThrow(); + + insertLinks(id, metadata.getLinks(), conn); + return id; + } + + private static final Query LINK_INSERT_QUERY = Query.of(""" + INSERT INTO metadata_link(metadata_id, link_id) + VALUES(:metadata_id, :link_id) + """ + ); + + private static void insertLinks( + final Long id, + final List links, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + links, + Dctor.of( + field("metadata_id", r -> id), + field("link_id", LinkAccess::insert) + ) + ); + + LINK_INSERT_QUERY.executeUpdate(batch, conn); + } + + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/PersonAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/PersonAccess.java new file mode 100644 index 00000000..8b05de28 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/PersonAccess.java @@ -0,0 +1,97 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; +import static io.jenetics.facilejdbc.Row.map; + +import java.sql.Connection; +import java.sql.SQLException; + +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Email; +import io.jenetics.jpx.Link; +import io.jenetics.jpx.Person; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class PersonAccess { + private PersonAccess() { + } + + private static final RowParser PARSER = (row, conn) -> Person.of( + row.getString("name"), + map(row.getString("email"), Email::of), + Link.of( + row.getString("link_href"), + row.getString("link_text"), + row.getString("link_type") + ) + ); + + private static final Dctor DCTOR = Dctor.of( + field("name", Person::getName), + field("email", p -> p.getEmail().map(Email::getAddress)), + field("link_id", (p, c) -> LinkAccess.insert(p.getLink().orElse(null), c)) + ); + + private static final Query SELECT = Query.of(""" + SELECT person.id, name, email, link_href, link_text, link_type + FROM person + INNER JOIN link on person.link_id = link.id + WHERE person.id = :id + """ + ); + + private static final Query INSERT = Query.of(""" + INSERT INTO person(name, email, link_id) + VALUES(:name, :email, :link_id) + """ + ); + + public static Person selectById(final Long id, final Connection conn) + throws SQLException + { + return id != null + ? SELECT + .on(value("id", id)) + .as(PARSER.singleNull(), conn) + : null; + } + + public static Long insert(final Person person, final Connection conn) + throws SQLException + { + return person != null && !person.isEmpty() + ? INSERT + .on(person, DCTOR) + .executeInsert(conn) + .orElseThrow() + : null; + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/RouteAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/RouteAccess.java new file mode 100644 index 00000000..9421fa4a --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/RouteAccess.java @@ -0,0 +1,123 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; + +import io.jenetics.jpx.Link; +import io.jenetics.jpx.Route; +import io.jenetics.jpx.UInt; +import io.jenetics.jpx.WayPoint; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class RouteAccess { + private RouteAccess() {} + + private static final Query INSERT_QUERY = Query.of(""" + INSERT INTO route(name, cmt, dscr, src, number, type) + VALUES(:name, :cmt, :dscr, :src, :number, :type) + """ + ); + + private static final Dctor DCTOR = Dctor.of( + field("name", Route::getName), + field("cmt", Route::getComment), + field("dscr", Route::getDescription), + field("src", Route::getSource), + field("number", r -> r.getNumber().map(UInt::getValue)), + field("type", Route::getType) + ); + + public static Long insert(final Route route, final Connection conn) + throws SQLException + { + if (route == null || route.isEmpty()) return null; + + final Long id = INSERT_QUERY + .on(route, DCTOR) + .executeInsert(conn) + .orElseThrow(); + + insertLinks(id, route.getLinks(), conn); + insertWayPoints(id, route.getPoints(), conn); + return id; + } + + private static final Query LINK_INSERT_QUERY = Query.of(""" + INSERT INTO route_link(route_id, link_id) + VALUES(:route_id, :link_id) + """ + ); + + private static void insertLinks( + final Long id, + final List links, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + links, + Dctor.of( + field("route_id", r -> id), + field("link_id", LinkAccess::insert) + ) + ); + + LINK_INSERT_QUERY.executeUpdate(batch, conn); + } + + private static final Query WAY_POINT_INSERT_QUERY = Query.of(""" + INSERT INTO route_way_point(route_id, way_point_id) + VALUES(:route_id, :way_point_id) + """ + ); + + private static void insertWayPoints( + final Long id, + final List points, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + points, + Dctor.of( + field("route_id", r -> id), + field("way_point_id", WayPointAccess::insert) + ) + ); + + WAY_POINT_INSERT_QUERY.executeUpdate(batch, conn); + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackAccess.java new file mode 100644 index 00000000..41c4ef71 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackAccess.java @@ -0,0 +1,127 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Param.value; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; + +import io.jenetics.jpx.Link; +import io.jenetics.jpx.Track; +import io.jenetics.jpx.TrackSegment; +import io.jenetics.jpx.UInt; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class TrackAccess { + private TrackAccess() {} + + private static final Query INSERT_QUERY = Query.of(""" + INSERT INTO track(name, cmt, dscr, src, number, type) + VALUES(:name, :cmt, :dscr, :src, :number, :type) + """ + ); + + private static final Dctor DCTOR = Dctor.of( + field("name", Track::getName), + field("cmt", Track::getComment), + field("dscr", Track::getDescription), + field("src", Track::getSource), + field("number", t -> t.getNumber().map(UInt::getValue)), + field("type", Track::getType) + ); + + public static Long insert(final Track track, final Connection conn) + throws SQLException + { + if (track == null || track.isEmpty()) return null; + + final Long id = INSERT_QUERY + .on(track, DCTOR) + .executeInsert(conn) + .orElseThrow(); + + insertLinks(id, track.getLinks(), conn); + insertSegments(id, track.getSegments(), conn); + return id; + } + + private static final Query LINK_INSERT_QUERY = Query.of(""" + INSERT INTO track_link(track_id, link_id) + VALUES(:track_id, :link_id) + """ + ); + + private static void insertLinks( + final Long id, + final List links, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + links, + Dctor.of( + field("track_id", r -> id), + field("link_id", LinkAccess::insert) + ) + ); + + LINK_INSERT_QUERY.executeUpdate(batch, conn); + } + + private static final Query SEGMENT_INSERT_QUERY = Query.of(""" + INSERT INTO track_track_segment(track_id, track_segment_id) + VALUES(:track_id, :track_segment_id) + """ + ); + + private static void insertSegments( + final Long id, + final List segments, + final Connection conn + ) + throws SQLException + { + for (int i = 0; i < segments.size(); ++i) { + final TrackSegment segment = segments.get(i); + final Long sid = TrackSegmentAccess.insert(segment, i, conn); + + if (sid != null) { + SEGMENT_INSERT_QUERY + .on( + value("track_id", id), + value("track_segment_id", sid)) + .executeUpdate(conn); + } + } + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackSegmentAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackSegmentAccess.java new file mode 100644 index 00000000..57cbdbd0 --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/TrackSegmentAccess.java @@ -0,0 +1,92 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; + +import io.jenetics.jpx.TrackSegment; +import io.jenetics.jpx.WayPoint; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class TrackSegmentAccess { + private TrackSegmentAccess() {} + + private static final Query INSERT_QUERY = Query.of(""" + INSERT INTO track_segment(number) + VALUES(:number) + """ + ); + + + public static Long insert( + final TrackSegment segment, + final int number, + final Connection conn + ) + throws SQLException + { + if (segment == null || segment.isEmpty()) return null; + + final Long id = INSERT_QUERY + .on(segment, Dctor.of(field("number", r -> number))) + .executeInsert(conn) + .orElseThrow(); + + insertWayPoints(id, segment.getPoints(), conn); + return id; + } + + private static final Query WAY_POINT_INSERT_QUERY = Query.of(""" + INSERT INTO track_segment_way_point(track_segment_id, way_point_id) + VALUES(:track_segment_id, :way_point_id) + """ + ); + + private static void insertWayPoints( + final Long id, + final List points, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + points, + Dctor.of( + field("track_segment_id", r -> id), + field("way_point_id", WayPointAccess::insert) + ) + ); + + WAY_POINT_INSERT_QUERY.executeUpdate(batch, conn); + } + +} diff --git a/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/WayPointAccess.java b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/WayPointAccess.java new file mode 100644 index 00000000..dac9095a --- /dev/null +++ b/jpx.jdbc/src/main/java/io/jenetics/jpx/jdbc/WayPointAccess.java @@ -0,0 +1,149 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static io.jenetics.facilejdbc.Dctor.field; +import static io.jenetics.facilejdbc.Row.map; +import static io.jenetics.jpx.Length.Unit.METER; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Dctor; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.RowParser; + +import io.jenetics.jpx.Degrees; +import io.jenetics.jpx.Length; +import io.jenetics.jpx.Link; +import io.jenetics.jpx.WayPoint; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public final class WayPointAccess { + private WayPointAccess() { + } + + static final RowParser PARSER = (row, conn) -> WayPoint.builder() + .lat(row.getDouble("lat")) + .lon(row.getDouble("lon")) + .ele(row.getDouble("ele")) + .speed(row.getDouble("speed")) + .time(row.getInstant("time")) + .magvar(map(row.getDouble("magvar"), Degrees::ofDegrees)) + .geoidheight(map(row.getDouble("geoidheight"), v -> Length.of(v, METER))) + .name(row.getString("name")) + .cmt(row.getString("cmt")) + .desc(row.getString("desc")) + .src(row.getString("scr")) + .sym(row.getString("sym")) + .type(row.getString("type")) + .fix(row.getString("fix")) + .sat(row.getInt("sat")) + .hdop(row.getDouble("hdop")) + .vdop(row.getDouble("vdop")) + .pdop(row.getDouble("pdop")) + .ageofdgpsdata(row.getDouble("argeofgpsdata")) + .dgpsid(row.getInt("dgpsid")) + .course(row.getDouble("course")) + .build(); + + static final Dctor DCTOR = Dctor.of( + field("lat", WayPoint::getLatitude), + field("lon", WayPoint::getLongitude), + field("ele", WayPoint::getElevation), + field("speed", WayPoint::getSpeed), + field("time", WayPoint::getTime), + field("magvar", WayPoint::getMagneticVariation), + field("geoidheight", WayPoint::getGeoidHeight), + field("name", WayPoint::getName), + field("cmt", WayPoint::getComment), + field("dscr", WayPoint::getDescription), + field("src", WayPoint::getSource), + field("sym", WayPoint::getSymbol), + field("type", WayPoint::getType), + field("fix", WayPoint::getFix), + field("sat", WayPoint::getSat), + field("hdop", WayPoint::getHdop), + field("vdop", WayPoint::getVdop), + field("pdop", WayPoint::getPdop), + field("ageofdgpsdata", WayPoint::getAgeOfGPSData), + field("dgpsid", WayPoint::getDGPSID), + field("course", WayPoint::getCourse) + ); + + private static final Query INSERT = Query.of(""" + INSERT INTO way_point( + lat, lon, ele, speed, time, magvar, geoidheight, name, cmt, + dscr, src, sym, type, fix, sat, hdop, vdop, pdop, + ageofdgpsdata, dgpsid, course + ) + VALUES( + :lat, :lon, :ele, :speed, :time, :magvar, :geoidheight, :name, :cmt, + :dscr, :src, :sym, :type, :fix, :sat, :hdop, :vdop, :pdop, + :ageofdgpsdata, :dgpsid, :course + ) + """ + ); + + public static Long insert(final WayPoint wp, final Connection conn) + throws SQLException + { + if (wp == null) return null; + + final Long id = INSERT + .on(wp, DCTOR) + .executeInsert(conn) + .orElseThrow(); + + insertLinks(id, wp.getLinks(), conn); + return id; + } + + private static final Query LINK_INSERT_QUERY = Query.of(""" + INSERT INTO way_point_link(way_point_id, link_id) + VALUES(:way_point_id, :link_id) + """ + ); + + private static void insertLinks( + final Long id, + final List links, + final Connection conn + ) + throws SQLException + { + final Batch batch = Batch.of( + links, + Dctor.of( + field("way_point_id", r -> id), + field("link_id", LinkAccess::insert) + ) + ); + + LINK_INSERT_QUERY.executeUpdate(batch, conn); + } + +} diff --git a/jpx.jdbc/src/main/java/module-info.java b/jpx.jdbc/src/main/java/module-info.java new file mode 100644 index 00000000..471658c9 --- /dev/null +++ b/jpx.jdbc/src/main/java/module-info.java @@ -0,0 +1,25 @@ +/* + * Java GPX Library (@__identifier__@). + * + * 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. + */ + +module io.jenetics.jpx.jdbc { + requires transitive io.jenetics.jpx; + requires transitive io.jenetics.facilejdbc; + + exports io.jenetics.jpx.jdbc; + + provides io.jenetics.facilejdbc.spi.SqlTypeMapper + with io.jenetics.jpx.jdbc.GpxTypeMapper; +} diff --git a/jpx.jdbc/src/main/resources/META-INF/services/io.jenetics.facilejdbc.spi.SqlTypeMapper b/jpx.jdbc/src/main/resources/META-INF/services/io.jenetics.facilejdbc.spi.SqlTypeMapper new file mode 100644 index 00000000..977f608f --- /dev/null +++ b/jpx.jdbc/src/main/resources/META-INF/services/io.jenetics.facilejdbc.spi.SqlTypeMapper @@ -0,0 +1 @@ +io.jenetics.jpx.jdbc.GpxTypeMapper diff --git a/jpx.jdbc/src/main/resources/drop.sql b/jpx.jdbc/src/main/resources/drop.sql new file mode 100644 index 00000000..04c2d6bd --- /dev/null +++ b/jpx.jdbc/src/main/resources/drop.sql @@ -0,0 +1,20 @@ +DROP TABLE gpx_track; +DROP TABLE gpx_route; +DROP TABLE gpx_way_point; +DROP TABLE gpx; +DROP TABLE track_link; +DROP TABLE track_track_segment; +DROP TABLE track; +DROP TABLE track_segment_way_point; +DROP TABLE track_segment; +DROP TABLE route_way_point; +DROP TABLE route_link; +DROP TABLE route; +DROP TABLE way_point_link; +DROP TABLE way_point; +DROP TABLE metadata_link; +DROP TABLE metadata; +DROP TABLE bounds; +DROP TABLE copyright; +DROP TABLE person; +DROP TABLE link; diff --git a/jpx.jdbc/src/main/resources/model-mysql.sql b/jpx.jdbc/src/main/resources/model-mysql.sql new file mode 100644 index 00000000..b51f5ff0 --- /dev/null +++ b/jpx.jdbc/src/main/resources/model-mysql.sql @@ -0,0 +1,223 @@ +-- ----------------------------------------------------------------------------- +-- Create the `link` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE link( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + href VARCHAR(255) NOT NULL, + text VARCHAR(255), + type VARCHAR(255) +); +CREATE INDEX i_link_href ON link(href); +CREATE INDEX i_link_text ON link(text); + +-- ----------------------------------------------------------------------------- +-- Create the `person` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE person( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + email VARCHAR(255), + link_id BIGINT REFERENCES link(id) +); +CREATE INDEX i_person_name ON person(name); +CREATE INDEX i_person_email ON person(email); +CREATE INDEX i_person_link_id ON person(link_id); + +-- ----------------------------------------------------------------------------- +-- Create the `copyright` table. Is bound to one metadata object. +-- ----------------------------------------------------------------------------- +CREATE TABLE copyright( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + author VARCHAR(255) NOT NULL, + year INT, + license VARCHAR(255) +); +CREATE INDEX i_copyright_author ON copyright(author); +CREATE INDEX i_copyright_year ON copyright(year); +CREATE INDEX i_copyright_license ON copyright(license); + +-- ----------------------------------------------------------------------------- +-- Create the `bounce` table. Is bound to one metadata object. +-- ----------------------------------------------------------------------------- +CREATE TABLE bounds( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + minlat DOUBLE PRECISION NOT NULL, + minlon DOUBLE PRECISION NOT NULL, + maxlat DOUBLE PRECISION NOT NULL, + maxlon DOUBLE PRECISION NOT NULL +); + +-- ----------------------------------------------------------------------------- +-- Create the `metadata` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE metadata( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255), + dscr TEXT, + time TIMESTAMP, + keywords VARCHAR(255), + person_id BIGINT REFERENCES person(id), + copyright_id BIGINT REFERENCES copyright(id), + bounds_id BIGINT REFERENCES bounds(id) +); +CREATE INDEX i_metadata_name ON metadata(name); +CREATE INDEX i_metadata_time ON metadata(time); +CREATE INDEX i_metadata_keywords ON metadata(keywords); +CREATE INDEX i_metadata_person_id ON metadata(person_id); +CREATE INDEX i_metadata_copyright_id ON metadata(copyright_id); +CREATE INDEX i_metadata_bounds_id ON metadata(bounds_id); + +CREATE TABLE metadata_link( + metadata_id BIGINT NOT NULL REFERENCES metadata(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_metadata_link_metadata_id_link_id UNIQUE (metadata_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `way_point` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE way_point( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + lat DOUBLE PRECISION NOT NULL, + lon DOUBLE PRECISION NOT NULL, + ele DOUBLE PRECISION, + speed DOUBLE PRECISION, + time DATETIME, + magvar DOUBLE PRECISION, + geoidheight DOUBLE PRECISION, + name VARCHAR(255), + cmt VARCHAR(255), + dscr TEXT, + src VARCHAR(255), + sym VARCHAR(255), + type VARCHAR(255), + fix VARCHAR(10), + sat INT, + hdop DOUBLE PRECISION, + vdop DOUBLE PRECISION, + pdop DOUBLE PRECISION, + ageofdgpsdata INT, + dgpsid INT, + course DOUBLE PRECISION, + extensions TEXT +); +CREATE INDEX i_way_point_lat ON way_point(lat); +CREATE INDEX i_way_point_lon ON way_point(lon); +CREATE INDEX i_way_point_ele ON way_point(ele); +CREATE INDEX i_way_point_time ON way_point(time); + +CREATE TABLE way_point_link( + way_point_id BIGINT NOT NULL REFERENCES way_point(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_way_point_link_way_point_id_link_id UNIQUE (way_point_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `route` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE route( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255), + cmt VARCHAR(255), + dscr TEXT, + src VARCHAR(255), + number INT, + type VARCHAR(255) +); +CREATE INDEX i_route_name ON route(name); + +CREATE TABLE route_link( + route_id BIGINT NOT NULL REFERENCES route(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_route_link_route_id_link_id UNIQUE (route_id, link_id) +); + +CREATE TABLE route_way_point( + route_id BIGINT NOT NULL REFERENCES route(id) ON DELETE CASCADE, + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_route_way_point_way_point_id UNIQUE (way_point_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `track_segment` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE track_segment( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + number INT NOT NULL +); +CREATE INDEX i_track_segment_number ON track_segment(number); + +CREATE TABLE track_segment_way_point( + track_segment_id BIGINT NOT NULL REFERENCES track_segment(id) ON DELETE CASCADE, + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_track_segment_way_point_track_segment_id_way_point_id + UNIQUE (track_segment_id, way_point_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `track` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE track( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255), + cmt VARCHAR(255), + dscr TEXT, + src VARCHAR(255), + number INT, + type VARCHAR(255) +); +CREATE INDEX i_track_name ON track(name); + +CREATE TABLE track_track_segment( + track_id BIGINT NOT NULL REFERENCES track(id), + track_segment_id BIGINT NOT NULL REFERENCES track_segment(id), + + CONSTRAINT c_track_track_segment_track_id_track_segment_id + UNIQUE (track_segment_id, track_id) +); + +CREATE TABLE track_link( + track_id BIGINT NOT NULL REFERENCES track(id), + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_track_link_track_id_link_id UNIQUE (track_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `gpx` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE gpx( + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + version VARCHAR(5) NOT NULL DEFAULT '1.1', + creator VARCHAR(255) NOT NULL, + metadata_id BIGINT REFERENCES metadata(id) +); +CREATE INDEX i_gpx_creator ON gpx(creator); +CREATE INDEX i_gpx_metadata_id ON gpx(metadata_id); + +CREATE TABLE gpx_way_point( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_gpx_way_point_gpx_id_way_point_id UNIQUE (gpx_id, way_point_id) +); + +CREATE TABLE gpx_route( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + route_id BIGINT NOT NULL REFERENCES route(id), + + CONSTRAINT c_gpx_track_gpx_id_route_id UNIQUE (gpx_id, route_id) +); + +CREATE TABLE gpx_track( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + track_id BIGINT NOT NULL REFERENCES track(id), + + CONSTRAINT c_gpx_track_gpx_id_track_id UNIQUE (gpx_id, track_id) +); + diff --git a/jpx.jdbc/src/main/resources/model-psql.sql b/jpx.jdbc/src/main/resources/model-psql.sql new file mode 100644 index 00000000..79760043 --- /dev/null +++ b/jpx.jdbc/src/main/resources/model-psql.sql @@ -0,0 +1,234 @@ +-- ----------------------------------------------------------------------------- +-- Create the `link` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE link( + id BIGSERIAL NOT NULL PRIMARY KEY, + href TEXT NOT NULL, + text TEXT, + type TEXT +); +CREATE INDEX i_link_href ON link(href); +CREATE INDEX i_link_text ON link(text); + +-- ----------------------------------------------------------------------------- +-- Create the `person` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE person( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + email TEXT, + link_id BIGINT REFERENCES link(id) +); +CREATE INDEX i_person_name ON person(name); +CREATE INDEX i_person_email ON person(email); +CREATE INDEX i_person_link_id ON person(link_id); + +-- ----------------------------------------------------------------------------- +-- Create the `copyright` table. Is bound to one metadata object. +-- ----------------------------------------------------------------------------- +CREATE TABLE copyright( + id BIGSERIAL NOT NULL PRIMARY KEY, + author TEXT NOT NULL, + copyright_year SMALLINT, + license TEXT +); +CREATE INDEX i_copyright_author ON copyright(author); +CREATE INDEX i_copyright_year ON copyright(copyright_year); +CREATE INDEX i_copyright_license ON copyright(license); + +-- ----------------------------------------------------------------------------- +-- Create the `bounce` table. Is bound to one metadata object. +-- ----------------------------------------------------------------------------- +CREATE TABLE bounds( + id BIGSERIAL NOT NULL PRIMARY KEY, + minlat NUMERIC(12, 9) NOT NULL, + minlon NUMERIC(12, 9) NOT NULL, + maxlat NUMERIC(12, 9) NOT NULL, + maxlon NUMERIC(12, 9) NOT NULL +); + +-- ----------------------------------------------------------------------------- +-- Create the `metadata` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE metadata( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT, + dscr TEXT, + person_id BIGINT REFERENCES person(id), + copyright_id BIGINT REFERENCES copyright(id), + time TIMESTAMP WITH TIME ZONE, + keywords TEXT, + bounds_id BIGINT REFERENCES bounds(id), + extensions TEXT +); +CREATE INDEX i_metadata_name ON metadata(name); +CREATE INDEX i_metadata_time ON metadata(time); +CREATE INDEX i_metadata_keywords ON metadata(keywords); +CREATE INDEX i_metadata_person_id ON metadata(person_id); +CREATE INDEX i_metadata_copyright_id ON metadata(copyright_id); +CREATE INDEX i_metadata_bounds_id ON metadata(bounds_id); + +CREATE TABLE metadata_link( + metadata_id BIGINT NOT NULL REFERENCES metadata(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_metadata_link_metadata_id_link_id UNIQUE (metadata_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `way_point` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE way_point( + id BIGSERIAL NOT NULL PRIMARY KEY, + lat NUMERIC(12, 9) NOT NULL, + lon NUMERIC(12, 9) NOT NULL, + ele NUMERIC(8, 2), + speed NUMERIC(11, 2), + time TIMESTAMP WITH TIME ZONE, + magvar NUMERIC(8, 5), + geoidheight NUMERIC(8, 2), + name TEXT, + cmt TEXT, + dscr TEXT, + src TEXT, + sym TEXT, + type TEXT, + fix VARCHAR(4), + sat INT, + hdop NUMERIC(12, 2), + vdop NUMERIC(12, 2), + pdop NUMERIC(12, 2), + ageofdgpsdata INT, + dgpsid INT, + course NUMERIC(6, 3), + extensions TEXT +); +CREATE INDEX i_way_point_lat ON way_point(lat); +CREATE INDEX i_way_point_lon ON way_point(lon); +CREATE INDEX i_way_point_ele ON way_point(ele); +CREATE INDEX i_way_point_time ON way_point(time); + +CREATE TABLE way_point_link( + way_point_id BIGINT NOT NULL REFERENCES way_point(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_way_point_link_way_point_id_link_id UNIQUE (way_point_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `route` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE route( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT, + cmt TEXT, + dscr TEXT, + src TEXT, + number INT, + type TEXT, + extensions TEXT +); +CREATE INDEX i_route_name ON route(name); +CREATE INDEX i_route_src ON route(src); +CREATE INDEX i_route_number ON route(number); +CREATE INDEX i_route_type ON route(type); + +CREATE TABLE route_link( + route_id BIGINT NOT NULL REFERENCES route(id) ON DELETE CASCADE, + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_route_link_route_id_link_id UNIQUE (route_id, link_id) +); + +CREATE TABLE route_way_point( + route_id BIGINT NOT NULL REFERENCES route(id) ON DELETE CASCADE, + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_route_way_point_way_point_id UNIQUE (way_point_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `track_segment` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE track_segment( + id BIGSERIAL NOT NULL PRIMARY KEY, + number INT NOT NULL, + extensions TEXT +); +CREATE INDEX i_track_segment_number ON track_segment(number); + +CREATE TABLE track_segment_way_point( + track_segment_id BIGINT NOT NULL REFERENCES track_segment(id) ON DELETE CASCADE, + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_track_segment_way_point_track_segment_id_way_point_id + UNIQUE (track_segment_id, way_point_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `track` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE track( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT, + cmt TEXT, + dscr TEXT, + src TEXT, + number INT, + type TEXT, + extensions TEXT +); +CREATE INDEX i_track_name ON track(name); +CREATE INDEX i_track_src ON track(src); +CREATE INDEX i_track_number ON track(number); +CREATE INDEX i_track_type ON track(type); + +CREATE TABLE track_track_segment( + track_id BIGINT NOT NULL REFERENCES track(id), + track_segment_id BIGINT NOT NULL REFERENCES track_segment(id), + + CONSTRAINT c_track_track_segment_track_id_track_segment_id + UNIQUE (track_segment_id, track_id) +); + +CREATE TABLE track_link( + track_id BIGINT NOT NULL REFERENCES track(id), + link_id BIGINT NOT NULL REFERENCES link(id), + + CONSTRAINT c_track_link_track_id_link_id UNIQUE (track_id, link_id) +); + +-- ----------------------------------------------------------------------------- +-- Create the `gpx` table. +-- ----------------------------------------------------------------------------- +CREATE TABLE gpx( + id BIGSERIAL NOT NULL PRIMARY KEY, + version VARCHAR(5) NOT NULL DEFAULT '1.1', + creator TEXT NOT NULL, + metadata_id BIGINT REFERENCES metadata(id), + extensions TEXT +); +CREATE INDEX i_gpx_creator ON gpx(creator); +CREATE INDEX i_gpx_metadata_id ON gpx(metadata_id); + +CREATE TABLE gpx_way_point( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + way_point_id BIGINT NOT NULL REFERENCES way_point(id), + + CONSTRAINT c_gpx_way_point_gpx_id_way_point_id UNIQUE (gpx_id, way_point_id) +); + +CREATE TABLE gpx_route( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + route_id BIGINT NOT NULL REFERENCES route(id), + + CONSTRAINT c_gpx_track_gpx_id_route_id UNIQUE (gpx_id, route_id) +); + +CREATE TABLE gpx_track( + gpx_id BIGINT NOT NULL REFERENCES gpx(id), + track_id BIGINT NOT NULL REFERENCES track(id), + + CONSTRAINT c_gpx_track_gpx_id_track_id UNIQUE (gpx_id, track_id) +); + diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DAOTestBase.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DAOTestBase.java new file mode 100644 index 00000000..1d8dd079 --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DAOTestBase.java @@ -0,0 +1,56 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Statement; + +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; + +/** + * @author Franz Wilhelmstötter + */ +public abstract class DAOTestBase { + + public DB db = H2DB.newTestInstance(); + + @BeforeClass + public void setup() throws IOException, SQLException { + final String[] queries = IO. + toSQLText(getClass().getResourceAsStream("/model-mysql.sql")) + .split(";"); + + db.transaction(conn -> { + for (String query : queries) { + try (Statement stmt = conn.createStatement()) { + stmt.execute(query); + } + } + }); + } + + @AfterClass + public void shutdown() throws SQLException { + db.close(); + } + +} diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DB.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DB.java new file mode 100644 index 00000000..8ade443e --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/DB.java @@ -0,0 +1,85 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Test DB abstraction. + * + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public abstract class DB { + + @FunctionalInterface + public interface Executable { + public T execute(final Connection conn) throws SQLException; + } + + @FunctionalInterface + public interface Callable { + public void call(final Connection conn) throws SQLException; + } + + public abstract Connection getConnection() throws SQLException; + + public T transaction( + final Connection connection, + final Executable executable + ) + throws SQLException + { + try { + if (connection.getAutoCommit()) { + connection.setAutoCommit(false); + } + + final T result = executable.execute(connection); + connection.commit(); + return result; + } catch (Throwable e) { + try { + connection.rollback(); + } catch (Exception suppressed) { + e.addSuppressed(suppressed); + } + throw e; + } + } + + public T transaction(final Executable executable) throws SQLException { + try (Connection conn = getConnection()) { + return transaction(conn, executable); + } + } + + public void transaction(final Callable callable) throws SQLException { + try (Connection conn = getConnection()) { + transaction(conn, c -> {callable.call(c); return null;}); + } + } + + public void close() throws SQLException { + } + +} diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/GPXAccessTest.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/GPXAccessTest.java new file mode 100644 index 00000000..e03433be --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/GPXAccessTest.java @@ -0,0 +1,160 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static java.lang.String.format; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.GPX.Reader.Mode; +import io.jenetics.jpx.GPX.Version; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public class GPXAccessTest { + + private final Random random = new Random(1231321); + private final GPX gpx = GPX.builder().build(); //GPXTest.nextGPX(random); + + //@BeforeClass + public void setup() throws IOException, SQLException { + final String[] queries = IO. + toSQLText(getClass().getResourceAsStream("/model-psql.sql")) + .split(";"); + + PSQLDB.INSTANCE.transaction(conn -> { + for (String query : queries) { + if (!query.trim().isEmpty()) { + try (Statement stmt = conn.createStatement()) { + stmt.execute(query); + } + } + } + }); + } + + //@Test + public void list() throws IOException { + final String dir = "/home/fwilhelm/Workspace/Documents/GPS/Split"; + + Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile( + final Path file, + final BasicFileAttributes attrs + ) + throws IOException + { + if (!Files.isDirectory(file)) { + System.out.println(file); + } + return FileVisitResult.CONTINUE; + } + }); + } + + //@Test + public void insert() throws SQLException, IOException { + final String dir = "/home/fwilhelm/Workspace/Documents/GPS"; + + Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile( + final Path file, + final BasicFileAttributes attrs + ) + throws IOException + { + if (!Files.isDirectory(file) && + file.toString().endsWith(".gpx") && + !file.toString().contains("Raw")) + { + final GPX gpx = GPX.Reader.of(Version.V10, Mode.LENIENT).read(file); + +// final Path export = Paths.get( +// "/home/fwilhelm/Downloads/gpx/", +// gpx.getMetadata().flatMap(Metadata::getName).orElse("null") + ".gpx" +// ); +// +// GPX.writer(" ").write(gpx, export); + + System.out.println("Inserting: " + file); + + long start = System.currentTimeMillis(); + try { + PSQLDB.INSTANCE.transaction(conn -> { + final Long id = GPXAccess.insert(gpx, conn); + }); + } catch (SQLException e) { + throw new IOException(e); + } + long stop = System.currentTimeMillis(); + System.out.println(format("%s: %s s", file, (stop - start)/1000.0)); + } + return FileVisitResult.CONTINUE; + } + }); + } + +// private static GPX fix(final GPX gpx) { +// final Person author = Person.of( +// "Franz Wilhelmstötter", +// Email.of("franz.wilhelmstoetter@gmail.com") +// ); +// final Copyright copyright = Copyright.of("Franz Wilhelmstötter"); +// final Bounds bounds = gpx.tracks() +// .flatMap(Track::segments) +// .flatMap(TrackSegment::points) +// .collect(Bounds.toBounds()); +// +// final ZonedDateTime time = gpx.tracks() +// .flatMap(Track::segments) +// .flatMap(TrackSegment::points) +// .flatMap(wp -> wp.getTime().map(Stream::of).orElse(Stream.empty())) +// .min(Comparator.naturalOrder()) +// .orElse(null); +// +// +// return gpx.toBuilder() +// .version(Version.V11) +// .creator("JPX - https://github.com/jenetics/jpx") +// .metadata(md -> md +// .name(format("tracks-%s", time != null ? time.toLocalDate() : null)) +// .author(author) +// .copyright(copyright) +// .bounds(bounds) +// .time(time)) +// .build(); +// } + +} diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/H2DB.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/H2DB.java new file mode 100644 index 00000000..d6670dac --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/H2DB.java @@ -0,0 +1,91 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Random; + +import javax.sql.DataSource; + +import org.h2.jdbcx.JdbcDataSource; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public class H2DB extends DB { + + public static final DB INSTANCE = new H2DB("jdbc:h2:mem:testdb-gpx;MODE=PostgreSQL"); + + private final DataSource _dataSource; + private Connection _connection; + + public H2DB(final DataSource dataSource) { + _dataSource = requireNonNull(dataSource); + } + + public H2DB(final String url) { + this(ds(url)); + } + + private static DataSource ds(final String url) { + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL(url); + return ds; + } + + @Override + public Connection getConnection() throws SQLException { + if (_connection == null) { + _connection = _dataSource.getConnection(); + } + + return _connection; + } + + @Override + public T transaction(final Executable executable) throws SQLException { + return transaction(getConnection(), executable); + } + + @Override + public void transaction(final Callable callable) throws SQLException { + transaction(getConnection(), c -> {callable.call(c); return null;}); + } + + public static DB newTestInstance() { + final String name = format("testdb_%s", Math.abs(new Random().nextLong())); + final String url = format("jdbc:h2:mem:%s;MODE=MySQL", name); + return new H2DB(url); + } + + @Override + public void close() throws SQLException { + if (_connection != null) { + _connection.close(); + } + } + +} diff --git a/buildSrc/src/main/java/io/jenetics/gradle/ColorizerTask.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/IO.java similarity index 51% rename from buildSrc/src/main/java/io/jenetics/gradle/ColorizerTask.java rename to jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/IO.java index 1b0c8e45..ce9fd251 100644 --- a/buildSrc/src/main/java/io/jenetics/gradle/ColorizerTask.java +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/IO.java @@ -17,46 +17,38 @@ * Author: * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) */ -package io.jenetics.gradle; +package io.jenetics.jpx.jdbc; -import java.io.File; +import java.io.BufferedReader; import java.io.IOException; - -import org.gradle.api.DefaultTask; -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.TaskAction; -import org.gradle.api.tasks.TaskExecutionException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; /** * @author Franz Wilhelmstötter - * @since 1.4 - * @version 6.1 + * @version !__version__! + * @since !__version__! */ -public class ColorizerTask extends DefaultTask { - - private File _directory; - - @InputFile - public File getDirectory() { - return _directory; +public class IO { + private IO() { } - public void setDirectory(final File directory) { - _directory = directory; - } - - @TaskAction - public void colorize() { - try { - final Colorizer colorizer = new Colorizer(_directory); - colorizer.colorize(); - - getLogger().lifecycle( - "Colorizer processed {} files and modified {}.", - colorizer.getProcessed(), colorizer.getModified() - ); - } catch (final IOException e) { - throw new TaskExecutionException(this, e); + public static String toSQLText(final InputStream in) throws IOException { + try(Reader r = new InputStreamReader(in); + BufferedReader br = new BufferedReader(r)) + { + final StringBuilder builder = new StringBuilder(); + + String line; + while ((line = br.readLine()) != null) { + if (!line.trim().startsWith("--")) { + builder.append(line); + builder.append("\n"); + } + } + + return builder.toString(); } } diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/LinkAccessTest.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/LinkAccessTest.java new file mode 100644 index 00000000..b64616a6 --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/LinkAccessTest.java @@ -0,0 +1,92 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import io.jenetics.facilejdbc.Batch; +import io.jenetics.facilejdbc.Query; +import io.jenetics.facilejdbc.Stored; + +import io.jenetics.jpx.Link; + +/** + * @author Franz Wilhelmstötter + */ +public class LinkAccessTest { + + private static final List LINKS = List.of( + Link.of("http://jenetics.io"), + Link.of("http://jenetics.io", "Jenetics", "web"), + Link.of("https://duckduckgo.com", "DuckDuckGo", "search") + ); + + @BeforeClass + public void setup() throws IOException, SQLException { + final String[] queries = IO. + toSQLText(getClass().getResourceAsStream("/model-psql.sql")) + .split(";"); + + H2DB.INSTANCE.transaction(conn -> { + for (String query : queries) { + if (!query.trim().isEmpty()) { + try (Statement stmt = conn.createStatement()) { + stmt.execute(query.trim()); + } + } + } + }); + } + + @Test + public void insert() throws SQLException { + H2DB.INSTANCE.transaction(conn -> { + final var query = LinkAccess.INSERT.prepareQuery(conn); + final var batch = Batch.of(LINKS, LinkAccess.DCTOR); + query.execute(batch); + + LinkAccess.INSERT.execute(batch, conn); + }); + } + +// @Test(dependsOnMethods = "insert") +// public void select() throws SQLException { +// final var select = Query.of("SELECT * FROM link ORDER BY id"); +// +// H2DB.INSTANCE.transaction(conn -> { +// final List> links = select +// .as(LinkAccess.PARSER.stored("id").list(), conn); +// +// links.forEach(System.out::println); +// +// assertThat(links.stream().map(Stored::value).toList()) +// .isEqualTo(LINKS); +// }); +// } + +} diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MariaDB.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MariaDB.java new file mode 100644 index 00000000..f61462bb --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MariaDB.java @@ -0,0 +1,43 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public class MariaDB extends DB { + + public final static DB INSTANCE = new MariaDB(); + + private static final String TEST_DB = + "jdbc:mariadb://playstation:3307/gpx_test?user=gpx_test&password=gpx_test"; + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(TEST_DB); + } + +} diff --git a/buildSrc/src/main/java/io/jenetics/gradle/package-info.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MetadataAccessTest.java similarity index 82% rename from buildSrc/src/main/java/io/jenetics/gradle/package-info.java rename to jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MetadataAccessTest.java index 989fa875..07789abf 100644 --- a/buildSrc/src/main/java/io/jenetics/gradle/package-info.java +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/MetadataAccessTest.java @@ -17,10 +17,7 @@ * Author: * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) */ +package io.jenetics.jpx.jdbc; -/** - * @author Franz Wilhelmstötter - * @since 1.4 - * @version 1.4 - */ -package io.jenetics.gradle; +public class MetadataAccessTest { +} diff --git a/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/PSQLDB.java b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/PSQLDB.java new file mode 100644 index 00000000..72841e41 --- /dev/null +++ b/jpx.jdbc/src/test/java/io/jenetics/jpx/jdbc/PSQLDB.java @@ -0,0 +1,43 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * @author Franz Wilhelmstötter + * @version !__version__! + * @since !__version__! + */ +public class PSQLDB extends DB { + + public final static DB INSTANCE = new PSQLDB(); + + private static final String TEST_DB = + "jdbc:postgresql://localhost/gpx_db?user=gpx_usr&password=gpx_pwd"; + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(TEST_DB); + } + +} diff --git a/jpx/build.gradle.kts b/jpx/build.gradle.kts index b4ecd682..aee84d9b 100644 --- a/jpx/build.gradle.kts +++ b/jpx/build.gradle.kts @@ -27,7 +27,6 @@ import io.jenetics.gradle.dsl.moduleName */ plugins { `java-library` - idea `maven-publish` } @@ -36,7 +35,7 @@ description = "JPX - Java GPX (GPS) Library" moduleName = "io.jenetics.jpx" dependencies { - testImplementation(libs.assertj) + testImplementation(libs.assertj.core) testImplementation(libs.equalsverifier) testImplementation(libs.prngine) testImplementation(libs.testng) diff --git a/jpx/src/main/java/io/jenetics/jpx/Bounds.java b/jpx/src/main/java/io/jenetics/jpx/Bounds.java index d717c078..1c811ea0 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Bounds.java +++ b/jpx/src/main/java/io/jenetics/jpx/Bounds.java @@ -141,12 +141,12 @@ public String toString() { * stream. The following example shows how to calculate the bounds of all * track-points of a given GPX object. * - *
{@code
+	 * {@snippet lang="java":
 	 * final Bounds bounds = gpx.tracks()
 	 *     .flatMap(Track::segments)
 	 *     .flatMap(TrackSegment::points)
 	 *     .collect(Bounds.toBounds());
-	 * }
+ * } * * If the collecting way-point stream is empty, the collected {@code Bounds} * object is {@code null}. diff --git a/jpx/src/main/java/io/jenetics/jpx/Degrees.java b/jpx/src/main/java/io/jenetics/jpx/Degrees.java index 6905dcd4..5f12f4a1 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Degrees.java +++ b/jpx/src/main/java/io/jenetics/jpx/Degrees.java @@ -31,7 +31,7 @@ /** * Used for bearing, heading, course. Base unit is decimal degree. Only values - * in the range of {@code [0..360]} are valid. + * in the range of {@code [0..360)} are valid. * * @see Value object * @@ -73,7 +73,7 @@ public final class Degrees * * @param value the decimal degree value * @throws IllegalArgumentException if the give value is not within the - * range of {@code [0..360]} + * range of {@code [0..360)} */ private Degrees(final double value) { if (value < MIN_VALUE || value >= MAX_VALUE) { diff --git a/jpx/src/main/java/io/jenetics/jpx/Filters.java b/jpx/src/main/java/io/jenetics/jpx/Filters.java index 41ba26fe..bdb83835 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Filters.java +++ b/jpx/src/main/java/io/jenetics/jpx/Filters.java @@ -43,7 +43,7 @@ private Filters() { /** * Merges the given segments into one segment containing all way-points. The * order of the way-points is preserved. - *
{@code
+	 * {@snippet lang="java":
 	 * final GPX merged = gpx.toBuilder()
 	 *     .trackFilter()
 	 *         .map(track -> track.toBuilder()
@@ -52,7 +52,7 @@ private Filters() {
 	 *             .build())
 	 *         .build()
 	 *     .build();
-	 * }
+ * } * * @param segments the segment list to merge * @return a list with one segment, containing all way-points of the original @@ -72,14 +72,14 @@ public static List mergeSegments( /** * Merges the given tracks into one track containing all segments. The order * of the segments is preserved. - *
{@code
+	 * {@snippet lang="java":
 	 * final GPX merged = gpx.toBuilder()
 	 *     .trackFilter()
 	 *         .listMap(Filters::mergeTracks)
 	 *         .filter(Track::nonEmpty)
 	 *         .build())
 	 *     .build();
-	 * }
+ * } * * @param tracks the track list to merge * @return a list with one track, containing all segments @@ -103,13 +103,13 @@ public static List mergeTracks(final List tracks) { * Merges all way-points of all segments of the given track list into one * track with one segment, containing all way-points. The order of the * way-points is preserved. - *
{@code
+	 * {@snippet lang="java":
 	 * final GPX merged = gpx.toBuilder()
 	 *     .trackFilter()
 	 *         .listMap(Filters::fullyMergeTracks)
 	 *         .build())
 	 *     .build();
-	 * }
+ * } * * * @param tracks the track list to merge @@ -135,10 +135,10 @@ public static List fullyMergeTracks(final List tracks) { * Return a new {@code GPX} object with all empty elements (tracks, * track-segments, routes and metadata) removed. This method can be used * to clean up the GPX object before writing it to file. - *
{@code
+	 * {@snippet lang="java":
 	 * final GPX gpx = ...;
 	 * final GPX.write(Filters.nonEmptyGPX(gpx), "tracks.gpx", "    ");
-	 * }
+ * } * * @param gpx the GPX object to clean up * @return a new {@code GPX} object with all empty elements removed diff --git a/jpx/src/main/java/io/jenetics/jpx/GPX.java b/jpx/src/main/java/io/jenetics/jpx/GPX.java index ac177a13..ec41e9ab 100644 --- a/jpx/src/main/java/io/jenetics/jpx/GPX.java +++ b/jpx/src/main/java/io/jenetics/jpx/GPX.java @@ -77,7 +77,7 @@ * Examples: *

* Creating a GPX object with one track-segment and 3 track-points - *

{@code
+ * {@snippet lang="java":
  * final GPX gpx = GPX.builder()
  *     .addTrack(track -> track
  *         .addSegment(segment -> segment
@@ -85,16 +85,16 @@
  *             .addPoint(p -> p.lat(48.20112).lon(16.31639).ele(278))
  *             .addPoint(p -> p.lat(48.20126).lon(16.31601).ele(274))))
  *     .build();
- * }
+ * } * * Writing a GPX file - *
{@code
+ * {@snippet lang="java":
  * final var indent = new GPX.Writer.Indent("    ");
  * GPX.Writer.of(indent).write(gpx, Path.of("points.gpx"));
- * }
+ * } * * This will produce the following output. - *
{@code
+ * {@snippet lang="java":
  * 
  *     
  *         
@@ -110,20 +110,20 @@
  *         
  *     
  * 
- * }
+ * } * * Reading a GPX file - *
{@code
+ * {@snippet lang="java":
  * final GPX gpx = GPX.read("points.xml");
- * }
+ * } * * Reading erroneous GPX files - *
{@code
+ * {@snippet lang="java":
  * final GPX gpx = GPX.Reader.of(GPX.Reader.Mode.LENIENT).read("track.xml");
- * }
+ * } * - * This allows reading otherwise invalid GPX files, like - *
{@code
+ * This allows to read otherwise invalid GPX files, like
+ * {@snippet lang="java":
  * 
  * 
  *   
@@ -155,10 +155,10 @@
  *     
  *   
  * 
- * }
+ * } * * which is read as (if you write it again) - *
{@code
+ * {@snippet lang="java":
  * 
  * 
  *     
@@ -190,10 +190,10 @@
  *         
  *     
  * 
- * }
+ * } * * Converting a GPX object to an XML {@link Document} - *
{@code
+ * {@snippet lang="java":
  * final GPX gpx = ...;
  *
  * final Document doc = XMLProvider.provider()
@@ -203,7 +203,7 @@
  *
  * // The GPX data are written to the empty `doc` object.
  * GPX.Writer.DEFAULT.write(gpx, new DOMResult(doc));
- * }
+ * } * * @author Franz Wilhelmstötter * @version 2.0 @@ -418,11 +418,11 @@ public Stream tracks() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -486,7 +486,7 @@ public boolean equals(final Object obj) { * Builder class for creating immutable {@code GPX} objects. *

* Creating a GPX object with one track-segment and 3 track-points: - *

{@code
+	 * {@snippet lang="java":
 	 * final GPX gpx = GPX.builder()
 	 *     .addTrack(track -> track
 	 *         .addSegment(segment -> segment
@@ -494,7 +494,7 @@ public boolean equals(final Object obj) {
 	 *             .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
 	 *             .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
 	 *     .build();
-	 * }
+ * } */ public static final class Builder { private String _creator; @@ -572,12 +572,12 @@ public Builder metadata(final Metadata metadata) { /** * Allows setting partial metadata without messing up with the * {@link Metadata.Builder} class. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX gpx = GPX.builder()
 		 *     .metadata(md -> md.author("Franz Wilhelmstötter"))
 		 *     .addTrack(...)
 		 *     .build();
-		 * }
+ * } * * @param metadata the metadata consumer * @return {@code this} {@code Builder} for method chaining @@ -634,11 +634,11 @@ public Builder addWayPoint(final WayPoint wayPoint) { /** * Add a way-point to the {@code GPX} object using a * {@link WayPoint.Builder}. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX gpx = GPX.builder()
 		 *     .addWayPoint(wp -> wp.lat(23.6).lon(13.5).ele(50))
 		 *     .build();
-		 * }
+ * } * * @param wayPoint the way-point to add, configured by the way-point * builder @@ -689,13 +689,13 @@ public Builder addRoute(final Route route) { /** * Add a route the {@code GPX} object. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX gpx = GPX.builder()
 		 *     .addRoute(route -> route
 		 *         .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
 		 *         .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161)))
 		 *     .build();
-		 * }
+ * } * * @param route the route to add, configured by the route builder * @return {@code this} {@code Builder} for method chaining @@ -745,7 +745,7 @@ public Builder addTrack(final Track track) { /** * Add a track the {@code GPX} object. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX gpx = GPX.builder()
 		 *     .addTrack(track -> track
 		 *         .addSegment(segment -> segment
@@ -753,7 +753,7 @@ public Builder addTrack(final Track track) {
 		 *             .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
 		 *             .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
 		 *     .build();
-		 * }
+ * } * * @param track the track to add, configured by the track builder * @return {@code this} {@code Builder} for method chaining @@ -780,11 +780,11 @@ public List tracks() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * @@ -828,13 +828,13 @@ public GPX build() { /** * Return a new {@link WayPoint} filter. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX filtered = gpx.toBuilder()
 		 *     .wayPointFilter()
 		 *         .filter(wp -> wp.getTime().isPresent())
 		 *         .build())
 		 *     .build();
-		 * }
+ * } * * @since 1.1 * @@ -900,13 +900,13 @@ public Builder build() { /** * Return a new {@link Route} filter. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX filtered = gpx.toBuilder()
 		 *     .routeFilter()
 		 *         .filter(Route::nonEmpty)
 		 *         .build())
 		 *     .build();
-		 * }
+ * } * * @since 1.1 * @@ -975,7 +975,7 @@ public Builder build() { /** * Return a new {@link Track} filter. - *
{@code
+		 * {@snippet lang="java":
 		 * final GPX merged = gpx.toBuilder()
 		 *     .trackFilter()
 		 *         .map(track -> track.toBuilder()
@@ -984,7 +984,7 @@ public Builder build() {
 		 *             .build())
 		 *         .build()
 		 *     .build();
-		 * }
+ * } * * @since 1.1 * @@ -1461,7 +1461,7 @@ public int maximumFractionDigits() { *

* The following example shows how to create an XML-Document from a * given {@code GPX} object. - *

{@code
+		 * {@snippet lang="java":
 		 * final GPX gpx = ...;
 		 *
 		 * final Document doc = XMLProvider.provider()
@@ -1471,7 +1471,7 @@ public int maximumFractionDigits() {
 		 *
 		 * // The GPX data are written to the empty `doc` object.
 		 * GPX.Writer.DEFAULT.write(gpx, new DOMResult(doc));
-		 * }
+ * } * * @since 3.0 * @@ -1625,13 +1625,13 @@ byte[] toByteArray(final GPX gpx) { * {@link WayPoint#getLongitude()}, ... *

* The example below shows the lat and lon values with - * maximal five fractional digits. - *

{@code
+		 * maximal 5 fractional digits.
+		 * {@snippet lang="java":
 		 * 
 		 *     1.2
 		 *     
 		 * 
-		 * }
+ * } * * The following table should give you a feeling about the accuracy of a * given fraction digits count, at the equator. @@ -2068,9 +2068,9 @@ private static GPX toGPXv10(final Object[] v) { * Writes the given {@code gpx} object (in GPX XML format) to the given * {@code path}. * This method is a shortcut for - *
{@code
+	 * {@snippet lang="java":
 	 * GPX.Writer.DEFAULT.write(gpx, path);
-	 * }
+ * } * * @see Writer * @@ -2088,9 +2088,9 @@ public static void write(final GPX gpx, final Path path) throws IOException { /** * Read a GPX object from the given {@code input} stream. * This method is a shortcut for - *
{@code
+	 * {@snippet lang="java":
 	 * GPX.Reader.DEFAULT.read(path);
-	 * }
+ * } * * @see Reader * diff --git a/jpx/src/main/java/io/jenetics/jpx/Length.java b/jpx/src/main/java/io/jenetics/jpx/Length.java index 52cb10c7..c9c9bef4 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Length.java +++ b/jpx/src/main/java/io/jenetics/jpx/Length.java @@ -112,9 +112,9 @@ public enum Unit { * length value of {@code this} length unit. The given example converts 3 * inches into yards. * - *
{@code
+		 * {@snippet lang="java":
 		 * final double yards = YARD.convert(3, INCH);
-		 * }
+ * } * * @param length the length value * @param sourceUnit the source length unit diff --git a/jpx/src/main/java/io/jenetics/jpx/Longitude.java b/jpx/src/main/java/io/jenetics/jpx/Longitude.java index 7d561f65..cec66395 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Longitude.java +++ b/jpx/src/main/java/io/jenetics/jpx/Longitude.java @@ -31,7 +31,7 @@ /** * The longitude of the point. Decimal degrees, WGS84 datum, which must be within - * the range of {@code [-180..180]}. + * the range of {@code [-180..180)}. * * @author Franz Wilhelmstötter * @version 2.0 @@ -81,7 +81,7 @@ public final class Longitude extends Number implements Serializable { * * @param value the longitude value in decimal degrees * @throws IllegalArgumentException if the given value is not within the - * range of {@code [-180..180]} + * range of {@code [-180..180)} */ private Longitude(final double value) { if (value < MIN_DEGREES || value > MAX_DEGREES) { diff --git a/jpx/src/main/java/io/jenetics/jpx/Metadata.java b/jpx/src/main/java/io/jenetics/jpx/Metadata.java index 96e696e7..15d098c4 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Metadata.java +++ b/jpx/src/main/java/io/jenetics/jpx/Metadata.java @@ -45,12 +45,12 @@ * files allows others to search for and use your GPS data. *

* Creating a GPX object with one track-segment and 3 track-points: - *

{@code
+ * {@snippet lang="java":
  * final Metadata gpx = Metadata.builder()
  *     .author("Franz Wilhelmstötter")
  *     .addLink(Link.of("http://jenetics.io"))
  *     .build();
- * }
+ * } * * @author Franz Wilhelmstötter * @version 3.0 @@ -188,11 +188,11 @@ public Optional getBounds() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -286,12 +286,12 @@ public boolean equals(final Object obj) { * Builder class for creating immutable {@code Metadata} objects. *

* Creating a GPX object with one track-segment and 3 track-points: - *

{@code
+	 * {@snippet lang="java":
 	 * final Metadata gpx = Metadata.builder()
 	 *     .author("Franz Wilhelmstötter")
 	 *     .addLink(Link.of("http://jenetics.io"))
 	 *     .build();
-	 * }
+ * } */ public static final class Builder { private String _name; @@ -555,11 +555,11 @@ public Optional bounds() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * diff --git a/jpx/src/main/java/io/jenetics/jpx/Person.java b/jpx/src/main/java/io/jenetics/jpx/Person.java index 257739f0..a65b66db 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Person.java +++ b/jpx/src/main/java/io/jenetics/jpx/Person.java @@ -125,7 +125,7 @@ public boolean equals(final Object obj) { @Override public String toString() { - return Objects.toString(_name); + return _name + ":" + _email + ":" + _link; } /* ************************************************************************* diff --git a/jpx/src/main/java/io/jenetics/jpx/Route.java b/jpx/src/main/java/io/jenetics/jpx/Route.java index 67ad122a..dff2df36 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Route.java +++ b/jpx/src/main/java/io/jenetics/jpx/Route.java @@ -53,7 +53,7 @@ * turn points leading to a destination. *

* Create a new route via the builder: - *

{@code
+ * {@snippet lang="java":
  * final Route route = Route.builder()
  *     .name("Route 1")
  *     .description("Fancy mountain-bike tour.")
@@ -61,7 +61,7 @@
  *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
  *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
  *     .build();
- * }
+ * } * * @author Franz Wilhelmstötter * @version 1.5 @@ -203,11 +203,11 @@ public Stream points() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -320,7 +320,7 @@ public static Builder builder() { /** * Builder class for building {@code Route} objects. - *
{@code
+	 * {@snippet lang="java":
 	 * final Route route = Route.builder()
 	 *     .name("Route 1")
 	 *     .description("Fancy mountain-bike tour.")
@@ -328,7 +328,7 @@ public static Builder builder() {
 	 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
 	 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
 	 *     .build();
-	 * }
+ * } */ public static final class Builder implements Filter { @@ -544,11 +544,11 @@ public Optional type() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * diff --git a/jpx/src/main/java/io/jenetics/jpx/Speed.java b/jpx/src/main/java/io/jenetics/jpx/Speed.java index 999fea06..186102a6 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Speed.java +++ b/jpx/src/main/java/io/jenetics/jpx/Speed.java @@ -89,9 +89,9 @@ public enum Unit { * speed value of {@code this} speed unit. The given example converts 3 * knots into kilometers per hour. * - *
{@code
+		 * {@snippet lang="java":
 		 * final double kilometersPerHour = KILOMETERS_PER_HOUR.convert(3, KNOTS);
-		 * }
+ * } * * @param speed the speed value * @param sourceUnit the source speed unit diff --git a/jpx/src/main/java/io/jenetics/jpx/Track.java b/jpx/src/main/java/io/jenetics/jpx/Track.java index 3c11cf51..613a0136 100644 --- a/jpx/src/main/java/io/jenetics/jpx/Track.java +++ b/jpx/src/main/java/io/jenetics/jpx/Track.java @@ -52,7 +52,7 @@ * Represents a GPX track - an ordered list of points describing a path. *

* Creating a Track object with one track-segment and 3 track-points: - *

{@code
+ * {@snippet lang="java":
  * final Track track = Track.builder()
  *     .name("Track 1")
  *     .description("Mountain bike tour.")
@@ -65,7 +65,7 @@
  *         .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
  *         .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
  *     .build();
- * }
+ * } * * @author Franz Wilhelmstötter * @version 1.5 @@ -192,11 +192,11 @@ public Optional getType() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -317,7 +317,7 @@ public String toString() { * Builder class for creating immutable {@code Track} objects. *

* Creating a Track object with one track-segment and 3 track-points: - *

{@code
+	 * {@snippet lang="java":
 	 * final Track track = Track.builder()
 	 *     .name("Track 1")
 	 *     .description("Mountain bike tour.")
@@ -330,7 +330,7 @@ public String toString() {
 	 *         .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
 	 *         .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
 	 *     .build();
-	 * }
+ * } */ public static final class Builder implements Filter { private String _name; @@ -536,11 +536,11 @@ public Optional type() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * @@ -592,7 +592,7 @@ public Builder addSegment(final TrackSegment segment) { /** * Add a track segment to the track, via the given builder. - *
{@code
+		 * {@snippet lang="java":
 		 * final Track track = Track.builder()
 		 *     .name("Track 1")
 		 *     .description("Mountain bike tour.")
@@ -605,7 +605,7 @@ public Builder addSegment(final TrackSegment segment) {
 		 *         .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
 		 *         .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
 		 *     .build();
-		 * }
+ * } * * @param segment the track segment * @return {@code this} {@code Builder} for method chaining diff --git a/jpx/src/main/java/io/jenetics/jpx/TrackSegment.java b/jpx/src/main/java/io/jenetics/jpx/TrackSegment.java index 5138d5e7..ce12f479 100644 --- a/jpx/src/main/java/io/jenetics/jpx/TrackSegment.java +++ b/jpx/src/main/java/io/jenetics/jpx/TrackSegment.java @@ -101,11 +101,11 @@ public Iterator iterator() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -178,13 +178,13 @@ public String toString() { * Builder class for creating immutable {@code TrackSegment} objects. *

* Creating a {@code TrackSegment} object with 3 track-points: - *

{@code
+	 * {@snippet lang="java":
 	 * final TrackSegment segment = TrackSegment.builder()
 	 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
 	 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
 	 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
 	 *     .build();
-	 * }
+ * } */ public static final class Builder implements Filter { private final List _points = new ArrayList<>(); @@ -223,13 +223,13 @@ public Builder addPoint(final WayPoint point) { * Add a way-point to the track-segment, via the given way-point builder. *

* Creating a {@code TrackSegment} object with 3 track-points: - *

{@code
+		 * {@snippet lang="java":
 		 * final TrackSegment segment = TrackSegment.builder()
 		 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
 		 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
 		 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
 		 *     .build();
-		 * }
+ * } * * @param point the segment way-point builder * @return {@code this} {@code Builder} for method chaining @@ -255,11 +255,11 @@ public List points() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * diff --git a/jpx/src/main/java/io/jenetics/jpx/WayPoint.java b/jpx/src/main/java/io/jenetics/jpx/WayPoint.java index 008382ca..ac4a8218 100644 --- a/jpx/src/main/java/io/jenetics/jpx/WayPoint.java +++ b/jpx/src/main/java/io/jenetics/jpx/WayPoint.java @@ -53,11 +53,11 @@ * feature on a map. *

* Creating a {@code WayPoint}: - *

{@code
+ * {@snippet lang="java":
  * final WayPoint point = WayPoint.builder()
  *     .lat(48.2081743).lon(16.3738189).ele(160)
  *     .build();
- * }
+ * } * * @author Franz Wilhelmstötter * @version 3.0 @@ -118,7 +118,7 @@ public final class WayPoint implements Point, Serializable { * @param source source of data. Included to give user some idea of * reliability and accuracy of data. "Garmin eTrex", "USGS quad * Boston North", e.g. (optional) - * @param links links to additional information about the way-point. May be + * @param links links to additional information about the way-point. Maybe * empty, but not {@code null}. * @param symbol text of GPS symbol name. For interchange with other * programs, use the exact spelling of the symbol as displayed on the @@ -385,11 +385,11 @@ public Optional getCourse() { /** * Return the (cloned) extensions document. The root element of the returned * document has the name {@code extensions}. - *
{@code
+	 * {@snippet lang="java":
 	 * 
 	 *     ...
 	 * 
-	 * }
+ * } * * @since 1.5 * @@ -507,11 +507,11 @@ public String toString() { * Builder for creating a way-point with different parameters. *

* Creating a {@code WayPoint}: - *

{@code
+	 * {@snippet lang="java":
 	 * final WayPoint point = WayPoint.builder()
 	 *     .lat(48.2081743).lon(16.3738189).ele(160)
 	 *     .build();
-	 * }
+ * } * * @see #builder() */ @@ -1259,11 +1259,11 @@ public Optional course() { /** * Sets the extensions object, which may be {@code null}. The root * element of the extensions document must be {@code extensions}. - *
{@code
+		 * {@snippet lang="java":
 		 * 
 		 *     ...
 		 * 
-		 * }
+ * } * * @since 1.5 * @@ -1771,8 +1771,8 @@ public static WayPoint of( public static WayPoint of(final Point point) { requireNonNull(point); - return point instanceof WayPoint - ? (WayPoint)point + return point instanceof WayPoint wp + ? wp : of( point.getLatitude(), point.getLongitude(), diff --git a/jpx/src/main/java/io/jenetics/jpx/XMLReader.java b/jpx/src/main/java/io/jenetics/jpx/XMLReader.java index 88877e94..a4cd4448 100644 --- a/jpx/src/main/java/io/jenetics/jpx/XMLReader.java +++ b/jpx/src/main/java/io/jenetics/jpx/XMLReader.java @@ -100,13 +100,13 @@ enum Type { /** * Read the given type from the underlying XML stream {@code reader}. * - *
{@code
+	 * {@snippet lang="java":
 	 * try (AutoCloseableXMLStreamReader xml = XML.reader(in)) {
 	 *     // Move XML stream to first element.
 	 *     xml.next();
 	 *     return reader.read(xml);
 	 * }
-	 * }
+ * } * * @param xml the underlying XML stream {@code reader} * @param lenient lenient read mode @@ -201,17 +201,17 @@ public String toString() { * Return a {@code Reader} for reading an attribute of an element. *

* XML - *

 {@code }
+ *
 {@code }
 	 *
 	 * Reader definition
-	 * 
{@code
+	 * {@snippet lang="java":
 	 * final Reader reader =
 	 *     elem(
 	 *         v -> (Integer)v[0],
 	 *         "element",
 	 *         attr("length").map(Integer::parseInt)
 	 *     );
-	 * }
+ * } * * @param name the attribute name * @return an attribute reader @@ -225,17 +225,17 @@ public static XMLReader attr(final String name) { * Return a {@code Reader} for reading the text of an element. *

* XML - *

 {@code 1234}
+ *
 {@code 1234}
 	 *
 	 * Reader definition
-	 * 
{@code
+	 * {@snippet lang="java":
 	 * final Reader reader =
 	 *     elem(
 	 *         v -> (Integer)v[0],
 	 *         "element",
 	 *         text().map(Integer::parseInt)
 	 *     );
-	 * }
+ * } * * @return an element text reader */ @@ -249,10 +249,10 @@ public static XMLReader text() { * *

* XML - *

 {@code 1234}
+ *
 {@code 1234}
 	 *
 	 * Reader definition
-	 * 
{@code
+	 * {@snippet lang="java":
 	 * final XMLReader reader =
 	 *     elem(
 	 *         v -> {
@@ -264,7 +264,7 @@ public static XMLReader text() {
 	 *         attr("name"),
 	 *         text().map(Integer::parseInt)
 	 *     );
-	 * }
+ * } * * @param generator the generator function, which build the result object * from the given parameter array @@ -294,10 +294,10 @@ public static XMLReader elem( * the given parent element {@code name}. *

* XML - *

 {@code 1234}
+ *
 {@code 1234}
 	 *
 	 * Reader definition
-	 * 
{@code
+	 * {@snippet lang="java":
 	 * final XMLReader reader =
 	 *     elem("min",
 	 *         elem(
@@ -311,7 +311,7 @@ public static  XMLReader elem(
 	 *             text().map(Integer::parseInt)
 	 *         )
 	 *     );
-	 * }
+ * } * * @param name the parent element name * @param reader the child elements reader @@ -356,17 +356,17 @@ public static XMLReader ignore(final String name) { * -957346595 * -88668137 * - * } + * } * * Reader definition - *
{@code
+	 * {@snippet lang="java":
 	 * XMLReader> reader =
 	 *     elem(
 	 *         v -> (List)v[0],
 	 *         "properties",
 	 *         elems(elem("property", text().map(Integer::parseInt)))
 	 *     );
-	 * }
+ * } * * @param reader the child element reader * @param the element type diff --git a/jpx/src/main/java/io/jenetics/jpx/XMLWriter.java b/jpx/src/main/java/io/jenetics/jpx/XMLWriter.java index 3027a76f..679e4161 100644 --- a/jpx/src/main/java/io/jenetics/jpx/XMLWriter.java +++ b/jpx/src/main/java/io/jenetics/jpx/XMLWriter.java @@ -110,9 +110,9 @@ void write(final XMLStreamWriter xml, final T data) * Writes the attribute with the given {@code name} to the current * outer element. * - *
{@code
+	 * {@snippet lang="java":
 	 * final XMLWriter writer1 = elem("element", attr("attribute"));
-	 * }
+ * } * * @param name the attribute name * @param the writer base type diff --git a/jpx/src/main/java/io/jenetics/jpx/format/CompositeFormat.java b/jpx/src/main/java/io/jenetics/jpx/format/CompositeFormat.java index 1160e2fe..c29bba1a 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/CompositeFormat.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/CompositeFormat.java @@ -26,10 +26,10 @@ /** * @author Franz Wilhelmstötter - * @version 2.2 + * @version 4.0 * @since 1.4 */ -class CompositeFormat implements Format { +final class CompositeFormat implements Format { private final List _formats; diff --git a/jpx/src/main/java/io/jenetics/jpx/format/Elevation.java b/jpx/src/main/java/io/jenetics/jpx/format/Elevation.java index 8217d5f8..04975c26 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/Elevation.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/Elevation.java @@ -23,7 +23,7 @@ /** * This field allows accessing the elevation (in meter) of a given location. * - * @version 2.2 + * @version 4.0 * @since 2.2 */ final class Elevation extends Field { diff --git a/jpx/src/main/java/io/jenetics/jpx/format/Field.java b/jpx/src/main/java/io/jenetics/jpx/format/Field.java index fb4a4b77..f65a2dc3 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/Field.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/Field.java @@ -34,10 +34,20 @@ * elevation. * * @author Franz Wilhelmstötter - * @version 3.0 + * @version 4.0 * @since 1.4 */ -abstract class Field implements Format { +abstract sealed class Field + implements Format + permits + Elevation, + LatitudeDegree, + LatitudeMinute, + LatitudeSecond, + LongitudeDegree, + LongitudeMinute, + LongitudeSecond +{ private static final DecimalFormatSymbols SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US); diff --git a/jpx/src/main/java/io/jenetics/jpx/format/Format.java b/jpx/src/main/java/io/jenetics/jpx/format/Format.java index 3464b9d7..817b3c24 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/Format.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/Format.java @@ -26,10 +26,19 @@ * Base interface for formatting and parsing a location. * * @author Franz Wilhelmstötter - * @version 2.2 + * @version 4.0 * @since 1.4 */ -interface Format { +sealed interface Format + permits + CompositeFormat, + ConstFormat, + Field, + LatitudeNS, + LongitudeEW, + OptionalFormat, + Plus +{ /** * Formats the given {@code value} to its string representation. If it is not diff --git a/jpx/src/main/java/io/jenetics/jpx/format/Location.java b/jpx/src/main/java/io/jenetics/jpx/format/Location.java index 5f0a652f..db30c627 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/Location.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/Location.java @@ -127,7 +127,7 @@ public String toString() { * Create a new location form the given GPS point. * * @param point the GPS point - * @return a new location forms the given GPS point + * @return a new location from the given GPS point * @throws NullPointerException if the given {@code point} is {@code null} */ public static Location of(final Point point) { diff --git a/jpx/src/main/java/io/jenetics/jpx/format/LocationFormatter.java b/jpx/src/main/java/io/jenetics/jpx/format/LocationFormatter.java index 14779d00..b6def30a 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/LocationFormatter.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/LocationFormatter.java @@ -650,52 +650,67 @@ private void validate(){ Elevation E = null; for (var format : _formats) { - if (format instanceof LatitudeDegree ld) { - if (D == null) { - D = ld; - } else { - throw iae("Only one 'D' pattern allowed."); + switch (format) { + case LatitudeDegree latd -> { + if (D == null) { + D = latd; + } else { + throw iae("Only one 'D' pattern allowed."); + } + } + case LatitudeMinute latm -> { + if (M == null) { + M = latm; + } else { + throw iae("Only one 'M' pattern allowed."); + } } - } else if (format instanceof LatitudeMinute lm) { - if (M == null) { - M = lm; - } else { - throw iae("Only one 'M' pattern allowed."); + case LatitudeSecond lats -> { + if (S == null) { + S = lats; + } else { + throw iae("Only one 'S' pattern allowed."); + } + } + case LatitudeNS latns -> { + if (X == null) { + X = (LatitudeNS)format; + } } - } else if (format instanceof LatitudeSecond ls) { - if (S == null) { - S = ls; - } else { - throw iae("Only one 'S' pattern allowed."); + case LongitudeDegree lond -> { + if (d == null) { + d = lond; + } else { + throw iae("Only one 'd' pattern allowed."); + } } - } else if (format instanceof LatitudeNS && X==null) { - X = (LatitudeNS)format; - } else if (format instanceof LongitudeDegree ld) { - if (d == null) { - d = ld; - } else { - throw iae("Only one 'd' pattern allowed."); + case LongitudeMinute lonm -> { + if (m == null) { + m = lonm; + } else { + throw iae("Only one 'm' pattern allowed."); + } } - } else if (format instanceof LongitudeMinute lm) { - if (m == null) { - m = lm; - } else { - throw iae("Only one 'm' pattern allowed."); + case LongitudeSecond lons -> { + if (s == null) { + s = lons; + } else { + throw iae("Only one 's' pattern allowed."); + } } - } else if (format instanceof LongitudeSecond ls) { - if (s == null) { - s = ls; - } else { - throw iae("Only one 's' pattern allowed."); + case LongitudeEW lonew -> { + if (x == null) { + x = lonew; + } } - } else if (format instanceof LongitudeEW lew && x == null) { - x = lew; - } else if (format instanceof Elevation ele) { - if (E == null) { - E = ele; - } else { - throw iae("Only one 'E' pattern allowed."); + case Elevation ele -> { + if (E == null) { + E = ele; + } else { + throw iae("Only one 'E' pattern allowed."); + } } + default -> { } } } diff --git a/jpx/src/main/java/io/jenetics/jpx/format/LongitudeDegree.java b/jpx/src/main/java/io/jenetics/jpx/format/LongitudeDegree.java index 4086dce1..b1c260c9 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/LongitudeDegree.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/LongitudeDegree.java @@ -29,10 +29,10 @@ * rounded, on the assumption that the fractional part will be represented by * minutes and seconds. * - * @version 2.2 + * @version 4.0 * @since 2.2 */ -class LongitudeDegree extends Field { +final class LongitudeDegree extends Field { LongitudeDegree(final String pattern) { super(pattern, 'd'); diff --git a/jpx/src/main/java/io/jenetics/jpx/format/OptionalFormat.java b/jpx/src/main/java/io/jenetics/jpx/format/OptionalFormat.java index c9ed4aa0..f6586ee3 100644 --- a/jpx/src/main/java/io/jenetics/jpx/format/OptionalFormat.java +++ b/jpx/src/main/java/io/jenetics/jpx/format/OptionalFormat.java @@ -27,10 +27,10 @@ /** * @author Franz Wilhelmstötter - * @version 2.2 + * @version 4.0 * @since 1.4 */ -class OptionalFormat implements Format { +final class OptionalFormat implements Format { private final Format _format; diff --git a/jpx/src/main/java/io/jenetics/jpx/geom/Ellipsoid.java b/jpx/src/main/java/io/jenetics/jpx/geom/Ellipsoid.java index eeddc3a8..ceecb662 100644 --- a/jpx/src/main/java/io/jenetics/jpx/geom/Ellipsoid.java +++ b/jpx/src/main/java/io/jenetics/jpx/geom/Ellipsoid.java @@ -21,7 +21,6 @@ import static java.util.Objects.requireNonNull; -import java.io.Serial; import java.io.Serializable; /** @@ -33,14 +32,18 @@ * @see Earth ellipsoid * @see Geoid * + * @param name the name of the earth ellipsoid model + * @param A the equatorial radius, in meter + * @param B the polar radius, in meter + * @param F the inverse flattening + * * @author Franz Wilhelmstötter - * @version 1.0 + * @version 4.0 * @since 1.0 */ -public final class Ellipsoid implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; +public record Ellipsoid(String name, double A, double B, double F) + implements Serializable +{ /** * The ellipsoid of the World Geodetic System: WGS 84 @@ -48,7 +51,7 @@ public final class Ellipsoid implements Serializable { * @see * WGS-84 */ - public static final Ellipsoid WGS84 = of( + public static final Ellipsoid WGS84 = new Ellipsoid( "WGS-84", 6_378_137, 6_356_752.314245, @@ -61,7 +64,7 @@ public final class Ellipsoid implements Serializable { * * @see IERS-89 */ - public static final Ellipsoid IERS_1989 = of( + public static final Ellipsoid IERS_1989 = new Ellipsoid( "IERS-1989", 6_378_136, 6_356_751.302, @@ -74,7 +77,7 @@ public final class Ellipsoid implements Serializable { * * @see IERS-89 */ - public static final Ellipsoid IERS_2003 = of( + public static final Ellipsoid IERS_2003 = new Ellipsoid( "IERS-2003", 6_378_136.6, 6_356_751.9, @@ -86,84 +89,17 @@ public final class Ellipsoid implements Serializable { */ public static final Ellipsoid DEFAULT = WGS84; - private final String _name; - private final double _a; - private final double _b; - private final double _f; - /** * Create a new earth ellipsoid with the given parameters. * * @param name the name of the earth ellipsoid model - * @param a the equatorial radius, in meter - * @param b the polar radius, in meter - * @param f the inverse flattening + * @param A the equatorial radius, in meter + * @param B the polar radius, in meter + * @param F the inverse flattening * @throws NullPointerException if the given {@code name} is {@code null} */ - private Ellipsoid( - final String name, - final double a, - final double b, - final double f - ) { - _name = requireNonNull(name); - _a = a; - _b = b; - _f = f; - } - - /** - * Return the name of the earth ellipsoid model. - * - * @return the name of the earth ellipsoid model - */ - public String getName() { - return _name; - } - - /** - * Return the equatorial radius, in meter. - * - * @return the equatorial radius, in meter - */ - public double A() { - return _a; - } - - /** - * Return the polar radius, in meter. - * - * @return the polar radius, in meter - */ - public double B() { - return _b; - } - - /** - * Return the inverse flattening. - * - * @return the inverse flattening - */ - public double F() { - return _f; - } - - /** - * Create a new earth ellipsoid with the given parameters. - * - * @param name the name of the earth ellipsoid model - * @param a the equatorial radius, in meter - * @param b the polar radius, in meter - * @param f the inverse flattening - * @return a new earth ellipsoid with the given parameters - */ - public static Ellipsoid of( - final String name, - final double a, - final double b, - final double f - ) { - return new Ellipsoid(name, a, b, f); + public Ellipsoid { + requireNonNull(name); } } diff --git a/jpx/src/main/java/io/jenetics/jpx/geom/Geoid.java b/jpx/src/main/java/io/jenetics/jpx/geom/Geoid.java index 766779fc..78729fe0 100644 --- a/jpx/src/main/java/io/jenetics/jpx/geom/Geoid.java +++ b/jpx/src/main/java/io/jenetics/jpx/geom/Geoid.java @@ -31,6 +31,7 @@ import static java.util.Objects.requireNonNull; import static io.jenetics.jpx.geom.MathUtils.equal; +import java.io.Serializable; import java.util.stream.Collector; import io.jenetics.jpx.Length; @@ -43,11 +44,13 @@ * @see Wikipedia: Geoid * @see Ellipsoid * + * @param ellipsoid the ellipsoid used by the geoid + * * @author Franz Wilhelmstötter - * @version 1.0 + * @version 4.0 * @since 1.0 */ -public final class Geoid { +public record Geoid(Ellipsoid ellipsoid) implements Serializable { private static final int EPSILON_ULP = 10_000; @@ -57,7 +60,7 @@ public final class Geoid { * @see * WGS-84 */ - public static final Geoid WGS84 = of(Ellipsoid.WGS84); + public static final Geoid WGS84 = new Geoid(Ellipsoid.WGS84); /** * {@link Geoid} using the International Earth Rotation and Reference @@ -65,7 +68,7 @@ public final class Geoid { * * @see IERS-89 */ - public static final Geoid IERS_1989 = of(Ellipsoid.IERS_1989); + public static final Geoid IERS_1989 = new Geoid(Ellipsoid.IERS_1989); /** * {@link Geoid} using the International Earth Rotation and Reference @@ -73,22 +76,12 @@ public final class Geoid { * * @see IERS-89 */ - public static final Geoid IERS_2003 = of(Ellipsoid.IERS_2003); + public static final Geoid IERS_2003 = new Geoid(Ellipsoid.IERS_2003); /** * {@link Geoid} using the {@link Ellipsoid#DEFAULT} ellipsoid. */ - public static final Geoid DEFAULT = of(Ellipsoid.DEFAULT); - - private final Ellipsoid _ellipsoid; - - // Minor semi-axes of the ellipsoid. - private final double B; - - private final double AABBBB; - - // Flattening (A - B)/A - private final double F; + public static final Geoid DEFAULT = new Geoid(Ellipsoid.DEFAULT); // The maximal iteration of the 'distance' private static final int DISTANCE_ITERATION_MAX = 1000; @@ -102,26 +95,8 @@ public final class Geoid { * @param ellipsoid the ellipsoid used by the geoid * @throws NullPointerException if the given {@code ellipsoid} is {@code null} */ - private Geoid(final Ellipsoid ellipsoid) { - _ellipsoid = requireNonNull(ellipsoid); - - final double a = ellipsoid.A(); - final double aa = a*a; - - B = ellipsoid.B(); - final double bb = B*B; - - AABBBB = (aa - bb)/bb; - F = 1.0/ellipsoid.F(); - } - - /** - * Return the ellipsoid the {@code Geom} object is using. - * - * @return the ellipsoid the {@code Geom} object is using - */ - public Ellipsoid ellipsoid() { - return _ellipsoid; + public Geoid { + requireNonNull(ellipsoid); } /** @@ -145,6 +120,14 @@ public Ellipsoid ellipsoid() { * which is the case for a point and its (near) antidote. */ public Length distance(final Point start, final Point end) { + final double aa = ellipsoid.A()*ellipsoid.A(); + + final double B = ellipsoid.B(); + final double bb = B*B; + + final double AABB_BB = (aa - bb)/bb; + final double F = 1.0/ellipsoid.F(); + final double lat1 = start.getLatitude().toRadians(); final double lon1 = start.getLongitude().toRadians(); final double lat2 = end.getLatitude().toRadians(); @@ -211,7 +194,7 @@ public Length distance(final Point start, final Point end) { final double cos2sigmam = equal(cos2alpha, 0.0, EPSILON_ULP) ? 0.0 : cossigma - 2*sinU1sinU2/cos2alpha; - final double u2 = cos2alpha*AABBBB; + final double u2 = cos2alpha*AABB_BB; final double cos2sigmam2 = cos2sigmam*cos2sigmam; @@ -253,13 +236,12 @@ public Length distance(final Point start, final Point end) { /** * Return a collector which calculates the length of the (open) path which * is defined by the {@code Point} stream. - * - *
{@code
+	 * {@snippet lang="java":
 	 * final Length length = gpx.tracks()
 	 *     .flatMap(Track::segments)
 	 *     .flatMap(TrackSegment::points)
 	 *     .collect(Geoid.WGSC_84.toPathLength());
-	 * }
+ * } * * The returned {@code Collector} doesn't work for parallel * stream. Using it for a parallel point stream will throw an @@ -282,13 +264,12 @@ public Length distance(final Point start, final Point end) { * Return a collector which calculates the length of the (closed) tour which * is defined by the {@code Point} stream. The tour length * additionally adds the distance of the last point back to the first point. - * - *
{@code
+	 * {@snippet lang="java":
 	 * final Length length = gpx.tracks()
 	 *     .flatMap(Track::segments)
 	 *     .flatMap(TrackSegment::points)
 	 *     .collect(Geoid.WGSC_84.toTourLength());
-	 * }
+ * } * * The returned {@code Collector} doesn't work for parallel * stream. Using it for a parallel point stream will throw an @@ -307,17 +288,4 @@ public Length distance(final Point start, final Point end) { ); } - - - /** - * Create a new {@code Geoid} object with the given ellipsoid. - * - * @param ellipsoid the ellipsoid used by the geoid - * @return a new {@code Geoid} object with the given ellipsoid - * @throws NullPointerException if the given {@code ellipsoid} is {@code null} - */ - public static Geoid of(final Ellipsoid ellipsoid) { - return new Geoid(ellipsoid); - } - } diff --git a/jpx/src/main/java/module-info.java b/jpx/src/main/java/module-info.java index b3bce315..3a92d48a 100644 --- a/jpx/src/main/java/module-info.java +++ b/jpx/src/main/java/module-info.java @@ -30,7 +30,7 @@ * Examples: *

* Creating a GPX object with one track-segment and 3 track-points - *

{@code
+ * {@snippet lang="java":
  * final GPX gpx = GPX.builder()
  *     .addTrack(track -> track
  *         .addSegment(segment -> segment
@@ -38,16 +38,16 @@
  *             .addPoint(p -> p.lat(48.20112).lon(16.31639).ele(278))
  *             .addPoint(p -> p.lat(48.20126).lon(16.31601).ele(274))))
  *     .build();
- * }
+ * } * * Writing a GPX file - *
{@code
+ * {@snippet lang="java":
  * final var indent = new GPX.Writer.Indent("    ");
  * GPX.Writer.of(indent).write(gpx, Path.of("points.gpx"));
- * }
+ * } * * This will produce the following output. - *
{@code
+ * {@snippet lang="java":
  * 
  *     
  *         
@@ -63,22 +63,20 @@
  *         
  *     
  * 
- * }
+ * } * * Reading a GPX file - *
{@code
- * final GPX gpx = GPX.read(Path.of("points.gpx"));
- * }
+ * {@snippet lang="java": + * final GPX gpx = GPX.read("points.xml"); + * } * * Reading erroneous GPX files - *
{@code
- * final GPX gpx = GPX.Reader
- *     .of(GPX.Reader.Mode.LENIENT)
- *     .read(Path.of("points.gpx"));
- * }
+ * {@snippet lang="java": + * final GPX gpx = GPX.Reader.of(GPX.Reader.Mode.LENIENT).read("track.xml"); + * } * - * This allows reading otherwise invalid GPX files, like - *
{@code
+ * This allows to read otherwise invalid GPX files, like
+ * {@snippet lang="java":
  * 
  * 
  *   
@@ -110,10 +108,10 @@
  *     
  *   
  * 
- * }
+ * } * * which is read as (if you write it again) - *
{@code
+ * {@snippet lang="java":
  * 
  * 
  *     
@@ -145,10 +143,10 @@
  *         
  *     
  * 
- * }
+ * } * * Converting a GPX object to an XML {@link org.w3c.dom.Document} - *
{@code
+ * {@snippet lang="java":
  * final GPX gpx = ...;
  *
  * final Document doc = XMLProvider.provider()
@@ -158,7 +156,7 @@
  *
  * // The GPX data are written to the empty `doc` object.
  * GPX.Writer.DEFAULT.write(gpx, new DOMResult(doc));
- * }
+ * } */ module io.jenetics.jpx { requires transitive java.xml; diff --git a/jpx/src/test/java/io/jenetics/jpx/FiltersTest.java b/jpx/src/test/java/io/jenetics/jpx/FiltersTest.java index 4e74ed08..e3a680b3 100644 --- a/jpx/src/test/java/io/jenetics/jpx/FiltersTest.java +++ b/jpx/src/test/java/io/jenetics/jpx/FiltersTest.java @@ -62,9 +62,9 @@ public void mergeTracks() { final GPX merged = gpx.toBuilder() .trackFilter() - .listMap(Filters::mergeTracks) - .filter(Track::nonEmpty) - .build() + .listMap(Filters::mergeTracks) + .filter(Track::nonEmpty) + .build() .build(); Assert.assertEquals(merged.getTracks().size(), 1); diff --git a/jpx/src/test/java/io/jenetics/jpx/GPXTest.java b/jpx/src/test/java/io/jenetics/jpx/GPXTest.java index d08cfb1b..a94edcaf 100644 --- a/jpx/src/test/java/io/jenetics/jpx/GPXTest.java +++ b/jpx/src/test/java/io/jenetics/jpx/GPXTest.java @@ -57,6 +57,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.w3c.dom.Document; +import org.w3c.dom.NodeList; import io.jenetics.jpx.GPX.Reader.Mode; import io.jenetics.jpx.GPX.Version; @@ -86,7 +87,7 @@ protected Params params(final Version version, final Random random) { ); } - static GPX nextGPX(final Random random) { + public static GPX nextGPX(final Random random) { return GPX.of( Version.V11, format("creator_%s", random.nextInt(100)), diff --git a/jpx/src/test/java/io/jenetics/jpx/LinkTest.java b/jpx/src/test/java/io/jenetics/jpx/LinkTest.java index 15dc7a23..85387292 100644 --- a/jpx/src/test/java/io/jenetics/jpx/LinkTest.java +++ b/jpx/src/test/java/io/jenetics/jpx/LinkTest.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.Random; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.testng.annotations.Test; @@ -39,7 +41,7 @@ public class LinkTest extends XMLStreamTestBase { @Override - public Supplier factory(Random random) { + public Supplier factory(final Random random) { return () -> nextLink(random); } diff --git a/jpx/src/test/java/io/jenetics/jpx/PersonTest.java b/jpx/src/test/java/io/jenetics/jpx/PersonTest.java index 89c168ca..19966222 100644 --- a/jpx/src/test/java/io/jenetics/jpx/PersonTest.java +++ b/jpx/src/test/java/io/jenetics/jpx/PersonTest.java @@ -23,6 +23,8 @@ import nl.jqno.equalsverifier.EqualsVerifier; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import java.util.function.Supplier; @@ -50,6 +52,15 @@ protected Params params(final Version version, final Random random) { ); } + public static List nextPersons(final Random random) { + final List persons = new ArrayList<>(); + for (int i = 0, n = random.nextInt(20); i < n; ++i) { + persons.add(PersonTest.nextPerson(random)); + } + + return persons; + } + public static Person nextPerson(final Random random) { return Person.of( random.nextBoolean() diff --git a/jpx/src/test/java/io/jenetics/jpx/tool/Normalizer.java b/jpx/src/test/java/io/jenetics/jpx/tool/Normalizer.java new file mode 100644 index 00000000..d5db4534 --- /dev/null +++ b/jpx/src/test/java/io/jenetics/jpx/tool/Normalizer.java @@ -0,0 +1,210 @@ +package io.jenetics.jpx.tool; + +import static java.time.ZoneId.systemDefault; +import static java.time.ZoneOffset.UTC; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.groupingBy; +import static io.jenetics.jpx.Bounds.toBounds; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.jenetics.jpx.Bounds; +import io.jenetics.jpx.Copyright; +import io.jenetics.jpx.Email; +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.GPX.Writer.Indent; +import io.jenetics.jpx.Metadata; +import io.jenetics.jpx.Person; +import io.jenetics.jpx.Track; +import io.jenetics.jpx.TrackSegment; +import io.jenetics.jpx.WayPoint; + +public class Normalizer { + + private static final + Collector>> + TRACK_GROUPS = groupingBy( + p -> p.getTime() + .map(i -> LocalDate.ofInstant(i, UTC)) + .orElse(LocalDate.MIN) + ); + + public static void main(final String[] args) throws Exception { + final Path gpxDir = Paths.get("/home/fwilhelm/Downloads/gpx/"); + final Path outputDir = Paths.get("/home/fwilhelm/Downloads/normalized/"); + + Files.walkFileTree(gpxDir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile( + final Path file, + final BasicFileAttributes attrs + ) + throws IOException + { + if (!Files.isDirectory(file) && file.toString().endsWith(".gpx")) { + normalize(file, outputDir); + } + return FileVisitResult.CONTINUE; + } + }); + } + + private static void normalize(final Path file, final Path dir) throws IOException { + System.out.println("Normalizing: " + file); + + final GPX gpx = GPX.Reader.of(GPX.Version.V11, GPX.Reader.Mode.LENIENT) + .read(file); + + final Map> split = split(gpx); + + final List normalized = split.values().stream() + .flatMap(Normalizer::errorFilter) + .flatMap(points -> toGPX(points).stream()) + .collect(Collectors.toList()); + + write(dir, normalized); + } + + private static void write(final Path dir, final List gpxs) + throws IOException + { + for (GPX gpx : gpxs) { + final Instant time = gpx.getMetadata() + .flatMap(Metadata::getTime) + .orElse(Instant.MAX); + + final Path file = Paths.get( + dir.toString(), + String.valueOf(LocalDate.ofInstant(time, UTC).getYear()), + fileName(gpx) + ); + if (!Files.exists(file.getParent())) { + Files.createDirectories(file.getParent() ); + } + + System.out.println("Writing " + file); + + GPX.Writer.of(new Indent(" ")).write(gpx, file); + setFileTime(file, LocalDateTime.ofInstant(time, systemDefault())); + //writeNative(file, gpx); + } + } + + private static void writeNative(final Path file, final Object gpx) throws IOException { + final Path f = Paths.get(file.toString() + ".bin"); + + try (FileOutputStream bout = new FileOutputStream(f.toFile()); + ObjectOutputStream oout = new ObjectOutputStream(bout)) + { + oout.writeObject(gpx); + } + } + + private static void setFileTime(final Path path, final LocalDateTime time) + throws IOException + { + final BasicFileAttributeView attr = Files.getFileAttributeView( + path, + BasicFileAttributeView.class + ); + final FileTime ft = FileTime.fromMillis( + time.toInstant(UTC).toEpochMilli() + ); + attr.setTimes(ft, ft, ft); + } + + private static Map> split(final GPX gpx) { + return gpx.tracks() + .flatMap(Track::segments) + .flatMap(TrackSegment::points) + .collect(TRACK_GROUPS); + } + + private static Stream> errorFilter(final List points) { + final List filtered = points.stream() + .filter(PointFilter.FAULTY_POINTS) + .collect(Collectors.toList()); + + return filtered.size() >= 10 + ? Stream.of(filtered) + : Stream.empty(); + } + + private static Optional toGPX(final List points) { + final Optional track = points.stream() + .collect(Tracks.toTrack(Duration.ofMinutes(5), 10)); + + return track.map(t -> normalizeMetadata( + GPX.builder() + .tracks(singletonList(t)) + .build() + )); + } + + private static GPX normalizeMetadata(final GPX gpx) { + final Instant time = gpx.tracks() + .flatMap(Track::segments) + .flatMap(TrackSegment::points) + .flatMap(wp -> wp.getTime().stream()) + .min(Comparator.naturalOrder()) + .orElse(Instant.now()); + + final Person author = Person.of( + "Franz Wilhelmstötter", + Email.of("franz.wilhelmstoetter@gmail.com") + ); + + final Copyright copyright = Copyright.of( + "Franz Wilhelmstötter", + LocalDate.ofInstant(time, UTC).getYear() + ); + + final Bounds bounds = gpx.tracks() + .flatMap(Track::segments) + .flatMap(TrackSegment::points) + .collect(toBounds()); + + final String name = LocalDate.ofInstant(time, UTC) + ".gpx"; + + return gpx.toBuilder() + .version(GPX.Version.V11) + .metadata(md -> md + .name(name) + .author(author) + .copyright(copyright) + .bounds(bounds) + .time(time)) + .build(); + } + + private static String fileName(final GPX gpx) { + return gpx.getMetadata() + .flatMap(Metadata::getTime) + .map(i -> LocalDate.ofInstant(i, UTC)) + .map(Objects::toString) + .orElse("" + System.currentTimeMillis()) + ".gpx"; + } + +} diff --git a/jpx/src/test/java/io/jenetics/jpx/tool/PointFilter.java b/jpx/src/test/java/io/jenetics/jpx/tool/PointFilter.java new file mode 100644 index 00000000..0bcc903f --- /dev/null +++ b/jpx/src/test/java/io/jenetics/jpx/tool/PointFilter.java @@ -0,0 +1,103 @@ +package io.jenetics.jpx.tool; + +import static java.util.Objects.requireNonNull; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +import io.jenetics.jpx.Length; +import io.jenetics.jpx.Speed; +import io.jenetics.jpx.WayPoint; + +final class PointFilter implements Predicate { + + public static Predicate + time(final Instant min, final Instant max) { + return predicate(min, max, WayPoint::getTime); + } + + public static Predicate + elevation(final Length min, final Length max) { + return predicate(min, max, WayPoint::getElevation); + } + + public static Predicate speed(final Speed min, final Speed max) { + return predicate(min, max, WayPoint::getSpeed); + } + + private static > Predicate + predicate(final C min, final C max, final Function> f) { + return wp -> { + final Optional value = f.apply(wp); + return value + .map(v -> min.compareTo(v) <= 0 && max.compareTo(v) >= 0) + .orElse(true); + }; + } + + static final Predicate FAULTY_POINTS = + time( + ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()).toInstant(), + Instant.now()) + .and( + speed( + Speed.of(0, Speed.Unit.METERS_PER_SECOND), + Speed.of(300, Speed.Unit.METERS_PER_SECOND))) + .and( + elevation( + Length.of(0.1, Length.Unit.METER), + Length.of(10000, Length.Unit.METER))); + + /* + static final Predicate FAULTY_POINTS = new PointFilter( + ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(2100, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()), + 6000, + 300 + ); + */ + + private final Instant _minTime; + private final Instant _maxTime; + private final double _maxElevation; + private final double _maxSpeed; + + PointFilter( + final Instant minTime, + final Instant maxTime, + final double maxElevation, + final double maxSpeed + ) { + _minTime = requireNonNull(minTime); + _maxTime = requireNonNull(maxTime); + _maxElevation = maxElevation; + _maxSpeed = maxSpeed; + } + + @Override + public boolean test(final WayPoint wp) { + final double speed = wp.getSpeed() + .map(Speed::doubleValue) + .orElse(0.0); + + final double ele = wp.getElevation() + .map(Length::doubleValue) + .orElse(0.0); + + final Instant time = wp.getTime() + .orElse(ZonedDateTime.of( + LocalDateTime.MAX, + ZoneId.systemDefault()).toInstant()); + + return speed >= 0 && speed < _maxSpeed && + ele > 0 && ele < _maxElevation && + time.isAfter(_minTime) && + time.isBefore(_maxTime); + } + +} diff --git a/jpx/src/test/java/io/jenetics/jpx/tool/TCX.java b/jpx/src/test/java/io/jenetics/jpx/tool/TCX.java new file mode 100644 index 00000000..d8e6baaf --- /dev/null +++ b/jpx/src/test/java/io/jenetics/jpx/tool/TCX.java @@ -0,0 +1,152 @@ +/* + * Java GPX Library (@__identifier__@). + * Copyright (c) @__year__@ Franz Wilhelmstötter + * + * 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. + * + * Author: + * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) + */ +package io.jenetics.jpx.tool; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION; +import static javax.xml.transform.OutputKeys.VERSION; + +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * @author Franz Wilhelmstötter + */ +public class TCX { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("YYYYMMdd'T'HHmm"); + + public static void main(final String[] args) throws Exception { + final var factory = DocumentBuilderFactory.newInstance(); + final var builder = factory.newDocumentBuilder(); + + final var path = Path.of("/home/fwilhelm/Downloads/sunibiker_2016.04.06_export.tcx"); + final var out = Path.of("/home/fwilhelm/Downloads/out"); + final var doc = parse(Files.readString(path)); + + final var activities = doc.getElementsByTagName("Activity"); + + for (int i = 0; i < activities.getLength(); ++i) { + final var result = builder.newDocument(); + final var root = result.createElement("TrainingCenterDatabase"); + root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + root.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema"); + root.setAttribute("xmlns", "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"); + result.appendChild(root); + + var node = activities.item(i); + node = node.getParentNode().removeChild(node); + result.adoptNode(node); + + final var sport = node.getAttributes().getNamedItem("Sport").getNodeValue(); + final var start = startTime(node); + + final var acts = result.createElement("Activities"); + acts.appendChild(node); + root.appendChild(acts); + + final var file = Path.of( + out.toString(), + format("%s-%s.tcx", DATE_TIME_FORMATTER.format(start), sport) + ); + System.out.println(file); + try (var os = Files.newOutputStream(file)) { + write(result, os); + } + } + } + + private static void write(final Node xml, final OutputStream out) + throws TransformerFactoryConfigurationError, TransformerException + { + Transformer tf = TransformerFactory.newInstance().newTransformer(); + tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + tf.setOutputProperty(OutputKeys.INDENT, "yes"); + + tf.transform(new DOMSource(xml), new StreamResult(out)); + } + + private static LocalDateTime startTime(final Node activity) { + final var nodes = activity.getChildNodes(); + for (int i = 0; i < nodes.getLength(); ++i) { + final var node = nodes.item(i); + if ("Lap".equals(node.getLocalName())) { + return LocalDateTime.parse(node.getAttributes().getNamedItem("StartTime").getNodeValue()); + } + } + return null; + } + + private static Document parse(final String xml) { + try { + final Document doc = DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .newDocument(); + + final ByteArrayInputStream in = new ByteArrayInputStream(xml.getBytes(UTF_8)); + __copy(new StreamSource(in), new DOMResult(doc)); + return doc; + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + private static void __copy(final Source source, final Result sink) + throws XMLStreamException + { + try { + final Transformer transformer = TransformerFactory + .newInstance() + .newTransformer(); + + transformer.setOutputProperty(OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(VERSION, "1.0"); + + transformer.transform(source, sink); + } catch (TransformerException e) { + throw new XMLStreamException(e); + } + } + +} diff --git a/jpx/src/test/java/io/jenetics/jpx/tool/TrackSegments.java b/jpx/src/test/java/io/jenetics/jpx/tool/TrackSegments.java new file mode 100644 index 00000000..4e2cee18 --- /dev/null +++ b/jpx/src/test/java/io/jenetics/jpx/tool/TrackSegments.java @@ -0,0 +1,111 @@ +package io.jenetics.jpx.tool; + +import static java.lang.String.format; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.Track; +import io.jenetics.jpx.TrackSegment; +import io.jenetics.jpx.WayPoint; + +public final class TrackSegments { + private TrackSegments() { + } + + /** + * Return a new collector, which collects a given way point list into a list + * of {@link TrackSegment}s. All way points without a timestamp are filtered + * out. A new segment is created if the timestamp of two consecutive points + * are greater than the give {@code maxGap} duration. Each segment will + * contain at least {@code minSegmentSize} points. + * + * @param maxGap the maximal allowed gap between two points within a + * track segment. If two points exceed the given gap, a new segment + * is created. + * @param minSegmentSize the minimal number of way points a segment must + * consist + * @return a new track segment collector + * @throws NullPointerException if the given {@code maxGap} is {@code null} + * @throws IllegalArgumentException if the {@code maxGap} or + * {@code minSegmentSize} is negative + */ + public static Collector> + toTrackSegments(final Duration maxGap, final int minSegmentSize) { + if (maxGap.isNegative()) { + throw new IllegalArgumentException(format( + "The maximal allowed point gap must not be negative: %s", + maxGap + )); + } + if (minSegmentSize < 1) { + throw new IllegalArgumentException(format( + "The minimal track segment size must be greater 0, but was %d.", + minSegmentSize + )); + } + + return Collectors.collectingAndThen( + Collectors.toList(), + points -> toTrackSegments(points, maxGap, minSegmentSize) + ); + } + + private static List toTrackSegments( + final List points, + final Duration gap, + final int minSegmentSize + ) { + final List wps = points.stream() + .filter(wp -> wp.getTime().isPresent()) + .toList(); + + if (wps.size() < minSegmentSize) { + return List.of(); + } + + final List segments = new ArrayList<>(); + Instant last = wps.get(0).getTime().orElseThrow(); + TrackSegment.Builder segment = TrackSegment.builder(); + + for (final WayPoint point : wps) { + final Instant zdt = point.getTime().orElseThrow(); + + if (last.plusNanos(gap.toNanos()).isAfter(zdt)) { + segment.addPoint(point); + } else { + if (segment.points().size() >= minSegmentSize) { + segments.add(segment.build()); + } + segment = TrackSegment.builder(); + } + + last = zdt; + } + + if (segment.points().size() >= minSegmentSize) { + segments.add(segment.build()); + } + + return segments; + } + + public static void main(String[] args) throws IOException { + final GPX gpx = GPX.Reader.DEFAULT.read("some_file.gpx"); + + final Stream points = gpx.tracks() + .flatMap(Track::segments) + .flatMap(TrackSegment::points); + + final List segments = points + .collect(toTrackSegments(Duration.ofMinutes(1), 10)); + } + +} diff --git a/jpx/src/test/java/io/jenetics/jpx/tool/Tracks.java b/jpx/src/test/java/io/jenetics/jpx/tool/Tracks.java new file mode 100644 index 00000000..05ddf8bc --- /dev/null +++ b/jpx/src/test/java/io/jenetics/jpx/tool/Tracks.java @@ -0,0 +1,100 @@ +package io.jenetics.jpx.tool; + +import static java.lang.String.format; +import static java.time.ZoneOffset.UTC; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import io.jenetics.jpx.Track; +import io.jenetics.jpx.TrackSegment; +import io.jenetics.jpx.WayPoint; + +public final class Tracks { + private Tracks() { + } + + /** + * Return a new collector, which collects a given way point list into an + * optional {@link Track}. All way points without a timestamp are filtered + * out. A new segment is created if the timestamp of two consecutive points + * are greater then the give {@code maxGap} duration. Each segment will + * contain at least {@code minSegmentSize} points. + * + * @param maxGap the maximal allowed gap between two points within a + * track segment. If two points exceed the given gap, a new segment + * is created. + * @param minSegmentSize the minimal number of way points a segment must + * consist + * @return a new track segment collector + * @throws NullPointerException if the given {@code maxGap} is {@code null} + * @throws IllegalArgumentException if the {@code maxGap} or + * {@code minSegmentSize} is negative + */ + public static Collector> + toTrack(final Duration maxGap, final int minSegmentSize) { + if (maxGap.isNegative()) { + throw new IllegalArgumentException(format( + "The maximal allowed point gap must not be negative: %s", + maxGap + )); + } + if (minSegmentSize < 1) { + throw new IllegalArgumentException(format( + "The minimal track segment size must be greater 0, but was %d.", + minSegmentSize + )); + } + + return Collectors.collectingAndThen( + TrackSegments.toTrackSegments(maxGap, minSegmentSize), + Tracks::toTrack + ); + } + + private static Optional toTrack(final List segments) { + if (segments.isEmpty()) { + return Optional.empty(); + } + + final Track.Builder track = Track.builder() + .number(1) + .name("Track 1") + .cmt(trackCmt(segments)) + .segments(segments); + + track.desc(format( + "%d segments; %d track points", + track.segments().size(), + track.segments().stream() + .flatMap(TrackSegment::points) + .count() + )); + + return Optional.of(track.build()); + } + + private static String trackCmt(final List segments) { + final List points = segments.stream() + .flatMap(TrackSegment::points) + .toList(); + + final ZonedDateTime start = points.get(0).getTime() + .map(i -> ZonedDateTime.ofInstant(i, UTC)) + .orElseThrow(); + + final ZonedDateTime end = points.get(points.size() - 1).getTime() + .map(i -> ZonedDateTime.ofInstant(i, UTC)) + .orElseThrow(); + + return format( + "Track[start=%s, end=%s, duration=%s]", + start, end, Duration.between(start, end) + ); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 84a4340f..9473f6fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,21 +30,8 @@ pluginManagement { } } -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - library("assertj", "org.assertj:assertj-core:3.26.3") - library("commons-math", "org.apache.commons:commons-math3:3.6.1") - library("equalsverifier", "nl.jqno.equalsverifier:equalsverifier:3.17.1") - library("guava", "com.google.guava:guava:33.3.1-jre") - library("prngine", "io.jenetics:prngine:2.0.0") - library("rxjava", "io.reactivex.rxjava2:rxjava:2.2.21") - library("testng", "org.testng:testng:7.10.2") - } - } -} - rootProject.name = "jpx" // The JPX projects. include("jpx") +include("jpx.jdbc")