From 50627af4be19c45d9814c9cf2b3849f3a1419dd6 Mon Sep 17 00:00:00 2001 From: ibrahimko Date: Fri, 16 Feb 2024 13:23:23 +0000 Subject: [PATCH] deploy: 8d5d64493ca4e4eda23036393187f03e084e2b5d --- ...9d204507b9bd90c00677937c78d37b04741317.css | 1 + ...743c3d4c0bc6b77f7029d9a73502f96b089860.css | 1 - docs/database-access/index.html | 258 ------------------ docs/design-for-testability/index.html | 252 ----------------- docs/generics/index.html | 192 ------------- docs/index.xml | 5 +- docs/javafx/index.html | 186 ------------- docs/jpms-lambda-streams/index.html | 211 -------------- docs/parameterized-tests/index.html | 213 --------------- docs/reflection/index.html | 25 -- docs/statemachine/index.html | 89 ------ docs/unit-testing-basics/index.html | 12 +- index.html | 12 +- index.xml | 5 +- pages/setup/index.html | 12 +- pages/tips/index.html | 12 +- sitemap.xml | 2 +- 17 files changed, 12 insertions(+), 1476 deletions(-) create mode 100644 css/main.min.832d0bcb11f7e92fee7e65397eb4dd48539f333195fcb1c8a6a957990228bfef1dafb4c5d3138b341463bc7de99d204507b9bd90c00677937c78d37b04741317.css delete mode 100644 css/main.min.bd363a626ae1c698f13409c9784ea6f1475d32782da4f7940f5ad0410559be2b5eefe173b448f56944635de717743c3d4c0bc6b77f7029d9a73502f96b089860.css delete mode 100644 docs/database-access/index.html delete mode 100644 docs/design-for-testability/index.html delete mode 100644 docs/generics/index.html delete mode 100644 docs/javafx/index.html delete mode 100644 docs/jpms-lambda-streams/index.html delete mode 100644 docs/parameterized-tests/index.html delete mode 100644 docs/reflection/index.html delete mode 100644 docs/statemachine/index.html diff --git a/css/main.min.832d0bcb11f7e92fee7e65397eb4dd48539f333195fcb1c8a6a957990228bfef1dafb4c5d3138b341463bc7de99d204507b9bd90c00677937c78d37b04741317.css b/css/main.min.832d0bcb11f7e92fee7e65397eb4dd48539f333195fcb1c8a6a957990228bfef1dafb4c5d3138b341463bc7de99d204507b9bd90c00677937c78d37b04741317.css new file mode 100644 index 0000000..4988ba9 --- /dev/null +++ b/css/main.min.832d0bcb11f7e92fee7e65397eb4dd48539f333195fcb1c8a6a957990228bfef1dafb4c5d3138b341463bc7de99d204507b9bd90c00677937c78d37b04741317.css @@ -0,0 +1 @@ +*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::before,::after{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,liberation mono,courier new,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::before,::after{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=As]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=as]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=Is]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=is]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *))::before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *))::after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose] *)){font-weight:900}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose] *)){font-weight:800}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose] *)){font-weight:700}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose] *)){font-weight:700}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose] *))::before{content:"`"}.prose :where(code):not(:where([class~=not-prose] *))::after{content:"`"}.prose :where(a code):not(:where([class~=not-prose] *)){color:var(--tw-prose-links)}.prose :where(pre):not(:where([class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding-top:.8571429em;padding-right:1.1428571em;padding-bottom:.8571429em;padding-left:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose] *))::before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *))::after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:baseline;padding-top:.5714286em;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:rgb(0 0 0 / 50%);--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(img):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(video):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(h2 code):not(:where([class~=not-prose] *)){font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){font-size:.9em}.prose :where(li):not(:where([class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose>:where(ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose>:where(ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose>:where(ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose>:where(ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose>:where(ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose>:where(:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose>:where(:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-slate{--tw-prose-body:#334155;--tw-prose-headings:#0f172a;--tw-prose-lead:#475569;--tw-prose-links:#0f172a;--tw-prose-bold:#0f172a;--tw-prose-counters:#64748b;--tw-prose-bullets:#cbd5e1;--tw-prose-hr:#e2e8f0;--tw-prose-quotes:#0f172a;--tw-prose-quote-borders:#e2e8f0;--tw-prose-captions:#64748b;--tw-prose-code:#0f172a;--tw-prose-pre-code:#e2e8f0;--tw-prose-pre-bg:#1e293b;--tw-prose-th-borders:#cbd5e1;--tw-prose-td-borders:#e2e8f0;--tw-prose-invert-body:#cbd5e1;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#94a3b8;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#94a3b8;--tw-prose-invert-bullets:#475569;--tw-prose-invert-hr:#334155;--tw-prose-invert-quotes:#f1f5f9;--tw-prose-invert-quote-borders:#334155;--tw-prose-invert-captions:#94a3b8;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#cbd5e1;--tw-prose-invert-pre-bg:rgb(0 0 0 / 50%);--tw-prose-invert-th-borders:#475569;--tw-prose-invert-td-borders:#334155}#toc #toc-title{display:none}#toc .toc-list.level-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}#toc .toc-list.level-1{border-left-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem}.dark #toc .toc-list.level-1{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}@media(min-width:1024px){#toc .toc-list.level-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}}#toc .toc-list.level-1 a{margin-left:-1px;display:block;border-left-width:1px;border-color:transparent;padding-left:1rem;font-weight:400;--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}#toc .toc-list.level-1 a:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity));--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark #toc .toc-list.level-1 a{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark #toc .toc-list.level-1 a:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity));--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}#toc .toc-list.level-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}#toc .toc-list.level-2{padding-top:.5rem;padding-bottom:.5rem}@media(min-width:1024px){#toc .toc-list.level-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}}#toc .toc-list.level-2 a{padding-left:2rem;--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity))}#toc .toc-list.level-2 a:hover{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity))}.dark #toc .toc-list.level-2 a{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.dark #toc .toc-list.level-2 a:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.image-block{margin-top:.5rem;margin-bottom:.5rem;padding-left:1rem;padding-right:1rem;text-align:center}.image-block.thumb{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media(min-width:640px){.image-block.thumb{max-width:100%}}@media(min-width:768px){.image-block.thumb{max-width:24rem}}@media(min-width:1024px){.image-block.thumb{max-width:28rem}}.image-block img{margin-left:auto;margin-right:auto;margin-top:.75rem;margin-bottom:.75rem;--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark .image-block img{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.image-block img[src$=svg]{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.table-block{max-width:100%;overflow-x:auto}.svg-wrapper img{margin:0}.code-copy{position:absolute;top:.75rem;right:.75rem;display:none;--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.code-copy:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .code-copy{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.dark .code-copy:hover{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.admonition-block{margin-top:1.5rem;margin-bottom:1.5rem;border-radius:.375rem;border-width:1px;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity));padding-left:1.25rem;padding-right:1.25rem}.dark .admonition-block{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.admonition-block .title-label{display:none}.admonition-block .block-title{margin-top:.75rem;font-weight:700}.dark .admonition-block .block-title{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.admonition-block .thumb{margin:0;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.admonition-block.note{--tw-border-opacity:1;border-left-color:rgb(8 145 178/var(--tw-border-opacity))}.admonition-block.tip{--tw-border-opacity:1;border-left-color:rgb(22 163 74/var(--tw-border-opacity))}.admonition-block.warning{--tw-border-opacity:1;border-left-color:rgb(234 88 12/var(--tw-border-opacity))}.admonition-block.caution{--tw-border-opacity:1;border-left-color:rgb(220 38 38/var(--tw-border-opacity))}.video-block iframe{aspect-ratio:16/9;width:100%}.listing-block figcaption+.highlight{margin-top:.5rem}pre.highlight code{display:block;overflow-x:auto;background-color:transparent;padding:.25rem}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-3{bottom:.75rem}.right-10{right:2.5rem}.top-0{top:0}.top-\[3\.5rem\]{top:3.5rem}.left-\[max\(0px\2c calc\(50\%-45rem\)\)\]{left:max(0px,calc(50% - 45rem))}.right-auto{right:auto}.top-\[3\.8125rem\]{top:3.8125rem}.bottom-0{bottom:0}.right-\[max\(0px\2c calc\(50\%-45rem\)\)\]{right:max(0px,calc(50% - 45rem))}.isolate{isolation:isolate}.z-10{z-index:10}.z-50{z-index:50}.z-40{z-index:40}.z-20{z-index:20}.clear-both{clear:both}.mx-auto{margin-left:auto;margin-right:auto}.mx-4{margin-left:1rem;margin-right:1rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mr-3{margin-right:.75rem}.mt-12{margin-top:3rem}.mb-5{margin-bottom:1.25rem}.ml-auto{margin-left:auto}.ml-6{margin-left:1.5rem}.mb-8{margin-bottom:2rem}.-ml-px{margin-left:-1px}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-5{height:1.25rem}.w-5{width:1.25rem}.w-full{width:100%}.w-\[19\.5rem\]{width:19.5rem}.max-w-none{max-width:none}.max-w-3xl{max-width:48rem}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.items-center{align-items:center}.justify-center{justify-content:center}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:.5rem}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-slate-900\/10{border-color:rgb(15 23 42/.1)}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-slate-500{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-gray-900\/50{background-color:rgb(17 24 39/.5)}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.pl-6{padding-left:1.5rem}.pb-10{padding-bottom:2.5rem}.pl-4{padding-left:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-6{line-height:1.5rem}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-pink-500{--tw-text-opacity:1;color:rgb(236 72 153/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:150ms}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.max-w-8xl{max-width:90rem}.red{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.blue{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.dark .blue{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.green{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.right{float:right}.left{float:left}.prose :where(code):not(:where([class~=not-prose] *))::before{display:none}.prose :where(code):not(:where([class~=not-prose] *))::after{display:none}.prose :where(code):not(:where([class~=not-prose] *)){border-radius:.25rem;--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity));padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;padding-right:.5rem;font-weight:400;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.dark .prose :where(code):not(:where([class~=not-prose] *)){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.prose :where(pre code):not(:where([class~=not-prose] *)){color:currentColor}.thumb{margin-top:.5rem;margin-bottom:.5rem;--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark .thumb{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.thumb.left{margin-right:.75rem}.thumb.right{margin-left:.75rem}.hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:text-sky-500:hover{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.hover\:text-sky-400:hover{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.prose-a\:text-fuchsia-500 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity:1;color:rgb(217 70 239/var(--tw-text-opacity))}.dark .dark\:prose-invert{--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}.dark .dark\:inline{display:inline}.dark .dark\:hidden{display:none}.dark .dark\:border-gray-50\/\[0\.06\]{border-color:rgb(249 250 251/6%)}.dark .dark\:border-slate-300\/10{border-color:rgb(203 213 225/.1)}.dark .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.dark .dark\:border-slate-400{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-900\/60{background-color:rgb(17 24 39/.6)}.dark .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.dark .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity))}.dark .dark\:text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark .dark\:shadow-slate-900{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.dark .dark\:invert{--tw-invert:invert(100%);filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.dark .dark\:hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.dark .dark\:hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.dark .dark\:hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:hover\:text-gray-200:hover{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:hover\:text-sky-400:hover{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity))}.dark .dark\:prose-a\:text-fuchsia-400 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity:1;color:rgb(232 121 249/var(--tw-text-opacity))}@media(min-width:640px){.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-auto{width:auto}.md\:px-8{padding-left:2rem;padding-right:2rem}}@media(min-width:1024px){.lg\:mx-0{margin-left:0;margin-right:0}.lg\:mt-8{margin-top:2rem}.lg\:mb-3{margin-bottom:.75rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:border-0{border-width:0}.lg\:border-b{border-bottom-width:1px}.lg\:border-l{border-left-width:1px}.lg\:border-gray-900\/10{border-color:rgb(17 24 39/.1)}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pl-\[19\.5rem\]{padding-left:19.5rem}.lg\:leading-6{line-height:1.5rem}}@media(min-width:1280px){.xl\:ml-0{margin-left:0}.xl\:mr-\[15\.5rem\]{margin-right:15.5rem}.xl\:block{display:block}.xl\:max-w-none{max-width:none}.xl\:pr-16{padding-right:4rem}}.highlight:hover .code-copy{display:block}pre.highlight code{background-color:transparent!important}.highlight{position:relative} \ No newline at end of file diff --git a/css/main.min.bd363a626ae1c698f13409c9784ea6f1475d32782da4f7940f5ad0410559be2b5eefe173b448f56944635de717743c3d4c0bc6b77f7029d9a73502f96b089860.css b/css/main.min.bd363a626ae1c698f13409c9784ea6f1475d32782da4f7940f5ad0410559be2b5eefe173b448f56944635de717743c3d4c0bc6b77f7029d9a73502f96b089860.css deleted file mode 100644 index 1019a61..0000000 --- a/css/main.min.bd363a626ae1c698f13409c9784ea6f1475d32782da4f7940f5ad0410559be2b5eefe173b448f56944635de717743c3d4c0bc6b77f7029d9a73502f96b089860.css +++ /dev/null @@ -1 +0,0 @@ -*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::before,::after{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,liberation mono,courier new,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::before,::after{--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=As]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=as]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=Is]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=is]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *))::before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *))::after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose] *)){font-weight:900}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose] *)){font-weight:800}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose] *)){font-weight:700}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose] *)){font-weight:700}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose] *))::before{content:"`"}.prose :where(code):not(:where([class~=not-prose] *))::after{content:"`"}.prose :where(a code):not(:where([class~=not-prose] *)){color:var(--tw-prose-links)}.prose :where(pre):not(:where([class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding-top:.8571429em;padding-right:1.1428571em;padding-bottom:.8571429em;padding-left:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose] *))::before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *))::after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:baseline;padding-top:.5714286em;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:rgb(0 0 0 / 50%);--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(img):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(video):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(h2 code):not(:where([class~=not-prose] *)){font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){font-size:.9em}.prose :where(li):not(:where([class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose>:where(ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose>:where(ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose>:where(ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose>:where(ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose>:where(ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose>:where(:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose>:where(:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-slate{--tw-prose-body:#334155;--tw-prose-headings:#0f172a;--tw-prose-lead:#475569;--tw-prose-links:#0f172a;--tw-prose-bold:#0f172a;--tw-prose-counters:#64748b;--tw-prose-bullets:#cbd5e1;--tw-prose-hr:#e2e8f0;--tw-prose-quotes:#0f172a;--tw-prose-quote-borders:#e2e8f0;--tw-prose-captions:#64748b;--tw-prose-code:#0f172a;--tw-prose-pre-code:#e2e8f0;--tw-prose-pre-bg:#1e293b;--tw-prose-th-borders:#cbd5e1;--tw-prose-td-borders:#e2e8f0;--tw-prose-invert-body:#cbd5e1;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#94a3b8;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#94a3b8;--tw-prose-invert-bullets:#475569;--tw-prose-invert-hr:#334155;--tw-prose-invert-quotes:#f1f5f9;--tw-prose-invert-quote-borders:#334155;--tw-prose-invert-captions:#94a3b8;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#cbd5e1;--tw-prose-invert-pre-bg:rgb(0 0 0 / 50%);--tw-prose-invert-th-borders:#475569;--tw-prose-invert-td-borders:#334155}#toc #toc-title{display:none}#toc .toc-list.level-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}#toc .toc-list.level-1{border-left-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity));font-size:.875rem;line-height:1.25rem}.dark #toc .toc-list.level-1{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}@media(min-width:1024px){#toc .toc-list.level-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}}#toc .toc-list.level-1 a{margin-left:-1px;display:block;border-left-width:1px;border-color:transparent;padding-left:1rem;font-weight:400;--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}#toc .toc-list.level-1 a:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity));--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark #toc .toc-list.level-1 a{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark #toc .toc-list.level-1 a:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity));--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}#toc .toc-list.level-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}#toc .toc-list.level-2{padding-top:.5rem;padding-bottom:.5rem}@media(min-width:1024px){#toc .toc-list.level-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}}#toc .toc-list.level-2 a{padding-left:2rem;--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity))}#toc .toc-list.level-2 a:hover{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity))}.dark #toc .toc-list.level-2 a{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.dark #toc .toc-list.level-2 a:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.image-block{margin-top:.5rem;margin-bottom:.5rem;padding-left:1rem;padding-right:1rem;text-align:center}.image-block.thumb{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media(min-width:640px){.image-block.thumb{max-width:100%}}@media(min-width:768px){.image-block.thumb{max-width:24rem}}@media(min-width:1024px){.image-block.thumb{max-width:28rem}}.image-block img{margin-left:auto;margin-right:auto;margin-top:.75rem;margin-bottom:.75rem;--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark .image-block img{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.image-block img[src$=svg]{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.table-block{max-width:100%;overflow-x:auto}.svg-wrapper img{margin:0}.code-copy{position:absolute;top:.75rem;right:.75rem;display:none;--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.code-copy:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .code-copy{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.dark .code-copy:hover{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.admonition-block{margin-top:1.5rem;margin-bottom:1.5rem;border-radius:.375rem;border-width:1px;border-left-width:4px;--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity));padding-left:1.25rem;padding-right:1.25rem}.dark .admonition-block{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.admonition-block .title-label{display:none}.admonition-block .block-title{margin-top:.75rem;font-weight:700}.dark .admonition-block .block-title{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.admonition-block .thumb{margin:0;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.admonition-block.note{--tw-border-opacity:1;border-left-color:rgb(8 145 178/var(--tw-border-opacity))}.admonition-block.tip{--tw-border-opacity:1;border-left-color:rgb(22 163 74/var(--tw-border-opacity))}.admonition-block.warning{--tw-border-opacity:1;border-left-color:rgb(234 88 12/var(--tw-border-opacity))}.admonition-block.caution{--tw-border-opacity:1;border-left-color:rgb(220 38 38/var(--tw-border-opacity))}.video-block iframe{aspect-ratio:16/9;width:100%}.listing-block figcaption+.highlight{margin-top:.5rem}pre.highlight code{display:block;overflow-x:auto;background-color:transparent;padding:.25rem}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-3{bottom:.75rem}.right-10{right:2.5rem}.top-0{top:0}.top-\[3\.5rem\]{top:3.5rem}.left-\[max\(0px\2c calc\(50\%-45rem\)\)\]{left:max(0px,calc(50% - 45rem))}.right-auto{right:auto}.top-\[3\.8125rem\]{top:3.8125rem}.bottom-0{bottom:0}.right-\[max\(0px\2c calc\(50\%-45rem\)\)\]{right:max(0px,calc(50% - 45rem))}.isolate{isolation:isolate}.z-10{z-index:10}.z-50{z-index:50}.z-40{z-index:40}.z-20{z-index:20}.clear-both{clear:both}.mx-auto{margin-left:auto;margin-right:auto}.mx-4{margin-left:1rem;margin-right:1rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mr-3{margin-right:.75rem}.mt-12{margin-top:3rem}.mb-5{margin-bottom:1.25rem}.ml-auto{margin-left:auto}.ml-6{margin-left:1.5rem}.mb-8{margin-bottom:2rem}.-ml-px{margin-left:-1px}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-5{height:1.25rem}.w-5{width:1.25rem}.w-full{width:100%}.w-\[19\.5rem\]{width:19.5rem}.max-w-none{max-width:none}.max-w-3xl{max-width:48rem}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.items-center{align-items:center}.justify-center{justify-content:center}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:.5rem}.rounded{border-radius:.25rem}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-slate-900\/10{border-color:rgb(15 23 42/.1)}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-slate-500{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-gray-900\/50{background-color:rgb(17 24 39/.5)}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.pl-6{padding-left:1.5rem}.pb-10{padding-bottom:2.5rem}.pl-4{padding-left:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-6{line-height:1.5rem}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-pink-500{--tw-text-opacity:1;color:rgb(236 72 153/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:150ms}.transition{transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:150ms}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.max-w-8xl{max-width:90rem}.red{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.blue{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.dark .blue{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.green{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.right{float:right}.left{float:left}.prose :where(code):not(:where([class~=not-prose] *))::before{display:none}.prose :where(code):not(:where([class~=not-prose] *))::after{display:none}.prose :where(code):not(:where([class~=not-prose] *)){border-radius:.25rem;--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity));padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;padding-right:.5rem;font-weight:400;--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.dark .prose :where(code):not(:where([class~=not-prose] *)){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.prose :where(pre code):not(:where([class~=not-prose] *)){color:currentColor}.thumb{margin-top:.5rem;margin-bottom:.5rem;--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark .thumb{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.thumb.left{margin-right:.75rem}.thumb.right{margin-left:.75rem}.hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:text-sky-500:hover{--tw-text-opacity:1;color:rgb(14 165 233/var(--tw-text-opacity))}.hover\:text-sky-400:hover{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.prose-a\:text-fuchsia-500 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity:1;color:rgb(217 70 239/var(--tw-text-opacity))}.dark .dark\:prose-invert{--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}.dark .dark\:inline{display:inline}.dark .dark\:hidden{display:none}.dark .dark\:border-gray-50\/\[0\.06\]{border-color:rgb(249 250 251/6%)}.dark .dark\:border-slate-300\/10{border-color:rgb(203 213 225/.1)}.dark .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}.dark .dark\:border-slate-400{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-900\/60{background-color:rgb(17 24 39/.6)}.dark .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.dark .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity))}.dark .dark\:text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark .dark\:shadow-slate-900{--tw-shadow-color:#0f172a;--tw-shadow:var(--tw-shadow-colored)}.dark .dark\:invert{--tw-invert:invert(100%);filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.dark .dark\:hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity))}.dark .dark\:hover\:border-slate-400:hover{--tw-border-opacity:1;border-color:rgb(148 163 184/var(--tw-border-opacity))}.dark .dark\:hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:hover\:text-gray-200:hover{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}.dark .dark\:hover\:text-sky-400:hover{--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity))}.dark .dark\:prose-a\:text-fuchsia-400 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity:1;color:rgb(232 121 249/var(--tw-text-opacity))}@media(min-width:640px){.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:768px){.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-auto{width:auto}.md\:px-8{padding-left:2rem;padding-right:2rem}}@media(min-width:1024px){.lg\:mx-0{margin-left:0;margin-right:0}.lg\:mt-8{margin-top:2rem}.lg\:mb-3{margin-bottom:.75rem}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:border-0{border-width:0}.lg\:border-b{border-bottom-width:1px}.lg\:border-l{border-left-width:1px}.lg\:border-gray-900\/10{border-color:rgb(17 24 39/.1)}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pl-\[19\.5rem\]{padding-left:19.5rem}.lg\:leading-6{line-height:1.5rem}}@media(min-width:1280px){.xl\:ml-0{margin-left:0}.xl\:mr-\[15\.5rem\]{margin-right:15.5rem}.xl\:block{display:block}.xl\:max-w-none{max-width:none}.xl\:pr-16{padding-right:4rem}}.highlight:hover .code-copy{display:block}pre.highlight code{background-color:transparent!important}.highlight{position:relative} \ No newline at end of file diff --git a/docs/database-access/index.html b/docs/database-access/index.html deleted file mode 100644 index 56dfcb2..0000000 --- a/docs/database-access/index.html +++ /dev/null @@ -1,258 +0,0 @@ -PRC2 - 06 Database Access JDBC -
-PRC2

Java DataBase Connectivity

DataBase connection credentials and Java Properties

Some things do NOT belong in source code. In particular do not put credentials of any kind -inside files that are committed to a version control system, such as source code. Make sure you configure your -version control system such that such files are excluded from commits.

Instead put credentials in separate files, that are easily understood by both a human that uses it -to configure access to a resource, and also by the programming language.
In java the tradition is to use so called properties files, which, also traditionally, have a file extension .properties. -It also helps to give such files well known names, so the program can refer to them by that name.

For the demonstrations in this part we will use the following properties file.

application.properties file specifying connection details for dev and prod
# You can add comments here.
-
-jdbc.pg.dev.dbname=dev
-jdbc.pg.dev.username=exam
-jdbc.pg.dev.password=exam
-jdbc.pg.dev.hostname=localhost
-
-jdbc.pg.prod.dbname=prod
-jdbc.pg.prod.username=appdbuser
-jdbc.pg.prod.password=verySecretPassw00rd
-jdbc.pg.prod.hostname=dbcluster

You can see that the properties file supports two environments, development and production.

Configuring a datasource. Put it in a utility class.
    static DataSource getDataSource( final String sourceName ) {
-        // dataSourceByName is a map, serving as a cache.
-        return datasourceByName.computeIfAbsent( sourceName,
-            ( s ) -> {
-                Properties props = properties( "application.properties" );
-
-                PGSimpleDataSource source = new PGSimpleDataSource();
-
-                String prefix = sourceName + "."; 1
-                String[] serverNames = {
-                    props.getProperty( prefix + "dbhost" )
-                };
-                source.setServerNames( serverNames );
-
-                String user = props.getProperty( prefix + "username" );
-                source.setUser( user );
-
-                source.setDatabaseName( props.getProperty( prefix + "dbname" ) );
-                source.setPassword( props
-                        .getProperty( prefix + "password" ) );
-                String pingQuery = "SELECT current_database(), now()::TIMESTAMP as now;";
-                try ( Connection con = source.getConnection();
-                    // ping the database for success.
-                    PreparedStatement pst = con.prepareStatement( pingQuery ); ) {
-                        try ( ResultSet rs = pst.executeQuery(); ) {
-                            if ( rs.next() ) {
-                                Object db = rs.getObject(  "current_database");
-                                Object now = rs.getObject(  "now");
-                                System.out.println("connected to db "+ db.toString()+ ", date/time is " + now.toString() );
-                            }
-                        }
-
-                } catch ( SQLException ex ) {
-                    Logger.getLogger( PgJDBCUtils.class.getName() ).log( Level.SEVERE, null, ex );
-                }
-                return source;
-            }
-            ); // end of lambda.
-      }
-
-      // read properties
-      static Properties properties( String propFileName ) {
-          Properties properties = new Properties();
-          try (
-                  FileInputStream fis = new FileInputStream( propFileName ); ) {
-              properties.load( fis );
-          } catch ( IOException ignored ) {
-              Logger.getLogger( PgJDBCUtils.class.getName() ).log(
-                      Level.INFO,
-                      "attempt to read file from well known location failed'",
-                      ignored );
-          }
-          return properties;
-      }
  1. The sourceName is the key or namespace from where to pickup the connection details. Simple and effective.

Using a Data source

There are some traditional ways to obtain a database connection. We use a DataSource, which itself -can be seen as a resource. -The data source can then be used to obtain a connection. In the example you see a class that needs a DataSource -that is provided at construction time of the class, so it is available when the instance is created. -A connection is AutoClosable so candidate for try-with-resources.

Using a datasource to obtain a connection and use the connection
package simplejdbc;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import javax.sql.DataSource;
-
-/**
- *
- * @author hom
- */
-public class DataSourceDemo {
-
-    final DataSource datasource; 1
-
-    public DataSourceDemo( DataSource datasource ) { 2
-        this.datasource = datasource;
-    }
-
-    void demo() throws SQLException {  3
-        String query
-                = "select state.name as state,p.name as president,state.year_entered \n"
-                  + " from president p join state state on(p.state_id_born=state.id)\n"
-                  + "where state.name like 'N%'";
-            doQuery( query, System.out );
-    }
-}
  1. Resource used in methods of this class.
  2. Constructor receives the DataSource.
  3. The method uses the DataSource to get a connection in the try-with-resources block
    and passes it on to the method that executes the query and deals with the result by printing it.

The doQuery(…​) method uses the supplied connection to create a statement which is then executed to produce a ResultSet. -You will see some similarities in what you have seen in project 1, using php PDO.

doQuery
    void doQuery( String query,
-                  PrintStream out ) throws SQLException {
-        try ( Connection con = datasource.getConnection();
-                Statement st = con.createStatement();
-                ResultSet rs = st.executeQuery( query ) ) {
-
-            new ResultSetPrinter( rs ).printTable( out );
-
-        }
-    }

The ResultSetPrinter tries to make a nice looking table of the result of the query. -You can imagine that this is a bit of code, but that is not relevant for this demo of JDBC.

ResultSet

For all queries that return a tabular result, the first JDBC class -you will use is the ResultSet. -The ResultSet also provides so called Meta Information that describes the types of the values -in the columns, the number of columns, display size etc.
This can be used to:

  • produce a nice tabular format
  • by using a translation or mapping between the database types and Java types, how the column data is to be used, type wise.

Anatomy of a prepared statement

In the example earlier, the sql text used to create the statement is constant, because it needs no user input. -If a statement does, you should always use a PreparedStatement.

In the JDBC standard you fill in the parameters from the user in multiple ways, but the simplest is to -just use question marks (?) as placeholders and specify which columns and column values -you want to insert or update, or you want to use in your select or delete query.

sql query text to insert a student.
insert into students (student_id , lastname, firstname, dob, gender) 1
-            values   (?        ,          ?,         ?,   ?,      ?) 2
-returning *                                                          3
  1. Fields can be supplied in any order, including definition order.
  2. You do not need the spaces before the commas, we added them for readability.
  3. It is smart to always expect back what has been inserted by the database, including generated id and -other fields such as the database’s notion of date or time. Even smarter, but a little more work -is to specify the columns instead of a *, so that the order in which you receive them is independent -of database schema organization and stable.

Now assume the SQL text contains the above. -Also assume that we have an array of student data, simply as an array of objects.

student data in array
 Object[] studentData = {123513, "Klaassen", "Jan", "1993-05-12" , "M"}; 1

Then creating the prepared statement and filling it with values is a simple loop:

loop to substitute placeholders.
     try (
-          Connection con = datasource.getConnection();
-          PreparedStatement stmt = con.prepareStatement( query ); ) {
-
-            int count = 0;
-            for ( Object param : studentData ) {
-                stmt.setObject( ++count, param ); 1
-            }
-            return stmt.executeUpdate();
-     }
  1. note the pre-increment, so count starts with column 1.

You see that this is quite simple and other than what is specified in the query, there is -no extra need for data conversion or named column use.

This approach can be used to make database access even simpler, so you only have to provide the data -in an array and the rest can be packed into utility methods.

The holy grail is to find a way to do all kind of queries against tables, and the only -thing you need to know is the table name and what entities as Java objects can be expected to -be read from or written to the table. However, start simple first! At some point you’ll find duplicated code and you’ll find a way to optimize your code. Typically, you’ll use Generics, Lambda’s and streams and a bit of reflection at some point. We think it’s good to first face the issue of code that is difficult to maintain and afterwards find a solution for that, instead of providing you with a very generic and maybe complex solution without knowing which underlying problems it solves.

Traditional preparing statements

In tests you should use traditional approaches, instead of the mechanisms you -are testing. The code below illustrates what that means.
It is used by code that tests the bank transfer service implemented in the database. This example -is filling the account tables from some List of accounts.

load accounts from list. The code is part of a test suite to prepare the database for tests.
    static String addAccount = "insert into account(accountid,balance,maxdebit,customerid,astate) "
-                             + "values(?,?,?,?,?)";
-    static void loadAccounts() throws SQLException {
-
-        try ( Connection con = olifantysSource.getConnection();
-                PreparedStatement pst = con.prepareStatement( addAccount ) ) {
-            for ( Account account : accounts ) {
-                pst.setObject( 1, account.accountid );
-                pst.setBigDecimal( 2, account.balance );
-                pst.setBigDecimal( 3, account.maxdebit );
-                pst.setObject( 4, account.customerid );
-                pst.setString( 5, account.astate );
-                pst.addBatch(); 1
-            }
-            int[] results = pst.executeBatch();
-            System.out.println( "results = " + Arrays.toString( results ) );
-
-        } catch ( PSQLException e ) {
-            System.out.println( "caused by " + e.getNextException() );
-            throw e;
-        }
-    }
  1. We do multiple inserts in one batch. Auto commit is off. -The try-with-resources block takes care of the jdbc transaction.

Database Meta Information

DO Mention the metadata…​

In the previous part we have seen how to use reflection on java classes.

A similar and standardized concept also exists for databases. You can retrieve all kind -of meta information about the objects (such as tables and views) defined in your database. -Accessing that data can be done with selecting data from special relations in a special schema, -called the information_schema.

Suppose we have the following definition of a table students in the schema public:

CREATE TABLE public.students (
-    student_id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, 1
-    firstname TEXT NOT NULL,  2
-    lastname TEXT NOT NULL,
-    dob DATE NOT NULL,
-    cohort integer NOT NULL DEFAULT  EXTRACT('year' from now()), 3
-    email TEXT NOT NULL,
-    gender CHARACTER(1) NOT NULL check (gender in ('F','M','U')), 4
-    student_grp TEXT NOT NULL,
-    active BOOLEAN DEFAULT false NOT NULL
-);
  1. ISO SQL for a serial column that by default is generated, and also is primary key.
  2. All columns that 'traditionally' would be varchar are now text. Just as efficient less hassle. It will always fit. -Only use varchar if your business requires a length constraint.
  3. The cohort is the year of registration, which can be derived from the insertion moment.
  4. Gender is a one character value with a restricted value set, much like an enum, but simpler.

If after the definition your would ask the database what it knows about this table with

SELECT ordinal_position, column_name,data_type, column_default, is_nullable,
-       is_generated, datetime_precision
-FROM   information_schema.columns WHERE table_name ='students' ORDER BY 1;

you would get the following as output:

┌──────────────────┬─────────────┬───────────┬──────────────────────────┬─────────────┬──────────────┬────────────────────┐
-│ ordinal_position │ column_name │ data_type │      column_default      │ is_nullable │ is_generated │ datetime_precision │
-╞══════════════════╪═════════════╪═══════════╪══════════════════════════╪═════════════╪══════════════╪════════════════════╡
-│                1 │ student_id  │ integer   │                          │ NO          │ NEVER        │                    │
-│                2 │ firstname   │ text      │                          │ NO          │ NEVER        │                    │
-│                3 │ lastname    │ text      │                          │ NO          │ NEVER        │                    │
-│                4 │ dob         │ date      │                          │ NO          │ NEVER        │                  0 │
-│                5 │ cohort      │ integer   │ EXTRACT(year FROM now()) │ NO          │ NEVER        │                    │
-│                6 │ email       │ text      │                          │ NO          │ NEVER        │                    │
-│                7 │ gender      │ character │                          │ NO          │ NEVER        │                    │
-│                8 │ student_grp │ text      │                          │ NO          │ NEVER        │                    │
-│                9 │ active      │ boolean   │ false                    │ NO          │ NEVER        │                    │
-└──────────────────┴─────────────┴───────────┴──────────────────────────┴─────────────┴──────────────┴────────────────────┘

From this information you can imagine that it is relatively easy to generate the matching Java types as record type.

The resulting record would look like this:

package entities;
-
-import java.time.LocalDate;
-
-public record Student (
-   int student_id,
-   String firstname,
-   String lastname,
-   LocalDate dob,
-   int cohort,
-   String email,
-   char gender,
-   String student_grp,
-   bool active
-){} 1
  1. indeed, no body whatsoever.

Because in the Java layer you would like to have the meta information handy in a Mapper, you can generate the mapper at the same time from the database information -instead of using reflection to the same effect.

Check constraints

Similarly you can also get the information of the check constraints with another, a bit more involved query.

-- retrieve the check constraints for the user schemas
-select tc.table_schema,                                                          
-       tc.table_name,
-       string_agg(col.column_name, ', ') as columns,
-       tc.constraint_name,
-       cc.check_clause
-from information_schema.table_constraints tc
-join information_schema.check_constraints cc
-     on tc.constraint_schema = cc.constraint_schema
-     and tc.constraint_name = cc.constraint_name
-join pg_namespace nsp on nsp.nspname = cc.constraint_schema
-join pg_constraint pgc on pgc.conname = cc.constraint_name
-                       and pgc.connamespace = nsp.oid
-                       and pgc.contype = 'c'
-join information_schema.columns col
-     on col.table_schema = tc.table_schema
-     and col.table_name = tc.table_name
-     and col.ordinal_position = ANY(pgc.conkey)
-where tc.constraint_schema not in('pg_catalog', 'information_schema')
-group by tc.table_schema,
-         tc.table_name,
-         tc.constraint_name,
-         cc.check_clause
-order by tc.table_schema,
-         tc.table_name;

which, because only one column in this schema actually declares a check constraint, results in:

┌──────────────┬────────────┬─────────┬───────────────────────┬─────────────────────────────────────────────────────────────────┐
-│ table_schema │ table_name │ columns │    constraint_name    │                          check_clause                           │
-╞══════════════╪════════════╪═════════╪═══════════════════════╪═════════════════════════════════════════════════════════════════╡
-│ public       │ students   │ gender  │ students_gender_check │ ((gender = ANY (ARRAY['F'::bpchar, 'M'::bpchar, 'U'::bpchar]))) │
-└──────────────┴────────────┴─────────┴───────────────────────┴─────────────────────────────────────────────────────────────────┘

You can use the check constraints in your business code too, but actually deferring those checks as final checks to the database is just fine. The database -layer with throw an appropriate exception when a value is not to the liking of a constraint.

You may want to use the check information in the user interface layer, to warn the user that a transaction will not succeed with a illegal or missing value. -Parsing the check clause is of course a bit complex, but with a regex you can do a lot, and also know that most check constraint are relatively simple.

Transactions

Things that can go wrong will go wrong. This problem is aggravated by having to use multiple things. -Murphy says hello again :-).

A transaction is defined as a sequence of operations that can either succeed completely or be undone -without any residual effect.
In plain English: It either happens fully, or we can forget about it.

For a persistence layer that means: if it cannot fulfill the obligation it should -make sure that nothing happens to compromise the data already stored before the transaction.

The simple explanation of how this works is that the party that is busy with a resource gets -the resource all for itself, locking any other parties out. -If something fails, the mess created can either be undone (rollback) or not committed, which in effect is the same.

Locking out sounds serious, and it is, on multiple accounts.

  • When using a BIG lock, as locking out all other parties, things become VERY slow.
  • When lock less, like just a table, or better still, only the records we want to modify, then -DEADlocks can occur, which is even more problematic than having to wait.
  • In all cases, when something goes awry, one may have to clean up mess in multiple places.

All this can be related to quite difficult problems. Luckily there are solutions to that and, -not unimportant, as far as the database goes, that can typically deal with the issues pretty well. Have a look at paragraph 5.9 for more information about transaction programming.

Using Stored Procedures

From the DBS1 course, you should know about stored procedures. Don’t hesitate to use functionalities of your DBMS.

DAO’s

- \ No newline at end of file diff --git a/docs/design-for-testability/index.html b/docs/design-for-testability/index.html deleted file mode 100644 index d2fcfda..0000000 --- a/docs/design-for-testability/index.html +++ /dev/null @@ -1,252 +0,0 @@ -PRC2 - 03 Design for testability -
-PRC2

Faking as a testing technique

"When you can’t or won’t make it, Fake it"

Let’s revisit the movie making metaphor. We know that almost everything in that industry -is fake, or maybe more politely make-belief. In that it only needs to look convincing without being the real thing. -So for instance, when someone needs to be hit hard in the face, it will not really be done and to make sure nothing untoward happens to -the expensive actors, they may be replaced by stunt men and women, so the punch will never land on the pretty face.

In our industry we have other reasons to not use the real thing.

  • It is not available (yet).
  • It is expensive (as in slow) to use and we want our tests to be quick.
  • We want to test behavior under circumstances that are rare, and difficult to achieve with the real thing.
  • It is inconvenient to use.

What we do is replace the "Dependent On Components" by so called mocks.
In all cases, the System Under Test (SUT), the class we are testing is always used as is. So we have no stunt doubles for our protagonist. -When testing, we want it to take all the punches, because we want to make sure it can handle them.

Caution:

A very common mistake made when using mocking as a testing technique, is faking the protagonist or SUT. -This is very wrong. What you are testing in that case is the mock, not the class you hope to test. None of the code of the SUT will be used in such tests.

fitness
Figure 1. Never imagined coding as a work out.

Suppose you have an application in which one of the required effects is -that a business class prints something to a printer. Turning this into a test will -give you a hard time, because you have to run between your desk an printer, but hey, -it will improve your fitness.[1].

To verify that the system under test (SUT) does indeed use the printer, make that fact observable, -so that you can verify that the printer call has been made. -You can use a trick common to many informatics problems and is available to you too: add a level of indirection, -or do not give the actual printer to the business class but instead something that the business code -thinks it is a printer and replace that with whatever suits you for the test.

annoyed 32 -'Nice' you say, now I have two problems':

  1. change the application and
  2. create a printer just for my tests.

Stay cool, have a Mockito.

mockito drink
Figure 2. it gets better.

You are not the first developer in this situation, so someone has automated the creation of 'fake' objects or mocks for us:
Mockito is a framework that can create various objects that are useful for testing.

Mock is an object that behave like a (alternate) implementation of a class or interface. This is akin to an actor (or actress) or stunt-double that -behaves exactly like you tell it to. A mock also saves any method call that you do on it.
Stub just holds data that you provide and are presented to the SUT.
Both mock and stub are stand-ins for dependent on components (DOC) or collaborators for the SUT.
There is also a -Spy which is in fact a wrapper around an actual implementation, that allows you to observe what goes in (method call+ parameters) and comes -out of (return values) of the real object while it is used by the SUT. You can make the spy only spy on certain method calls, and leave the rest unchanged.

mockstubsspies
Figure 3. Class diagram explaining mocks, stubs and spies

Lets see what this looks like in code.

The printer interface.
public interface Printer {
-
-    void println( String print );
-
-    /**
-     * Make it deal with objects too.
-     * @param o to print.
-     */
-    default void println( Object o ) {
-        printLn( o.toString() );
-    }
-}

Now over to the test class, which creates a mocked printer and hands it to the Business class.

Business test class.
    @Test
-    public void doesItPrint() {
-        Printer pr = mock( Printer.class );  1
-
-        Business b = new Business( pr );     2
-
-        b.work();                                 3
-
-        verify( pr ).println( anyString() ); 4
-
-    }
  1. Create the mocked printer.
  2. Create a Business object passing it the printer.
  3. Make the business class work.
  4. Ask the mocked printer if it was used.

This is of course a simple test and the only thing that it verifies is that the printer.printLn(String) has been used.

Verify that what is printed is okay.
    @Mock
-    Printer printer; 1
-
-    Business business;
-
-    @BeforeEach
-    void setup() {
-        business = new Business( printer );  2
-    }
-
-    @Test
-    void doesItPrintBusiness() {
-        ArgumentCaptor<String> lineCaptor = ArgumentCaptor.forClass( String.class ); 3
-
-        business.work( "Linda" ); 4
-
-        verify( printer ).println( lineCaptor.capture() ); 5
-        assertThat( lineCaptor.getAllValues() ).contains( "Hello Linda" ); 6
-        fail("test does It print ended. You know what to do.");
  1. Setup mock as annotated field.
  2. Create the business (SUT) object in a setup method, so tests can avoid repeating this,
    again passing the printer object via the business constructor.
  3. Prepare a Mockito helper object to capture the data passed to the printer.
  4. Make business do its thing.
  5. Verify that printer.println(…​) is called. The captor object is used to -collect all that has been received by the printer’s printLn method.
  6. The expected data is passed to the printer, so a real printer would -print it. The method lineCaptor.getAllValues() produces a List of the captured things, strings in this case.

Mockito is well documented with lots of examples in its java doc. -When you look to the test dependencies of your Maven project in NetBeans-IDE, right-click on the -mockito dependency, then download the javadoc, you can enjoy that while you are developing.

To Mock or To Configure

Sometimes the class that you want to pass as a mock to the business code has a big interface, with -many methods, and you do not want to restrict the business code to just a few trained methods. That would -constrain the methods the business class could use, and that serves no real purpose. In such cases it -may be better to provide a differently configured real class.

Input stubbing with a real,class.

An object that contains preconfigured data is called a Stub.
As an example: Assume you business class -uses a Scanner as it’s input. Scanner has 50-odd methods, which you certainly do not want to train all when mocking.

But since you will pass a special Scanner to the business class anyway, the better way is to use a Scanner configured with one of -it’s constructors. -In this case use new Scanner( String s ), in which the string s contains exactly the input you want to use in your test. The string -can contain newlines, so that it appears as if the user entered multiple lines. This allows you to test a program with a dialog. -Something along the line of new Scanner("12\n13\n");, that is 12 newline 13.

Output mocking with a real classes

THIS PARAGRAPH DESCRIBES AN ADVANCED TOPIC THAT YOU DON’T NEED IMMEDIATELY. FINE IF YOU UNDERSTAND IT, IF NOT THEN JUST CONTINUE WITH THE NEXT PARAGRAPH ABOUT TESTABLE DESIGN.

For the output side something similar is in effect. The most common way of outputting something -is to use System.out to print or otherwise output the results of the program. But System.out is -just a PrintStream, which also has a lot of convenience methods with a total of 33 methods and 10 constructors. -You do not want to restrict the use of any of its methods in the business class.

The trick here is to use one of the constructors to given PrintStream something to 'print on', which you can inspect afterwards. -Usage: The business class has a constructor.[2] or setter to take a PrintStream and always uses that to print to, if -it needs to print anyway.

Usage is business code
class PrintingBusiness
-      final PrintStream out; 1
-
-      PrintingBusiness() { 2
-          this.out=System.out;
-      }
-
-      PrintingBusiness( PrintStream ps ) { 3
-          this.out=ps;
-      }
-
-      void businessMethod(){
-          out.format( "%s at %d", "Oplegkaas", 957 ); 4
-      }
-}
  1. final to have a constructor set it
  2. Default constructor uses System.out to print to.
  3. Redirect output to other output, for test or 'printing' in a GUI.
  4. All facilities of PrintStream are available, including formatted printing.
Use in tests
    @Test
-    public void tformattedPrint() {
-        ConsoleOutput cout = new ConsoleOutput();
-        PrintStream out = cout.asPrintStream();
-
-        out.format( "%s at %d", "Oplegkaas", 957 );
-
-        assertThat( cout.toString() ).contains( "at", "Opleg kaas", "957" );
-    }

By implementing the AppendAndClear interface you can print to anything, for instance -by using a StringBuilder as intermediate and then transfer the output to a GUI element in JavaFX or Swing.

Definition of AppendAndClear interface.
@FunctionalInterface
-public interface AppendAndClear {
-
-    /**
-     * Append a string to this AppendAndClear.
-     *
-     * @param toAppend text to add.
-     */
-    void appendText( String toAppend );
-
-    /**
-     * Clear the output. Optional operation.
-     */
-    default void clear() {
-    }
-}
Implementation of ConsoleOutput
package consoleoutput;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-/**
- * Test helper, to be able to redirect a PrintStream to any writable.
- *
- * Use case: write to string or StringBuilder for test or e.g. javafx
- * TextInputControl for UI. Implement AppendAndClear (which can be done as a
- * lambda) and you're set.
- *
- * @author Pieter van den Hombergh {@code <p.vandenhombergh@fontys.nl>}
- */
-public class ConsoleOutput extends OutputStream {
-
-    protected final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-    final AppendAndClear aac;
-
-    /**
-     * Create a Console output copying data to given AppendAndClear.
-     *
-     * @param ta append and clear to copy all written text to.
-     */
-    public ConsoleOutput( AppendAndClear ta ) {
-        this.aac = ta;
-    }
-
-    /**
-     * Without appendable.
-     */
-    public ConsoleOutput() {
-        this( null );
-    }
-
-    @Override
-    public void write( int b ) throws IOException {
-        if ( aac != null ) {
-            String s = "" + ( (char) b );
-            aac.appendText( s );
-        }
-        baos.write( (byte) b );
-    }
-
-    /**
-     * Clear and discard output.
-     */
-    public void clear() {
-        if ( aac != null ) {
-            aac.clear();
-        }
-        baos.reset();
-    }
-
-    /**
-     * Get the accumulated string since start or clear.
-     *
-     * @return the string
-     */
-    @Override
-    public String toString() {
-        return new String( baos.toByteArray(), StandardCharsets.UTF_8 );
-    }
-
-    /**
-     * Factory method to get this ConsoleOutput as PrintStream.
-     *
-     * @return the print stream.
-     */
-    public PrintStream asPrintStream() {
-        return new PrintStream( this, true, Charset.defaultCharset() );
-    }
-} // eof ConsoleOutput

Design for Test-ability

Also known as dependency injection made simple.

A very important non functional requirement of good software is Test-ability.
Test-ability can be achieved or improved by decoupling the class you are developing (the SUT) from it’s environment, such that you have -full control over that environment in your test and can exactly control and observe what goes in or out of the SUT.

One first easy setup is to use package private visibility more often. It is actually the default in java, and with reason. -When you then put your unit test in the same package (but it its own source tree), you can have the test class look at -or use the package private members of the class in its assertion or verification very easily. -Most of unit testing is white box testing anyway, which almost always includes inspecting private members.

Test-ability is also improved by using the design rule program against interfaces, not concrete classes.
If you do that in your design, you can very easily make specialized implementations of those interface methods that you need in your test, to provide -those specific values or reactions that you need in your test, without having to jump through a lot of hoops to get the test set up.
This easy setup is where Mockito comes in.

It is not the business' business to worry about:
persistence
where service come from or who implements them.

By injecting or otherwise providing the business with the services it needs makes -the business code testable without making it dependent or even know the actual implementing classes. And that is really all that counts. The business code -makes the application valuable, the rest is either plumbing or already available code.[3].

Warning:

Never let the business code create its own persistence or or service object. You would -loose control over what flows between service and business class, -and makes it unobservable, which is bad from the testing standpoint.

Instead hand the service or resource to the business, or give it some provider of a resource.

Business class fit for testing

WE’LL FURTHER EXPLAIN THIS CONCEPT IN CLASS AND IN THE CONTEXT OF PRJ2. THERE ARE QUITE SOME DESIGN IDEAS BEHIND IT, SO DON’T WORRY IF YOU DON’T UNDERSTAND IT IMMEDIATELY.

Let us look at an example as a class diagram.

testable business
Figure 4. Giving the business what it really needs make the business testable

For readers that like to watch still pictures:

businessarch
Figure 5. business class in the application
businesstest
Figure 6. business class in the test lab

In the picture above you see a class diagram which shows a detail in which a business class -'lives' in an environment that is designed to make sure the business class sticks to its business:

Dealing with business entities such as customers and products.
Uses services to do the business' bidding, such as saving stuff to a database, -provided as a service to the business class.
And the business code NOT responsible for how to show things in a UI.

In the above example we provide a factory or provider of the services that the business code requires.

The business class appears in two worlds:

  1. A business world, the Application. This is the actual use and role in the whole application. -In that world there is a so called Assembler, that stitches the parts of the application together and sets it on its way. Typically this Assembler -role is what the main class in a project does. Find the parts, put them together, and put them to work.
  2. In the Test world, call it the laboratory or business.[4] school, where the business class is trained and tested. -Here the test class does the assembling with a dash of Mockito to get good grips on what goes into and out of the business class.

Before the business class can used in real life, it has to be trained and tested. Feels a bit like a student right?

You should note the arrows in the diagram. They provide direction and make it the diagram a directed graph, -and lets a dependent point to a depends-on, like -the BusinessClass depends on a Service, which is an abstract class or better still, an interface. -Also note that the service does not point to, or is dependent on the business, which is good, because it makes the service reusable in other contexts.
The service implementing classes are dependent on the Service definition which is shown with the "implements arrow", pointing from sub to super type.

In the business school, there is a test class, that creates instances of the business class and hands it implementations of the services the business -needs, but does that by giving it a specialized service factory that produces mock -service implementations. The business class is NOT aware of that. -It uses the service in the way it is programmed, which allows the test class to verify that that programming is correct. -The test class is in complete control and can also give the business class situations -that are extensions to the normal happy path to ensure that those situations are also handled properly. If that sounds like an -exam setting at school, you would be right.

You can tell these worlds in the class diagrams apart by the fact that one has a pink BusinessTest class with test methods.

In the 'normal' non-testing Application world, the design has taken some provisions to make sure the business class can work in both worlds. -To provide the business class with a factory from which it can obtain Service implementations, a so called Assembler is added. This Assembler, -in the figure at the top of the application class diagram, typically creates or instantiates the business class and provides it with all it needs, -in this case an implementation of a ServiceFactory. -Note that the assembler in this case also creates the UI part. In desktop applications this is what the Main class of the application typically is for.

This approach of providing an instance of some class implementing an interface or -realizing an abstract class to a business class that needs it is called dependency injection.

Dependency injection is providing the services -required by some class by means of constructor parameters, setters or otherwise, -to avoid that the dependencies of the class are created by that class itself.


  1. (source picture "https://www.menshealth.com/nl/fitness/")
  2. best
  3. And hopefully tested
  4. pun intended
- \ No newline at end of file diff --git a/docs/generics/index.html b/docs/generics/index.html deleted file mode 100644 index a9cd02f..0000000 --- a/docs/generics/index.html +++ /dev/null @@ -1,192 +0,0 @@ -PRC2 - 04 Generics -
-PRC2

Generics

Generics is mainly a compiler feature. In java (at least the versions up to this moment, March 2022) it -is even more so, because after the compiler had it’s go with it, it largely discards the generic information, and the runtime, the JVM, -does not really care about generics and does not use it. The role of generics is to keep the programs type-safe. -The compiler will check that a use of a method, field, or class complies with its definition, -and if the use does not, the compiler will produce an error, to prevent the programmer from doing unsafe things.

Generics, the fine print

Generics were introduced in Java 5.

Generics help keeping the language safer by giving the compiler more ways to reason -about the code and reject it when it is bad. That is what a strictly typed language is all about.

With generics also auto-boxing was introduced. The concept was borrowed from C# and the dot-net framework. -Auto-boxing is auto-magically converting/wrapping an int into an Integer (auto-box) when a -reference type is needed and backwards when the reference type Integer is given and an int is needed (auto-unboxing). -This makes primitive types to play 'nicely' with generics to some degree.

Generics also helps avoiding code duplication, because you can have a something of anything -like a List of Student without any code duplication or any code generation under the hood, to create -specialized classes per anything type. -So this List exists once and is just as applicable to Student as is to Donkey.

A strong point of Java has always been the preservation of investment (in code). What worked once should still work. -So code that was legal (and compiled) in the Java 1.1 era should still run, even if used today in its binary format. Heck, in closed -source stuff that might be the only form you have, but your investment is preserved because of this backwards compatibility.

This strong point has a flip side. Pré Java 5 does not understand generics, which would break the backwards compatibility. That -is why the generic info, after being used by the compiler for correctness verification is then thrown away by the same compiler. Little -if anything of the generic information is preserved for the run time. As a matter of fact, the JVM has no notion of generics, nor does it -need it to function. This has all kind of consequences.

In modern Java code, in particular since Java 8 with Functional interfaces and Streams, generics are a must, because the provide safety in the one hand -and the required flexibility in the other, required to make the Java 8 features possible at all.

Raw types, pre-Java 5

The pre-java 5 days did not have generics. All types that have been generified since then still exist in their original, -nowadays called raw form. From the above you can infer that this is what the JVM sees and uses.

Using a map in pre Java 5 times
   Map studentsMap = new HashMap(); 1
-
-   studentsMap.put( Integer.valueOf( 313581 ) ,new Student( "Potter","Harry" ) );
-
-   // some time later
-   Student harry = (Student) studentsMap.get(Integer.valueOf( 313581 ) ); 2
  1. The map has no clue about of the types of key or value, so Object is the best it can do.
    Also note that you had to turn the int into an Integer object manually, because auto-boxing did not exist.
  2. Retrieving the object from a map is again wrapping the int and also cast the object back to the expected type.

In the above pieces of code all kinds of things can go wrong. You could put any pair of objects of any type in the map and the compiler -had no way of checking what you did made sense.

Generic types, the modern way.

example lists and maps
   List<String> l1 = new ArrayList<>();
-   List<Student> stuList = new ArrayList<>();
-   Map<String,BiFunction<Double,Integer,Integer>> operatorMap;

The thing is that the compiler is very finicky. It should be, very precise and meticulous. -As an example, say you have a simple person hierarchy: Person is extended by Student.

some regular people in lists.
class Person{}
-class Student extends Person{}
-
-// in some code
-    void m(){
-      Person paul = ....
-      Student serena = ...
-      List<Person> pl;
-      List<Student> sl;
-
-    }

The compiler will allow you to do pl.add(serena), and sl.add(serena) because serena is a Student and by extension a Person, but -paul can’t be added to sl, because as far as the compiler is concerned, paul is not a Student and 'would not fit'.

What the compiler tries to achieve is that if you define a collection to contain students, then it promises -that only students will go in and will ever come out. It does that by checking and not accepting any potential violation of the promise.

By sticking to this promise, the objects you can receive will be of the correct type and you can do what the type allows, like method calls etc.

Cheating the compiler

You can trick the compiler into breaking its promises. In the end, of course the compiler is not the victim, but the program is. -It will generate a ClassCastException on retrieval of the non-fitting object. And as programs go when they generate uncaught exceptions: -they will be aborted by the JVM.

Sneaky code. Do not do this at home. You are only fooling yourselves.
public static void main( String[] args ) {
-        List<Student> sl = new ArrayList<Student>();
-        sl.add( new Student( "Hermione" )); 1
-        List ol = (List) sl;                2
-        ol.add( new Person("Voldemort") );  3
-
-        // somewhere else in the scenario
-        Student hermione = sl.get( 0 );       4
-        Student lilly = sl.get( 1 );        5
-        System.out.println( "lilly = " + lilly ); 6
-    }
  1. accepted.
  2. give the compiler a raw alias.
  3. so it will gladly accept this. You cheated!
  4. no sweat, our heroine pops out,
  5. but the other object will make your program fail at this statement with a ClassCastException.
  6. Statement never reached because your program will have been terminated by now.

Type token.

towerguard -Sometimes you have a problem, which can only be solved by explicitly providing the generic information to -the code at run-time. This use is called 'providing a type-token'.
An example of such use is the method Collections.checkedList( <List<E>, Class<E> type ),
whose usage example is List<Student> studList = Collections.checkedList( students, Student.class ).
The type token will play the role of a sentinel or guard, because all Class objects have a cast method that checks if the cast is legal, and if it is, -applies the cast to the incoming object; if not it will throw a ClassCastException. -This will prevent even the sneakiest code, including code predating Java 5 and the code above, to sneak in a none-student object. -This guard will also throw an exception, and actually the same one, but now at the side of the culprit, because the exception will be thrown at insertion. -You should not do this as normal usage, because the check on every insertion goes with a cost (CPU cycles), but it can be helpful to detect problems caused -by some naughty code, because now a violation against the promise will result in an exception at run-time, -a ClassCastException to be exact at the moment of insertion, so any sneaky code can be caught red-handed, -leaving a nice big stack trace as a trail for all to see.

There are other uses of type-tokens, which you may see later in the course.

Wildcard and bounds

Read Horstmann section 8.8.

With the definition in the previous paragraphs, you are losing some flexibility, compared to raw types. In particular, you may want -to insert sub types of a class into a collection, or retrieve sub types of a class from a collection.

Using wildcards and bounds will give back some of the flexibility while still upholding type-safety. -The wildcard has been given the character symbol '?', the question mark.

Unbound wildcard.

The unbound wildcard is something that should be used sparingly. The best example is the new definition of the Class type. -In pre Java-5 times that was Class someClass that is a raw class, nowadays it is Class<?> someClass which actually sounds as a class of any kind. -There is little difference but the compiler will be nicer to you. There are some corner cases where you can’t avoid it and then the form using the wildcard -is the correct choice.

Upper bound or Covariant

acceptablesupers
Figure 1. Hierarchy of shapes, assume mathematical shapes, so a line has no width and hence no surface area. Nor does an arc.
Quiz: Can you name the super types of circle in the class diagram above?

Shape, Object, Serializable, ArcLike, Surface, and Circle itself.

When you intend to write code with high flexibility, in particular when you use inheritance as in extends and implements, -then there are a few rules to apply to your generic code. It all has to do with what a container, defined with generic types should accept or will produce.

We will refer to the picture above. We have two scenarios:

  1. A class that makes or supplies shapes. Imagine reading the shape definitions from a drawing file with vector graphics.
  2. A class that should accept shapes, like a canvas on which you can draw.

The supplier or Producer as it is called in the documentation, could be some collection such as a list of any of the types in the class diagram: -Object, Serializable (that is probably how the Objects were read from disk), but the most likely and useful would be the Shape type.

Passing things to be drawn to the canvas.
  interface Canvas {
-    void draw( List<Shape> shapes ); 1
-  }
  1. as far as the draw method is concerned, the list produces the shapes to be drawn.

Now imagine that you have a list of circles: List<Circle> circles = new ArrayList<>(). Then the above definition of the Canvas.draw() method -forbids you to do canvas.draw( circles );, which is quite counter-intuitive, because the class diagram and the code says that a Circle is a Shape. -The compiler will say: 'incompatible types: List<Circle> cannot be converted to List<Shape>'.

This is because, although a Circle is a sub-class of Shape, a List<Circle> is NOT a sub-class of List<Shape>. -To amend this problem, you must change the definition of the draw method so that it accepts shapes but also -any derived type. You do that by adding the wild-card which expresses anything (within limits or bounds).

Flexible shape that also accepts lists of sub-classes of Shape.
  interface Canvas {
-    void draw( List<? extends Shape> shapes ); 1
-  }
  1. This accepts Shape but also anything that is a sub type of Shape, so Circles, Triangles, Lines etc.

This makes the draw method more flexible and reusable, because the draw method can draw any shape now. Type theory calls this kind -of relation between Shape and List of Shape Covariant. They both develop in the same direction, so semantically a List<? extends Shape> is covariant with -Circle extends Shape. You could say, both extend down and therefor there is an upper bound, Shape in the example.

Lower bound or Contra variant

Now imagine that you have a method in some other class that wants to collect Shapes in a collection like a List. -In the documentation such list, which should accept objects, would be called a Consumer.

Passing a container that should receive/consume collected shapes.
  class Collector {
-    // objects are added to be added to receiver or consumer
-    void collect( List<Circle> receiver ){ 1
-      Circle c =...
-      receiver.add( c ); 2
-    }
-  }
  1. Too strict
  2. Problematic line when widening 1 with extends.

Again, the compiler will complain if you call collect with your list of Circles as input. -Use case: you want to tally up the total area covered by circles.

This time it says the same thing on the call statement collector.collect(circles). Let’s apply the same solution as above. -If we define the collect method like void collect( List<? extends Circle> receiver ), the compiler will not complain on this line, but -on the line receiver.add( c ); with a nasty curse:

Compiler complaint.
incompatible types: Circle cannot be converted to CAP#1
-  where CAP#1 is a fresh type variable:
-    CAP#1 extends Shape from capture of ? extends Shape

So extends is not the way to go. You could infer that the list should be defined such that it accepts the Circle but also any type -that circle extends or, from the stand point of the circle, a super type of Circle. You are actually saying that circle and any of its super types will -be acceptable to the receiver. Circle is the lower bound in the example.

Super, that does it.
class Collector {
-    // objects are added to receiver
-      void collect( List<? super Circle> receiver ){ 1
-        Circle c =...
-        receiver.add( c ); 2
-      }
-}
  1. super here makes any List of any super type of Circle acceptable to
  2. the add operation.

The List<? super Circle> in the above example is now contra variant with Circle.

Bounds explained.

The wild card construct with extends or super is called a bounded wild card.

  • With extends, which defines the upper-bound, the boundary is the top most type of the acceptable -types, so that that type defines what can be done with it and all its derivatives, like draw().
    extends puts a upper bound on the element type.
  • With super, which defines the lower-bound, the bound is the bottom-most type of the acceptable types. It defines the receiver -or consumer of the type. Because that lower bound is the sub-most type of the inheritance hierarchy it is it and all above it, which says -that any consumer (receiver) of such super-type will accept the sub-most or lowest type but also any of the super types.
    super puts a bound on what a consumer like a collection, set or list should accept.

Extends and covariance is easiest to understand. Contra-variance less so.
As an example for super, have a look at the Consumer -functional interface. There is a default helper method given that specifies that the Consumer named after in the andThen(..) -method must be of type Consumer<? super T>, -saying that said consumer must accept Ts, but may also accept any super type of T. And again any consumer that accepts T or any of its super types will do. -You will see this pattern throughout the java.util.function package where a default followup method such as andThen or compose is specified.

The last paragraph makes the point why Functional interfaces and all other goodies introduced in Java 8 heavily rely on generics.

Self use in Generic definitions

Generics is not only applicable for collections, although that aspect of the standard Java library is a big customer of this -language feature. But it can be used in other places too, as you have seen in the functional interfaces which define the shape of lambda expressions.

You may have come across a generic type definition that looks a bit weird as in, the type is used in its own definition. -An example of that is the definition of the -Enum class, -which the abstract super class of all enums:

Enum class definition, a bit of a mouth-full.
public abstract class Enum<E extends Enum<E>>
-        implements Comparable<E>, Serializable {
-}

This definition puts bounds or constraints on the type E, which must hence be a sub type (sub class) of Enum itself.

Another one is in the use of Comparable, like in the String class, -it says among others String implements Comparable<String>, to make Strings comparable to each other, but not to other types. -Again a bound or constraint of types, in the example the parameter of the int compareTo(T) method declared in Comparable.

Let us make a simple and silly but instructive example: A zoo. -We want it to be such that a class inherits all the traits of its super, -and we want to be able to use it in a fluent style of programming.

zoo
Figure 2. Mini zoo

In our programs we want to express things like donald.fly().swim().walk(), -which is a common sequence of operations for ducks. We want each of the methods -in the chain to return the duck, so that Donald can do his operations in any order he likes.

This is also understood by the IDE, which will show completions fitting for the duck when the object is a Duck. -For the Bird class on the other hand, the IDE will not complete with swim(), and for the Penguin not with fly().

To make this work, you need to make your classes as you could say self-aware. -It means that the class in the hierarchy defines a method called self() whose return type is that of the class, not one of the super types. -The self-use in the generics declaration makes this work, in combination with the fact that -each leaf class [1] -has a self() method which, through convincing the compiler with generics, returns an object of the correct type. -In the definition of each leaf class, the leaf class itself is the type parameter, like in Penguin implements Bird<Penguin>, Swimmer<Penguin> etc. -In particular, it is not parameterized any further such as Penguin<P>, because they themselves are the desired final type or leaf types.

If you want a non-leaf (abstract) class or interface, your have to further use the self reference in the generic definition.

EBird is an extendable Bird
public interface EBird<EB extends EBird<EB>>  extends Flyer<EB>, Walker<EB>{
-
-     EB afterBurner(){
-         System.out.println("VROOOAAAR");
-         return self();
-     }
-}
Thunderbird is it’s child.
public class ThunderBird implements EBird<ThunderBird>{
-    public static void main( String[] args ) {
-        ThunderBird tb = new ThunderBird();
-        tb.fly().move().walk().fly().brood().fly().afterBurner();
-    }
-}

The code below shows what all this looks like for Bird (providing self), Flyer, and Duck:

Animals can move and are self aware.
package zoo;
-
-public interface Animal<A extends Animal<A>> {
-
-    default A move() {
-        System.out.println( "twitch " );
-        return self();
-    }
-
-    default A brood() {
-        System.out.println( "cosy with my offspring " );
-        return self();
-    }
-
-    // makes object self aware.
-    default A self() {
-        return (A) this;
-    }
-
-}

The common trait of animals is that they can move. It is in the name. Even coral can move, if not very far.

Flyer adds flying functionality.
package zoo;
-
-public interface Flyer<F extends Flyer<F>> extends Animal<F> {
-
-    default F fly() {
-        System.out.println( "swoosh" );
-        return self();
-    }
-}
Ducks are Animals with many traits, all inherited, and little work. You might think ducks are lazy. 🦆 😊
package zoo;
-
-public class Duck implements  Flyer<Duck>, Swimmer<Duck>, Walker<Duck> {
-
-}
you know it is just a model, think movies …​

duckling -The Duck class above is the complete concrete leaf class, nothing has been left out. Everything is in the supers (the interfaces in this case), generics is -just the thread that stitches the Duck together.

This all provides the most benefit in Java 8 and up, because from Java 8 interfaces can have default and static methods, -thereby implementing things, based on the abstract methods the implementing class must provide, as is the case with self(). And even self() is generified out -by way of convincing the compiler that the default implementation does the trick. Minimal code with maximum effect.

The techniques described here are heavily used in the AssertJ library. -The details of this SELF use can be found here .

Testing generics

As explained, generics is mainly used at the source code level and compiler to check the program for correct use of the declared types and methods. As such, testing of the generic aspect of your code is done when your compiler is happy with it AND you have expressed your real intent. Sometimes the last part is the trickiest.

Reading

  • Horstmann, Ed 11, chapter 8, Generic Programming section 8.1-8.8

  1. those at the bottom in the class diagram tree
- \ No newline at end of file diff --git a/docs/index.xml b/docs/index.xml index efc4a47..63243ab 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -1,4 +1 @@ -Docs on PRC2https://fontysvenlo.github.io/prc2/docs/Recent content in Docs on PRC2Hugo -- gohugo.ioen-usWed, 11 May 2022 12:02:24 +020001 Unit Testing Basicshttps://fontysvenlo.github.io/prc2/docs/unit-testing-basics/Tue, 01 Feb 2022 11:32:04 +0100https://fontysvenlo.github.io/prc2/docs/unit-testing-basics/Table of ContentsWrite your own tests!Testing / Test Driven DevelopmentWhat are tests and why do we need them?Test Driven Development (TDD)Arrange Act AssertClues neededAssertJ examples.Simple TestsString ContainmentCollection ContainmentAssert ExceptionsSoft AssertionsAssumptionsAdditional pointersWrite your own tests!Throughout the exercises of PRC1, you have become acquainted with the value of tests: You have a way of checking if your code is any good without having to test each and every part manually. You clicked on the nice TMC button in NetBeans and then you could see which parts of your code worked, and which didn’t.02 Parameterized testshttps://fontysvenlo.github.io/prc2/docs/parameterized-tests/Tue, 01 Feb 2022 14:17:33 +0100https://fontysvenlo.github.io/prc2/docs/parameterized-tests/Table of ContentsParameterized testsParameterized test, Junit 5 styleLookup in a map.Test data from a fileRepeated use of same data.Test Recipe I, Test Equals and hashCodeLinksParameterized testsYou will often see that test methods look a lot like each other. As an example: In the fraction exercise, in most test methods you have two inputs and one or two results, then an operation is done followed by some assertion, often of the same kind.03 Design for testabilityhttps://fontysvenlo.github.io/prc2/docs/design-for-testability/Wed, 02 Feb 2022 11:28:06 +0100https://fontysvenlo.github.io/prc2/docs/design-for-testability/Table of ContentsFaking as a testing techniqueStay cool, have a Mockito.To Mock or To ConfigureDesign for Test-abilityBusiness class fit for testingFaking as a testing technique&#34;When you can’t or won’t make it, Fake it&#34; -Let’s revisit the movie making metaphor. We know that almost everything in that industry is fake, or maybe more politely make-belief. In that it only needs to look convincing without being the real thing. So for instance, when someone needs to be hit hard in the face, it will not really be done and to make sure nothing untoward happens to the expensive actors, they may be replaced by stunt men and women, so the punch will never land on the pretty face.04 Genericshttps://fontysvenlo.github.io/prc2/docs/generics/Wed, 09 Mar 2022 13:53:06 +0100https://fontysvenlo.github.io/prc2/docs/generics/Table of ContentsGenericsGenerics, the fine printCheating the compilerWildcard and boundsLower bound or Contra variantSelf use in Generic definitionsTesting genericsReadingGenericsGenerics is mainly a compiler feature. In java (at least the versions up to this moment, March 2022) it is even more so, because after the compiler had it’s go with it, it largely discards the generic information, and the runtime, the JVM, does not really care about generics and does not use it.05 JPMS, Lambda and Streamshttps://fontysvenlo.github.io/prc2/docs/jpms-lambda-streams/Wed, 16 Mar 2022 13:53:06 +0100https://fontysvenlo.github.io/prc2/docs/jpms-lambda-streams/Table of ContentsJava Platform Module SystemChanges in visibilityJava Modules and TestingBlack box vs White BoxStandard Source code organization.Lambda and StreamsFunctional Interface Rationale&lt;S.H.A.P.E.&gt; of java.util.functionsWhere is toString?Method ReferencesStreams, SQL like operations expressed in Java 8SQL like functionsCollection Enhancements and general tipsImproved Map in Java 8Optional demystifiedReadingJPMSLambda and streamsJava Platform Module SystemThe Java Platform Module System has been introduced with Java 9 and had a long development history at the point of introduction.06 Database Access JDBChttps://fontysvenlo.github.io/prc2/docs/database-access/Wed, 23 Mar 2022 13:53:06 +0100https://fontysvenlo.github.io/prc2/docs/database-access/Table of ContentsJava DataBase ConnectivityDataBase connection credentials and Java PropertiesUsing a Data sourceResultSetAnatomy of a prepared statementTraditional preparing statementsDatabase Meta InformationCheck constraintsTransactionsUsing Stored ProceduresDAO’sJava DataBase ConnectivityDataBase connection credentials and Java PropertiesSome things do NOT belong in source code. In particular do not put credentials of any kind inside files that are committed to a version control system, such as source code. Make sure you configure your version control system such that such files are excluded from commits.07 JavaFXhttps://fontysvenlo.github.io/prc2/docs/javafx/Wed, 30 Mar 2022 13:53:06 +0100https://fontysvenlo.github.io/prc2/docs/javafx/Table of ContentsJava FX BindingsBinding APIsUnbinding DoubleBinding dependenciesCaching bindingsUsing SceneBuilderDefining Widgets using FXMLReadingMore LinksJava FX BindingsThe graphic style of JavaFX is already quite nice. The architecture of JavaFX applied the lessons learned from the earlier Java framework AWT and Swing. -Tip: Swing and AWT are the Java GUI frameworks with which NetBeans IDE and Intelij is built. That proves that these framworks are still in use in the industry. There is another Java GUI framework word mentioning, which is SWT, developed by IBM under the Eclipse flag.08 The State of Thingshttps://fontysvenlo.github.io/prc2/docs/statemachine/Thu, 14 Apr 2022 12:02:24 +0200https://fontysvenlo.github.io/prc2/docs/statemachine/Table of ContentsThe state of thingsImplementing behavior using state machines.State machines and regular expressionsRegular expressionsBasic regex syntax and rulesGroupingThe state of things Implementing behavior using state machines.Systems sometimes have to exhibit some kind of behavior that is different observable output over time. This is different from a pure function, which will always produce the same output for the same input. The behavior differs depending on the &#39;history&#39; the system experienced, typically in the form of &#39;events&#39; or method calls that happened over time.09 Reflectionhttps://fontysvenlo.github.io/prc2/docs/reflection/Wed, 11 May 2022 12:02:24 +0200https://fontysvenlo.github.io/prc2/docs/reflection/Table of ContentsReflectionNoteReflectionReflection enables a developer to write programs that manipulate Java code dynamically. It is an API that enables you to inspect types (classes, interfaces), fields, constructors, methods, packages, annotations etc. during runtime. -Reclection or class introspection is used to leverage the infomation that is avialable in classes. Using the information that is available about a class and by implication about the instances of the class can help to avoid copy-and-waste programming. \ No newline at end of file +Docs on PRC2https://fontysvenlo.github.io/prc2/docs/Recent content in Docs on PRC2Hugo -- gohugo.ioen-usTue, 01 Feb 2022 11:32:04 +010001 Unit Testing Basicshttps://fontysvenlo.github.io/prc2/docs/unit-testing-basics/Tue, 01 Feb 2022 11:32:04 +0100https://fontysvenlo.github.io/prc2/docs/unit-testing-basics/Table of ContentsWrite your own tests!Testing / Test Driven DevelopmentWhat are tests and why do we need them?Test Driven Development (TDD)Arrange Act AssertClues neededAssertJ examples.Simple TestsString ContainmentCollection ContainmentAssert ExceptionsSoft AssertionsAssumptionsAdditional pointersWrite your own tests!Throughout the exercises of PRC1, you have become acquainted with the value of tests: You have a way of checking if your code is any good without having to test each and every part manually. You clicked on the nice TMC button in NetBeans and then you could see which parts of your code worked, and which didn’t. \ No newline at end of file diff --git a/docs/javafx/index.html b/docs/javafx/index.html deleted file mode 100644 index c5cefb5..0000000 --- a/docs/javafx/index.html +++ /dev/null @@ -1,186 +0,0 @@ -PRC2 - 07 JavaFX -
-PRC2

Java FX Bindings

The graphic style of JavaFX is already quite nice. The architecture -of JavaFX applied the lessons learned from the earlier Java framework AWT and Swing.

One of the other important innovations are the JavaFX bindings. -They provide an elegant way to distribute dynamic information throughout a -GUI application, and help separating business and GUI code.

The concept of bindings relies on the Observer Pattern, -which says that a change producing instance can inform an interested party about a change that happenend.

The classic observer pattern informs all of its Observers, or Listeners immediately of and change. -The JavaFX approach is to relax a bit and take the Lazy approach. -Being lazy is good, because avoiding work that is (not yet) needed is a way to reduce work and -in the end speed up an application.

tubmling dominos. source stkinfo.com

dominos2 -The idea is to only invalidate a value when a change happens, and only start the computation when some one -actually needs the result, for instance the GUI layer that needs to do a redraw.
Since Observables can be chained, changing one value may trigger a whole avalanche of computations. -In JavaFx, it is still an avalanche, but of invalidations, and no computations. The computation is only done -when some one needs the result.


JavaFX defines so called Properties in the javafx.beans package and its sub-packages javafx.beans.properties and javafx.beans.bindings.

The Oracle tutorial by Scott Hommel sheds some light on the matter.

The Bindings and Properties packages provide several ways to create bindings, including expressions of various types, -with specialized properties for int, long, and double as the most important -specializations.

Binding APIs

In all examples we use two IntegerProperties.
    IntegerProperty num1 = new SimpleIntegerProperty( 1 );
-    IntegerProperty num2 = new SimpleIntegerProperty( 2 );
Bindings using Fluent API, from Oracle Tutorial
    NumberBinding sum = num1.add( num2 ); 1
-    System.out.println( sum.getValue() );
-    num1.set(2);                          2
-    int result= sum.getValue();           3
-    System.out.println( result );
  1. Creates the binding but does NOT compute anything
  2. Set the value, and invalidates the value, so observers must recalculate.
  3. The get is the observer’s action that actually triggers the actual computation.
Bindings class
    NumberBinding sum = Bindings.add(num1,num2); 1
-    System.out.println(sum.getValue());
-    num1.setValue(2);                            2
-    int result= sum.getValue();                  3
-    System.err.println( result );
  1. Creates the binding but does NOT compute anything
  2. Set the value, and invalidates the value, so observers must recalculate.
  3. The get is the observer’s action that actually triggers the actual computation.
Low level API
    IntegerBinding sum( IntegerProperty n1, IntegerProperty n2 ) {
-        return new IntegerBinding() {      1
-
-            {                              2
-                super.bind( n1, n2 );
-            }
-
-            @Override
-            protected int computeValue() { 3
-                return n1.get()+n2.get();
-            }
-        };
-    }
  1. Anonymous inner class with IntegerBinding as super class.
  2. Anonymous 'constructor' that tells the binding what its dependencies are.
  3. The computation, which can be arbitrarily complex, and is only executed if -some one ask the super for get(), which will then invoke computeValue(). Note the use of protected, so -this method is only callable from within the hierarchy between the IntegerBinding and its anonymous sub class. -computeValue is the only abstract method in abstract class IntegerBinding.
Warning:

Quite often, and certainly when dealing with a GUI, you need to translate the values of a binding into -its String representation. In this case toString() does NOT cut it.
What you need is a StringBinding that is created -from the Binding you want to show as text. That is what public StringBinding asString() in the Binding framework is for.

Unbinding DoubleBinding dependencies

There are use cases where you have to unbind bindings. One such use case is the exercise FXTriangulate. -The DoubleBinding abstract class implements javafx.beans.binding.Binding, which states in its doc that the getDepencies() method should not be used -in production code. We therefore will us a simpler approach.

Since the protected# bind(…​) and protected unbind() methods both take a varargs argument, which is an array in disguise, -the only thing you can do is to remember all bound bindings and -when you have to unbind one, unbind all and then rebind the remaining. -This appears the most reliable method.

The binding and unbinding is necessary to make the binding listen to its dependencies.

The code for that looks like this.
class ModifyableSumBinding extends DoubleBinding {
-
-    Set<ObservableDoubleValue> values = new HashSet<>();
-
-    @Override
-    protected double computeValue() {
-        double result = values.stream()
-                .mapToDouble( ObservableDoubleValue::get ).sum();
-        return result;
-    }
-
-    void add( ObservableDoubleValue v ) {
-        unbind( values.toArray( ObservableDoubleValue[]::new ) ); 1
-        values.add( v );
-        bind( values.toArray( ObservableDoubleValue[]::new ) );
-        invalidate();
-    }
-
-    void remove( ObservableDoubleValue v ) {
-        unbind( values.toArray( ObservableDoubleValue[]::new ) );
-        values.remove( v );
-        bind( values.toArray( ObservableDoubleValue[]::new ) );
-        invalidate();
-    }
-}
  1. Collection.toArray(IntFunction<T[]> generator) is a method introduced in Java 11.

Caching bindings

Creating bindings is powerful but can still lead to a performance bottleneck. If you create bindings willy-nilly, it might very well -be that the same computation is done at several places and in a GUI screen redraw at almost the same time (or at least the reason) too.

To mitigate that problem, it can be wise to cache a binding once it’s created, and serve out the same binding on each subsequent call. -A binding can have many Listeners, and it will dutifully inform all of them with either an invalidation call or change event. -The Bindings themselves cache the value of the (last) computation and serve that out until it is 'discarded' on an invalidate call.

Naive approach using computeIfAbsent.
    DoubleBinding lengthBinding( Line aLine ) { 1
-        return lengthBindings
-            .computeIfAbsent( aLine,
-                l -> lengthBinding(
-                      l.startXProperty(),
-                      l.startYProperty(),
-                      l.endXProperty(),
-                      l.endYProperty()
-                )
-            );
-    }
  1. We use the line object as key in the map

A naive approach to caching is to inspect a value for null and then instantiate one value when it is. -In this case, because both the creation of the cache entries and the retrieval of the vale take place on the event thread, this naive approach works.

Caching a single value.
   /**
-    * Cache for area binding.
-    */
-   private DoubleBinding areaBinding = null;
-
-   /**
-    * Create a binding to compute the area with a triangle determined by side
-    * lengths a, b and c. This method consults the cache and has the value computed
-    *
-    * @return the area binding
-    */
-   final DoubleBinding areaBinding() {
-       if ( areaBinding == null ) {
-           areaBinding = areaBinding( distanceBinding( redCircle, greenCircle ), 1
-                   distanceBinding( greenCircle, blueCircle ),
-                   distanceBinding( blueCircle, redCircle ) );
-       }
-       return areaBinding;
-   }
-   // method that creates the binding is left as an exercise
  1. Compute the binding if not yet available.
Warning:

The approach of inspecting a value for null and then instantiating a new object when null is NOT threadsafe. -The approach works in this case, because it all happens on the event thread, on the same thread, so there will -be no interfering threads.

Using SceneBuilder

When using SceneBuilder the idea is that you design the scene using SceneBuilder, which effectively -is a what you see is what you get FXML editor. The JavaFx, more specifically the FXML part -builds the graphical components or even the complete UI from such FXML files.

You can have multiple FXML files per application, much like you can have many scenes in a move or stage play.

There are various demos on Scenebuilder on youtube.

Creating a first App in Scene Builder
JavaFX Tutorial for Beginners 5 - Installing SceneBuilder and Integrating it into Netbeans IDE
Creating or updating the java controller from the FXML file.

MakeController -One of the most valuable tips from one of those videos is the fact that you can start with -scenebuilder and define your graphical components and methods in SceneBuilder and hence in the -fxml file and that then NetBeans IDE can generate or update the controller for you. -Working in this way can help you avoid typing or (different) spelling errors.

Defining Widgets using FXML

You can use FXML to define whole scenes for your application, but also to make your own specialized components. -You can then layout them as you want and import them into SceneBuilder, where they behave like any other widget already available. -This allows you to extend the available choices, and make special widgets that can play nicely with something like a game or a planning application.

When you want to define your own component, you have to make a few changes to the FXML file and have the component class -load the FXML by itself.

Typical FXML file for an application.
<AnchorPane id="AnchorPane" fx:id="root" prefHeight="564.0" prefWidth="535.0" stylesheets="@stylesheet.css"
-  >
-</AnchorPane>
With your own component, you have to define a fx:root. Example from FXTriangulator exercise.
<fx:root type="javafx.scene.Group" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
-    <Circle fx:id="redCircle" centerX="10.0" centerY="10.0" fill="RED" radius="5.0" />
-    <Circle fx:id="greenCircle" centerX="400.0" centerY="10.0" fill="GREEN" radius="5.0" />
-    <Circle fx:id="blueCircle" centerX="10.0" centerY="400.0" fill="BLUE" radius="5.0" />
-    <Circle fx:id="centerCircle" fill="GREY" radius="5.0" />
-   <Line id="redLine" fx:id="redLine" endX="10.0" endY="400.0" startX="400.5" startY="9.5" stroke="RED" />
-   <Line id="greenLine" fx:id="greenLine" endX="10.0" endY="400.0" startX="10.0" startY="10.0" stroke="GREEN" />
-   <Line id="blueLine" fx:id="blueLine" endX="10.0" endY="10.0" startX="400.0" startY="10.0" stroke="BLUE" />
-   <Label fx:id="redLabel" text="a" />
-   <Label fx:id="greenLabel" text="b" />
-   <Label fx:id="blueLabel" text="c" />
-   <Label fx:id="areaLabel" text="" />
-</fx:root>

From the FXML file you can infer that the widget (in this case) is a specialized java.fx.Group. -It can be any type that is a subclass of Parent (including Parent), -if you want the widget to have sub-components.

Then in the Java file that is both controller and the subclass of the component you want to specialize, Group in the example.

Class header of specialized widget.
public class Triangulator extends Group implements Initializable {
-
-    @FXML
-    Circle redCircle;
-    @FXML
-    Circle blueCircle;
-    @FXML
-    Circle greenCircle;
-
-
-// stuff left out
-    Triangulator( BindingBusiness bb ) {
-        this.bb = bb;
-        FXMLLoader fxmlLoader =
-           new FXMLLoader( getClass().getResource( "triangulator.fxml" ) ); 1
-
-        connectLoader( fxmlLoader ); 2
-
-        try {
-            fxmlLoader.load();
-        } catch ( IOException exception ) {
-            throw new RuntimeException( exception );
-        }
-    }
-
-    final void connectLoader( FXMLLoader fxmlLoader ) { 3
-        fxmlLoader.setRoot( this );
-        fxmlLoader.setController( this );
-    }
-
-// more stuff left out
-}
  1. Load the widgets fxml definitions. This will insert all @FXML annotated fields -and would attach the actions to the @FXML annotated methods.
  2. Connect this to the loaded FXML document.
  3. Use a final or private method to set the root and controller of the loaded object to this, to -avoid the warning of exposing this in the constructor.

In the example widget we have three dots connected with lines. The dots are draggable, while the lines stay connected. -The lengths can be computed using Bindings and are thus automatically updated, and are shown next to the lines.

We will use such a widget in the next example.

Library settings hidden behind a cog-wheel.

SceneBuilderLibraryCog -Importing into scenebuilder is then done using the tiny cog next to the search text-field labeled Library. When you choose Jar/FXML Manager, you can add -widget libraries like you would with maven or point at a library on you machine.

SceenBuilderLibraryManager
Figure 1. Library Manager

Reading

  • Horstmann Core Java, Ed 11, Vol II, Ch 9, Bonus Chapter Volume I on JavaFX
- \ No newline at end of file diff --git a/docs/jpms-lambda-streams/index.html b/docs/jpms-lambda-streams/index.html deleted file mode 100644 index 86ef337..0000000 --- a/docs/jpms-lambda-streams/index.html +++ /dev/null @@ -1,211 +0,0 @@ -PRC2 - 05 JPMS, Lambda and Streams -
-PRC2

Java Platform Module System

The Java Platform Module System has been introduced with Java 9 and had a long development history at -the point of introduction. For the Java developers at Oracle and others involved in the OpenJDK project it -was considered a necessary step to make Java fit for the future and make it possible to shed some old -nasty problems. In the development of Java there have been only a few major release, although each release of -Java introduces new features that are supposed to play nice with what is already available. This is called forward -and backward compatibility, so old programs keep working but can be maintained by using new features.

The important major releases were:

  • Java 1. Duh.
  • Java 1.2 introducing the collection framework.
  • Java 5 introduces Generics and the first version of the concurrency framework. It also changed the naming scheme. Internally it still is Java 1.5.
  • Java 8 introduced a functional style of programming using Functional interfaces and lambda expressions
  • Java 9 introduced the Java Platform Module System, and the project under which it came into being was called JigSaw.
    That last name points to the idea of taking the JDK and run-time library apart and recompose it using a better partitioning, -actually applying separation of concerns in a big way. The project has been a multi year undertaking.

The changes provide the following advantages for the Java platform developers (from project jigsaw)

  • Make it easier for developers to construct and maintain libraries and large applications;
  • Improve the security and maintainability of Java SE Platform Implementations in general, and the JDK in particular;
  • Enable improved application performance; and
  • Enable the Java SE Platform, and the JDK, to scale down for use in small computing devices and dense cloud deployments.

As always with Java, the intent is that the new Java release (9) should be able to work very much the same as the earlier versions, and it does, without -problems. If you use the JVM and JDK in classpath mode, it works very much like older versions. You define the jar files you need for you app and off you go.

Although the intent always has been to make the transition as smooth as possible, when you want to use the module system to the fullest, -it has some consequences.

  • Not everything in the JDK can be used in the same way as before, more protection is taking place as are checks.
  • A module is closed by default and must explicitly export packages, if they are needed from outside the module.
  • The classes in a module can’t be reflected on by default. If that is needed, the package must be made opened for reflection.
  • At the moment of writing these restrictions can be modified with startup flags of the JVM. Later Java releases may change the default settings, -from rather permissive in Java 9 to more restricted in later releases. As an example, some settings became more strict with -Java 16 and JEP 396 Strongly Encapsulate JDK Internals by Default.
Keynote session by Mark Reinhold 2015 introducing JigSaw
module graph
Figure 1. Java SE module graph

Having a modular project or a non-modular project depends on the presence of a small file with a well defined name in a specific place.

The module-info.java file should reside in the root folder of the java sources (which makes the defaul package). The name of the file -is not a valid java type name and that is on purpose. The resulting class file contains the module declarations -which specifies what a module needs (reads in JPMS terms) and what it provides (exports).

The keywords specific to the module declaration are

  • exports What the module provides. Use with one package per line.
  • module starts the declaration
  • open if before the module, opens the whole module for reflection. -For instance for a module that defines all entities of a multi-module application.
  • opens allows reflection of a package. Use with one per package per line.
  • provides To indicate that a module implements some interface with one or more implementations.
  • requires Indicates what the module itself needs or reads. One package per line.
  • transitive Used with requires to indicate that there is a requirement that is then made readable to the users of this module as well.
  • to Used to restrict either exports or opens to specific other module(s).
  • uses To indicate that a module uses a service provided by another module.
  • with Used with provides to indicate which implementation(s) are available for the -provided interface implementation. See the Service Loader API.

As a rule, the module java.base is 'required' by default, the same a java.lang is available or imported by default, considering packages to import.

Some examples of module-info.java files from the PRJ2-AIS-demo-project:

Assembler
module assembler_module{
-    requires persistence_module;
-    requires datarecords_module;
-    requires businesslogic_module;
-    requires gui_module;
-}
DataRecords
module datarecords_module {
-    exports datarecords;
-}
Persistence
module persistence_module {
-    requires datarecords_module;
-    exports persistence;
-}
BusinessLogic
module businesslogic_module {
-    requires transitive datarecords_module;
-    requires transitive persistence_module;
-
-    exports businesslogic;
-}
GUI
module gui_module {
-    requires javafx.controls;
-    requires javafx.fxml;
-    requires java.logging;
-    requires java.base;
-    requires businesslogic_module;
-
-    opens gui to javafx.fxml;
-
-    exports gui;
-}

And another example from our ALDA course in which a Service is provided:

ALDA sorting srevices
module sortingserviceapi {
-    exports sortingservice; 1
-}
-// and
-open module asortingservice {
-    requires java.logging;
-    requires sortingserviceapi;
-    uses sortingservice.SortingServiceFactory; 2
-    provides sortingservice.SortingServiceFactory with asortingservice.SortingServices; 3
-}
  1. defines sortingservice.SortingServiceFactory in package sortingservice; this is an interface.
  2. uses and
  3. provides an inplementation of the SortingServiceFactory interface with a specific implementation: SortingServices.
Warning:

JPMS explicitely forbids:

  • Split packages, that is using the same package name in different modules with different content.
  • Cyclic dependencies as in A requires B requires A.

The dependencies are validated at the startup of the JVM, when you start Java. -If any of the requirements is not met, the JVM refuses to start. This has the benefit -that it is immediately obvious what is missing, instead of having a runtime error, when a class can’t be found, because -the module has not been specified in the path, as is the case with the classpath mode.

The split package has been a bit of an issue for testing, because in a modern project, (business) source and tests are separated into -separate source trees, often with the same package structure, which looks a bit like split packages. The build tools (e.g. maven) understand -this and can organise the files such that the JVM accepts it anyway.

Changes in visibility

Java has always had the visibility modifiers public, protected, default, and private, in descending order of access. -This is still effective with the same rules within a module. However the module boundaries add an extra line of visibility defense. -A public element from module A is only visible to elements in module B if the public elements is part of an exported package. -As far as reflection goes. An element in module A is accessible via reflection only if the package of the element is open (with the opens keyword) -either globally or explicitly to module B ( e.g. module A { exports aPackage to B;}).

Java Modules and Testing

Encapsulation gone wrong: So much for shining security armor.

shiningArmor cropped Project Jigsaw, -now known as the Java Platform Module System solves problems -that lingered in the Java ecosystem since its inception. The standard encapsulation or visibility model -with protection or visibility modes private, package private (default) provide too -little defense against (ab)use of non-public API’s. That plus the fact that reflection is very powerful. -A java security manager can put up some defense against reflection abuse, but enabling this kind of security is optional.[1] -This made many internal details of the JDK free game. Much like a knight in shiny armor is defenseless against a can opener.


This is all fine and good, but testing, in particular Unit testing relies on access to the 'private' parts -of a class and package, in particular the package private parts.

Black box vs White Box

In testing, one distinguishes between so called black box and white box tests.

Black box in this respect means that you cannot see the internals, but only those parts defined in the API. -This is the way that a client package will use the API. Black box testing is easily supported by the module system, because the test classes behave as ordinary -clients.
White box would better be called transparent box, but the name white box stuck. In white box tests you DO want access to the (package) private parts -of your business code. This is a bit handicapped by the way the JPMS allows access.

Standard Source code organization.

source tree of genericdao
src
-├── main
-│   └── java
-│       └── genericdao
-│           ├── dao
-│           ├── memory
-│           └── pgdao
-└── test
-    └── java
-        ├── genericdao
-            ├── dao
-            ├── memory
-            └── pgdao

We are using maven, in which the unit test plugin is called surefire, the integration test plugin (which we -did not use up to this point yet) is called failsafe.

The standard way source code is organized is by means of separate directories for -the 'business' code and the test code in two separate directory trees inside the same project.
This practice, which prevents test code to land in the final application or library jar file is useful, so we will keep it as it is.

However, JPMS does not allow so called split packages. Surefire addresses this problem by telling -the JVM to patch the module we are testing with the classes defined in the test source tree. -It is as if the test classes are put in the business source tree.
This allows the test classes access to the package private parts of the classes, the way it worked in before JPMS.

Module definition of sebi dao.
module nl.fontys.sebidao {
-    exports nl.fontys.sebivenlo.dao;
-    exports nl.fontys.sebivenlo.dao.memory;
-    exports nl.fontys.sebivenlo.dao.pg;
-    requires java.logging;
-    requires java.naming;
-    requires java.sql;
-    requires org.postgresql.jdbc;
-}

The module definition above exports three packages and declares itself -dependent on 3 modules from Java API and one postgresql module.

Unit tests, and in particular the testing libraries such as AssertJ, and Mockito -use reflection to do their work. The simplest example is JUnit itself, which uses reflection to read the annotations -in the class file. So does Mockito. AssertJ uses reflection to get to fields (for deepEquals) and methods. Reflection is a tool very sharp indeed, to -get to the guts of the System Under Test. Of these Test libraries JUnit and AssertJ have module definitions already. Mockito and postgresql do not have that -at the time of writing this document (April 2020).

The quickest way to allow the testing tools access is to fully OPEN the packages of the SUT to world. -Because this happens during testing, surefire is instructed to tell the jvm (java), that runs the test, to -open those packages to the world. The snippet from the maven pom file that realizes that is given below. You can copy -and adapt it for your own purposes.

sebipom project with surefire.opens property to open op required packages for testing. Requires sebipom >= 3.0.2
  <properties>
-    <surefire.opens> 1
-        --add-opens nl.fontys.sebivenlo.genericdao/genericdao.dao=ALL-UNNAMED 2
-        --add-opens nl.fontys.sebivenlo.genericdao/genericdao.memory=ALL-UNNAMED
-        --add-opens nl.fontys.sebivenlo.genericdao/genericdao.pgdao=ALL-UNNAMED
-        --add-opens nl.fontys.sebivenlo.genericdao/usertypes=ALL-UNNAMED
-        --add-opens nl.fontys.sebivenlo.genericdao/entities=ALL-UNNAMED,genericmapper
-    </surefire.opens>
-    3
-  </properties>
  1. We are adding a property for the surefire plugin which is picked up by sebipom.
  2. In particular the arguments that are passed to the JVM that runs the tests, -which appends --add-opens commands for all packages that require unit tests.
  3. Other properties are left out for brevity.

Of particular importance, and project specific are the --add-opens commands, which you must adapt to your own project’s -pom file with your own module and package names.

opens
Figure 2. opens sets module and package

Opens is the most powerful way of exporting a package. It allows full access (as in exports) plus reflection. -We could try to make more specific export lines such as --add-export, but although more precise, that will not help very much, because the -access enabling lines will only take effect during the (surefire) unit tests.
If you have failsafe integration tests, you will have to do -the same for the failsafe plugin, although failsafe tests should stick to black box testing, where such can opener style configuration should not be required.

Lambda and Streams

We will start with a short recap and some refinements on λ-expressions, -followed by application in streams.

Functional Interface Rationale

  • Java: backwards compatible: do not break existing code with new features.
  • Way forward: enhancements to the collection framework.
    • Extending interfaces without breaking implementations
      • add default functions
      • add static helper functions

Example: compare java 7 and java 8 Comparator:

  • The Java 7 version has two abstract methods, which were the only kind in Java before 8.
  • The Java 8 version has one abstract method, making it into a functional interface,
    7 default methods and
    8 static methods whose functionality is now available to every class implementing Comparator.

Have a look yourselves: Java 7 Comparator -and Java 8 Comparator

The concept of functional interfaces or single abstract method is the secret ingredient to let the compiler guess the -appropriate shape of the function that matches a lambda expression.

<S.H.A.P.E.> of java.util.functions

The java.util.functions package, -which was introduced in Java 8, contains only functional interfaces and nothing else. -The package contains the most common shapes of functions that may occur in lambda expressions. -You can see that all reference type functional interfaces heavily rely on generics. The use of the proper generics definition -is essential for functional interfaces to function properly.

We will use the notation <input type> → <result type> for the shape of a Functional interface. -Note that the shape is determined by the one (abstract) method -of the functional interface. The style used in java.util.function is somewhat mnemonic -in choosing the generic type parameters in the type interface type definition.

  • T as only parameter, either a input or output e.g.
    • Consumer<T>: shape <T> → {} black hole? Eats Ts.
    • Supplier<T> : shape () → <T> , source of Ts.
  • Function<T,R>: shape <T> → <R> also known as mapping function, from one type to another.
  • BiFunction<T,U,R>: shape <T,U> → <R>.

There are also a few specialized interfaces that deal with the primitive types int, long and double. -Their rationale is performance and memory footprint, in short: efficiency. Dealing with -e.g. arrays of primitive types is much more efficient than using the associated wrapper types, int vs Integer. -You save the object allocation memory-wise and the dereferencing and unwrapping execution time-wise.

  • ToDoubleBiFunction<T,U>: shape <T,U> → double producing a double from two arguments.
  • LongConsumer: shape long →() munges longs to make them disappear. :-)

The package documentation makes an interesting read.

Exceptions in Lambda expressions need special consideration. In particular checked -exceptions actually counteract the conciseness of lambda expression. -In those cases, where the lambda expression or reference method throws an exception, -that exceptions is commonly wrapped in an unchecked exception, e.g. UncheckedIOException to wrap an IOException. -UncheckedIOException has been introduced in Java 8 for just that purpose.

You can also roll your own.
If you need to declare a functional interface that potentially throws an exception, the following is a way to do that:

throwing functional interface
interface ThrowingBiFunction<T,U, R,<X extends Throwable>> {
-    R apply(T t, U u) throws X;
-}

Shape wise it looks like <T,U> → R | X pronounced as from T and U to either, R the result, or X, the thrown exception.

Where is toString?

You might miss toString() on lambda expressions, and you would be right: Lambda expressions lack a useful toString. What you get -by calling toString() on a lambda expression is something rather useless, concocted by the JVM.

toString() is useless with lambda expressions
  Comparator<Student> gradeComp = (a,b) -> Double.compare(a.getGrade(),b.getGrade());

When you invoke toString() in say jshell, you get.

jshell> class Student{ double grade; double getGrade(){return grade;}}
-|  created class Student
-
-jshell> Comparator<Student> gradeComp = (a,b) -> Double.compare(a.getGrade(),b.getGrade());
-gradeComp ==> $Lambda$16/0x00000008000b3040@1e88b3c
-
-jshell> gradeComp
-jshell> gradeComp.toString()
-$3 ==> "$Lambda$16/0x00000008000b3040@1e88b3c"

Method References

Since the shape of a functional interface has everything a method definition has, bar its name, it should -be no surprise that this can be used to advantage. Everywhere where a functional interface is allowed you can also provide a -so called Method reference. Quite often, but not always this can be a shorthand and thereby improving readability.

Method references have two parts, separated by two colons: ::.

  • The left hand side of the :: defines the method context, either instance of class (for method references to static methods)
  • The right hand side is the method name. The compiler will do the matching in case of overloaded methods, -but sometimes it may give up and will let you know by using the word ambiguous in its complaint.

There are four kinds of references:

Table 1. Source Orace tuturial.
KindExample
Reference to static methodContainingClass::staticMethodName
Reference to an instance method of a particular objectcontainingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular typeContainingType::methodName
Reference to a constructorClassName::new
Reference to a method of this instancethis::instanceMethodName

Streams, SQL like operations expressed in Java 8

Here too, the package documentation is an interesting read.

A stream can be considered a conveyor belt on which objects experience operations. -The belt starts at a source (for instance a Supplier) and can on or more intermediate operations and end with a Terminal operation.

streamops

from Lucas Jellema of amis blogs

Here your see a physical stream and a collect operation.

Conveyor and collecting

SQL like functions

assume you want to know the sum of the wages for women.
select sum(salary)
-from   people
-where  gender = 'FEMALE'
same in Java, get the sum of payments to women.
    int womensPay =
-        people.stream()                                         1
-            .filter(p -> p.getGender() == Person.Gender.FEMALE) 2
-            .mapToInt(p -> p.getSalary())                       3
-            .sum();                                             4
  1. use people collection as source and start stream
  2. where …​ where gender = female
  3. mapping is often retrieving the value of a member, get salary, note that it is a Map ToIntFunction<Person> specialised function
  4. aggregation, like sum() here is a terminal operation
variant using a method reference.
    int womensPay =
-        people.stream()
-            .filter(p -> p.getGender() == Person.Gender.FEMALE)
-            .mapToInt( Person::getSalary )                      1
-            .sum();
  1. This alternative uses a method reference, which can be substituted for a lambda expression when their shapes match.

Collection Enhancements and general tips

It never hurts to browse through an API once in a while. -You might get new ideas or find a cool feature.

Improved Map in Java 8

I personally find the java.util.Map quit interesting because it got a number of very cool features in -Java 7 and 8 that are really very helpful if you want to implement things like caches. -If you are into caching and expect many entries, consider a WeakHashMap, -that also helps to solve the problem of keeping the cache small and auto-evacuate when entries are no longer used.

In order of appearance in the api doc:

  • compute Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).
  • computeIfAbsent If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.
  • computeIfPresent If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.
  • getOrDefault Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.
  • putIfAbsent If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value. -and of course
  • forEach Performs the given action for each entry in this map until all entries have been processed or the action throws an exception.
which allows you to quickly look at the contents of a map with a one-liner
    map.forEach((k, v) -> System.out.println(k + "=" + v));

The fun thing is that all these methods are default methods, so they apply to all classes that implement the java.util.Map interface, including the ones you roll yourselves, -might you feel the inclination. To implement a special map that has specific extra properties, you only need to extend AbstractMap -which needs the implementation of one or two methods only, depending if you want an unmodifiable of modifiable one.

Optional demystified

Many terminal stream operations return an Optional. Rationale: the required element -may not be present, or the Stream is empty to start with. Or the filter throws everything out.

Optional is an interesting concept all by itself.

  • An Optional can be considered as a very short stream of at most one element.
  • You can apply a map operation to an Optional, which transforms the content of the optional (if any) and -produces an optional containing the result of the mapping.
  • You can even stream() the optional, which allows you to apply all operation that a stream provides (since 9).

All this can be used in an algorithm, in which you want to chain a bunch of methods, each of which -requiring that the parameter is non-null.

Traditional returning null
  SomeType result;
-  var a = m1();
-  var b=null;
-  var c=null;
-  if ( null != a ) {
-    b = m2( a );
-  }
-  if ( null != b ) {
-    c = m3( b );
-  }
-
-  if ( null != c ) {
-    result = m4( c );
-  }
-  return result;
-  // do something with result
Using Optional. Returning Optional<SomeType>
  Optional<SomeType> result
-     = m1ReturningOptional() 1
-      .map( a-> m2( a ) )
-      .map( b-> m3( b ) )
-      .map( c-> m4( c ) );
-  return result;
-  // do something with Optional result
  1. The chain starts with a method returning an optional. The remaining methods m2(…​), m3(…​), and m4(…​) are unchanged.

In the last example, an Optional<SomeType> is returned, that can either be unwrapped of passed on as such for further processing.

Reading

JPMS

Lambda and streams

  • Horstmann Core Java, Ed 11, Vol I CH 6.2 Lambda Expressions
  • Horstmann Core Java, Ed 11, Vol I CH 9 Collections
  • Horstmann Core Java, Ed 11, Vol II CH 1 Streams

  1. It was enforced in the now deprecated web applets
- \ No newline at end of file diff --git a/docs/parameterized-tests/index.html b/docs/parameterized-tests/index.html deleted file mode 100644 index 34100b1..0000000 --- a/docs/parameterized-tests/index.html +++ /dev/null @@ -1,213 +0,0 @@ -PRC2 - 02 Parameterized tests -
-PRC2

Parameterized tests

You will often see that test methods look a lot like each other. As an example: -In the fraction exercise, in most test methods you have two inputs and one or two results, -then an operation is done followed by some assertion, often of the same kind. -This quickly leads to the habit of copy and waste programming. Many errors are introduced this way: You copy the original, -tweak the copy a bit and you are done. Then you forget one required tweak, because they are easy to miss, but you do not notice it until too late.

Warning:

Avoid copy and waste at almost all times. It is NOT a good programming style. If you see it in your code, refactor it to -remove the copies, but instead make calls to one version of it. This will make you have less code overall. Coding excellence is never measured -in number of code lines, but in quality of the code. Think of gems. They are precious because they are rare.

The copy and waste problem can even become worse: When the original has a flaw, you are just multiplying the -number of flaws in your code. This observation applies to test code just as well.

CODE THAT ISN’T WRITTEN CAN’T BE WRONG.

Parameterized test, Junit 5 style

Below you see an example on how you can condense the toString() tests of fraction from 5 very similar test methods into 5 strings containing the test data -and 1 (say one) test method.
Paraphrasing a saying: Killing many bugs with one test.

Complete fraction test class with parameterized test, using an in-code csv source
package fraction;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
-import org.junit.jupiter.params.provider.CsvSource;
-
-public class FractionCSVSourceTest {
-
-    @ParameterizedTest
-    @CsvSource( {
-           //"message,          expected,     n,   d", 1
-           "'two thirds',        '(2/3)',     2,   3", 2
-           "'whole number,           '2',     2,   1",
-           "'one third',         '(1/3)',    -3,  -9",
-           "'minus two fifths',  '(-2/5)',   12, -30",
-           "'one + two fifths', '(-1-(2/5))',   35, -25"
-    } )
-    void fractionOps( String message, String expectedString,
-            int numerator, int denominator ) { 3
-
-        Fraction f = new Fraction( numerator, denominator );
-        assertThat( f.toString() )
-                .as( message )
-                .isEqualTo( expectedString );
-    }
-}
  1. Adding a comment is always a good idea. You may also want your values aligned for improved readability.
  2. Parameters are separated by commas, which maybe in the test values. In that case you can demarcate Strings with single quotes inside the csv record string. -If you need another separator instead of comma, you can specify that too, -see CsvSource API .
  3. The parameters are passed in as the type(s) provided by the test method’s signature. The Junit-5 framework will (try to) parse the csv record elements accordingly.

For more details see Junit 5 parameterized tests .

The CsvSource version of parameterized test is the simplest to use and easily understood. -It keeps data and the test together, nicely near to each other, so it make it easy to read the -tests and data in one glance. -They typically fit easily on a screen. Remember the importance of readability of code. That too applies to test code.


Lookup in a map.

Sometimes there is no easy way to get from a string to some object, or even class. -Examples are that you want to test for the correct type or exception, or supply a method name, but you cannot put that into a string without doing complex -things like reflection which we may only do in later parts of the course. The trick is to use a Map that maps a string to the object of choice.

The lookup trick might also be applicable when you want to have short values in your test data table, like a message number which is mapped -to the actual longer expected message, or even a message that is a format string and can be used in Assertion.as( formatString, Object…​ args).

map from short string to test (Person) object.
 static Map<String, Person> emap = Map.of(
-            "piet", new Person("Piet", "Puk", LocalDate.of(1955-03-18),"M"),
-            "piet2", new Person("Piet", "Puk", LocalDate.of(1955-03-18),"M"), // for equals test.
-            "rembrandt", new Person("Rembrandt", "van Rijn", LocalDate.of(1606,7,15),"M"),
-            "saskia", new Person("Saskia", "van Uylenburgh", LocalDate.of(1612,8,11),"F"),
-    );

It is particularly applicable to lambda expressions. -You can translate a string into a lambda, by using a map. You can then use simple names (strings), -that can be put in a csv record.

Map of string to lambda
    final Map<String, BiFunction<Fraction, Fraction, Fraction>> ops = 1
-      Map.of(
-              "times", ( f1, f2 ) -> f1.times( f2 ),
-              "plus", ( f1, f2 ) -> f1.plus( f2 ),
-              "flip", ( f1, f2 ) -> f1.flip(), 2
-              "minus", ( f1, f2 ) -> f1.minus( f2 ),
-              "divideBy", ( f1, f2 ) -> f1.divideBy( f2 )
-      );
  1. Note that we use a BiFunction<T,U,R> existing functional interface, -with T, U, and R all of the same type: Fraction. This is legal.
  2. f2 is not used in the right hand side of the arrow. This is legal too.
Using lambda names in test data
    @ParameterizedTest
-    @CsvSource(
-             {
-                "'one half times one third is 1 sixth', 'times', '(1/6)',1,2,1,3", 1
-                "'one thirds plus two thirds is 1'    , 'plus',      '1',1,3,2,3",
-                "'flip'                               , 'flip',      '3',1,3,1,3", 2
-                "'one half minus two thirds is'       , 'minus', '(-1/6)',1,2,2,3"
-            } )
  1. The operation name is the second value in the csv record, here times. Note that you can quote strings, but that is not required.
  2. In the flip operation, the second fraction is ignored, so any legal value is allowed. Here we use the same values for both fractions.
Test method using the test data (Annotations left out, they are above).
    void fractionOps( String message, String opName, String expected,
-                  int a,int b, int c, int d ) { 1
-        // Arrange: read test values
-        Fraction f1 = frac( a, b ); 2
-        Fraction f2 = frac( c, d );
-        BiFunction<Fraction, Fraction, Fraction> op = ops.get( opName ); 3
-
-        // Act
-        Fraction result = op.apply( f1, f2 ); 4
-
-        // Assert(That) left out as exercise.
-        // Use assertThat on the fraction object result
-        //   and check if it has the correct string value.
-        // Use the message in the as(...) method.
-
-    }
  1. The fraction parameters a,b,c, and d are captured from the csvrecord. This makes the parameter list a
    tad longer, but also more understandable. JUnit 5 csvsource uses the annotation and the signature of the test method and can deal -with most common types such as primitive, String and LocalDate (preferably in ISO-8601 format such as '2012-01-14' for the day of writing this). Strings in the csv-records will be automatically converted to the parameter types in your test methods in these cases.
  2. The fraction instances are created from a, b, c, and d.
  3. The operation (op) is looked up in the map.
  4. Apply the operation, or rather the function and capture the result.

You can apply the same trick of looking up with enums too, even easier, because the enum itself can translate from String to value, -as long as the spelling is exact.

Study the examples above, they might give you inspiration with the exercises coming up and will score you points during the exam.

Test data from a file

Sometimes the amount of data you have in your test data-set is so big that it does not comfortably fit inside a @CsvSoure annotation. -You specify an external file as data source with to achieve the same, the annotation now is @CsvFileSource, which takes files as argument.

The file, as you might have guessed, can be a csv file, which can be edited quite comfortably with a NetBeans plugin -or with the help of a spreadsheet program like Libreoffice calc or Microsoft excel.

Suppose you need to develop an input validator that has many test cases. Putting the inputs in a file along with other information relevant -to your validator.

csvfile source
  @ParameterizedTest
-  @CsvFileSource( resources = { "testdata.csv" } )
-  void testRegex( String message, String input, boolean matches, int groupCount ){
-    // your code here
-  }

Repeated use of same data.

In some cases, multiple tests need the same data. In the case of a CsvSourceFile, that is easily covered: Simple copy the annotations to all places where you need -them. This is an acceptable form of copy and waste, because the annotations all point to one source of truth, the CSV file.

Sometimes you would like to keep or even generate the test data inside the test class. -Do not take the simple and naive route to simply copy-and-waste the (largish) cvssource data, -but instead stick to the D.R.Y. rule.

One problem is that a method in Java can return only one result, either object or primitive type. Luckily -an array is also an object in Java.

There are two solutions to this issue.

  1. Create special test data objects of a special test data class, either inside your or outside your test class -easy to make a test data class to carry the test data. In this case make a data providing method and use the method name in the @MethodSource annotation. -The test method should understand the test data object.
  2. Use the JUnit5 provided ArgumentsProvider. This wraps an array of objects into one object, so that all can be returned as one (array) value in a stream.
    This saves the implementation of a (simple) test data class.

Have a look at the JUnit5 documentation to find a further explanation and examples.


Because we are collecting test tricks, here is another one:

Test Recipe I, Test Equals and hashCode

We may sprinkle our testing stuff with a few recipes for often occurring tests. -This is the the first installment.

Equals and hashCode are not twins in the direct sense, but indeed methods whose implementation should -have a very direct connection. -From the java Object API follows -that:

  • Two objects that are equal by their equal method, than their hashCode should also be equal.
  • Note that the reverse is not true. If two hashCode are the same, that does not imply that the objects are equal.
  • A good hashCode 'spreads' the objects well, but this is not a very strict requirement or a requirement that can be enforced. A poor hashCode -will lead to poor Hash{Map|Set} lookup performance.

Although hashCodes are invented to speedup finding object in a hash map or hash set, these collections use hashCode in the first part of the search, -but must verify equality as final step(s).

The thing is that the equals method must consider quite a few things, expressed with conditional evaluation (if-then-else). -The good thing is an IDE typically provides a way to generate equals and hashCode for you and these implementations are typically of good quality. But -in particular the equals method there are quite some ifs, sometimes in disguise, coded as &&, so this will throw some flies in your code-coverage ointment.

However, we can balance this generated code by a piece of reusable test code, that can be used for almost all cases. -In fact we have not found a case where it does not apply.
Let us first explain the usage and then the implementation.
Suppose you have an object with three fields, name, birthDate and id. All these fields should be considered in equals and hashCode.
As an exercise, create such and object now in your IDE, call it Student, why not.

class Student {
-  final String name;
-  final LocalDate birthDate;
-  final int id;
-}

From the above, the IDE can generate a constructor, equals and hashCode and toString. What are you waiting for? Because it is not test driven? -You would be almost right, but why test drive something that can be generated.
However, if your spec does not demand equals and hashCode, -then do not write/generate them. That would be unwanted code. But if the requirements DO insist on equals and hashCode, -make sure that the fields to be considered match the requirements. Choose only final fields.

After having such a generated equals and hashCode you have the predicament of writing a test. HashCode is relatively easy. It should produce an -integer, but what value is unspecified, so just invoking it would do the trick for test coverage. -The devil is in the equals details, because it has to consider:

  • Is the other object this? If yes, return true.
  • Is the other object null? Return false if it is.
  • Now consider the type.[1].
    • Is the other of the same type as this? If not return false.
    • Now we are in known terrain, the other is of the same type, so the object has the same fields.
      For each field test it this.field.equals(other.field). If not return false.
    • Using Objects.equals(this.fieldA, other.fieldB) can be an efficient solution to avoid testing for nullity of either field.
Generated equals. It is fine.
    @Override
-    public boolean equals( Object obj ) {
-        if ( this == obj ) {
-            return true;
-        }
-        if ( obj == null ) {
-            return false;
-        }
-        if ( getClass() != obj.getClass() ) {
-            return false;
-        }
-        final Student other = (Student) obj;
-        if ( this.id != other.id ) {
-            return false;
-        }
-        if ( !Objects.equals( this.name, other.name ) ) {
-            return false;
-        }
-        return Objects.equals( this.birthDate, other.birthDate );
-    }

You see a pattern here: The number of ifs is 3 + the number of fields.
To test this, and to make sure you hit all code paths, you need to test with this, -with null, with an distinct (read newly constructed) object with all fields equal, -and then one for each field, which differs from the reference object only in said field.

Define those instances (for this example) as follows.

Instances for complete equals and hashCode test and coverage
    //@Disabled
-    @Test
-    void testEqualsAndHash() {
-        Student ref = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 123 );
-        Student equ = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 123 );
-        Student sna = new Student( "Jen", LocalDate.of( 1999, 03, 23 ), 123 ); 1
-        Student sda = new Student( "Jan", LocalDate.of( 1998, 03, 23 ), 123 ); 2
-        Student sid = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 456 ); 3
-        verifyEqualsAndHashCode( ref, equ, sna, sda, sid );
-        //fail( "testMethod reached it's and. You will know what to do." );
-    }
  1. Differs in name.
  2. Differs in birthdate (year).
  3. Differs in id.

The implementation of the verifyEqualsAndHashCode has been done with generics and a dash of AssertJ stuff.

Sample test helper in separate class.
    /**
-     * Helper for equals tests, which are tedious to get completely covered.
-     *
-     * @param <T> type of class to test
-     * @param ref reference value
-     * @param equal one that should test equals true
-     * @param unEqual list of elements that should test unequal in all cases.
-     */
-     public static <T> void verifyEqualsAndHashCode( T ref, T equal, T... unEqual ) {
-         Object object = "Hello";
-         T tnull = null;
-         String cname = ref.getClass().getCanonicalName();
-         // I got bitten here, assertJ equalTo does not invoke equals on the
-         // object when ref and 'other' are same.
-         // THAT's why the first one differs from the rest.
-         SoftAssertions.assertSoftly( softly-> {
-           softly.assertThat( ref.equals( ref ) )
-                   .as( cname + ".equals(this): with self should produce true" )
-                   .isTrue();
-           softly.assertThat( ref.equals( tnull ) )
-                   .as( cname + ".equals(null): ref object "
-                           + safeToString( ref ) + " and null should produce false"
-                   )
-                   .isFalse();
-           softly.assertThat( ref.equals( object ) )
-                   .as( cname + ".equals(new Object()): ref object"
-                           + " compared to other type should produce false"
-                   )
-                   .isFalse();
-           softly.assertThat( ref.equals( equal ) )
-                   .as( cname + " ref object [" + safeToString( ref )
-                           + "] and equal object [" + safeToString( equal )
-                           + "] should report equal"
-                   )
-                   .isTrue();
-           for ( int i = 0; i < unEqual.length; i++ ) {
-               T ueq = unEqual[ i ];
-               softly.assertThat( ref )
-                       .as("testing supposed unequal objects")
-               .isNotEqualTo( ueq );
-           }
-           // ref and equal should have same hashCode
-           softly.assertThat( ref.hashCode() )
-                   .as( cname + " equal objects "
-                           + ref.toString() + " and "
-                           + equal.toString() + " should have same hashcode"
-                   )
-                   .isEqualTo( equal.hashCode() );
-        });
-     }

The above code has been used before but now adapted for AssertJ and JUnit 5.

It is of course best to put this in some kind of test helper library, so you can reuse it over and over without having to -resort to copy and waste.



  1. Not all equals implementation look at the type of this, See the java.util.List doc for a counter example
- \ No newline at end of file diff --git a/docs/reflection/index.html b/docs/reflection/index.html deleted file mode 100644 index 2169170..0000000 --- a/docs/reflection/index.html +++ /dev/null @@ -1,25 +0,0 @@ -PRC2 - 09 Reflection -
-PRC2

Reflection

Reflection enables a developer to write programs that manipulate Java code dynamically. It is an API that enables you to inspect -types (classes, interfaces), fields, constructors, methods, packages, annotations etc. during runtime.

Reclection or class introspection is used to leverage the infomation that is avialable in classes. Using the information that is -available about a class and by implication about the instances of the class can help to avoid copy-and-waste programming. -It helps keeping the code DRY

Please find a nice tutorial here: Baeldung Reflection tutorial

Reflection is a multi-edged tool.

  • Reflection can be used to access parts of instances that would otherwise not be available.
  • Reflection can be used to list information about fields, methods, and constructors.
  • Access via reflection is slower than regular access, because of the safety/security checks that are made on each access
  • It is also slow for the extra indirection needed to do the work when compared to the optimized instructions for say access a field.
  • It is less type-safe, so you loose much of the comfort you have in the IDE, such as code-completion or intellisense(tm). For -instance you lookup a method by name (a String) and that makes you deal with at least one exception. This still does not produce -the actual method, but instead a Method object which you must give the reference to the object on which you want to apply the method and the parameters -to that method in an Object[] array.

Some of the problems can be mitigated a little with the proper amount of smartness, in particular caching previous results.

Reflection is typically used in frameworks, like Unit Testing frameworks or Object Relational Mappers (ORM). We’ll have a look at these -use cases in the lecture.

Note

Reflection should NOT be used on a daily basis but be reserved for special cases, such as making a tool set (maybe called a framework), that can then make work a bit more efficient for the programmer. Frameworks typically pay their keep with a little performance degradation, because they need some extra work either at application startup, in preparation of the executable, when used or all of the former.

If the use of a framework makes the normal programming more complex, you might want to consider not using it. -In all cases, you should be aware of the consequences.

- \ No newline at end of file diff --git a/docs/statemachine/index.html b/docs/statemachine/index.html deleted file mode 100644 index c8decf6..0000000 --- a/docs/statemachine/index.html +++ /dev/null @@ -1,89 +0,0 @@ -PRC2 - 08 The State of Things -
-PRC2

The state of things

Implementing behavior using state machines.

Systems sometimes have to exhibit some kind of behavior that is different observable -output over time. This is different from a pure function, which will always produce the -same output for the same input. The behavior differs depending on the 'history' -the system experienced, typically in the form of 'events' or method calls that happened over time. -Such method calls change the state the system is in. We call such behavior state-full -and this way of operating is often implemented using a 'state machine'. -In this week and in the AADE course we will be using two concepts:

  1. The state (machine) diagram
  2. The State pattern

lamp battery switch A state machine -is a software concept used to implement the behavior of something whose 'reaction' -or outcome depends on the events that happened over time. A very simple example is a lamp and a switch. -When properly connected and none of the parts is defect, the bulb will light up when the switch is in the on state. -As state machine: the switch passes current when it has been turned on, which can be expressed as a turn-on event, making the bulb light up. -From the diagram it should be obvious that a current can only flow if the switch is in the on (or closed) state.

In software we model this as the path the code takes, which then depends on some condition or value.

switch -The state diagram is a way to express the behavior in a graphical manner. Each state of the system is drawn as a box with rounded corners, like in the diagram above. -In the GoF State pattern, which is an object oriented way of modeling state behavior, one uses different -objects to realize the effect of changing the software path. The objects all implement the same interface, but have a different implementation. -Then, whenever a state switching event occurs, the current state object is replaced by the state object for the new state.

Transition
Figure 1. Transitions

From AADE course, we learned that a life cycle of a system changes throw using by so called transitions. To change the state from wait to lock, trigger event was needed with a guard (condition) and then we did some activity.

The killer rabit
Figure 2. state machine diagram

railway switch As another analogy, you can think of a railway switch. -The active state object will always have the same behavior. One makes the train go straight, the other makes it go right. -Each state on itself always does the same, like a pure function, so with the same input (train) the same output is produced. -The switching between states makes it possible to control the direction of the train traffic.

State machines or state charts can be implemented in several ways. In this week we will apply an OO approach using the well known -State Pattern , combined with enums and some Java 8 features, in particular default methods.

The State Pattern has the following UML class diagram:

StatePattern
Figure 3. State Pattern

We deal with a context, an abstract state and multiple concrete state implementations that do the work -and represent the state elements in the diagram. In the diagram above there -would be a state instance for both ConcreteStateA and ConcreteStateB. -In the implementation of the exercise of this week we will be using enum as the basic construct for our work.

Using a functional state approach by using enum has the following advantages:

  • There is no chance of creating new states. Enum ensures that the only -instances there ever will be are created at class loading time. You always know all states there ever will be.
  • A state can be implemented as a pure function, making the number of tests needed minimal. The inputs to the state are -the state of the context (or technically, the state stored in the context), the inputs, and the event or method call.
  • Such functions and thereby state instances can be shared between contexts, because functional states do not have any -state themselves. The actual state is saved in the context. This is not because enums -cannot have state, they can, but because a functional state in a state-machine shouldn’t. -It IS the state and should not have any. It should always do the same. More importantly, they are immutable, -meaning no one can break them by setting some wrong value.
  • A functional state is easily tested without a need for a complex context. -The context can easily be mocked as can be seen in this week’s olifantysballs exercise.

The disadvantage of using functional states in this way is that they cannot keep information themselves, such as counting something. -But quite often the counting involves only a few possible values, which can be states themselves, or can be realized in the context. -As an example for few counting states you might consider a traffic light which counts green, yellow, red.

Warning:

A common error made by novice programmers is to call a method inside a constructor that takes the constructor’s type as a parameter.

Do not do this inside the constructor
    StateMachine(){
-      this.initialState = State.IDLE;
-      this.initialState.enter( this ); 1
-
-    }
  1. Problematic call of someField.enter(this) inside constructor. Something to avoid.

The problem is that the constructor may not yet be complete, in particular when fields are defined and -initialized later in the constructor or even after the constructor definition.

To avoid this problem, you typically have some init() method that should be called after construction or use a factory method that does something similar. -In the gumball machine we chose the later. You can find the details in the javadoc documentation of the exercise.

Use init method.
StateMachine(){
-  this.initialState = State.IDLE;
-
-}
-
-StateMachine init(){
-  this.initialState.enter( this ); 1
-  return this;
-}
Caller has not much extra work.
    StateMachine sm = new StateMachine().init();

State machines and regular expressions

You can find a lot on the relation between state machines and regular expressions. -In most cases, including in Java, the regex is translated or compiled into a statemachine (just) before matches are attempted.

A nice illustration is given by the -REGEXPER website, that turns a regex into a railroad diagram. This make the understanding of a regular expression way easier.

A simple regex : Dutch postal code: "\d{4}\s[A-Z]{2}"

GitHub for simple regex tester

postcode regex
Figure 4. with diagram

The postcode railroad diagram reads as a digit followed by 3 more digits, a space, a letter and one more letter.

More complex example:
// note that you can break off the regex in java and intersperse it with comments.
-String emailRegex="^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+"+ // quite some rubish before the at sign
-     "@"+ // the at sign followed by a more restricted set of chars,
-          //  with some lengths restrictions
-     "[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"+ // optional non capt grp
-     // any number of letter-number combos
-     "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
email regex
Figure 5. Produces this 'railroad' diagram. which is equivalent to a state machine diagram.

Note that there is not one TRUE regex for email addresses. If you want to know the nitty-gritty details -of valid email addresses look at RFC 8398 or RFC 5322, or ask in CSAR class.

Warning:

Once you understand that regexes are state machine specifications, and that it is easy to create state machines -that never 'terminate', you understand that regular expressions can also be dangerous.

Accepting a regular expression -from any source may cause a denial of service attack, because such expression may put your server into an endless loop.

Advice: only accept a restricted set of regexes, maybe parse them before use. In particular avoid back tracking constructs.

Regular expression Denial of Service - ReDoS

So beware.

Regular expressions

An often heard saying is: When you have a problem that can be solved with a regular -expression, you actually have two problems, the problem and how to write the expression.

There is indeed some truth in the saying: Regular expressions can be hard to read (and hence maintain), and sometime hard to write as well.

The problem lies in the fact that the format of the most popular form, the Perl dialect, is quite terse. -However you can do quite a few things to improve at least their readability.

Basic regex syntax and rules

A simple string is also a regular expression. As far as there are no meta characters involved, -a string simply matches itself.

  • The first meta character you need to know is the dot '.', which matches any character. So a line -with a dot will match any line that is the same as the line with any substution for the dot character (including itself).
  • The second meta character is the backslash '\'. It takes the special meaning (as in meta) of the following character away. -Back to our matching line: if you want exactly a dot at the place of the dot, prefix it with a backslash, so it will be understood as a dot. Like this: [red]"\.". -But since we using Java, and the backslash already has a special role in strings, you must double the backslash in most cases, thus: "\\.".
  • The next are the quantifier: how many of something do you want.
    • ? 0 or one, aka at most. So "a?" means at most one a.
    • + at least one.
    • * any number of times, including zero.
    • numberic quantifier, using braces ('{' and '}''). For instance "a{1,4}" means a between 1 and 4 times, inclusive. -You can also write "a{3}" meaning exaclty 3 as. Or a lower (a{2,} for at least 2 a) or upper boundary (a{,12}) at most 12.
  • character classes
    • user specified: [aeiou] matches the English vowels, [a-p] the first 16 characters of the alphabet, [a-pA-P] the same, but ignoring case.
  • predefined :
    • \w = word, which is the set of characters in [a-zA-Z_0-9], so a through z in both upper and lower case, the underscore and the digits 0-9.
    • \W negates the class, in this case it matches the non-word characters
    • \d the decimal digits
    • \D not a digit. -etc.
charMeaning
.match all
+at least one quantifier
?ate most one
{start of quantifier spec
}end of quantifier spec
[start character class
]end character class

The definition as far as java is concerned is given in the java.util.Pattern class. -You do not have to know all details, the summary above suffices for the most cases. The java doc of pattern gives a complete overview.

Grouping

Sometimes you are not just interested in a match or not, but want to capture parts of the input for further processing. -For that you use the parenthesis '(' and ')'. A group, when found, will get a number (or even a name, but that is not in this lesson). -Group 0 is typically the whole expression, and number one the first group identified with the parenthesis.

So if you have a regular expression string line "a(eiou)", group number one will be the group containing the vowel following a, if any.

To get acquainted with regular expressions, it is very helpful to write some tests, to verify your pattern or assumptions on the pattern.

- \ No newline at end of file diff --git a/docs/unit-testing-basics/index.html b/docs/unit-testing-basics/index.html index 042a7fb..a5cc2c7 100644 --- a/docs/unit-testing-basics/index.html +++ b/docs/unit-testing-basics/index.html @@ -1,19 +1,11 @@ -PRC2 - 01 Unit Testing Basics +PRC2 - 01 Unit Testing Basics
PRC2

Write your own tests!

Throughout the exercises of PRC1, you have become acquainted with the value of tests: You have a way of checking if your code is any good without having to +border-slate-500 dark:border-slate-400 text-black dark:text-slate-200">01 Unit Testing Basics

Write your own tests!

Throughout the exercises of PRC1, you have become acquainted with the value of tests: You have a way of checking if your code is any good without having to test each and every part manually. You clicked on the nice TMC button in NetBeans and then you could see which parts of your code worked, and which didn’t. There is a catch though: Out there in the real world, there won’t be any NetBeans button doing that magic for you, nor will some teacher or school provide the tests for you, so you will be on your own.

But fret not! Writing tests is typically much simpler than writing the actual code. At least, it is when you follow a basic set of steps:

  1. Arrange: Prepare or set up the thing you want to test
  2. Act: Interact with the object you are testing
  3. Assert or Ensure that the observable result(s) is/are as expected.
  4. If it says boom, then at least you learned something …​.

Topics week 1

  • Test Driven Development.
  • Maven configuration as exercise.
  • Arrange Act Assert
  • JUnit (5) as test framework.
  • AssertJ as assertion library.

Testing / Test Driven Development

What are tests and why do we need them?

The way that you have worked with Java so far is that you had to write some code diff --git a/index.html b/index.html index 06fac1e..93f74a3 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,11 @@ -PRC2 - Welcome +PRC2 - Welcome

PRC2

Module description

  • Module description can be found on canvas

This site contains the up-to-date course material, exercises and announcements about PRC2 +border-transparent hover:border-slate-400 dark:hover:border-slate-400 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200">01 Unit Testing Basics

Module description

  • Module description can be found on canvas

This site contains the up-to-date course material, exercises and announcements about PRC2 (Programming Concepts 2: the Test Driven Way), starting in February 2023.

Responsible Teachers for 2023 PRC2:

  • Martijn Bonajo
  • Ibrahim Kouzak
  • Richard van den Ham

Most contents on this website were originally developed by Pieter van den Hombergh.

Study materials

  • For the Java basics and advanced features we are using Cay Horstmann’s fine books from the core Java series, 12th edition. They are on the book-list from September.
  • The exercises will be published via a personal github repository.
  • The Junit version is Junit 5.
  • "Using frameworks for testing" will start with using AssertJ, which is a modern and up to date Open Source testing framework. The documentation is of excellent quality and fun to read.
  • JavaFX bonus chapter to Core Java Volume I
  • Core Java, Volume I: Fundamentals
  • Core Java, Volume II: Fundamentals

Getting Started

To work successfully with the programs needed for PRC2, you need to install them first. Properly installing Java and other programs is not hard but must be done precisely. What will follow is a description on how to do that under Ubuntu Linux, macOS and Windows. You can adapt this configuration for other operating systems too, possibly with a few tweaks.

Java Style

Java is a language and languages come with a culture. One such cultural aspect is the way you format your code, like placing your brackets, parenthesis, spaces and curly braces. This coding style is intended to help you read the code, recognize the structure at a glance and find the interesting details easily and spot on.

The official Java style is derived from the Kernighan and Ritchie (K&R) style for the C programming language. Since syntactically Java inherits quite a lot from C, that would be logical choice.

The current and preferred way of using this java style is best described in the Google Java Style Guide.

Where have you put your Curly Braces.

Most holy wars are fought over the placement of the curlies '{}', and in some cases other brackets [] or parenthesis () and <> too. In particular: Put them at the beginning of the line or at the end. The Java Style Guide is quite clear about that: Braces.

My personal motivation would be: If your understand that C and Java are block oriented languages, then you immediately will understand that placing a brace at the beginning of a line diff --git a/sitemap.xml b/sitemap.xml index fa480c6..3ee1b4e 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1 +1 @@ -https://fontysvenlo.github.io/prc2/pages/tips/2022-02-02T10:43:27+01:00https://fontysvenlo.github.io/prc2/docs/unit-testing-basics/2022-02-01T11:32:04+01:00https://fontysvenlo.github.io/prc2/pages/setup/2022-02-01T14:30:36+01:00https://fontysvenlo.github.io/prc2/docs/parameterized-tests/2022-02-01T14:17:33+01:00https://fontysvenlo.github.io/prc2/docs/design-for-testability/2022-02-02T11:28:06+01:00https://fontysvenlo.github.io/prc2/docs/generics/2022-03-09T13:53:06+01:00https://fontysvenlo.github.io/prc2/docs/jpms-lambda-streams/2022-03-16T13:53:06+01:00https://fontysvenlo.github.io/prc2/docs/database-access/2022-03-23T13:53:06+01:00https://fontysvenlo.github.io/prc2/docs/javafx/2022-03-30T13:53:06+01:00https://fontysvenlo.github.io/prc2/docs/statemachine/2022-04-14T12:02:24+02:00https://fontysvenlo.github.io/prc2/docs/reflection/2022-05-11T12:02:24+02:00https://fontysvenlo.github.io/prc2/docs/2022-05-11T12:02:24+02:00https://fontysvenlo.github.io/prc2/2022-02-02T15:04:33+01:00https://fontysvenlo.github.io/prc2/pages/2022-02-02T10:43:27+01:00https://fontysvenlo.github.io/prc2/categories/https://fontysvenlo.github.io/prc2/tags/ \ No newline at end of file +https://fontysvenlo.github.io/prc2/pages/tips/2022-02-02T10:43:27+01:00https://fontysvenlo.github.io/prc2/docs/unit-testing-basics/2022-02-01T11:32:04+01:00https://fontysvenlo.github.io/prc2/pages/setup/2022-02-01T14:30:36+01:00https://fontysvenlo.github.io/prc2/2022-02-02T15:04:33+01:00https://fontysvenlo.github.io/prc2/pages/2022-02-02T10:43:27+01:00https://fontysvenlo.github.io/prc2/docs/2022-02-01T11:32:04+01:00https://fontysvenlo.github.io/prc2/categories/https://fontysvenlo.github.io/prc2/tags/ \ No newline at end of file