{"record":{"id":"80170e164182ca2d","repo":"languagetool-org/languagetool","slug":"plaintextposition-must-be-0-its-value-starts-a","errorCode":null,"errorMessage":"plainTextPosition must be > 0 - its value starts at 1","messagePattern":"plainTextPosition must be > 0 - its value starts at 1","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"languagetool-wikipedia/src/main/java/org/languagetool/dev/wikipedia/PlainTextMapping.java","lineNumber":52,"sourceCode":"  public PlainTextMapping(String plainText, Map<Integer, Location> mapping) {\n    this.plainText = plainText;\n    this.mapping = mapping;\n  }\n\n  public String getPlainText() {\n    return plainText;\n  }\n\n  public Map<Integer, Location> getMapping() {\n    return mapping;\n  }\n\n  /**\n   * @param plainTextPosition not zero-based - smallest value is 1!\n   */\n  public Location getOriginalTextPositionFor(int plainTextPosition) {\n    if (plainTextPosition < 1) {\n      throw new RuntimeException(\"plainTextPosition must be > 0 - its value starts at 1\");\n    }\n    Location origPosition = mapping.get(plainTextPosition);\n    if (origPosition != null) {\n      //System.out.println(\"mapping \" + plainTextPosition + \" to \" + origPosition + \" [direct]\");\n      return origPosition;\n    }\n    int minDiff = Integer.MAX_VALUE;\n    Location bestMatch = null;\n    //Integer bestMaybeClosePosition = null;\n    // algorithm: find the closest lower position\n    for (Map.Entry<Integer, Location> entry : mapping.entrySet()) {\n      int maybeClosePosition = entry.getKey();\n      if (plainTextPosition > maybeClosePosition) {\n        int diff = plainTextPosition - maybeClosePosition;\n        if (diff >= 0 && diff < minDiff) {\n          bestMatch = entry.getValue();\n          //bestMaybeClosePosition = maybeClosePosition;\n          minDiff = diff;","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-wikipedia/src/main/java/org/languagetool/dev/wikipedia/PlainTextMapping.java#L34-L70","documentation":"PlainTextMapping maps positions in converted plain text back to original Wikipedia text positions. Positions are one-based, so getOriginalTextPositionFor rejects values below 1 with this RuntimeException. It indicates a caller passed a zero-based or invalid position.","triggerScenarios":"Calling getOriginalTextPositionFor(0) or with a negative value — typically passing a zero-based offset/char index into this one-based API.","commonSituations":"Off-by-one errors when tracking text positions in downstream tools; iterating arrays with 0-based indices and feeding them directly to the mapping.","solutions":["Pass 1-based positions: add 1 to zero-based offsets before calling","Clamp/validate positions before the call","Check loop bounds so the position never drops below 1"],"exampleFix":"// before\nLocation loc = mapping.getOriginalTextPositionFor(idx);\n// after\nLocation loc = mapping.getOriginalTextPositionFor(idx + 1); // positions are 1-based","handlingStrategy":"validation","validationCode":"if (pos < 1) throw new IllegalArgumentException(\"plainTextPosition is 1-based, got \" + pos);","typeGuard":"boolean isValidPosition(Integer p) { return p != null && p >= 1; }","tryCatchPattern":"try {\n  loc = mapping.getOriginalTextPositionFor(pos);\n} catch (RuntimeException e) {\n  loc = mapping.getOriginalTextPositionFor(Math.max(1, pos));\n}","preventionTips":["Treat all PlainTextMapping positions as 1-based","Convert array indices with +1 before mapping","Add asserts in loops producing positions"],"tags":["position-mapping","off-by-one","wikipedia"],"backgroundTag":"argument-out-of-range","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}