package com.dmiki.metaplugin.xom; import com.dmiki.metaplugin.xsd.model.XsdConfigItem; import com.dmiki.metaplugin.xsd.model.XsdConfigMode; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; public class JavaEntityGenerator { public File generate(XsdConfigItem root, XomGenerateOptions options, File outputDirectory) throws XomGenerationException { if (root == null) { throw new XomGenerationException("XOM-004", "实体生成失败: 根节点为空"); } if (outputDirectory == null) { throw new XomGenerationException("XOM-006", "实体生成失败: 输出目录为空"); } if (!outputDirectory.exists() && !outputDirectory.mkdirs()) { throw new XomGenerationException("XOM-006", "实体生成失败: 无法创建目录 " + outputDirectory.getAbsolutePath()); } JavaTypeDescriptor rootType = parseRootType(options, root); GenerationContext context = new GenerationContext(); String rootClassName = ensureUniqueClassName(rootType.className, context.classCounters); List classModels = new ArrayList(); ClassModel rootModel = new ClassModel(rootClassName); classModels.add(rootModel); buildClassModel(root, rootModel, classModels, context); List targetFiles = new ArrayList(); for (ClassModel model : classModels) { File file = new File(outputDirectory, model.className + ".java"); if (file.exists() && !options.isOverwrite()) { throw new XomGenerationException("XOM-006", "Java文件已存在且overwrite=false: " + file.getAbsolutePath()); } targetFiles.add(file); } for (int i = 0; i < classModels.size(); i++) { String source = renderSource(classModels.get(i), rootType.packageName); writeSource(targetFiles.get(i), source); } return targetFiles.get(0); } private JavaTypeDescriptor parseRootType(XomGenerateOptions options, XsdConfigItem root) { String fullType = trimToNull(options.getResultMapType()); if (fullType == null) { return new JavaTypeDescriptor(null, toUpperCamel(safe(root.getXmlName())) + "Entity"); } int idx = fullType.lastIndexOf('.'); if (idx < 0 || idx == fullType.length() - 1) { return new JavaTypeDescriptor(null, sanitizeDeclaredClassName(fullType)); } String packageName = fullType.substring(0, idx).trim(); String className = sanitizeDeclaredClassName(fullType.substring(idx + 1)); return new JavaTypeDescriptor(packageName.isEmpty() ? null : packageName, className); } private void buildClassModel(XsdConfigItem node, ClassModel classModel, List allClasses, GenerationContext context) { Map fieldCounters = new LinkedHashMap(); appendSelfValueFieldIfNeeded(node, classModel, fieldCounters); for (XsdConfigItem child : node.getChildren()) { appendNodeAsField(classModel, child, fieldCounters, context, allClasses); } } private void appendSelfValueFieldIfNeeded(XsdConfigItem node, ClassModel classModel, Map fieldCounters) { if (!shouldAppendSelfValueField(node)) { return; } String fieldName = ensureUniqueField(resolveFieldName(node), fieldCounters); String fieldType = resolveLeafType(node.getJavaType()); classModel.fields.add(new FieldModel(fieldName, fieldType)); } private boolean shouldAppendSelfValueField(XsdConfigItem node) { return isSimpleContentNode(node); } private void appendNodeAsField(ClassModel classModel, XsdConfigItem item, Map fieldCounters, GenerationContext context, List allClasses) { boolean leaf = item.isEffectiveLeaf(); XsdConfigMode mode = effectiveMode(item, leaf); if (leaf && mode == XsdConfigMode.IGNORE) { return; } if (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) { return; } if (!leaf && mode == XsdConfigMode.FLATTEN) { for (XsdConfigItem child : item.getChildren()) { appendNodeAsField(classModel, child, fieldCounters, context, allClasses); } return; } if (shouldInlineSimpleContentNode(item)) { appendSimpleContentFieldsToParent(classModel, item, fieldCounters, context, allClasses); return; } if (shouldInlineAttributeCarrierWrapperNode(item)) { appendWrapperChildrenToParent(classModel, item, fieldCounters, context, allClasses); return; } String fieldName = ensureUniqueField(resolveFieldName(item), fieldCounters); boolean complexChild = !item.isEffectiveLeaf() && !item.isAttribute(); String baseType; if (complexChild) { String candidateClassName = sanitizeClassName(toUpperCamel(safe(item.getXmlName()))); String signature = buildClassSignature(item, context.signatureCache, new LinkedHashSet()); String shapeKey = buildShapeKey(candidateClassName, signature); String reusedClassName = context.classNameByShapeKey.get(shapeKey); if (reusedClassName != null) { baseType = reusedClassName; } else { String nestedClassName = ensureUniqueClassName(candidateClassName, context.classCounters); ClassModel nestedClass = new ClassModel(nestedClassName); allClasses.add(nestedClass); context.classNameByShapeKey.put(shapeKey, nestedClassName); buildClassModel(item, nestedClass, allClasses, context); baseType = nestedClassName; } } else { baseType = resolveLeafType(item.getJavaType()); } String fieldType = item.isRepeated() ? "List<" + baseType + ">" : baseType; classModel.fields.add(new FieldModel(fieldName, fieldType)); } private boolean shouldInlineSimpleContentNode(XsdConfigItem item) { return isSimpleContentNode(item) && !item.isRepeated(); } private boolean isSimpleContentNode(XsdConfigItem node) { return node != null && !node.isAttribute() && node.hasOnlyAttributeChildren(); } private void appendSimpleContentFieldsToParent(ClassModel classModel, XsdConfigItem item, Map fieldCounters, GenerationContext context, List allClasses) { appendSelfValueFieldIfNeeded(item, classModel, fieldCounters); for (XsdConfigItem child : item.getChildren()) { appendNodeAsField(classModel, child, fieldCounters, context, allClasses); } } private boolean shouldInlineAttributeCarrierWrapperNode(XsdConfigItem item) { if (item == null || item.isRepeated() || item.isAttribute() || !item.hasChildren()) { return false; } boolean hasAttributeCarrierChild = false; for (XsdConfigItem child : item.getChildren()) { if (!child.isEffectiveLeaf()) { return false; } if (child.hasOnlyAttributeChildren()) { hasAttributeCarrierChild = true; } } return hasAttributeCarrierChild; } private void appendWrapperChildrenToParent(ClassModel classModel, XsdConfigItem item, Map fieldCounters, GenerationContext context, List allClasses) { for (XsdConfigItem child : item.getChildren()) { appendNodeAsField(classModel, child, fieldCounters, context, allClasses); } } private String buildClassSignature(XsdConfigItem node, Map signatureCache, Set visiting) { if (node == null) { return ""; } String cached = signatureCache.get(node); if (cached != null) { return cached; } if (visiting.contains(node)) { return "RECURSIVE(" + sanitizeClassName(toUpperCamel(safe(node.getXmlName()))) + ")"; } visiting.add(node); Map fieldCounters = new LinkedHashMap(); List entries = new ArrayList(); appendSelfValueSignatureIfNeeded(node, fieldCounters, entries); for (XsdConfigItem child : node.getChildren()) { appendNodeSignatureEntries(child, fieldCounters, entries, signatureCache, visiting); } visiting.remove(node); String signature = String.join("|", entries); signatureCache.put(node, signature); return signature; } private void appendSelfValueSignatureIfNeeded(XsdConfigItem node, Map fieldCounters, List entries) { if (!shouldAppendSelfValueField(node)) { return; } String fieldName = ensureUniqueField(resolveFieldName(node), fieldCounters); String fieldType = resolveLeafType(node.getJavaType()); entries.add(fieldName + ":" + fieldType); } private void appendNodeSignatureEntries(XsdConfigItem item, Map fieldCounters, List entries, Map signatureCache, Set visiting) { boolean leaf = item.isEffectiveLeaf(); XsdConfigMode mode = effectiveMode(item, leaf); if (leaf && mode == XsdConfigMode.IGNORE) { return; } if (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) { return; } if (!leaf && mode == XsdConfigMode.FLATTEN) { for (XsdConfigItem child : item.getChildren()) { appendNodeSignatureEntries(child, fieldCounters, entries, signatureCache, visiting); } return; } if (shouldInlineSimpleContentNode(item)) { appendSelfValueSignatureIfNeeded(item, fieldCounters, entries); for (XsdConfigItem child : item.getChildren()) { appendNodeSignatureEntries(child, fieldCounters, entries, signatureCache, visiting); } return; } if (shouldInlineAttributeCarrierWrapperNode(item)) { for (XsdConfigItem child : item.getChildren()) { appendNodeSignatureEntries(child, fieldCounters, entries, signatureCache, visiting); } return; } String fieldName = ensureUniqueField(resolveFieldName(item), fieldCounters); boolean complexChild = !item.isEffectiveLeaf() && !item.isAttribute(); String baseType; if (complexChild) { String childBaseName = sanitizeClassName(toUpperCamel(safe(item.getXmlName()))); String childSignature = buildClassSignature(item, signatureCache, visiting); baseType = "{" + childBaseName + "#" + childSignature + "}"; } else { baseType = resolveLeafType(item.getJavaType()); } String fieldType = item.isRepeated() ? "List<" + baseType + ">" : baseType; entries.add(fieldName + ":" + fieldType); } private String buildShapeKey(String className, String signature) { return className.toLowerCase(Locale.ROOT) + "|" + signature; } private XsdConfigMode effectiveMode(XsdConfigItem item, boolean leaf) { XsdConfigMode mode = item.getConfigMode(); if (leaf) { return mode == XsdConfigMode.IGNORE ? XsdConfigMode.IGNORE : XsdConfigMode.KEEP; } if (mode == XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES || mode == XsdConfigMode.PRESERVE_HIERARCHY || mode == XsdConfigMode.FLATTEN || mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) { return mode; } if (mode == XsdConfigMode.IGNORE) { return XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN; } return XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES; } private String renderSource(ClassModel rootModel, String packageName) { StringBuilder builder = new StringBuilder(2048); Set imports = new LinkedHashSet(); collectImports(rootModel, imports); if (packageName != null) { builder.append("package ").append(packageName).append(";\n\n"); } for (String anImport : imports) { builder.append("import ").append(anImport).append(";\n"); } if (!imports.isEmpty()) { builder.append("\n"); } builder.append("public class ").append(rootModel.className).append(" {\n\n"); renderFieldsAndAccessors(builder, rootModel, 1); builder.append("}\n"); return builder.toString(); } private void renderFieldsAndAccessors(StringBuilder builder, ClassModel classModel, int indentLevel) { for (FieldModel field : classModel.fields) { indent(builder, indentLevel); builder.append("private ").append(field.type).append(" ").append(field.name).append(";\n"); } if (!classModel.fields.isEmpty()) { builder.append("\n"); } for (FieldModel field : classModel.fields) { String getterName = "get" + toUpperCamel(field.name); String setterName = "set" + toUpperCamel(field.name); indent(builder, indentLevel); builder.append("public ").append(field.type).append(" ").append(getterName).append("() {\n"); indent(builder, indentLevel + 1); builder.append("return ").append(field.name).append(";\n"); indent(builder, indentLevel); builder.append("}\n\n"); indent(builder, indentLevel); builder.append("public void ").append(setterName).append("(").append(field.type).append(" ").append(field.name).append(") {\n"); indent(builder, indentLevel + 1); builder.append("this.").append(field.name).append(" = ").append(field.name).append(";\n"); indent(builder, indentLevel); builder.append("}\n\n"); } } private void collectImports(ClassModel classModel, Set imports) { for (FieldModel field : classModel.fields) { String type = field.type; if (type.startsWith("List<")) { imports.add("java.util.List"); String inner = type.substring("List<".length(), type.length() - 1); addTypeImport(inner, imports); } else { addTypeImport(type, imports); } } } private void writeSource(File javaFile, String source) throws XomGenerationException { try (Writer writer = new OutputStreamWriter(new FileOutputStream(javaFile), StandardCharsets.UTF_8)) { writer.write(source); writer.flush(); } catch (Exception ex) { throw new XomGenerationException("XOM-006", "写入Java实体失败: " + ex.getMessage()); } } private void addTypeImport(String type, Set imports) { if (type == null) { return; } if ("BigDecimal".equals(type)) { imports.add("java.math.BigDecimal"); return; } if ("LocalDate".equals(type)) { imports.add("java.time.LocalDate"); return; } if ("LocalDateTime".equals(type)) { imports.add("java.time.LocalDateTime"); return; } if ("LocalTime".equals(type)) { imports.add("java.time.LocalTime"); return; } if (type.contains(".") && !type.startsWith("java.lang.")) { imports.add(type); } } private String resolveFieldName(XsdConfigItem item) { String candidate = trimToNull(item.getJavaProperty()); if (candidate == null) { candidate = toLowerCamel(safe(item.getXmlName())); } return sanitizeFieldName(candidate); } private String resolveLeafType(String configuredType) { String type = trimToNull(configuredType); if (type == null) { return "String"; } if ("byte[]".equals(type)) { return "byte[]"; } if (type.contains(".")) { return type; } return sanitizeClassName(type); } private String ensureUniqueField(String name, Map counters) { String key = name.toLowerCase(Locale.ROOT); Integer counter = counters.get(key); if (counter == null) { counters.put(key, 1); return name; } int next = counter + 1; counters.put(key, next); return name + next; } private String ensureUniqueClassName(String className, Map counters) { String key = className.toLowerCase(Locale.ROOT); Integer counter = counters.get(key); if (counter == null) { counters.put(key, 1); return className; } int next = counter + 1; counters.put(key, next); return className + next; } private String sanitizeClassName(String text) { String value = trimToNull(text); if (value == null) { return "GeneratedEntity"; } String className = toUpperCamel(value); if (className.isEmpty()) { className = "GeneratedEntity"; } if (!Character.isJavaIdentifierStart(className.charAt(0))) { className = "C" + className; } return stripInvalidIdentifierChars(className); } private String sanitizeDeclaredClassName(String text) { String value = trimToNull(text); if (value == null) { return "GeneratedEntity"; } String className = value .replaceAll("[^A-Za-z0-9_]+", "_") .replaceAll("_+", "_") .replaceAll("^_+|_+$", ""); if (className.isEmpty()) { return "GeneratedEntity"; } if (!Character.isJavaIdentifierStart(className.charAt(0))) { className = "C_" + className; } className = stripInvalidIdentifierChars(className); if (className.isEmpty() || "_".equals(className)) { return "GeneratedEntity"; } if (JAVA_KEYWORDS.contains(className.toLowerCase(Locale.ROOT))) { className = className + "Type"; } return className; } private String sanitizeFieldName(String text) { String value = trimToNull(text); if (value == null) { return "field"; } String fieldName = toLowerCamel(value); if (fieldName.isEmpty()) { fieldName = "field"; } if (!Character.isJavaIdentifierStart(fieldName.charAt(0))) { fieldName = "_" + fieldName; } fieldName = stripInvalidIdentifierChars(fieldName); if (JAVA_KEYWORDS.contains(fieldName)) { fieldName = fieldName + "Field"; } return fieldName; } private String stripInvalidIdentifierChars(String text) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char current = text.charAt(i); if (i == 0) { if (Character.isJavaIdentifierStart(current)) { builder.append(current); } continue; } if (Character.isJavaIdentifierPart(current)) { builder.append(current); } } return builder.toString(); } private void indent(StringBuilder builder, int indentLevel) { for (int i = 0; i < indentLevel * 4; i++) { builder.append(' '); } } private String safe(String text) { return text == null ? "" : text; } private String trimToNull(String text) { if (text == null) { return null; } String trimmed = text.trim(); if (trimmed.isEmpty()) { return null; } return trimmed; } private String toUpperCamel(String source) { if (source == null || source.trim().isEmpty()) { return ""; } String cleaned = source.replaceAll("[^A-Za-z0-9]+", " ").trim(); if (cleaned.isEmpty()) { return ""; } String[] parts = cleaned.split("\\s+"); StringBuilder builder = new StringBuilder(); for (String part : parts) { if (part.isEmpty()) { continue; } if (part.length() == 1) { builder.append(part.toUpperCase(Locale.ROOT)); } else { builder.append(part.substring(0, 1).toUpperCase(Locale.ROOT)); builder.append(part.substring(1)); } } return builder.toString(); } private String toLowerCamel(String source) { String upper = toUpperCamel(source); if (upper.isEmpty()) { return ""; } if (isAllUpperCaseLetters(upper)) { return upper.toLowerCase(Locale.ROOT); } if (upper.length() == 1) { return upper.toLowerCase(Locale.ROOT); } return upper.substring(0, 1).toLowerCase(Locale.ROOT) + upper.substring(1); } private boolean isAllUpperCaseLetters(String text) { boolean hasLetter = false; for (int i = 0; i < text.length(); i++) { char current = text.charAt(i); if (!Character.isLetter(current)) { continue; } hasLetter = true; if (!Character.isUpperCase(current)) { return false; } } return hasLetter; } private static class JavaTypeDescriptor { private final String packageName; private final String className; private JavaTypeDescriptor(String packageName, String className) { this.packageName = packageName; this.className = className; } } private static class ClassModel { private final String className; private final List fields = new ArrayList(); private ClassModel(String className) { this.className = className; } } private static class GenerationContext { private final Map classCounters = new LinkedHashMap(); private final Map classNameByShapeKey = new LinkedHashMap(); private final Map signatureCache = new IdentityHashMap(); } private static class FieldModel { private final String name; private final String type; private FieldModel(String name, String type) { this.name = name; this.type = type; } } private static final Set JAVA_KEYWORDS = new LinkedHashSet(Arrays.asList( "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while" )); }