Prototype: tokenize(text) lowercases with Locale.ROOT then returns ASCII [a-z0-9]+ runs in order with duplicates; punctuation/non-ASCII characters separate runs. New list each call.
Final: SearchEngine validates nonempty document IDs. add returns false on duplicate preserving text; get is nullable; update/delete return false when absent. search(term,limit) requires exactly one query token even at limit zero. searchPhrase(phrase,limit) counts consecutive-token starts, including overlaps; empty phrase yields empty. Both return Hit(documentId,score), sorted score descending/ID, at most limit; negative limit raises. stats counts current documents (including empty) and current tokens. No stale data may remain after replacement/deletion/reuse.
The complete public API is in Exercise.java. All domain classes are public static nested classes of Exercise; construct them as new Exercise.ClassName(...). Do not implement a solution in the test runner. Each instance owns independent state. Unless explicitly allowed, null inputs are outside the domain. Failed operations leave state unchanged; validation happens before mutation.
Complete the starter code, then run the checks.
Results and errors will appear here.
Java runs on this device with CheerpJ 4.3.