# NOTES — eight files, and the two lines that decide whether the gate holds a rate

## Run it first

```
..\..\..\..\.toolchain\jdk-21\bin\javac.exe -Xlint:all -d out src\*.java
..\..\..\..\.toolchain\jdk-21\bin\java.exe -cp out Main
```

`javac -Xlint:all` prints nothing and exits 0. Real output, verbatim:

```
1. today's tariff: FlatHourlyPricing, a flat rate per started hour
  car, 1 minute          fee = 2000   stay = PT1M
  car, 61 minutes        fee = 4000   stay = PT1H1M
  car, 3h 1m             fee = 8000   stay = PT3H1M
  truck, 4 hours         fee = 16000  stay = PT4H
  motorbike, 0 seconds   fee = 1000   stay = PT0S

2. the new tariff: TieredPricing, first hour free
  car, 1 minute          fee = 0      stay = PT1M
  car, 61 minutes        fee = 1000   stay = PT1H1M
  car, 3h 1m             fee = 5000   stay = PT3H1M
  truck, 4 hours         fee = 10000  stay = PT4H
  motorbike, 0 seconds   fee = 0      stay = PT0S

3. one gate, two tariffs: the same stay after the change
  new ExitGate(new TieredPricing())    5000
  new ExitGate(new FlatHourlyPricing()) 8000  (last month's revenue is still billable)

4. what both tariffs agree on, because it is not behind the seam
  car, 1 minute          started hours = 1
  car, 61 minutes        started hours = 2
  car, 3h 1m             started hours = 4
  truck, 4 hours         started hours = 4
  motorbike, 0 seconds   started hours = 1
```

Two rows in that output are worth stopping on before reading any code.

`car, 3h 1m` costs 8000 on one tariff and 5000 on the other, and it is the requirement's own
worked example: four started hours, free then 1000 then 1000 then 3000. Block 3 bills the same
stay both ways from one `ExitGate`, which is the property that survives the tariff change.

`motorbike, 0 seconds` costs 1000 today and 0 next month. That is not a bug in either tariff. The
contract says a zero-length stay is one started hour, and the new requirement says the first
started hour is free. Two correct rules compose into free parking for a vehicle that never
stopped. Raise that out loud in a round rather than discovering it in the suite.

---

## `PricingPolicy.java` — the seam

```java
long feeMinor(VehicleType type, Duration stay);
```

**One method, and the whole design rests on which arguments it takes.** `VehicleType` rather than
`Vehicle`: a registration plate is not a pricing input, and passing the whole vehicle would let a
future tariff quietly start charging by plate. `Duration` rather than two `Instant`s: the gate has
already measured the stay, so every tariff is spared the subtraction and none of them can disagree
about it.

**`long`, and the unit is minor units.** A `double` fee loses paise on the way to a receipt, and no
audit forgives that. This is the contract's rule, not a preference: *"Money is always minor units
(paise/cents) in a `long`. No doubles, no rounding of currency."*

**One abstract method also means the interface is a lambda.** `(type, stay) -> 0L` is a valid
`PricingPolicy`, which is why a test double needs no file. Add a default body to this method and
that stops being true:

```
Stub.java:3: error: incompatible types: PricingPolicy is not a functional interface
    static final PricingPolicy FREE = (type, stay) -> 0L;
                                      ^
    no abstract method found in interface PricingPolicy
1 error
```

**What a default body would break is worse than the lambda.** A tariff someone writes next quarter
and forgets to implement would compile and charge nothing, on every stay, silently.
`faded/GapTest.java` asserts this interface has exactly one unanswered question for that reason.

```java
static long startedHours(Duration stay) {
    long seconds = Math.max(0L, stay.toSeconds());
    return Math.max(1L, (seconds + 3599L) / 3600L);
}
```

**This is the boundary of the seam, and drawing it here is the judgement the lesson is about.**
Rates vary. The unit they are charged in does not. A `RoundingPolicy` interface would be a second
seam with one implementation, which `when-not.md` measures the cost of.

