Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions content/collections/collectors-teeing.json

This file was deleted.

48 changes: 48 additions & 0 deletions content/collections/collectors-teeing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
id: 26
slug: collectors-teeing
title: Collectors.teeing()
category: collections
difficulty: intermediate
jdkVersion: "12"
oldLabel: Java 8
modernLabel: Java 12+
oldApproach: Two Passes
modernApproach: teeing()
oldCode: |
long count = items.stream().count();
double sum = items.stream()
.mapToDouble(Item::price)
.sum();
var result = new Stats(count, sum);
modernCode: |
var result = items.stream().collect(
Collectors.teeing(
Collectors.counting(),
Collectors.summingDouble(Item::price),
Stats::new
)
);
Comment on lines +11 to +24
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using YAML block scalars with | will include a trailing newline in the resulting oldCode/modernCode strings, which can make generated output differ from the previous JSON version. If you want byte-for-byte stability, use |- (strip final newline) for code blocks or strip a single trailing newline in the generators when reading YAML.

Copilot uses AI. Check for mistakes.
summary: Compute two aggregations in a single stream pass.
explanation: Collectors.teeing() sends each element to two downstream collectors and merges the results. This avoids streaming the data twice or using a mutable accumulator.
whyModernWins:
- icon: ⚡
title: Single pass
desc: Process the stream once instead of twice.
- icon: 🧩
title: Composable
desc: Combine any two collectors with a merger function.
- icon: 🔒
title: Immutable result
desc: Merge into a record or value object directly.
support:
state: available
description: Widely available since JDK 12 (March 2019)
prev: collections/sequenced-collections
next: collections/stream-toarray-typed
related:
- collections/copying-collections-immutably
- collections/unmodifiable-collectors
- collections/stream-toarray-typed
docs:
- title: Collectors.teeing()
href: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/Collectors.html#teeing(java.util.stream.Collector,java.util.stream.Collector,java.util.function.BiFunction)
36 changes: 25 additions & 11 deletions html-generators/generate.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 25
//DEPS com.fasterxml.jackson.core:jackson-databind:2.18.3
//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3

Comment on lines 1 to 5
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR adds YAML support for patterns, but the Pages deploy workflow currently only triggers on content/**/*.json. That means future changes to .yaml/.yml patterns won’t automatically redeploy the site. Update the workflow path filters to include YAML extensions so the feature works end-to-end in CI.

Copilot uses AI. Check for mistakes.
import module java.base;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

