Miscellaneous — Core Contracts, Collections, Pooling, and Utilities¶
Overview and Motivation¶
The name org.tentackle.misc suggests a junk drawer of leftovers, but that undersells it. The
package holds several of the contracts the upper layers are built from, plus the collections,
pooling and formatting machinery those layers depend on. The clearest evidence is
PersistentObject,
the persistence half of every PDO, which extends
five interfaces defined here:
public interface PersistentObject<T extends PersistentDomainObject<T>>
extends ..., Snapshotable<T>, Modifiable, SerialNumbered, ..., Immutable, ... {
So "is this object modified?", "give me a snapshot to revert to", "what is your id and serial?" and
"can you be made read-only?" are all questions answered by contracts in org.tentackle.misc. They
live here — rather than in the PDO layer — precisely because they are not about persistence: a
plain value object, a UI model, or a collection can implement them just as well, and the framework's
own collections do.
The rest of the package earns its place by being genuinely cross-cutting: the
Pool that both database sessions and TRIP connections are
managed by, the TrackedList that makes OR-mapping
of collections possible, the locale-aware FormatHelper
used in three dozen classes across the framework, and the
SmartDateTimeParser in misc.time
that turns ".+1d15h =paris" into tomorrow, 3pm local time, in Europe/Paris.
This document groups the package by what the classes are for. Two topics that live here are
documented in depth elsewhere and are only cross-referenced below:
Immutable and Snapshotable.
Core Contracts¶
Small interfaces that the rest of the framework implements. They are the reason the package sits so low in the dependency graph.
| Interface | Contract |
|---|---|
Identifiable |
An object with an id (and thus a class + id identity). Extends Serializable. |
SerialNumbered |
An Identifiable that also carries a long serial — a version, sequence or serial number. |
Modifiable |
An object that can tell whether it has been modified. |
Snapshotable<T> |
An object that can take snapshots and revert to them — see snapshot.md. |
Immutable |
An object that can be switched read-only — see immutable.md. |
Convertible |
A type that maps to and from another representation (toExternal / toInternal / getDefault) — how an enum or custom type becomes a database column via ConvertibleType. |
ShortLongText |
An object offering both a short label and a longer description. |
ScrollableResource<T> |
An AutoCloseable resource with a current row (1-based) — the abstraction behind database cursors. |
Snapshotable deserves one clarification that is easy to get wrong: a snapshot is not a copy.
It holds the information needed to revert the object to its state at the time the snapshot was
taken; use copy() if you want an independent object.
Two functional interfaces round this out:
Provider<T,E> and
Acceptor<T,E> mirror Supplier and Consumer but
permit a checked exception — the escape hatch you want when a lambda has to do I/O.
Collections¶
TrackedList — knowing what changed¶
TrackedList<E> (with the concrete
TrackedArrayList) is a List that records
its structural modifications: additions and removals are tracked separately, a replacement
counts as a removal plus an addition, and every removed object is kept in a separate list.
That is exactly what OR-mapping needs. When an aggregate is saved, the persistence layer asks the list what happened: which elements are new (insert), which are gone (delete), which remain (update). Without the removal history, a deleted component would simply vanish from memory with no record that the row must go. The class is not restricted to PDOs, though — any list that needs a dirty flag can use it.
TrackedList also composes with the other contracts, which is what makes aggregates work as a unit:
- it is
Immutable— and setting the list immutable cascades to any elements that are themselvesImmutable; - it is
Snapshotable— with unlimited snapshots, cascading intoSnapshotableelements, and reverting invalidates all later snapshots; - it can be configured to reject
nullelements outright; TrackedListListenerreports the events.
A related question is why OR-mapped relations are lists at all and never sets. Two properties of
PDOs make a hash- or tree-based Set unsound: their hashCode/equals are id-based and change when
a transient object is first persisted, and cyclic graphs are deserialized through partially built
placeholders whose ids are not yet set — either way a set's bucketing would be corrupted, while a
list stores by position and never hashes its elements. See
Object identity — why relations are lists.
Copy-on-write wrappers¶
CopyOnWriteCollection,
CopyOnWriteList and
CopyOnWriteSet wrap an existing collection and
clone it lazily before the first modification, leaving the original untouched.
Not the
java.util.concurrentcopy-on-write classes. Those are thread-safe and copy on every mutation. These are not thread-safe and copy once. The purpose is different: cheaply handing out a collection that the receiver may modify without disturbing the owner.
With iteratorModifying=false every operation works except Iterator.remove(); set it to true
and iterator() copies the collection so the iterator can modify it. CopyOnWriteList has the same
switch for its list iterators (listIteratorModifying, on by default, since List.sort modifies
through them). The views returned by subList() and reversed() may be written to as well, so they
are backed by the copy and create it on demand. The wrapped collection must be Cloneable with a
public clone() method.
Identifiable maps and keys¶
IdentifiableMap<T> maps
Identifiables by a key composed of class and
object id — necessary because ids are only unique per class, so a naive Map<Long,?> would
collide across entity types. IdentifiableKey<T>
is that composite key, usable for hashing or as a Comparable (sorted by class name, then id). The
map starts as a hash map; the first call to getAllSorted() adds a tree map that is maintained
alongside from then on, trading a little speed for ordering only when ordering is actually asked for. It is not thread-safe: build and consume it
from a single thread (as loadComponents() does), or synchronize externally.
Immutable collections¶
ImmutableCollection,
ImmutableArrayList and
ImmutableException implement the switchable
read-only model — see immutable.md for the full contract and how mutators are
guarded.
Pooling¶
A small, complete pooling framework used by the framework's own expensive resources —
DbPool for database
sessions and AbstractConnectionPool for TRIP connections both build on it.
| Type | Role |
|---|---|
Pool<T> |
The pool: get() / put(), sizes, timeouts, shutdown(), monitor() |
Poolable<T> |
What a pooled resource must offer: its pool and its pool id |
PoolSlot<T> |
One slot in the pool |
AbstractPool / AbstractPoolSlot |
Base implementations: min/max sizes, fixed increments, idle timeouts |
PoolTimeoutThread |
One thread for all pools, closing instances idle or in use too long |
A poolable's poolId doubles as its state: 0 when idle in the pool, greater than 0 while lent out,
-1 once removed. Resources are created lazily on the first get(), which also registers the pool
with the timeout thread. A resource that died while idle in the pool (closed by the peer, for
example) is dropped and replaced instead of being handed out, and returning a closed resource
removes it from the pool. The monitor replaces dead idle resources as well, up to the minimum size.
A pool that cannot create a resource without blocking for long overrides
AbstractPool.restoreMinimumSize() to leave that to the next get(), since the monitor runs on the
one thread all pools share.
By default, get() creates a new resource with the pool's monitor held, so no other thread can get
or return a resource meanwhile. A pool whose createSlot() is thread-safe overrides
isCreatingSlotsConcurrently() to return true: get() then only reserves the slot under the
monitor (it counts against the maximum size), creates the resource without holding it, and lends it
to the calling thread. TcpConnectionPool does so, since a TCP connect and TLS handshake may take a
while. The initial resources and those restoring the minimum size are always created under the monitor.
While a resource is lent, its slot holds only a weak reference to it. A resource that was lent but
never returned and has been garbage collected is thus detected by the monitor and removed from the
pool with a warning naming the thread that took it. AbstractPool.getSlotCountPerThread() and
logSlotCountPerThread() report which threads hold how many resources — logged automatically when
the pool is exhausted, since that is often a sign of such a leak.
The timeout thread exists for long-running servers: releasing resources and
capping memory matters more, over days of uptime, than keeping a warm connection around forever.
Both an idle timeout (getIdleMinutes(), honored only above the minimum size) and a maximum usage
time (getUsageMinutes(), counted from the first use) are supported — the latter recycles resources
that have been alive too long regardless of activity. A negative minimum size shuts the pool down
once all of its resources have timed out.
Formatting and Parsing¶
FormatHelper — localized patterns in one place¶
FormatHelper is the framework's most widely used
misc class. It provides the localized format patterns for integers, floating-point numbers,
money, dates, times and timestamps — each in a long and a short variant, and for temporals also
with a zone offset or a time zone.
The patterns come from a resource bundle per locale, so they follow the
i18n story rather than being hard-coded,
and every getter exists both with an explicit Locale and without (using the current locale from
the LocaleProvider). An application that dislikes a default overrides it once at startup with
FormatHelper.applyPatterns(...), passing a Patterns record; every formatter in the application —
including the FX components and table cells —
picks the change up.
SmartDateTimeParser — typing dates the way people think¶
SmartDateTimeParser<T> in
org.tentackle.misc.time parses any Temporal from input that a user would plausibly type.
Instead of demanding a full, correctly formatted date, it accepts shorthand and relative
expressions. The often-quoted example says it best:
Parsing runs in four steps:
- Text filters normalize the input — turning
5/30/21into053021for the number shortcut, expanding+2:30to+02:30, resolvingberlintoEurope/Berlin. - Shortcuts modify the current value according to patterns found in the first word of the
input, removing what they consume. If a shortcut applied, an optional second word sets the zone
offset or time zone — prefixed with
=, it converts the value instead. - If no shortcut applied, the input goes to a regular
DateTimeFormatter. - If that throws, error handlers get a chance to repair the input and retry, each at most once.
The built-in vocabulary:
| Element | Effect |
|---|---|
NowShortcut |
Input starting with ., , or / means now |
AddShortcut |
+10h adds ten hours; units are y, M, d, h, m, s; a bare -10 defaults to days (or hours for time-only types) |
NumberShortcut |
5h → 05:00:00.000; 7M → July 1st, midnight. Sets lesser fields to their minimum. Four and six digits are read as ddMM, ddMMyy, HHmm, HHmmss (in the order of the locale's pattern) |
ZoneShortcut / OffsetShortcut |
A zone or offset; prefixed with = it converts the value, otherwise it just sets it |
DateFilter, ZoneFilter, OffsetFilter |
The normalizing filters |
MissingTimeHandler, MissingZoneIdHandler, MissingOffsetHandler |
The error handlers that fill in what the input omitted: midnight or the rest of a partial time, the default zone, the current offset |
NumberShortcut's sliding window deserves a mention: 010120 becomes 2020-01-01 while 010199
becomes 1999-01-01, not 2099 — a WindowProvider decides where the century flips.
Applications can register their own filters and shortcuts. Most code, though, reaches for
DateTimeUtilities — a @Service singleton
wrapping preconfigured parsers with parseLocalDate, parseZonedDateTime, parseShortLocalTime
and friends for the whole java.time family.
CSV and text¶
CsvConverter— a@Serviceconverting values to and from delimiter-separated strings, RFC 4180 compliant.toCsvLenient()picks the first of comma, semicolon, blank and tab that avoids quoting altogether. Each line is one record, so quoted values must not span lines. It is what the RDC table export writes when tentackle-fx-rdc-poi is absent.SubString— a string segment described by its indexes, materialized lazily, so index arithmetic costs no string allocations.CompoundValue— builds objects from strings: a constant, a$-prefixed dotted reference to a field/method (negatable with!for booleans), or a script in Groovy or JRuby. Used for dynamic parameters in annotations — see CompoundValue for the full syntax and its role in validation.
Utilities¶
| Class | Purpose |
|---|---|
ObjectUtilities |
Object helpers, mainly for CompoundValue conversions; a @Service, so applications extend it for their own types |
DiagnosticUtilities |
Diagnostics (thread dumps and friends); also a @Service |
ConcurrencyHelper |
Concurrency and multithreading helpers |
TimeKeeper |
A nanoTime-based duration with a fluent API — used throughout for timing and statistics |
Canonicalizer<T> |
Makes equal objects share one reference, cutting memory in large object graphs |
CommandLine |
Parses --option / --option=value plus plain arguments |
XUUID |
An id plus a UUID ("id-uuid"), so an external reference can be checked against brute-force guessing without changing the domain logic |
NamedCounter / NamedValue<T> |
Thread-safe counters/values by name — e.g. numbering threads |
InheritableThreadLocalHashTable |
A thread-local hashtable where each child thread gets its own copy |
UserHomeJavaDir |
The per-user cache directory, ${user.home}/.java/tentackle; the system property tentackle.user.home.java.dir replaces ${user.home} |
IdSerialTuple / IdSerialNameTuple |
Id + serial (+ name) tuples, mainly for compact serialization |
Holders¶
Holder<T> is a Consumer and Supplier in one — the
standard trick for setting a value from inside a lambda when the enclosing method needs it back.
VolatileHolder does the same with a volatile
reference, and BlockingHolder turns it into a
tiny handoff: one thread blocks in get() until another calls accept().
Properties¶
PropertiesUtilities (a @Service) loads
java.util.Properties through PropertiesResource
loaders registered with
@PropertiesResourceService. Out of
the box only DefaultPropertiesResource
is present, which tries the name as a file first and as a classpath resource second. An application
can add loaders to override properties from environment variables, a YAML file, or anywhere else —
see the service API. All loaders are
applied in ascending priority, so a higher priority overrides a lower one; without an explicit
priority, the loaders get 100, 200, … in reverse discovery order. A loader annotated with
stopIfNotFound = true stops the chain once it did find the resource — the attribute's name
notwithstanding.
For the common case of overriding keys from the environment — a container deployment passing the
database URL, for instance — applyEnvironment(properties, prefix) does this without a custom
loader: for every key starting with a letter, it looks up the environment variable derived by
toEnvironmentVariable(key) (dots become underscores, other non-alphanumeric characters are dropped,
the rest is upper-cased, e.g. tentackle.url → TENTACKLE_URL), optionally with a prefix such as
MYAPP_, and replaces the value if the variable is set. Only existing keys are overridden; the
framework does not call it by itself.
ChainedProperties is a Properties whose
defaults survive TRIP. A plain Properties keeps its defaults in a java.util field,
which TRIP must not reflect into, so it arrives with its own entries only — silently dropping
everything it inherited. ChainedProperties exposes the chain through a @CanonicalGetter and
takes it back through a @CanonicalConstructor, so the whole chain travels. The entire chain must
consist of ChainedProperties, and must not be cyclic.
PropertySupport,
PropertyEvent and
PropertyListener replace
java.beans.PropertyChangeSupport and friends, which belong to the java.desktop module since Java 9
— a dependency a headless server has no business carrying. Listeners are invoked before the
property changes, so a listener can veto the change by throwing a RuntimeException.
Package Layout¶
| Group | Types |
|---|---|
| Core contracts | Identifiable, SerialNumbered, Modifiable, Snapshotable, Immutable, Convertible, ShortLongText, ScrollableResource, Provider, Acceptor |
| Collections | TrackedList, TrackedArrayList, TrackedListListener, CopyOnWrite{Collection,List,Set}, IdentifiableMap, IdentifiableKey, Immutable{Collection,ArrayList}, ImmutableException |
| Pooling | Pool, Poolable, PoolSlot, AbstractPool, AbstractPoolSlot, PoolTimeoutThread |
| Formatting | FormatHelper, DateTimeUtilities, CsvConverter, SubString, CompoundValue |
| Utilities | ObjectUtilities, DiagnosticUtilities, ConcurrencyHelper, TimeKeeper, Canonicalizer, CommandLine, XUUID, NamedCounter, NamedValue, InheritableThreadLocalHashTable, UserHomeJavaDir, IdSerialTuple, IdSerialNameTuple |
| Holders | Holder, VolatileHolder, BlockingHolder |
| Properties | PropertiesUtilities, PropertiesResource, DefaultPropertiesResource, PropertiesResourceService, ChainedProperties, PropertySupport, PropertyEvent, PropertyListener |
misc.time |
SmartDateTimeParser, the shortcuts (Now, Add, Number, Zone, Offset), the filters (Date, Zone, Offset), the missing-value handlers |
Related Documentation¶
- Tentackle Core — the runtime foundation this package belongs to.
- CompoundValue —
CompoundValueandObjectUtilitiesin depth. - Immutable — the switchable read-only contract and its collections.
- Snapshots and Copies —
SnapshotableandTrackedListsnapshot semantics in depth. - PDO —
PersistentObjectimplements the contracts defined here. - Resource Bundles — where
FormatHelper's localized patterns come from. - Service and Configuration API — how the
@Servicesingletons here are replaced. - Tentackle Database — session pooling and cursors in practice.