**`(seconds + 3599L) / 3600L` is integer ceiling division, and `toHours()` is the trap.**
`Duration.ofMinutes(61).toHours()` returns 1, so a tariff written that way charges one hour for 61
minutes. Block 4 of the output is the check: 61 minutes is 2.

**`Math.max(1L, …)` is the contract's floor, not defensive habit.** *"A zero-length stay costs one
hour."* Without it, a vehicle that arrives and leaves in the same second parks free under every
tariff, including the flat one.

**`Math.max(0L, …)` on the seconds guards a case the contract says cannot happen.** `ExitGate`
rejects an exit before an entry, so `stay` is never negative here. It costs one call and it means
this method cannot return a negative unit count to a tariff that would multiply by it.

**Static, on the interface, rather than a shared abstract base class.** An abstract
`AbstractPricingPolicy` would give every tariff a superclass it does not need, and Java has one of
those to spend. J5 covers the choice; here the point is that a helper both implementations call is
not a reason to make them siblings.

---

## `FlatHourlyPricing.java` — the implementation that exists today

```java
private static final Map<VehicleType, Long> PER_STARTED_HOUR = Map.of(
        VehicleType.MOTORBIKE, 1000L,
        VehicleType.CAR, 2000L,
        VehicleType.TRUCK, 4000L);
```

**`static final` on a table shared by every instance, and `Map.of` makes it unmodifiable.** No
caller can reach it and no method can add to it, so the rates in this file are the rates in
production. `Map.of` also refuses a duplicate key at class-initialisation time with
`IllegalArgumentException: duplicate key: CAR`, which is the sort of paste error a rate table
attracts.

```java
private long rateFor(VehicleType type) {
    Long rate = PER_STARTED_HOUR.get(type);
    if (rate == null) {
        throw new IllegalArgumentException("no tariff configured for " + type);
    }
    return rate;
}
```

**The null check is what stops a new vehicle type parking free.** Add `BICYCLE` to `VehicleType`
and this file still compiles. Measured, by adding that constant and billing a bicycle for an hour:

```
Exception in thread "main" java.lang.IllegalArgumentException: no tariff configured for BICYCLE
	at FlatHourlyPricing.rateFor(FlatHourlyPricing.java:21)
	at FlatHourlyPricing.feeMinor(FlatHourlyPricing.java:15)
	at ExitGate.charge(ExitGate.java:34)
```

Loud, at the till, on the first bicycle. Without the check, `rate` is `null`, the multiplication
unboxes it and you get a `NullPointerException` with nothing in the message about tariffs.

**Here is the one axis on which `contrast/a`'s conditional beats this table, and it is real.**
Design A writes the rates as a `switch` expression over the enum with no `default`, so adding
`BICYCLE` is a compile error:

```
ExitGate.java:28: error: the switch expression does not cover all possible input values
        long ratePerStartedHour = switch (type) {
                                  ^
1 error
```

Compile time beats run time. A table plus a throw is the corpus's choice because tariffs are data
that finance edits. An exhaustive `switch` is the better tool when the set of cases is closed and
known while you compile. Knowing which one you picked, and why, is the D3 answer; A1 covers the
enum side of it.

---

## `TieredPricing.java` — the second implementation, added not edited

**This whole file is the answer to curveball 02, and no existing tariff was touched.**
`corpus/parking-lot/curveballs/02-tiered-pricing/budget.json` records `reference_diff: 2`, and the
2 is the convenience-constructor line in `ExitGate`, not anything in here.

```java
private static final long FREE_HOURS = 1;
private static final long STANDARD_UNTIL_HOUR = 3;
```

**Two named constants so the requirement's sentences are findable in the code.** When finance says
"make it two free hours", the edit is one number in one place, and it is the number the sentence
named.

```java
long fee = 0;
long hours = PricingPolicy.startedHours(stay);
for (long hour = FREE_HOURS + 1; hour <= hours; hour++) {
    fee += rate(hour <= STANDARD_UNTIL_HOUR ? STANDARD : LONG_STAY, type);
}
return fee;
```

**Billing hour by hour is slower than the closed-form version and it is the right call.** The
closed form is three multiplications and two `Math.min` calls, and every one of them is a place to
put the boundary off by one. This loop reads as the requirement reads: hour 1 free, hours 2 and 3
standard, the rest long-stay. Five started hours here is 0 + 1000 + 1000 + 3000 + 3000.

