diff --git a/css/main.min.095e14fd72b098c88a1463ed59623e8b584d00900f2f292b1b1fe8f924f9a2dc967014725b7ac7682d58dfcad9598ce8f03f6c4d5ecbcb74b739ce746a80594b.css b/css/main.min.095e14fd72b098c88a1463ed59623e8b584d00900f2f292b1b1fe8f924f9a2dc967014725b7ac7682d58dfcad9598ce8f03f6c4d5ecbcb74b739ce746a80594b.css new file mode 100644 index 0000000..ec0d92d --- /dev/null +++ b/css/main.min.095e14fd72b098c88a1463ed59623e8b584d00900f2f292b1b1fe8f924f9a2dc967014725b7ac7682d58dfcad9598ce8f03f6c4d5ecbcb74b739ce746a80594b.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}.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}.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/css/main.min.b5213bfa69338b5bd3f52962686760790723f9149968f60d0aa8a272d6916555ea183899da0901d1cbea63956dcac637a0284fa0469e93b7268e06593f4ad5be.css b/css/main.min.b5213bfa69338b5bd3f52962686760790723f9149968f60d0aa8a272d6916555ea183899da0901d1cbea63956dcac637a0284fa0469e93b7268e06593f4ad5be.css deleted file mode 100644 index 47d82e7..0000000 --- a/css/main.min.b5213bfa69338b5bd3f52962686760790723f9149968f60d0aa8a272d6916555ea183899da0901d1cbea63956dcac637a0284fa0469e93b7268e06593f4ad5be.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}.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/docs/design-for-testability/index.html b/docs/design-for-testability/index.html deleted file mode 100644 index f65ef58..0000000 --- a/docs/design-for-testability/index.html +++ /dev/null @@ -1,248 +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 that the business class could use, which 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 pre-configured data is called a Stub.
As an example: Assume your 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', and '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( 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 and forwards it to the next constructor.
  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", "Oplegkaas", "957" );
-    }

By implementing the AppendAndClear interface you can print to anything, for instance -by using a StringBuilder as intermediate carrier 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 the responsibility of the Main class of the application.

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 3b62dad..0000000 --- a/docs/generics/index.html +++ /dev/null @@ -1,188 +0,0 @@ -PRC2 - 04 Generics -
-PRC2

Generics

Generics is mainly a compiler feature. In java (at least the versions up to this moment, january 2024) it -is even more so, because after the compiler had it’s go with it, that same compiler 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 such as Java 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. The compiler prevent you to stick non-student objects in said list. -So this List exists once and is just as applicable to Student as is to Donkey, if you use the list accordingly -like List<Student> or List<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 the reason 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 they provide safety on the one hand -and the required flexibility on the other, required to make the new 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 make the JVM throw a ClassCastException on retrieval of the non-fitting object. And as programs go, when they generate uncaught exceptions: -they will be killed (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, produces, or supplies shapes. Imagine reading the shape definitions from a drawing file with vector graphics.
  2. A class that should accept, or consume 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.

This 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>{
-
-     default 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 12, 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 e048a58..b8784c7 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -1,2 +1 @@ -Docs on PRC2https://fontysvenlo.github.io/prc2/docs/Recent content in Docs on PRC2Hugo -- gohugo.ioen-usWed, 09 Mar 2022 13:53:06 +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.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, january 2024) it is even more so, because after the compiler had it’s go with it, that same compiler largely discards the generic information, and the runtime, the JVM, does not really care about generics and does not use it. \ No newline at end of file +Docs on PRC2https://fontysvenlo.github.io/prc2/docs/Recent content in Docs on PRC2Hugo -- gohugo.ioen-usTue, 19 Mar 2024 14:43:06 +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.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.05 JPMS, Lambda and Streamshttps://fontysvenlo.github.io/prc2/docs/jpms-lambda-streams/Tue, 19 Mar 2024 14:43: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. \ No newline at end of file diff --git a/docs/jpms-lambda-streams/index.html b/docs/jpms-lambda-streams/index.html new file mode 100644 index 0000000..07ce33b --- /dev/null +++ b/docs/jpms-lambda-streams/index.html @@ -0,0 +1,205 @@ +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 index 1d6ea30..fdf69aa 100644 --- a/docs/parameterized-tests/index.html +++ b/docs/parameterized-tests/index.html @@ -1,4 +1,4 @@ -PRC2 - 02 Parameterized tests +PRC2 - 02 Parameterized tests
@@ -6,9 +6,8 @@

Parameterized tests

You will often see that test methods look a lot like each other. As an example: +border-slate-500 dark:border-slate-400 text-black dark:text-slate-200">02 Parameterized tests

  • 05 JPMS, Lambda and Streams
  • 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, diff --git a/docs/unit-testing-basics/index.html b/docs/unit-testing-basics/index.html index decf01e..a45b54f 100644 --- a/docs/unit-testing-basics/index.html +++ b/docs/unit-testing-basics/index.html @@ -1,4 +1,4 @@ -PRC2 - 01 Unit Testing Basics +PRC2 - 01 Unit Testing Basics

    @@ -6,9 +6,8 @@

    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-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">02 Parameterized tests

  • 05 JPMS, Lambda and Streams
  • 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 774d213..07f53a6 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ -PRC2 - Welcome +PRC2 - Welcome

    @@ -6,9 +6,8 @@

    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">02 Parameterized tests

  • 05 JPMS, Lambda and Streams
  • 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 2024.

    Responsible Teachers for 2024 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 (for the 11th Edition.)
    • 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. +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">02 Parameterized tests

  • 05 JPMS, Lambda and Streams
  • 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. +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">02 Parameterized tests

  • 05 JPMS, Lambda and Streams
  • 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 ace4e4e..17b5256 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/2022-03-09T13:53:06+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/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/docs/parameterized-tests/2022-02-01T14:17:33+01:00https://fontysvenlo.github.io/prc2/docs/jpms-lambda-streams/2024-03-19T14:43:06+01:00https://fontysvenlo.github.io/prc2/docs/2024-03-19T14:43:06+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/categories/https://fontysvenlo.github.io/prc2/tags/ \ No newline at end of file