add_impl_javadoc.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """Add JavaDoc to impl package public methods. Documentation-only; no logic changes."""
  4. import os
  5. import re
  6. import sys
  7. from pathlib import Path
  8. ROOT = Path(r"d:/ylrz_saas_new/java")
  9. IMPL_DIR = ROOT / "fs-service/src/main/java/com/fs/company/service/workflow/impl"
  10. WORKFLOW_DIR = ROOT / "fs-service/src/main/java/com/fs/company/service/workflow"
  11. CLASS_DESC = {
  12. "LobsterEvolutionEngineImpl": "��Ϻ��������ʵ�֣����ֶԻ�����������װ�� AI ���ɡ��������֡��Ϲ������дʹ��˵�������ˮ�ߡ�",
  13. "LobsterWorkflowExecutorImpl": "��Ϻ������ִ����ʵ�֣�ʵ���������ڹ������ڵ��ƽ�����ϢͶ����������漯�ɡ�",
  14. "DynamicNodeExecutorImpl": "��̬�ڵ�ִ����ʵ�֣����ڵ�����ע�� handler��ִ��ö�ٽڵ��� AI �����߼���",
  15. "ContextAssemblerImpl": "��������װ��ʵ�֣����м��ػ���֪ʶ�⡢��ʷ�������ȶ�Դ�����Ĺ� AI ʹ�á�",
  16. "SummaryGeneratorImpl": "�Ի�ժҪ������ʵ�֣�ʵ��/ȫ��ժҪ��������ȡ���첽ժҪ���¡�",
  17. "LobsterE2eTestServiceImpl": "��Ϻ�˵��˲��Է���ʵ�֣������� E2E �����������ƽ�����ֶԻ����ԡ�",
  18. "VariableStoreImpl": "�����������洢ʵ�֣��ڴ� + Redis + ���ݿ�������д����ʵ������ʱ�����־û���",
  19. "MultiTurnDialogueManagerImpl": "���ֶԻ�������ʵ�֣�������Ϣ�ռ��ڵ���ִο��ơ�������ȡ������ж���",
  20. "QualityScoringServiceImpl": "�������ַ���ʵ�֣���ά�� AI �ظ����֡���������У��������������ɡ�",
  21. "SensitiveWordServiceImpl": "���дʹ��˷���ʵ�֣��⻧�����дʼ�⡢�滻��߷��մʴ��á�",
  22. "ComplianceServiceImpl": "�Ϲ������ʵ�֣�AI �ظ��Ϲ���У����ּ���ȫ�����滻��",
  23. "ConditionEvaluatorImpl": "��������ʽ��ֵ��ʵ�֣���������֧�ڵ�������жϡ�",
  24. "DynamicNodeAdjusterImpl": "��̬�ڵ������ʵ�֣��������������·�ɾ�����ת�˹��ж���",
  25. "DynamicNodeImplServiceImpl": "��̬�ڵ�ʵ�ֹ������񣺽ڵ� handler ע�������� CRUD��",
  26. "DuplicateReplyDetector": "�ظ��ظ����������ֹ��������ͬһ�ͻ��ظ�������ͬ���ݡ�",
  27. "FactDecayScheduler": "��ʵ˥����ʱ�������������û���ʵ�빵ͨϰ�߻��档",
  28. "LobsterBillingServiceImpl": "��Ϻ�Ʒѷ���ʵ�֣��⻧��Token ���ļ�¼��ͳ�ƻ��ܡ�",
  29. "LobsterConversationSummaryServiceImpl": "�Ի�ժҪ��ѯ����ʵ�֣�������ժҪ�б������顣",
  30. "LobsterEventAuditServiceImpl": "�¼���Ʒ���ʵ�֣�����/ѧϰ�¼����˹������ת��",
  31. "LobsterEvolutionSuggestionServiceImpl": "�����������ʵ�֣�AI ���������б��������ָ��ͳ�ơ�",
  32. "LobsterInstanceStatsServiceImpl": "ʵ��ͳ�Ʒ���ʵ�֣�������ʵ������ָ����ܡ�",
  33. "LobsterIntegrationTestServiceImpl": "���ɲ��Է���ʵ�֣��������ð�̲�����ڵ�������֤��",
  34. "LobsterLearningCorpusServiceImpl": "ѧϰ���Ϸ���ʵ�֣��⻧ѧϰ���ϲ�ѯ��",
  35. "LobsterModelConfigServiceImpl": "ģ�����÷���ʵ�֣��⻧�� LLM ·�����������ģ�����á�",
  36. "LobsterNodeTypeServiceImpl": "�ڵ�����ע�����ʵ�֣��������ڵ�����Ԫ���ݻ����� CRUD��",
  37. "LobsterPromptServiceImpl": "ϵͳ Prompt ��������ʵ�֣����� Prompt ��ϵͳ Prompt CRUD��",
  38. "LobsterSalesCorpusServiceImpl": "�������Ϸ���ʵ�֣����ڶԻ����롢�����볡����ѯ��",
  39. "LobsterTestScenarioServiceImpl": "���Գ�������ʵ�֣�E2E ���Գ��� CRUD �붨ʱ������",
  40. "MultiModelWorkflowGeneratorImpl": "��ģ�͹�����������ʵ�֣�AI �Զ�����/�����Ż������� JSON��",
  41. "NodePromptServiceImpl": "�ڵ� Prompt �󶨷���ʵ�֣��ڵ��� Prompt ģ��Ĺ������档",
  42. "OutputQualityCheckerImpl": "������������ʵ�֣��������ݵĻ�������У���������",
  43. "PendingAuditKnowledgeServiceImpl": "�����֪ʶ����ʵ�֣��������ʴ������������֪ʶ�⡣",
  44. "ProfileEnrichmentService": "�û����񸻻����񣺻�����Ϊ�����첽���¿ͻ�����",
  45. "PromptManagerImpl": "Prompt ������ʵ�֣��ڵ�/ȫ�� Prompt ��ȡ��־û���",
  46. "QualityScoreRuleEngine": "�������ֹ������棺��̬����У�������ⳡ���ӷ���Ϲ�����",
  47. "ReplaceWordServiceImpl": "Υ����/�״����滻����ʵ�֣����⻧�����滻�ʱ���Ӧ�á�",
  48. "SemanticTakeoverDetectorImpl": "����ӹܼ����ʵ�֣����ͻ�Ҫ��ת�˹��� AI ����ת����ͼ��",
  49. "ToolCallFrameworkImpl": "���ߵ��ÿ��ʵ�֣����� AI ���ߵ��ò�ִ�ж���/SMS �����ù��ߡ�",
  50. "VariableSubstitutionEngineImpl": "�����滻����ʵ�֣�ģ�� ${var} ռλ���滻�������ȡ��",
  51. "WorkflowTemplateAdminServiceImpl": "������ģ���������ʵ�֣�ģ���б����ڵ㱣���뷢����ѯ��",
  52. }
  53. METHOD_CN = {
  54. "init": "��ʼ�������ע�� handler�����ػ���ȣ���",
  55. "refreshCache": "ˢ�±���/Redis ���档",
  56. "evolve": "ִ�е��ֶԻ�������ˮ�ߣ����� AI �ظ�����һ�ڵ㡣",
  57. "enrichWithDynamicNode": "AI ��̬���ɲ�������Ի��������ڵ㡣",
  58. "learnCustomerHabit": "�ӱ��ֽ���ѧϰ�ͻ���ͨϰ�߲�д�� habit ����",
  59. "startWorkflow": "�����µĹ�����ʵ����ִ���׽ڵ㡣",
  60. "executeNextNode": "���տͻ��ظ����ƽ�����������һ�ڵ㡣",
  61. "pauseWorkflow": "��ͣ������ʵ��ִ�С�",
  62. "resumeWorkflow": "�ָ�����ͣ�Ĺ�����ʵ����",
  63. "terminateWorkflow": "��ֹ������ʵ������¼ԭ��",
  64. "getInstanceState": "��ѯ������ʵ����ǰ״̬��������ա�",
  65. "simulateExecution": "ģ��ִ�й���������ʵ�ʷ���Ϣ�������ڵ��ԡ�",
  66. "deliverProactiveMessage": "������ͻ�Ͷ�ݹ�������Ϣ������/���ѣ���",
  67. "execute": "ִ��ָ�����͵Ķ�̬�ڵ� handler��",
  68. "supports": "�жϽڵ������Ƿ���ע�� handler��",
  69. "registerHandler": "ע���Զ���ڵ����� handler��",
  70. "getRegisteredHandlers": "������ע���ȫ���ڵ� handler ӳ�䡣",
  71. "assembleContext": "��װ�����������ı���token ���ޣ���",
  72. "assembleForNode": "Ϊָ���ڵ�������װ�����ġ�",
  73. "getContextVariables": "��ȡʵ�������ı��� Map��",
  74. "assembleFullContext": "���м��ض�Դ�����IJ���װ���� Map��",
  75. "buildAiPrompt": "�������� Map ��ʽ��Ϊ AI Prompt �ı���",
  76. "formatKnowledgeForScoring": "��ʽ��֪ʶ��Ƭ�ι���������ʹ�á�",
  77. "generateSummary": "���ڶԻ���ʷ����ժҪ�ı���",
  78. "extractKeyPoints": "�ӶԻ���ʷ��ȡ�ؼ�Ҫ�㡣",
  79. "analyzeSentiment": "����������Ϣ�������",
  80. "generateSummaryAsync": "�첽����ʵ��ժҪ���£�����ֵ����",
  81. "forceGenerateSummary": "ǿ���������ɲ��־û�ʵ��ժҪ��",
  82. "forceGenerateSummaryFromHistory": "���ڸ�����ʷ�ı�ǿ������ժҪ��",
  83. "doGenerateSummary": "ִ��ժҪ���ɵĺ����߼���ͬ������",
  84. "getCachedSummary": "��ȡ�ѻ�����û�/ʵ��ժҪ��",
  85. "updateUserProfile": "��ժҪ��Ϣ�����û������ֶΡ�",
  86. "extractConversationVariables": "�ӿͻ���Ϣ�� LLM ��ȡ�ṹ��������",
  87. "generateMicroSummary": "������� N �ֶԻ���΢ժҪ��",
  88. "generateGlobalSummary": "���ɿ�Ự��ȫ���û�ժҪ��",
  89. "runE2e": "�������� E2E ���Բ����ر��档",
  90. "getReport": "�� runId ��ѯ E2E ���Ա��档",
  91. "stepNext": "E2E �����ƽ���ע���û����벢ִ����һ�ڵ㡣",
  92. "multiTurn": "E2E ���ֶԻ����ԣ�����ע������û����롣",
  93. "listRuns": "��ҳ�г� E2E �������м�¼��",
  94. "setVariable": "���õ���ʵ���������ڴ�+Redis+DB����",
  95. "setVariables": "��������ʵ��������",
  96. "getVariable": "��ȡ����ʵ��������",
  97. "getAllVariables": "��ȡʵ��ȫ��������",
  98. "removeVariable": "ɾ������ʵ��������",
  99. "clearVariables": "���ʵ��ȫ��������",
  100. "persistToDatabase": "���ڴ�����־û������ݿ⡣",
  101. "loadFromDatabase": "�����ݿ���ر������ڴ滺�档",
  102. "processDialogue": "�������ֶԻ��ڵ��һ�ֽ�����",
  103. "isMultiTurnNode": "�жϽڵ��Ƿ�����Ϊ���ֶԻ���",
  104. "isDialogueComplete": "�ж϶��ֶԻ��Ƿ�����ɡ�",
  105. "getDialogueRound": "��ȡ��ǰ���ֶԻ��ִΡ�",
  106. "resetDialogue": "���ö��ֶԻ�״̬��",
  107. "scoreWithRetry": "�������ֲ��ڲ����ʱ�����������ɡ�",
  108. "score": "�� AI �ظ����ж�ά���������֡�",
  109. "getImprovementHint": "���ݵ�ǰ�÷����ɸĽ���ʾ��",
  110. "getNodeWarmupHint": "��ȡ�ڵ㼶����Ԥ����ʾ�ʡ�",
  111. "filterSensitiveWords": "�����ı��е����дʲ��滻��",
  112. "containsSensitiveWord": "����ı��Ƿ�������дʡ�",
  113. "detectSensitiveWords": "��Ⲣ�������д���ϸ�б���",
  114. "isHighRiskSensitiveWord": "�ж��Ƿ񺬸߷������дʣ�����/����/թƭ����",
  115. "addSensitiveWord": "�����⻧���дʲ�ˢ�»��档",
  116. "removeSensitiveWord": "�������дʲ�ˢ�»��档",
  117. "getSensitiveWordList": "��ѯ�⻧���õ����д��б���",
  118. "substitute": "��ģ���еı���ռλ���滻Ϊʵ��ֵ��",
  119. "extractVariables": "���������г�ȡ������ֵ��",
  120. "checkAiReplyCompliance": "У�� AI �ظ��Ϲ��Բ����طּ������",
  121. "adjustNode": "��������������ڽڵ�·���������",
  122. "getPrompt": "��ȡ�ڵ�/ȫ�� Prompt ���ݡ�",
  123. "getSystemPrompt": "��ȡ�ڵ�ϵͳ Prompt��",
  124. "getInstructionPrompt": "��ȡ�ڵ�ָ�� Prompt��",
  125. "getFallbackPrompt": "��ȡ�ڵ㶵�� Prompt��",
  126. "hasConfiguredNodePrompt": "�жϽڵ��Ƿ�������ָ������ Prompt��",
  127. "saveNodePrompt": "����ڵ� Prompt �����ݿ⡣",
  128. "saveGlobalPrompt": "����ȫ�� Prompt �����ݿ⡣",
  129. "listPrompts": "�г��⻧ Prompt ���á�",
  130. "applyReplace": "���ı�Ӧ��Υ�����������״��ʾ�����",
  131. "listWords": "��ҳ��ѯ�滻���б���",
  132. "saveWord": "�����滻�����á�",
  133. "deleteWord": "ɾ���滻�ʡ�",
  134. "executeTool": "ִ��ָ�����ƵĹ��ߵ��á�",
  135. "extractToolCall": "�� AI �ظ� JSON �н������ߵ��á�",
  136. "isToolAvailable": "�жϹ��߶��⻧�Ƿ���á�",
  137. "getAvailableTools": "�г��⻧���ù��ߡ�",
  138. "registerTool": "ע���Զ��幤�ߡ�",
  139. "recordExecLog": "��¼����ִ����־��",
  140. "queryOrder": "��ѯ�������飨���ù��ߣ���",
  141. "queryUserOrders": "��ѯ�û������б������ù��ߣ���",
  142. "sendSms": "���Ͷ��ţ����ù��ߣ���",
  143. "detectTakeover": "���ͻ��Ƿ�Ҫ��ת�˹��ӹܡ�",
  144. "detectAITransfer": "��� AI �Ƿ�Ӧ����ת���˹���",
  145. "checkQuality": "�����������������",
  146. "checkAndCorrect": "����������ڱ�Ҫʱ�������ݡ�",
  147. "cleanupExpiredFacts": "��ʱ����������ʵ��ϰ�����ݡ�",
  148. "isDuplicate": "���ظ��Ƿ�����ڷ��������ظ���",
  149. "enrichProfile": "�첽�����û�����",
  150. "repairJson": "�޸� LLM ���صĻ��� JSON �ַ�����",
  151. }
  152. # Build caller index once
  153. def build_caller_index():
  154. index = {}
  155. java_files = list(ROOT.rglob("*.java"))
  156. for jf in java_files:
  157. if "impl" in jf.parts and jf.parent.name == "impl" and jf.parent.parent.name == "workflow":
  158. continue # skip self for class name search
  159. try:
  160. text = jf.read_text(encoding="utf-8", errors="replace")
  161. except Exception:
  162. continue
  163. for m in re.finditer(r'\b([A-Z][a-zA-Z0-9]+(?:Service|Executor|Engine|Manager|Store|Assembler|Generator|Framework|Detector|Evaluator|Adjuster|Scheduler|Checker|Router)?)\b', text):
  164. name = m.group(1)
  165. if name.endswith("Impl"):
  166. iface = name[:-4]
  167. index.setdefault(iface, set()).add(jf.stem)
  168. index.setdefault(name, set()).add(jf.stem)
  169. # Also scan for interface usage patterns like "VariableStore variableStore"
  170. for jf in java_files:
  171. try:
  172. text = jf.read_text(encoding="utf-8", errors="replace")
  173. except Exception:
  174. continue
  175. for m in re.finditer(r'\b(Lobster[A-Za-z]+|ContextAssembler|VariableStore|SummaryGenerator|QualityScoringService|SensitiveWordService|DynamicNodeExecutor|MultiTurnDialogueManager|PromptManager|ComplianceService|ToolCallFramework|ConditionEvaluator|DynamicNodeAdjuster|ReplaceWordService|OutputQualityChecker|SemanticTakeoverDetector|VariableSubstitutionEngine|MultiModelWorkflowGenerator|PendingAuditKnowledgeService|I[A-Z][a-zA-Z]+Service)\s+\w+', text):
  176. iface = m.group(1)
  177. if jf.stem not in {iface, iface + "Impl"}:
  178. index.setdefault(iface, set()).add(jf.stem)
  179. return index
  180. CALLER_INDEX = None
  181. def get_callers(class_name, method_name=None):
  182. global CALLER_INDEX
  183. if CALLER_INDEX is None:
  184. CALLER_INDEX = build_caller_index()
  185. iface = class_name.replace("Impl", "")
  186. if class_name == "ProfileEnrichmentService":
  187. iface = class_name
  188. callers = set()
  189. for key in (class_name, iface, "I" + iface if not iface.startswith("I") else iface):
  190. callers.update(CALLER_INDEX.get(key, set()))
  191. callers.discard(class_name)
  192. # Filter impl siblings unless they're real callers
  193. filtered = sorted(c for c in callers if not c.endswith("Impl") or c in {
  194. "LobsterWorkflowExecutorImpl", "LobsterEvolutionEngineImpl", "DynamicNodeExecutorImpl",
  195. "ContextAssemblerImpl", "MultiTurnDialogueManagerImpl", "LobsterE2eTestServiceImpl",
  196. "LobsterTravelValidationServiceImpl", "LobsterIntegrationTestServiceImpl",
  197. })
  198. if not filtered:
  199. filtered = sorted(callers)[:6]
  200. return filtered[:8]
  201. def parse_interface_methods(class_name):
  202. iface = class_name.replace("Impl", "")
  203. if class_name.startswith("I") and class_name.endswith("ServiceImpl"):
  204. iface = class_name # ILobsterXServiceImpl -> ILobsterXService
  205. iface_file = WORKFLOW_DIR / (iface.replace("Impl", "") + ".java")
  206. else:
  207. candidates = [
  208. WORKFLOW_DIR / (iface + ".java"),
  209. WORKFLOW_DIR / ("I" + iface + ".java"),
  210. ]
  211. iface_file = next((c for c in candidates if c.exists()), None)
  212. methods = {}
  213. if iface_file and iface_file.exists():
  214. text = iface_file.read_text(encoding="utf-8", errors="replace")
  215. # Match javadoc + method signature
  216. pattern = re.compile(
  217. r'/\*\*(.*?)\*/\s*(?:@[\w.]+\s*)*([\w<>,\[\]\s.?]+?\s+(\w+)\s*\([^)]*\)\s*;)',
  218. re.DOTALL,
  219. )
  220. for m in pattern.finditer(text):
  221. doc = m.group(1).strip()
  222. method = m.group(3)
  223. # Extract first line of javadoc as purpose
  224. first_line = re.sub(r'^\s*\*\s?', '', doc.split('\n')[0]).strip()
  225. if first_line:
  226. methods[method] = first_line
  227. return methods
  228. def has_javadoc(lines, idx):
  229. """Check if line at idx already has javadoc immediately above (skipping annotations)."""
  230. j = idx - 1
  231. while j >= 0 and lines[j].strip().startswith("@"):
  232. j -= 1
  233. if j >= 0 and lines[j].strip().endswith("*/"):
  234. return True
  235. if j >= 0 and lines[j].strip().startswith("/**"):
  236. return True
  237. return False
  238. def method_purpose(class_name, method_name, iface_methods):
  239. if method_name in iface_methods:
  240. return iface_methods[method_name]
  241. if method_name in METHOD_CN:
  242. return METHOD_CN[method_name]
  243. if method_name.startswith("get") and method_name != "get":
  244. return f"��ȡ{method_name[3:]}��"
  245. if method_name.startswith("set") and method_name != "set":
  246. return f"����{method_name[3:]}��"
  247. if method_name.startswith("is") and method_name != "is":
  248. return f"�ж��Ƿ�{method_name[2:]}��"
  249. if method_name.startswith("list"):
  250. return f"��ѯ{method_name[4:]}�б���"
  251. if method_name.startswith("save") or method_name.startswith("create"):
  252. return f"����/����{method_name}������ݡ�"
  253. if method_name.startswith("delete") or method_name.startswith("remove"):
  254. return f"ɾ��{method_name}������ݡ�"
  255. if method_name.startswith("update"):
  256. return f"����{method_name[6:]}��"
  257. if method_name.startswith("batch"):
  258. return f"����{method_name[5:]}��"
  259. if method_name.startswith("run") or method_name.startswith("test"):
  260. return f"����{method_name}���Ի�����"
  261. return f"{method_name} �Ĺ�����ڡ�"
  262. def format_callers(callers):
  263. if not callers:
  264. return "Spring ����ע�����"
  265. return "��".join(f"{{@code {c}}}" for c in callers)
  266. def make_method_javadoc(class_name, method_name, iface_methods):
  267. purpose = method_purpose(class_name, method_name, iface_methods)
  268. callers = get_callers(class_name, method_name)
  269. lines = [
  270. " /**",
  271. f" * {purpose}",
  272. f" * <p>���÷���{format_callers(callers)}��",
  273. " */",
  274. ]
  275. return lines
  276. def make_class_javadoc(class_name, implements=None):
  277. desc = CLASS_DESC.get(class_name, f"{class_name}��workflow impl �� Spring ����ʵ�֡�")
  278. callers = get_callers(class_name)
  279. lines = [
  280. "/**",
  281. f" * {desc}",
  282. f" * <p>��Ҫ���÷���{format_callers(callers)}��",
  283. " */",
  284. ]
  285. return lines
  286. def process_file(path: Path):
  287. text = path.read_text(encoding="utf-8")
  288. class_name = path.stem
  289. lines = text.splitlines(keepends=True)
  290. iface_methods = parse_interface_methods(class_name)
  291. # Find class declaration
  292. class_idx = None
  293. for i, line in enumerate(lines):
  294. if re.match(r'public\s+(?:final\s+)?class\s+' + re.escape(class_name), line):
  295. class_idx = i
  296. break
  297. if class_idx is None:
  298. return False
  299. modified = False
  300. new_lines = []
  301. # Check class-level javadoc
  302. has_class_doc = False
  303. if class_idx > 0:
  304. j = class_idx - 1
  305. while j >= 0 and (lines[j].strip().startswith("@") or lines[j].strip() == ""):
  306. j -= 1
  307. if j >= 0 and (lines[j].strip().startswith("/**") or lines[j].strip().endswith("*/")):
  308. has_class_doc = True
  309. i = 0
  310. while i < len(lines):
  311. line = lines[i]
  312. stripped = line.strip()
  313. # Insert class javadoc before class if missing
  314. if i == class_idx and not has_class_doc:
  315. doc = make_class_javadoc(class_name)
  316. for dl in doc:
  317. new_lines.append(dl + "\n")
  318. modified = True
  319. # Detect public methods (including @Override public)
  320. pub_match = re.match(r'^(\s*)(@Override\s+)?public\s+(?!class\b|interface\b|enum\b|static\s+class\b)', stripped)
  321. if pub_match and not has_javadoc(lines, i):
  322. indent = pub_match.group(1)
  323. # Extract method name
  324. sig = stripped
  325. # combine with next line if signature spans lines
  326. combined = sig
  327. k = i
  328. while ";" not in combined and "{" not in combined and k + 1 < len(lines):
  329. k += 1
  330. combined += " " + lines[k].strip()
  331. mm = re.search(r'\b(\w+)\s*\(', combined)
  332. if mm:
  333. method_name = mm.group(1)
  334. if method_name not in ("if", "for", "while", "switch", "catch", "synchronized"):
  335. doc_lines = make_method_javadoc(class_name, method_name, iface_methods)
  336. # Adjust indent
  337. for dl in doc_lines:
  338. new_lines.append(indent + dl.strip() + "\n")
  339. modified = True
  340. new_lines.append(line)
  341. i += 1
  342. if modified:
  343. path.write_text("".join(new_lines), encoding="utf-8")
  344. return modified
  345. def main():
  346. modified_files = []
  347. for jf in sorted(IMPL_DIR.glob("*.java")):
  348. if process_file(jf):
  349. modified_files.append(str(jf))
  350. print(f"MODIFIED: {jf.name}")
  351. else:
  352. print(f"SKIP (no changes): {jf.name}")
  353. print(f"\nTotal modified: {len(modified_files)}")
  354. return modified_files
  355. if __name__ == "__main__":
  356. main()