**The loop starting at `FREE_HOURS + 1` is where the free hour lives.** Start it at 1 and a car
staying 3h 1m pays 6000 instead of 5000. That is the failure `faded/GapTest.java` catches by name,
because it is the mistake this arithmetic invites.

**Calling `PricingPolicy.startedHours` rather than dividing by 3600 again is the invariant.** Both
tariffs round identically, so no stay can be two started hours to the flat tariff and one to this
one. A tariff that rounds for itself passes every test you write for it and disagrees with its
sibling at the 61-minute boundary.

---

## `ExitGate.java` — the caller that asks

```java
private final PricingPolicy pricing;
```

**`final` means no code path can leave this gate without a tariff, and none can swap it mid-stay.**
Drop `final` and a setter becomes possible, and a gate whose tariff changes between `park` and
`unpark` bills a stay under rules that were never in force for it.

```java
public ExitGate() {
    this(new FlatHourlyPricing());
}
```

**This line is the entire measured cost of the tariff change.** `contrast/` puts it at
`diffLines: 2`, one added and one removed, and `corpus/parking-lot`'s own reference patch changes
the same single line in `ParkingLot`'s convenience constructor. Wiring named in exactly one place
is what makes the number 2 rather than 12.

**The no-argument constructor exists so `Entry.create()` stays one line.** That is the corpus's
reason, and it is worth keeping: a design that needs a paragraph of assembly to instantiate is a
design an interviewer cannot run.

```java
public ExitGate(PricingPolicy pricing) {
    this.pricing = Objects.requireNonNull(pricing, "pricing");
}
```

**The check fails at construction rather than at the first fee.** A gate built with a null tariff
and no check throws `NullPointerException` on the first vehicle to leave, in `charge`, pointing at
a line that is not the mistake. C3 is the general form of this: dependencies arrive through the
constructor and are validated there.

```java
if (exitTime.isBefore(entryTime)) {
    throw new IllegalArgumentException("exit before entry for ticket " + ticketId);
}
```

**Rejected explicitly, with the ticket id in the message.** The contract requires this, and it is
also what keeps `startedHours` from ever seeing a negative duration. The message names the ticket
because a fee dispute starts with a ticket id and nothing else.

```java
Duration stayed = Duration.between(entryTime, exitTime);
return new Receipt(ticketId, pricing.feeMinor(vehicle.type(), stayed), stayed);
```

**The gate measures and the policy decides, and this line is where that split is either honoured or
lost.** `stayed` goes to the policy unrounded and onto the receipt unrounded. Round it here first,
say by passing `Duration.ofHours(stayed.toHours())`, and the same stay is rounded twice. 3h 1m
becomes 3 hours, then 3 started hours, and a car pays 6000 instead of 8000.

**`Receipt.stay()` is exact and only the fee is rounded.** That is the contract's sentence, and the
output above shows it: `stay = PT3H1M` next to a fee computed from four started hours.

**Nothing in this file names a rate.** Search it for a number and you find none. That is the
property the D3 anchor is describing when it says variation points sit behind interfaces, and it is
checkable in one look.

---

## `Main.java` — the driver

**A missing driver caps D2 at 0, whatever the code does.** Interviewers run it first. This one
prints both tariffs over the same five stays rather than one happy path, which is the difference
between D2 level 2 and level 3.

```java
private record Stay(String label, VehicleType type, Duration length) {}
```

**A local record so the five cases are data, not five copied blocks.** Each stay sits on a boundary
the two tariffs disagree about: one minute, the 61-minute rounding step, the requirement's own 3h
1m example, the fourth hour, and a zero-length stay.

```java
System.out.printf("  new ExitGate(new FlatHourlyPricing()) %d%n", …);
```

**Block 3 is the argument for the seam, in one line of output.** After the tariff change, a gate on
last month's rates is still constructible, still billable, and still 8000. In `contrast/a-after`
that line does not compile, because `FlatHourlyPricing` was never a class there.