/**
* Generate HTML detail pages from JSON snippet files and slug-template.html.
Expand All @@ -14,7 +16,13 @@
static final String CONTENT_DIR = "content";
static final String SITE_DIR = "site";
static final Pattern TOKEN = Pattern.compile("\\{\\{(\\w+)}}");
static final ObjectMapper MAPPER = new ObjectMapper();
static final ObjectMapper JSON_MAPPER = new ObjectMapper();
static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory());
static final Map<String, ObjectMapper> MAPPERS = Map.of(
"json", JSON_MAPPER,
"yaml", YAML_MAPPER,
"yml", YAML_MAPPER
);

static final String CATEGORIES_FILE = "html-generators/categories.properties";
static final SequencedMap<String, String> CATEGORY_DISPLAY = loadCategoryDisplay();
Expand Down Expand Up @@ -100,7 +108,7 @@ void main() throws IOException {
// Rebuild data/snippets.json
var snippetsList = allSnippets.values().stream()
.map(s -> {
Map<String, Object> map = MAPPER.convertValue(s.node(), new TypeReference<LinkedHashMap<String, Object>>() {});
Map<String, Object> map = JSON_MAPPER.convertValue(s.node(), new TypeReference<LinkedHashMap<String, Object>>() {});
EXCLUDED_KEYS.forEach(map::remove);
return map;
})
Expand All @@ -127,14 +135,20 @@ SequencedMap<String, Snippet> loadAllSnippets() throws IOException {
for (var cat : CATEGORY_DISPLAY.sequencedKeySet()) {
var catDir = Path.of(CONTENT_DIR, cat);
if (!Files.isDirectory(catDir)) continue;
try (var stream = Files.newDirectoryStream(catDir, "*.json")) {
var sorted = new ArrayList<Path>();
stream.forEach(sorted::add);
sorted.sort(Path::compareTo);
for (var path : sorted) {
var snippet = new Snippet(MAPPER.readTree(path.toFile()));
snippets.put(snippet.key(), snippet);
}
var sorted = new ArrayList<Path>();
// first collect and sortall files
for (var ext : MAPPERS.keySet()) {
try (var stream = Files.newDirectoryStream(catDir, "*." + ext)) {
stream.forEach(sorted::add);
}
}
sorted.sort(Path::compareTo);
for (var path : sorted) {
var filename = path.getFileName().toString();
var ext = filename.substring(filename.lastIndexOf('.') + 1);
var json = MAPPERS.get(ext).readTree(path.toFile());
var snippet = new Snippet(json);
snippets.put(snippet.key(), snippet);
}
}
return snippets;
Expand All @@ -145,7 +159,7 @@ String escape(String text) {
}

String jsonEscape(String text) throws IOException {
var quoted = MAPPER.writeValueAsString(text);
var quoted = JSON_MAPPER.writeValueAsString(text);
var inner = quoted.substring(1, quoted.length() - 1);
var sb = new StringBuilder(inner.length());
for (int i = 0; i < inner.length(); i++) {
Expand Down
11 changes: 9 additions & 2 deletions html-generators/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""Generate HTML detail pages from JSON snippet files and slug-template.html."""

import json
import yaml
import glob
Comment on lines 2 to 6
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import yaml adds a new runtime dependency (PyYAML). As-is, running python3 html-generators/generate.py will fail with ImportError on machines that don’t already have it. Consider importing YAML conditionally with a clear error message (e.g., instruct to install PyYAML), and update the module docstring to reflect JSON+YAML support.

Copilot uses AI. Check for mistakes.
import os
import html
Expand Down Expand Up @@ -73,10 +74,16 @@ def load_all_snippets():
snippets = {}
json_files = []
for cat in CATEGORY_DISPLAY:
json_files.extend(sorted(glob.glob(f"{CONTENT_DIR}/{cat}/*.json")))
json_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.json"))
json_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.yaml"))
json_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.yml"))
json_files.sort()
Comment on lines 75 to +80
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new json_files.sort() globally sorts all snippet paths across categories, which changes the insertion order of all_snippets (and therefore the order of cards in index.html and entries in site/data/snippets.json). Previously ordering followed categories.properties category order. Also, this makes Python output diverge from generate.java, which still preserves category order. Preserve per-category ordering (sort within each category) or update both generators to use the same deterministic ordering strategy.

See below for a potential fix:

    # Preserve category order from CATEGORY_DISPLAY and sort files within each category
    for cat in CATEGORY_DISPLAY:
        cat_files = []
        cat_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.json"))
        cat_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.yaml"))
        cat_files.extend(glob.glob(f"{CONTENT_DIR}/{cat}/*.yml"))
        for path in sorted(cat_files):
            json_files.append(path)

Copilot uses AI. Check for mistakes.
for path in json_files:
with open(path) as f:
data = json.load(f)
if path.endswith(".yaml") or path.endswith(".yml"):
data = yaml.safe_load(f)
else:
data = json.load(f)
key = f"{data['category']}/{data['slug']}"
Comment on lines 82 to 87
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YAML parsing can produce non-string scalars (e.g., jdkVersion: 12 becomes an int). replace_tokens() expects all replacement values to be strings; returning an int from the replacer will raise a TypeError in re.sub. To make YAML support robust, coerce token values to strings (e.g., in replace_tokens() or normalize fields like jdkVersion right after safe_load).

Copilot uses AI. Check for mistakes.
data["_path"] = key
snippets[key] = data
Expand Down