Skip to content

Commit 54fce7f

Browse files
committed
Merge branch 'master' of github.com:google/mug
2 parents 393a177 + 5e14d45 commit 54fce7f

File tree

1 file changed

+28
-263
lines changed

1 file changed

+28
-263
lines changed

README.md

Lines changed: 28 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,34 @@ Disclaimer: This is not an official Google product.
33
# Mug
44
A small Java 8+ utilities library ([javadoc](http://google.github.io/mug/apidocs/index.html)), widely used in Google's internal Java codebase, with **0 deps** (Proto, BigQuery, Guava addons are in separate artifacts). ![](https://travis-ci.org/google/mug.svg?branch=master)
55

6-
Offers:
7-
* **Intuitive to read**, powerful string manipulation ([StringFormat](https://github.com/google/mug/wiki/StringFormat-Explained), [Substring](https://github.com/google/mug/wiki/Substring-Explained))
8-
* `new StringFormat("/{user}-home/{yyyy}/{mm}/{dd}").parse(path, (user, yyyy, mm, dd) -> ...)`
9-
* `String user = before(first('@')).from(email).orElseThrow();`
10-
* Streaming pairs ([BiStream](https://github.com/google/mug/wiki/BiStream-Explained))
11-
* `Map<Instant, Long> histogram = zip(times, counts).toMap();`
12-
* `Map<K, V> combined = concat(map1, map2).toMap();`
13-
* `Map<Principal, V> keyedByPrincipal = BiStream.from(keyedByUserId).mapKeys(UserId::principal).toMap();`
14-
* Parse any legit date/time string (without a pattern string):
15-
* `Instant timestamp = DateTimeFormats.parseToInstant("2024-01-30 15:30:00-08")`
16-
* `DateTimeFormats.formatOf("Tue, 10 Jan 2023 10:00:00.123 America/Los_Angeles")`
17-
* Injection-safe SQL templates:
18-
```java
19-
List<String> emails = SafeSql.of("SELECT email from Users where user_name = {user_name}", user.name())
20-
.query(connection, row -> row.getString("email"));
21-
```
22-
* More ([MoreStreams](#morestreams), [Optionals](#optionals), [DateTimeFormats](https://github.com/google/mug/wiki/Parsing-Date-Time-Should-Be-10x-Easier), [...](https://github.com/google/mug/wiki))
23-
* Create `Optional` with a guard condition:
24-
* `return optionally(count > 0, () -> total / count);`
6+
## Highlights
7+
8+
-`BiStream` – streams `Map` and pair-wise collections
9+
`BiStream.map(map).filterKeys(...).mapValues(...).toMap()`
10+
-`Substring` – composable substring extraction
11+
`Substring.between("(", ")").from("call(foo)") → "foo"`
12+
-`StringFormat` – compile-time-safe bidirectional formatting
13+
`new StringFormat("/home/{user}/{year}-{month}-{day}").parse(filePath, (user, year, month, day) -> ...)`
14+
-`SafeSql` – SQL string interpolation with injection protection
15+
`SafeSql.of("select id, ``{col}`` from Users where id = {id}", col, id)` (injection impossible!)
16+
-`DateTimeFormats` – parse datetimes by example
17+
`DateTimeFormatter format = formatOf("2024-03-14 10:00:00.123 America/New_York")`
18+
19+
## Docs
20+
21+
- 📄 [`BiStream`](./mug/src/main/java/com/google/mu/util/stream/README.md)
22+
- 📄 [`Substring`](https://github.com/google/mug/wiki/Substring-Explained)
23+
- 📄 [`StringFormat`](https://github.com/google/mug/wiki/StringFormat-Explained)
24+
- 📄 [`SafeSql`](./mug-guava/src/main/java/com/google/mu/safesql/README.md)
25+
- 📄 [`DateTimeFormats`](./mug/src/main/java/com/google/mu/time/README.md)
26+
27+
<details>
28+
<summary>More tools</summary>
29+
30+
- [`BinarySearch`](./mug-guava/src/main/java/com/google/mu/util/collect/README.md)
31+
- [`StructuredConcurrency`](https://github.com/google/mug/wiki/Structured-Concurrency-Explained)
32+
33+
</details>
2534

2635
## Installation
2736
### Maven
@@ -91,247 +100,3 @@ Add to build.gradle:
91100
```
92101

93102

94-
## StringFormat
95-
96-
Extracts structured data from string:
97-
98-
```java
99-
new StringFormat("/users/{user}/.{hidden_file_name}")
100-
.parse(filePath, (user, fileName) -> ...);
101-
```
102-
103-
```java
104-
new StringFormat("{hour}:{minute}:{second}.{millis}")
105-
.parse(“10:26:30.748”, (hour, minute, second, millis) -> ...);
106-
```
107-
108-
An ErrorProne check is in place to check that the number of lambda parameters and
109-
the parameter names match the format string.
110-
111-
This allows you to define `StringFormat` objects as private class constant, and safely use them
112-
many lines away.
113-
114-
115-
## Substring
116-
117-
**Example 1: strip off a prefix if existent:**
118-
```java
119-
String httpStripped = Substring.prefix("http://").removeFrom(uri);
120-
```
121-
122-
**Example 2: strip off any scheme prefix from a uri:**
123-
```java
124-
String schemeStripped = Substring.upToIncluding(first("://")).removeFrom(uri);
125-
```
126-
127-
**Example 3: split a string in the format of "name=value" into `name` and `value`:**
128-
```java
129-
Substring.first('=').split("name=value").map((name, value) -> ...);
130-
```
131-
132-
**Example 4: replace trailing "//" with "/" :**
133-
```java
134-
Substring.suffix("//").replaceFrom(path, "/");
135-
```
136-
137-
138-
**Example 5: strip off the suffix starting with a dash (-) character :**
139-
```java
140-
last('-').toEnd().removeFrom(str);
141-
```
142-
143-
**Example 6: extract a substring using regex :**
144-
```java
145-
String quoted = Substring.first(Pattern.compile("'(.*?)'"), 1)
146-
.from(str)
147-
.orElseThrow(...);
148-
```
149-
150-
**Example 7: find the substring between the first and last curly braces ({) :**
151-
```java
152-
String body = Substring.between(first('{'), last('}'))
153-
.from(source)
154-
.orElseThrow(...);
155-
```
156-
157-
## Stream
158-
159-
#### [BiStream](https://google.github.io/mug/apidocs/com/google/mu/util/stream/BiStream.html) streams pairs of objects.
160-
161-
This class closely mirrors JDK `Stream` API (the few extra methods of "its own" are very straight-forward). If you are familiar with Jdk stream, learning curve is minimal.
162-
163-
**Example 1: to concatenate `Map`s:**
164-
165-
```java
166-
import static com.google.mu.util.stream.BiStream.concat;
167-
168-
Map<AccountId, Account> allAccounts = concat(primaryAccouunts, secondaryAccounts).toMap();
169-
```
170-
171-
**Example 2: to combine two streams:**
172-
173-
```java
174-
BiStream.zip(requests, responses)
175-
.mapToObj(RequestAndResponseLog::new);
176-
```
177-
178-
**Example 3: to build a Map fluently:**
179-
180-
```java
181-
Map<DoctorId, Patient> patientsByDoctorId = BiStream.zip(doctors, patients)
182-
.filter((doctor, patient) -> patient.likes(doctor))
183-
.mapKeys(Doctor::getId)
184-
.collect(toMap());
185-
```
186-
187-
**Example 4: to build Guava ImmutableListMultimap fluently:**
188-
189-
```java
190-
ImmutableListMultimap<ZipCode, Address> addressesByZipCode = BiStream.from(addresses)
191-
.mapKeys(Address::getZipCode)
192-
.collect(ImmutableListMultimap::toImmutableListMultimap);
193-
```
194-
195-
**Example 5: to
196-
a `Map` into sub-maps:**
197-
198-
```java
199-
import static com.google.mu.util.stream.BiCollectors.groupingBy;
200-
201-
Map<Address, PhoneNumber> phonebooks = ...;
202-
Map<State, Map<Address, PhoneNumber>> statePhonebooks = BiStream.from(phonebooks)
203-
.collect(groupingBy(Address::state, Collectors::toMap))
204-
.toMap();
205-
```
206-
207-
**Example 6: to merge `Map` entries:**
208-
209-
```java
210-
import static com.google.mu.util.stream.BiCollectors.toMap;
211-
import static com.google.mu.util.stream.MoreCollectors.flatteningMaps;
212-
213-
Map<Account, Money> totalPayouts = projects.stream()
214-
.map(Project::payments) // Stream<Map<Account, Money>>
215-
.collect(flatteningMaps(toMap(Money::add)));
216-
```
217-
218-
**Example 7: to apply grouping over `Map` entries:**
219-
220-
```java
221-
import static com.google.mu.util.stream.BiCollectors.toMap;
222-
import static com.google.mu.util.stream.MoreCollectors.flatteningMaps;
223-
import static java.util.stream.Collectors.summingInt;
224-
225-
Map<EmployeeId, Integer> workerHours = projects.stream()
226-
.map(Project::getTaskAssignments) // Stream<Map<Employee, Task>>
227-
.collect(flatteningMaps(toMap(summingInt(Task::hours))));
228-
```
229-
230-
**Example 8: to turn a `Collection<Pair<K, V>>` to `BiStream<K, V>`:**
231-
232-
```java
233-
BiStream<K, V> stream = RiStream.from(pairs, Pair::getKey, Pair::getValue);
234-
```
235-
236-
**Q: Why not `Map<Foo, Bar>` or `Multimap<Foo, Bar>`?**
237-
238-
A: Sometimes Foo and Bar are just an arbitrary pair of objects, with no key-value relationship. Or you may not trust `Foo#equals()` and `hashCode()`. Instead, drop-in replace your `Stream<Pair<Foo, Bar>>`/`List<Pair<Foo, Bar>>` with `BiStream<Foo, Bar>`/`BiCollection<Foo, Bar>` to get better readability.
239-
240-
**Q: Why not `Stream<FooAndBar>`?**
241-
242-
A: When you already have a proper domain object, sure. But you might find it cumbersome to define a bunch of FooAndBar, PatioChairAndKitchenSink one-off classes especially if the relationship between the two types is only relevant in the local code context.
243-
244-
**Q: Why not `Stream<Pair<Foo, Bar>>`?**
245-
246-
A: It's distracting to read code littered with opaque method names like `getFirst()` and `getSecond()`.
247-
248-
#### [MoreStreams](https://google.github.io/mug/apidocs/com/google/mu/util/stream/MoreStreams.html)
249-
250-
**Example 1: to group consecutive elements in a stream:**
251-
252-
```java
253-
List<StockPrice> pricesOrderedByTime = ...;
254-
255-
List<List<StockPrice>> priceSequences =
256-
MoreStreams.groupConsecutive(
257-
pricesOrderedByTime.stream(), (p1, p2) -> closeEnough(p1, p2), toList())
258-
.collect(toList());
259-
```
260-
261-
**Example 2: to iterate over `Stream`s in the presence of checked exceptions or control flow:**
262-
263-
The `Stream` API provides `forEach()` to iterate over a stream, if you don't have to throw checked exceptions.
264-
265-
When checked exception is in the way, or if you need control flow (`continue`, `return` etc.), `iterateThrough()` and `iterateOnce()` can help. The following code uses `iterateThrough()` to write objects into an `ObjectOutputStream`, with `IOException` propagated:
266-
267-
```java
268-
Stream<?> stream = ...;
269-
ObjectOutput out = ...;
270-
iterateThrough(stream, out::writeObject);
271-
```
272-
273-
with control flow:
274-
```java
275-
for (Object obj : iterateOnce(stream)) {
276-
if (...) continue;
277-
else if (...) return;
278-
out.writeObject(obj);
279-
}
280-
```
281-
282-
**Example 3: to merge maps:**
283-
```java
284-
interface Page {
285-
Map<Day, Long> getTrafficHistogram();
286-
}
287-
288-
List<Page> pages = ...;
289-
290-
// Merge traffic histogram across all pages of the web site
291-
Map<Day, Long> siteTrafficHistogram = pages.stream()
292-
.map(Page::getTrafficHistogram)
293-
.collect(flatteningMaps(groupingBy(day -> day, Long::sum)))
294-
.toMap();
295-
```
296-
297-
298-
## Optionals
299-
300-
**Example 1: to combine two Optional instances into a single one:**
301-
```java
302-
Optional<Couple> couple = Optionals.both(optionalHusband, optionalWife).map(Couple::new);
303-
```
304-
305-
**Example 2: to run code when two Optional instances are both present:**
306-
```java
307-
Optionals.both(findTeacher(), findStudent()).ifPresent(Teacher::teach);
308-
```
309-
310-
**Example 3: or else run a fallback code block:**
311-
```java
312-
static import com.google.mu.util.Optionals.ifPresent;
313-
314-
Optional<Teacher> teacher = findTeacher(...);
315-
Optional<Student> student = findStudent(...);
316-
ifPresent(teacher, student, Teacher::teach) // teach if both present
317-
.or(() -> ifPresent(teacher, Teacher::workOut)) // teacher work out if present
318-
.or(() -> ifPresent(student, Student::doHomework)) // student do homework if present
319-
.orElse(() -> log("no teacher. no student")); // or else log
320-
```
321-
322-
**Example 4: wrap a value in Optional if it exists:**
323-
```java
324-
static import com.google.mu.util.Optionals.optionally;
325-
326-
Optional<String> id = optionally(request.hasId(), request::getId);
327-
```
328-
329-
**Example 5: add an optional element to a list if present:**
330-
```java
331-
static import com.google.mu.util.Optionals.asSet;
332-
333-
names.addAll(asSet(optionalName));
334-
```
335-
336-
All Optionals utilites propagate checked exception from the the lambda/method references.
337-

0 commit comments

Comments
 (0)