#!/usr/bin/env python3 # -*- coding: utf-8 -*- r""" P2 纠偏:把存量 POI 3.x 的写法改到 POI 4.x。 ## 为什么必须改 `parking-dependencies` 里 `poi-ooxml` 定的是 **4.1.2**(被 EasyExcel 3.3.4 要求,见 BOM 注释)。 但 BOM 注释里那句「3.9→4.1 未破坏」是**错的**:POI 4.0 一次性删掉了 3.x 里所有 「常量式」的旧 API,只保留枚举版。已用 javap 对 4.1.2 的 jar 逐个核对: | 3.x 写法 | 4.1.2 实况 | 改成 | |---|---|---| | `CellStyle.BORDER_THIN` / `BORDER_DASHED` | 已删 | `BorderStyle.THIN` / `DASHED` | | `Cell.CELL_TYPE_STRING` / `HSSFCell.CELL_TYPE_*` / `XSSFCell.CELL_TYPE_*` | 已删(`getCellType()` 现在返回 `CellType`) | `CellType.STRING` / … | | `CellStyle.ALIGN_CENTER/LEFT/RIGHT` | 已删 | `HorizontalAlignment.CENTER/LEFT/RIGHT` | | `CellStyle.VERTICAL_CENTER` | 已删 | `VerticalAlignment.CENTER` | | `CellStyle.SOLID_FOREGROUND` | 已删 | `FillPatternType.SOLID_FOREGROUND` | | `HSSFColor.GREY_25_PERCENT` / `ROSE` / `WHITE` | 内嵌类已删,改为枚举 `HSSFColorPredefined` | `HSSFColor.HSSFColorPredefined.XXX.getIndex()` | | `font.setBoldweight(Font.BOLDWEIGHT_BOLD)` | 方法已删;`Font.BOLDWEIGHT_BOLD` 也没了(`HSSFFont` 那份变成包内可见) | `font.setBold(true)` | ⚠️ 最后一行的语义确认:`setBoldweight(BOLDWEIGHT_BOLD)` 就是「加粗」,等价于 `setBold(true)`;本仓库里 `setBoldweight` 的实参**只有** `BOLDWEIGHT_BOLD` (`HSSFFont` 4 处 + `Font` 7 处,共 11 处,已全量核对),所以没有丢分支的风险。 ⚠️ `setCellType(...)` **本身**在 4.1.2 还在(签名已改成收 `CellType`),所以只改传进去的常量。 ## 为什么有两个包 `com/clouds/common/utils/` 下有 **两份**同源代码:`excle/`(6 个文件)与 `poiexcle/`(3 个文件)。第一次编译只报了 `excle/` 的 200 条错—— 那是 javac 的默认 `-Xmaxerrs 200` **截断**,`poiexcle/` 的错被挡在后面没打印出来。 所以本脚本两个包一起改,不能只看报错文件清单。 ## 幂等 全部规则都是「找不到就不改」,可重复执行。已改过的文件不会二次变形。 用法: python3 -I tools/p2_fix_poi3.py [--dry-run] """ import argparse import os import re import sys CLOUD = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE = os.path.join( CLOUD, "parking-framework/parking-legacy-common/src/main/java/com/clouds/common/utils", ) DIRS = ["excle", "poiexcle"] # 需要补的 import:枚举类型名 -> 全限定名 ENUM_IMPORTS = { "BorderStyle": "org.apache.poi.ss.usermodel.BorderStyle", "CellType": "org.apache.poi.ss.usermodel.CellType", "FillPatternType": "org.apache.poi.ss.usermodel.FillPatternType", "HorizontalAlignment": "org.apache.poi.ss.usermodel.HorizontalAlignment", "VerticalAlignment": "org.apache.poi.ss.usermodel.VerticalAlignment", } # ---- 规则:(说明, 正则, 替换函数或字符串) ---- # 顺序有意义:CELL_TYPE / BORDER / ALIGN 的限定符可能是「类名」也可能是「实例变量名」 # (如 `hssfCell.CELL_TYPE_BOOLEAN`),所以 CELL_TYPE 用 \w+ 兜住两种情况, # 其余三个按实测的类名枚举限定(避免误伤同名的业务常量)。 RULES = [ ( "Cell.CELL_TYPE_* → CellType.*", re.compile(r"\b[A-Za-z_]\w*\.CELL_TYPE_([A-Z_]+)\b"), lambda m: "CellType." + m.group(1), ), ( "CellStyle.BORDER_* → BorderStyle.*", re.compile(r"\b(?:HSSFCellStyle|XSSFCellStyle|CellStyle)\.BORDER_([A-Z_]+)\b"), lambda m: "BorderStyle." + m.group(1), ), ( "CellStyle.ALIGN_* → HorizontalAlignment.*", re.compile(r"\b(?:HSSFCellStyle|XSSFCellStyle|CellStyle)\.ALIGN_([A-Z_]+)\b"), lambda m: "HorizontalAlignment." + m.group(1), ), ( "CellStyle.VERTICAL_* → VerticalAlignment.*", re.compile(r"\b(?:HSSFCellStyle|XSSFCellStyle|CellStyle)\.VERTICAL_([A-Z_]+)\b"), lambda m: "VerticalAlignment." + m.group(1), ), ( "CellStyle.SOLID_FOREGROUND → FillPatternType.SOLID_FOREGROUND", re.compile(r"\b(?:HSSFCellStyle|XSSFCellStyle|CellStyle)\.(SOLID_FOREGROUND)\b"), lambda m: "FillPatternType." + m.group(1), ), ( "HSSFColor.XXX.index → HSSFColorPredefined.XXX.getIndex()", re.compile(r"\bHSSFColor\.([A-Z][A-Z0-9_]*)\.index\b"), lambda m: f"HSSFColor.HSSFColorPredefined.{m.group(1)}.getIndex()", ), ( "setBoldweight(X.BOLDWEIGHT_BOLD) → setBold(true)", re.compile( r"\.setBoldweight\(\s*(?:HSSFFont|Font)\.BOLDWEIGHT_BOLD\s*\)" ), lambda m: ".setBold(true)", ), ] def add_imports(src, needed): """把缺的枚举 import 插进 import 区。已是 FQN 或已存在的不重复插。""" lines = src.split("\n") existing = {ln.strip() for ln in lines} missing = [ (name, fqn) for name, fqn in sorted(ENUM_IMPORTS.items()) if name in needed and f"import {fqn};" not in existing ] if not missing: return src, [] # 定位插入点:最后一个 import 行之后;没有 import 就放在 package 行之后 last_import = -1 pkg = -1 for i, ln in enumerate(lines): s = ln.strip() if s.startswith("import "): last_import = i elif s.startswith("package ") and pkg < 0: pkg = i at = (last_import if last_import >= 0 else pkg) + 1 lines[at:at] = [f"import {fqn};" for _, fqn in missing] return "\n".join(lines), [n for n, _ in missing] def migrate(path, dry): with open(path, encoding="utf-8", errors="surrogateescape") as fh: src = original = fh.read() hits = [] for desc, pat, repl in RULES: src, n = pat.subn(repl, src) if n: hits.append(f"{desc} x{n}") # 兜底:还有 setBoldweight 就说明实参不是 BOLDWEIGHT_BOLD,需人工确认 if ".setBoldweight(" in src: raise SystemExit(f"!! {path}: 仍有 setBoldweight 调用且实参非 BOLDWEIGHT_BOLD,需人工处理") if src == original: return None needed = {name for name in ENUM_IMPORTS if re.search(rf"\b{name}\.", src)} src, added = add_imports(src, needed) if added: hits.append("补 import: " + ", ".join(added)) if not dry: with open(path, "w", encoding="utf-8", errors="surrogateescape") as fh: fh.write(src) return hits def main(): ap = argparse.ArgumentParser() ap.add_argument("--dry-run", action="store_true") args = ap.parse_args() changed = 0 for d in DIRS: base = os.path.join(BASE, d) if not os.path.isdir(base): print(f"!! 目录不存在: {base}") continue for f in sorted(os.listdir(base)): if not f.endswith(".java"): continue p = os.path.join(base, f) hits = migrate(p, args.dry_run) if hits: changed += 1 print(f" {d}/{f}") for h in hits: print(f" · {h}") else: print(f" {d}/{f} (无需改动)") print(f"\n改动文件 {changed} 个" + ("(--dry-run,未落盘)" if args.dry_run else "")) if __name__ == "__main__": main()