init
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package com.dmiki.metaplugin.action;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.ui.XsdConfigDialog;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.Presentation;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
public class XsdStructureConfigAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent event) {
|
||||
VirtualFile selectedFile = event.getData(CommonDataKeys.VIRTUAL_FILE);
|
||||
File xsdFile = toXsdFile(selectedFile);
|
||||
File xomFile = toXomFile(selectedFile);
|
||||
XsdConfigDialog dialog;
|
||||
if (xsdFile != null) {
|
||||
dialog = new XsdConfigDialog(event.getProject(), xsdFile, true);
|
||||
} else if (xomFile != null) {
|
||||
dialog = new XsdConfigDialog(event.getProject(), null, xomFile, true);
|
||||
} else {
|
||||
dialog = new XsdConfigDialog(event.getProject());
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent event) {
|
||||
Presentation presentation = event.getPresentation();
|
||||
String place = event.getPlace();
|
||||
boolean popupPlace = ActionPlaces.PROJECT_VIEW_POPUP.equals(place) || ActionPlaces.EDITOR_POPUP.equals(place);
|
||||
|
||||
VirtualFile selectedFile = event.getData(CommonDataKeys.VIRTUAL_FILE);
|
||||
boolean visible = popupPlace && (toXsdFile(selectedFile) != null || toXomFile(selectedFile) != null);
|
||||
presentation.setVisible(visible);
|
||||
presentation.setEnabled(visible);
|
||||
presentation.setText("XOM生成");
|
||||
}
|
||||
|
||||
private File toXsdFile(VirtualFile file) {
|
||||
if (file == null || file.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
String extension = file.getExtension();
|
||||
if (extension == null || !"xsd".equalsIgnoreCase(extension)) {
|
||||
return null;
|
||||
}
|
||||
return new File(file.getPath());
|
||||
}
|
||||
|
||||
private File toXomFile(VirtualFile file) {
|
||||
if (file == null || file.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
String extension = file.getExtension();
|
||||
if (extension == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = extension.toLowerCase(Locale.ROOT);
|
||||
if (!"xml".equals(normalized) && !"xom".equals(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return new File(file.getPath());
|
||||
}
|
||||
}
|
||||
648
src/main/java/com/dmiki/metaplugin/xom/JavaEntityGenerator.java
Normal file
648
src/main/java/com/dmiki/metaplugin/xom/JavaEntityGenerator.java
Normal file
@@ -0,0 +1,648 @@
|
||||
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<ClassModel> classModels = new ArrayList<ClassModel>();
|
||||
ClassModel rootModel = new ClassModel(rootClassName);
|
||||
classModels.add(rootModel);
|
||||
buildClassModel(root, rootModel, classModels, context);
|
||||
|
||||
List<File> targetFiles = new ArrayList<File>();
|
||||
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<ClassModel> allClasses,
|
||||
GenerationContext context) {
|
||||
Map<String, Integer> fieldCounters = new LinkedHashMap<String, Integer>();
|
||||
appendSelfValueFieldIfNeeded(node, classModel, fieldCounters);
|
||||
|
||||
for (XsdConfigItem child : node.getChildren()) {
|
||||
appendNodeAsField(classModel, child, fieldCounters, context, allClasses);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSelfValueFieldIfNeeded(XsdConfigItem node,
|
||||
ClassModel classModel,
|
||||
Map<String, Integer> 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<String, Integer> fieldCounters,
|
||||
GenerationContext context,
|
||||
List<ClassModel> 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<XsdConfigItem>());
|
||||
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<String, Integer> fieldCounters,
|
||||
GenerationContext context,
|
||||
List<ClassModel> 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<String, Integer> fieldCounters,
|
||||
GenerationContext context,
|
||||
List<ClassModel> allClasses) {
|
||||
for (XsdConfigItem child : item.getChildren()) {
|
||||
appendNodeAsField(classModel, child, fieldCounters, context, allClasses);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildClassSignature(XsdConfigItem node,
|
||||
Map<XsdConfigItem, String> signatureCache,
|
||||
Set<XsdConfigItem> 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<String, Integer> fieldCounters = new LinkedHashMap<String, Integer>();
|
||||
List<String> entries = new ArrayList<String>();
|
||||
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<String, Integer> fieldCounters,
|
||||
List<String> 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<String, Integer> fieldCounters,
|
||||
List<String> entries,
|
||||
Map<XsdConfigItem, String> signatureCache,
|
||||
Set<XsdConfigItem> 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<String> imports = new LinkedHashSet<String>();
|
||||
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<String> 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<String> 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<String, Integer> 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<String, Integer> 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<FieldModel> fields = new ArrayList<FieldModel>();
|
||||
|
||||
private ClassModel(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
|
||||
private static class GenerationContext {
|
||||
private final Map<String, Integer> classCounters = new LinkedHashMap<String, Integer>();
|
||||
private final Map<String, String> classNameByShapeKey = new LinkedHashMap<String, String>();
|
||||
private final Map<XsdConfigItem, String> signatureCache = new IdentityHashMap<XsdConfigItem, String>();
|
||||
}
|
||||
|
||||
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<String> JAVA_KEYWORDS = new LinkedHashSet<String>(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"
|
||||
));
|
||||
}
|
||||
168
src/main/java/com/dmiki/metaplugin/xom/XomConfigValidator.java
Normal file
168
src/main/java/com/dmiki/metaplugin/xom/XomConfigValidator.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class XomConfigValidator {
|
||||
|
||||
public void validate(XomGenerateOptions options, XsdConfigItem root) throws XomGenerationException {
|
||||
if (root == null) {
|
||||
throw new XomGenerationException("XOM-003", "未解析到XSD节点结构");
|
||||
}
|
||||
if (options == null) {
|
||||
throw new XomGenerationException("XOM-002", "生成参数为空");
|
||||
}
|
||||
String xsdPath = trimToNull(options.getXsdPath());
|
||||
if (xsdPath == null) {
|
||||
throw new XomGenerationException("XOM-001", "未提供XSD路径");
|
||||
}
|
||||
|
||||
File xsdFile = new File(xsdPath);
|
||||
if (!xsdFile.isFile()) {
|
||||
throw new XomGenerationException("XOM-001", "XSD文件不存在或不可读: " + xsdPath);
|
||||
}
|
||||
|
||||
String schemaId = trimToNull(options.getSchemaId());
|
||||
if (schemaId == null) {
|
||||
throw new XomGenerationException("XOM-002", "schemaId不能为空");
|
||||
}
|
||||
if (options.getMaxDepth() < 1) {
|
||||
throw new XomGenerationException("XOM-005", "maxDepth必须大于0");
|
||||
}
|
||||
if (options.isStrictMode() && trimToNull(options.getResultMapType()) == null) {
|
||||
throw new XomGenerationException("XOM-004", "strictMode=true 时 resultMap.type 不能为空");
|
||||
}
|
||||
|
||||
if (!hasRenderableNode(root)) {
|
||||
throw new XomGenerationException("XOM-003", "所有节点都被忽略,无法生成result映射");
|
||||
}
|
||||
validateTree(root, options, 1);
|
||||
}
|
||||
|
||||
private boolean hasRenderableNode(XsdConfigItem item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
boolean leaf = item.isEffectiveLeaf();
|
||||
XsdConfigMode mode = effectiveMode(item, leaf);
|
||||
if (leaf) {
|
||||
return mode != XsdConfigMode.IGNORE;
|
||||
}
|
||||
if (mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) {
|
||||
return false;
|
||||
}
|
||||
if (mode == XsdConfigMode.FLATTEN) {
|
||||
for (XsdConfigItem child : item.getChildren()) {
|
||||
if (hasRenderableNode(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void validateTree(XsdConfigItem item, XomGenerateOptions options, int depth) throws XomGenerationException {
|
||||
if (depth > options.getMaxDepth()) {
|
||||
throw new XomGenerationException("XOM-005", "节点层级超出最大深度: " + item.getXmlName());
|
||||
}
|
||||
|
||||
boolean leaf = item.isEffectiveLeaf();
|
||||
XsdConfigMode mode = effectiveMode(item, leaf);
|
||||
if (leaf && mode == XsdConfigMode.IGNORE) {
|
||||
return;
|
||||
}
|
||||
if (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean renderCurrentNode = leaf
|
||||
|| mode == XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES
|
||||
|| mode == XsdConfigMode.PRESERVE_HIERARCHY;
|
||||
if (options.isFailOnUnknownType() && renderCurrentNode) {
|
||||
String javaType = trimToNull(item.getJavaType());
|
||||
if (javaType == null) {
|
||||
throw new XomGenerationException("XOM-004", "节点Java类型缺失: " + item.getXmlName());
|
||||
}
|
||||
}
|
||||
|
||||
if (leaf) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.isStrictMode()) {
|
||||
Set<String> childLabels = new LinkedHashSet<String>();
|
||||
List<XsdConfigItem> directOutputChildren = collectDirectOutputChildren(item, options, depth);
|
||||
for (XsdConfigItem child : directOutputChildren) {
|
||||
String childName = trimToNull(child.getXmlName());
|
||||
if (childName != null && !childLabels.add(childName)) {
|
||||
throw new XomGenerationException("XOM-003",
|
||||
"同级节点存在重复label: " + childName + " (父节点: " + item.getXmlName() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (XsdConfigItem child : item.getChildren()) {
|
||||
validateTree(child, options, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private List<XsdConfigItem> collectDirectOutputChildren(XsdConfigItem parent, XomGenerateOptions options, int depth) {
|
||||
List<XsdConfigItem> output = new ArrayList<XsdConfigItem>();
|
||||
for (XsdConfigItem child : parent.getChildren()) {
|
||||
boolean leaf = child.isEffectiveLeaf();
|
||||
XsdConfigMode mode = effectiveMode(child, leaf);
|
||||
if ((leaf && mode == XsdConfigMode.IGNORE)
|
||||
|| (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (options.isFlattenChoice() && child.isChoiceContainer() && !child.isEffectiveLeaf() && depth < options.getMaxDepth()) {
|
||||
output.addAll(collectDirectOutputChildren(child, options, depth + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!leaf && mode == XsdConfigMode.FLATTEN) {
|
||||
output.addAll(collectDirectOutputChildren(child, options, depth + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
output.add(child);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
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 trimToNull(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = text.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
141
src/main/java/com/dmiki/metaplugin/xom/XomGenerateOptions.java
Normal file
141
src/main/java/com/dmiki/metaplugin/xom/XomGenerateOptions.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
public class XomGenerateOptions {
|
||||
private final String projectBasePath;
|
||||
private final String messageName;
|
||||
private final String schemaId;
|
||||
private final String resultMapType;
|
||||
private final String xsdPath;
|
||||
private final String outputPath;
|
||||
private final boolean strictMode;
|
||||
private final boolean prettyPrint;
|
||||
private final boolean overwrite;
|
||||
private final boolean failOnUnknownType;
|
||||
private final boolean includeAnnotations;
|
||||
private final boolean flattenChoice;
|
||||
private final int maxDepth;
|
||||
private final String customAttribute;
|
||||
private final String preProcessor;
|
||||
|
||||
public XomGenerateOptions(String projectBasePath,
|
||||
String messageName,
|
||||
String schemaId,
|
||||
String resultMapType,
|
||||
String xsdPath,
|
||||
String outputPath,
|
||||
boolean strictMode,
|
||||
boolean prettyPrint,
|
||||
boolean overwrite,
|
||||
boolean failOnUnknownType,
|
||||
boolean includeAnnotations,
|
||||
boolean flattenChoice,
|
||||
int maxDepth) {
|
||||
this(projectBasePath,
|
||||
messageName,
|
||||
schemaId,
|
||||
resultMapType,
|
||||
xsdPath,
|
||||
outputPath,
|
||||
strictMode,
|
||||
prettyPrint,
|
||||
overwrite,
|
||||
failOnUnknownType,
|
||||
includeAnnotations,
|
||||
flattenChoice,
|
||||
maxDepth,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
public XomGenerateOptions(String projectBasePath,
|
||||
String messageName,
|
||||
String schemaId,
|
||||
String resultMapType,
|
||||
String xsdPath,
|
||||
String outputPath,
|
||||
boolean strictMode,
|
||||
boolean prettyPrint,
|
||||
boolean overwrite,
|
||||
boolean failOnUnknownType,
|
||||
boolean includeAnnotations,
|
||||
boolean flattenChoice,
|
||||
int maxDepth,
|
||||
String customAttribute,
|
||||
String preProcessor) {
|
||||
this.projectBasePath = projectBasePath;
|
||||
this.messageName = messageName;
|
||||
this.schemaId = schemaId;
|
||||
this.resultMapType = resultMapType;
|
||||
this.xsdPath = xsdPath;
|
||||
this.outputPath = outputPath;
|
||||
this.strictMode = strictMode;
|
||||
this.prettyPrint = prettyPrint;
|
||||
this.overwrite = overwrite;
|
||||
this.failOnUnknownType = failOnUnknownType;
|
||||
this.includeAnnotations = includeAnnotations;
|
||||
this.flattenChoice = flattenChoice;
|
||||
this.maxDepth = maxDepth;
|
||||
this.customAttribute = customAttribute;
|
||||
this.preProcessor = preProcessor;
|
||||
}
|
||||
|
||||
public String getProjectBasePath() {
|
||||
return projectBasePath;
|
||||
}
|
||||
|
||||
public String getMessageName() {
|
||||
return messageName;
|
||||
}
|
||||
|
||||
public String getSchemaId() {
|
||||
return schemaId;
|
||||
}
|
||||
|
||||
public String getResultMapType() {
|
||||
return resultMapType;
|
||||
}
|
||||
|
||||
public String getXsdPath() {
|
||||
return xsdPath;
|
||||
}
|
||||
|
||||
public String getOutputPath() {
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
public boolean isStrictMode() {
|
||||
return strictMode;
|
||||
}
|
||||
|
||||
public boolean isPrettyPrint() {
|
||||
return prettyPrint;
|
||||
}
|
||||
|
||||
public boolean isOverwrite() {
|
||||
return overwrite;
|
||||
}
|
||||
|
||||
public boolean isFailOnUnknownType() {
|
||||
return failOnUnknownType;
|
||||
}
|
||||
|
||||
public boolean isIncludeAnnotations() {
|
||||
return includeAnnotations;
|
||||
}
|
||||
|
||||
public boolean isFlattenChoice() {
|
||||
return flattenChoice;
|
||||
}
|
||||
|
||||
public int getMaxDepth() {
|
||||
return maxDepth;
|
||||
}
|
||||
|
||||
public String getCustomAttribute() {
|
||||
return customAttribute;
|
||||
}
|
||||
|
||||
public String getPreProcessor() {
|
||||
return preProcessor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
public final class XomGenerationDefaults {
|
||||
public static final boolean STRICT_MODE = true;
|
||||
public static final boolean PRETTY_PRINT = true;
|
||||
public static final boolean OVERWRITE = true;
|
||||
public static final boolean FAIL_ON_UNKNOWN_TYPE = false;
|
||||
public static final boolean INCLUDE_ANNOTATIONS = false;
|
||||
public static final boolean FLATTEN_CHOICE = false;
|
||||
public static final int MAX_DEPTH = 64;
|
||||
|
||||
private XomGenerationDefaults() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
public class XomGenerationException extends Exception {
|
||||
private final String code;
|
||||
|
||||
public XomGenerationException(String code, String message) {
|
||||
super("[" + code + "] " + message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class XomGenerationResult {
|
||||
private final File mappingXmlFile;
|
||||
private final File javaEntityFile;
|
||||
|
||||
public XomGenerationResult(File mappingXmlFile, File javaEntityFile) {
|
||||
this.mappingXmlFile = mappingXmlFile;
|
||||
this.javaEntityFile = javaEntityFile;
|
||||
}
|
||||
|
||||
public File getMappingXmlFile() {
|
||||
return mappingXmlFile;
|
||||
}
|
||||
|
||||
public File getJavaEntityFile() {
|
||||
return javaEntityFile;
|
||||
}
|
||||
}
|
||||
159
src/main/java/com/dmiki/metaplugin/xom/XomGenerationService.java
Normal file
159
src/main/java/com/dmiki/metaplugin/xom/XomGenerationService.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class XomGenerationService {
|
||||
private final XomConfigValidator validator = new XomConfigValidator();
|
||||
private final XomXmlRenderer renderer = new XomXmlRenderer();
|
||||
private final JavaEntityGenerator javaEntityGenerator = new JavaEntityGenerator();
|
||||
|
||||
public String preview(XsdConfigItem root, XomGenerateOptions options) throws XomGenerationException {
|
||||
validator.validate(options, root);
|
||||
return renderer.render(root, options);
|
||||
}
|
||||
|
||||
public XomGenerationResult generate(XsdConfigItem root, XomGenerateOptions options) throws XomGenerationException {
|
||||
validator.validate(options, root);
|
||||
|
||||
String outputPath = resolveOutputPath(options);
|
||||
File outputFile = new File(outputPath);
|
||||
|
||||
if (outputFile.exists() && !options.isOverwrite()) {
|
||||
throw new XomGenerationException("XOM-006", "输出文件已存在且overwrite=false: " + outputPath);
|
||||
}
|
||||
|
||||
File parent = outputFile.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
throw new XomGenerationException("XOM-006", "无法创建输出目录: " + parent.getAbsolutePath());
|
||||
}
|
||||
|
||||
String xml = renderer.render(root, options);
|
||||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8)) {
|
||||
writer.write(xml);
|
||||
writer.flush();
|
||||
} catch (Exception ex) {
|
||||
throw new XomGenerationException("XOM-006", "写入输出文件失败: " + ex.getMessage());
|
||||
}
|
||||
File javaEntityFile = shouldGenerateJavaEntity(options)
|
||||
? javaEntityGenerator.generate(root, options, resolveJavaOutputDirectory(outputFile))
|
||||
: null;
|
||||
return new XomGenerationResult(outputFile, javaEntityFile);
|
||||
}
|
||||
|
||||
private File resolveJavaOutputDirectory(File mappingXmlFile) {
|
||||
File mappingDirectory = mappingXmlFile == null ? null : mappingXmlFile.getParentFile();
|
||||
if (mappingDirectory == null) {
|
||||
return new File("java");
|
||||
}
|
||||
return new File(mappingDirectory, "java");
|
||||
}
|
||||
|
||||
private boolean shouldGenerateJavaEntity(XomGenerateOptions options) {
|
||||
String resultMapType = trimToNull(options.getResultMapType());
|
||||
if (resultMapType == null) {
|
||||
return false;
|
||||
}
|
||||
int index = resultMapType.lastIndexOf('.');
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
private String resolveOutputPath(XomGenerateOptions options) throws XomGenerationException {
|
||||
String outputPath = trimToNull(options.getOutputPath());
|
||||
if (outputPath != null) {
|
||||
File outputFile = resolveOutputFile(outputPath, options);
|
||||
if (outputFile.isDirectory()) {
|
||||
return new File(outputFile, safeName(options.getSchemaId()) + ".xml").getAbsolutePath();
|
||||
}
|
||||
return outputFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
File resourcesDir = resolveResourcesDirectory(options);
|
||||
File targetDirectory = resolveNextMsgMapperDirectory(resourcesDir);
|
||||
String schemaId = trimToNull(options.getSchemaId());
|
||||
if (schemaId == null) {
|
||||
schemaId = nameFromXsd(options);
|
||||
}
|
||||
return new File(targetDirectory, safeName(schemaId) + ".xml").getAbsolutePath();
|
||||
}
|
||||
|
||||
private File resolveOutputFile(String outputPath, XomGenerateOptions options) {
|
||||
File outputFile = new File(outputPath);
|
||||
if (outputFile.isAbsolute()) {
|
||||
return outputFile;
|
||||
}
|
||||
String projectBasePath = trimToNull(options.getProjectBasePath());
|
||||
if (projectBasePath == null) {
|
||||
return outputFile;
|
||||
}
|
||||
return new File(projectBasePath, outputPath);
|
||||
}
|
||||
|
||||
private File resolveResourcesDirectory(XomGenerateOptions options) throws XomGenerationException {
|
||||
String projectBasePath = trimToNull(options.getProjectBasePath());
|
||||
if (projectBasePath != null) {
|
||||
return new File(projectBasePath, "src/main/resources");
|
||||
}
|
||||
|
||||
String xsdPath = trimToNull(options.getXsdPath());
|
||||
if (xsdPath == null) {
|
||||
throw new XomGenerationException("XOM-001", "无法推导输出路径,xsdPath为空");
|
||||
}
|
||||
File xsdFile = new File(xsdPath);
|
||||
File parent = xsdFile.getParentFile();
|
||||
if (parent == null) {
|
||||
throw new XomGenerationException("XOM-001", "无法推导输出路径,XSD父目录为空");
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
private File resolveNextMsgMapperDirectory(File resourcesDir) {
|
||||
int suffix = 0;
|
||||
while (true) {
|
||||
String name = suffix == 0 ? "msgmapper" : "msgmapper" + suffix;
|
||||
File candidate = new File(resourcesDir, name);
|
||||
if (!candidate.exists()) {
|
||||
return candidate;
|
||||
}
|
||||
suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
private String nameFromXsd(XomGenerateOptions options) {
|
||||
String xsdPath = trimToNull(options.getXsdPath());
|
||||
if (xsdPath == null) {
|
||||
return "mapping";
|
||||
}
|
||||
File xsdFile = new File(xsdPath);
|
||||
String fileName = xsdFile.getName();
|
||||
int index = fileName.lastIndexOf('.');
|
||||
if (index > 0) {
|
||||
fileName = fileName.substring(0, index);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private String safeName(String text) {
|
||||
String fallback = trimToNull(text);
|
||||
if (fallback == null) {
|
||||
return "result";
|
||||
}
|
||||
return fallback.replaceAll("[^A-Za-z0-9_.-]+", "_");
|
||||
}
|
||||
|
||||
private String trimToNull(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = text.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
271
src/main/java/com/dmiki/metaplugin/xom/XomXmlRenderer.java
Normal file
271
src/main/java/com/dmiki/metaplugin/xom/XomXmlRenderer.java
Normal file
@@ -0,0 +1,271 @@
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class XomXmlRenderer {
|
||||
|
||||
public String render(XsdConfigItem root, XomGenerateOptions options) {
|
||||
boolean prettyPrint = options.isPrettyPrint();
|
||||
String newline = prettyPrint ? "\n" : "";
|
||||
String renderedXsdPath = resolveRenderedXsdPath(options);
|
||||
|
||||
StringBuilder builder = new StringBuilder(1024);
|
||||
builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append(newline);
|
||||
appendMessageOpen(builder, options, prettyPrint, 0, newline);
|
||||
appendSchema(builder, options, renderedXsdPath, prettyPrint, 1, newline);
|
||||
appendResultMapOpen(builder, options, prettyPrint, 1, newline);
|
||||
List<XsdConfigItem> topResults = root == null
|
||||
? new ArrayList<XsdConfigItem>()
|
||||
: collectIncludedChildren(root, options, 1);
|
||||
for (XsdConfigItem child : topResults) {
|
||||
appendResult(builder, child, options, prettyPrint, 2, 2, newline, null);
|
||||
}
|
||||
appendResultMapClose(builder, prettyPrint, 1, newline);
|
||||
appendMessageClose(builder, prettyPrint, 0, newline);
|
||||
if (prettyPrint && builder.length() > 0 && builder.charAt(builder.length() - 1) != '\n') {
|
||||
builder.append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void appendMessageOpen(StringBuilder builder,
|
||||
XomGenerateOptions options,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
String newline) {
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("<message>").append(newline);
|
||||
}
|
||||
|
||||
private void appendMessageClose(StringBuilder builder,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
String newline) {
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("</message>").append(newline);
|
||||
}
|
||||
|
||||
private void appendSchema(StringBuilder builder,
|
||||
XomGenerateOptions options,
|
||||
String renderedXsdPath,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
String newline) {
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("<schema");
|
||||
appendXmlAttr(builder, "id", safe(options.getSchemaId()));
|
||||
appendXmlAttr(builder, "xsdPath", safe(renderedXsdPath));
|
||||
builder.append("/>").append(newline);
|
||||
}
|
||||
|
||||
private void appendResultMapOpen(StringBuilder builder,
|
||||
XomGenerateOptions options,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
String newline) {
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("<resultMap");
|
||||
appendXmlAttr(builder, "id", safe(options.getSchemaId()));
|
||||
String type = trimToNull(options.getResultMapType());
|
||||
appendXmlAttr(builder, "type", type == null ? "java.lang.Object" : type);
|
||||
builder.append(">").append(newline);
|
||||
}
|
||||
|
||||
private void appendResultMapClose(StringBuilder builder,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
String newline) {
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("</resultMap>").append(newline);
|
||||
}
|
||||
|
||||
private void appendResult(StringBuilder builder,
|
||||
XsdConfigItem item,
|
||||
XomGenerateOptions options,
|
||||
boolean prettyPrint,
|
||||
int indentLevel,
|
||||
int depth,
|
||||
String newline,
|
||||
String labelOverride) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (depth > options.getMaxDepth()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean leaf = item.isEffectiveLeaf();
|
||||
boolean attributeCarrierNode = !item.isAttribute() && item.hasOnlyAttributeChildren();
|
||||
XsdConfigMode mode = effectiveMode(item, leaf);
|
||||
if (leaf && mode == XsdConfigMode.IGNORE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<XsdConfigItem> children = (leaf && !attributeCarrierNode)
|
||||
? new ArrayList<XsdConfigItem>()
|
||||
: collectIncludedChildren(item, options, depth);
|
||||
if (!leaf && mode == XsdConfigMode.FLATTEN) {
|
||||
String parentLabel = safe(item.getXmlName());
|
||||
for (XsdConfigItem child : children) {
|
||||
String childLabel = safe(child.getXmlName());
|
||||
String flattenedLabel = parentLabel + ":" + childLabel;
|
||||
appendResult(builder, child, options, prettyPrint, indentLevel, depth + 1, newline, flattenedLabel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean includeProperty = leaf || mode == XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES;
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("<result");
|
||||
if (includeProperty) {
|
||||
String javaProperty = trimToNull(item.getJavaProperty());
|
||||
if (javaProperty != null) {
|
||||
appendXmlAttr(builder, "property", javaProperty);
|
||||
}
|
||||
}
|
||||
String renderedLabel = trimToNull(labelOverride);
|
||||
if (renderedLabel == null) {
|
||||
renderedLabel = safe(item.getXmlName());
|
||||
}
|
||||
String labelKey = item.isAttribute() ? "lable-attribute" : "lable";
|
||||
appendXmlAttr(builder, labelKey, renderedLabel);
|
||||
if (item.isCustomAttributeEnabled()) {
|
||||
appendXmlAttr(builder, "attribute",
|
||||
safe(resolveCellValue(item.getCustomAttributeValue(), options.getCustomAttribute())));
|
||||
}
|
||||
if (item.isPreProcessorEnabled()) {
|
||||
appendXmlAttr(builder, "pre-processor",
|
||||
safe(resolveCellValue(item.getPreProcessorValue(), options.getPreProcessor())));
|
||||
}
|
||||
|
||||
if (children.isEmpty()) {
|
||||
builder.append("/>").append(newline);
|
||||
return;
|
||||
}
|
||||
|
||||
builder.append(">").append(newline);
|
||||
for (XsdConfigItem child : children) {
|
||||
appendResult(builder, child, options, prettyPrint, indentLevel + 1, depth + 1, newline, null);
|
||||
}
|
||||
appendIndent(builder, prettyPrint, indentLevel);
|
||||
builder.append("</result>").append(newline);
|
||||
}
|
||||
|
||||
private List<XsdConfigItem> collectIncludedChildren(XsdConfigItem parent, XomGenerateOptions options, int depth) {
|
||||
List<XsdConfigItem> included = new ArrayList<XsdConfigItem>();
|
||||
for (XsdConfigItem child : parent.getChildren()) {
|
||||
boolean leaf = child.isEffectiveLeaf();
|
||||
XsdConfigMode mode = effectiveMode(child, leaf);
|
||||
if ((leaf && mode == XsdConfigMode.IGNORE)
|
||||
|| (!leaf && mode == XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN)) {
|
||||
continue;
|
||||
}
|
||||
if (options.isFlattenChoice() && child.isChoiceContainer() && !child.isEffectiveLeaf() && depth < options.getMaxDepth()) {
|
||||
included.addAll(collectIncludedChildren(child, options, depth + 1));
|
||||
continue;
|
||||
}
|
||||
included.add(child);
|
||||
}
|
||||
return included;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Backward compatibility: historical non-leaf mode "IGNORE" means drop this subtree.
|
||||
if (mode == XsdConfigMode.IGNORE) {
|
||||
return XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN;
|
||||
}
|
||||
return XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES;
|
||||
}
|
||||
|
||||
private void appendXmlAttr(StringBuilder builder, String key, String value) {
|
||||
builder.append(" ").append(key).append("=\"").append(escapeXml(value)).append("\"");
|
||||
}
|
||||
|
||||
private void appendIndent(StringBuilder builder, boolean prettyPrint, int indentLevel) {
|
||||
if (!prettyPrint) {
|
||||
return;
|
||||
}
|
||||
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 resolveCellValue(String rowValue, String globalValue) {
|
||||
String cell = trimToNull(rowValue);
|
||||
if (cell != null) {
|
||||
return cell;
|
||||
}
|
||||
return trimToNull(globalValue);
|
||||
}
|
||||
|
||||
private String escapeXml(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.replace("&", "&")
|
||||
.replace("\"", """)
|
||||
.replace("<", "<")
|
||||
.replace(">", ">");
|
||||
}
|
||||
|
||||
private String resolveRenderedXsdPath(XomGenerateOptions options) {
|
||||
String rawXsdPath = trimToNull(options.getXsdPath());
|
||||
if (rawXsdPath == null) {
|
||||
return "";
|
||||
}
|
||||
String projectBasePath = trimToNull(options.getProjectBasePath());
|
||||
if (projectBasePath == null) {
|
||||
return rawXsdPath;
|
||||
}
|
||||
try {
|
||||
Path resourcePath = Paths.get(projectBasePath, "src", "main", "resources")
|
||||
.toAbsolutePath()
|
||||
.normalize();
|
||||
Path xsdPath = Paths.get(rawXsdPath).toAbsolutePath().normalize();
|
||||
String relative = resourcePath.relativize(xsdPath).toString();
|
||||
if (relative.isEmpty()) {
|
||||
return rawXsdPath;
|
||||
}
|
||||
return relative.replace(File.separatorChar, '/');
|
||||
} catch (Exception ex) {
|
||||
return rawXsdPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
package com.dmiki.metaplugin.xom.reverse;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
import com.dmiki.metaplugin.xsd.parser.XsdSchemaParser;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class ReservedXomReverseDisplayService implements XomReverseDisplayService {
|
||||
private final XsdSchemaParser parser = new XsdSchemaParser();
|
||||
|
||||
@Override
|
||||
public XomReverseDisplayResult loadForDisplay(File mappingXmlFile, String projectBasePath) throws Exception {
|
||||
MappingDocument mappingDocument = parseMappingDocument(mappingXmlFile);
|
||||
File xsdFile = resolveXsdFile(mappingDocument.getXsdPath(), mappingXmlFile, projectBasePath);
|
||||
|
||||
XsdConfigItem root = parser.parse(xsdFile);
|
||||
prepareForReplay(root);
|
||||
applyReplay(root, mappingDocument.getResults());
|
||||
|
||||
return new XomReverseDisplayResult(
|
||||
root,
|
||||
xsdFile,
|
||||
mappingDocument.getSchemaId(),
|
||||
mappingDocument.getResultMapType(),
|
||||
inferTopInput(mappingDocument.getCustomAttributeValues()),
|
||||
inferTopInput(mappingDocument.getPreProcessorValues())
|
||||
);
|
||||
}
|
||||
|
||||
private MappingDocument parseMappingDocument(File mappingXmlFile) throws Exception {
|
||||
if (mappingXmlFile == null || !mappingXmlFile.isFile()) {
|
||||
throw new IllegalArgumentException("XOM文件不存在: " + mappingXmlFile);
|
||||
}
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
Document document = factory.newDocumentBuilder().parse(mappingXmlFile);
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
Element schema = findFirstDirectChildByLocalName(root, "schema");
|
||||
Element resultMap = findFirstDirectChildByLocalName(root, "resultMap");
|
||||
|
||||
if (resultMap == null) {
|
||||
throw new IllegalArgumentException("XOM文件缺少resultMap节点");
|
||||
}
|
||||
|
||||
String schemaId = trimToNull(attribute(schema, "id"));
|
||||
if (schemaId == null) {
|
||||
schemaId = trimToNull(resultMap.getAttribute("id"));
|
||||
}
|
||||
|
||||
String xsdPath = trimToNull(attribute(schema, "xsdPath"));
|
||||
String resultMapType = trimToNull(resultMap.getAttribute("type"));
|
||||
|
||||
Set<String> customAttributeValues = new LinkedHashSet<String>();
|
||||
Set<String> preProcessorValues = new LinkedHashSet<String>();
|
||||
List<ResultNode> results = parseResultChildren(resultMap, customAttributeValues, preProcessorValues);
|
||||
|
||||
return new MappingDocument(schemaId, resultMapType, xsdPath, results, customAttributeValues, preProcessorValues);
|
||||
}
|
||||
|
||||
private void prepareForReplay(XsdConfigItem item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
item.setConfigMode(item.isEffectiveLeaf() ? XsdConfigMode.IGNORE : XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN);
|
||||
item.setCustomAttributeEnabled(false);
|
||||
item.setCustomAttributeValue(null);
|
||||
item.setPreProcessorEnabled(false);
|
||||
item.setPreProcessorValue(null);
|
||||
for (XsdConfigItem child : item.getChildren()) {
|
||||
prepareForReplay(child);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyReplay(XsdConfigItem root, List<ResultNode> topResults) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ResultNode> pool = new ArrayList<ResultNode>(topResults);
|
||||
ResultNode direct = removeFirstMatch(pool, root);
|
||||
if (direct != null) {
|
||||
applyDirectConfig(root, direct);
|
||||
applyChildrenReplay(root, new ArrayList<ResultNode>(direct.getChildren()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.isEffectiveLeaf()) {
|
||||
root.setConfigMode(XsdConfigMode.IGNORE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAnySubtreeMatch(root, pool)) {
|
||||
root.setConfigMode(XsdConfigMode.FLATTEN);
|
||||
applyChildrenReplay(root, pool);
|
||||
return;
|
||||
}
|
||||
|
||||
root.setConfigMode(XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN);
|
||||
}
|
||||
|
||||
private void applyChildrenReplay(XsdConfigItem parent, List<ResultNode> pool) {
|
||||
for (XsdConfigItem child : parent.getChildren()) {
|
||||
ResultNode direct = removeFirstMatch(pool, child);
|
||||
if (direct != null) {
|
||||
applyDirectConfig(child, direct);
|
||||
applyChildrenReplay(child, new ArrayList<ResultNode>(direct.getChildren()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.isEffectiveLeaf()) {
|
||||
child.setConfigMode(XsdConfigMode.IGNORE);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasAnySubtreeMatch(child, pool)) {
|
||||
child.setConfigMode(XsdConfigMode.FLATTEN);
|
||||
applyChildrenReplay(child, pool);
|
||||
} else {
|
||||
child.setConfigMode(XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAnySubtreeMatch(XsdConfigItem node, List<ResultNode> pool) {
|
||||
for (XsdConfigItem child : node.getChildren()) {
|
||||
if (hasDirectMatch(child, pool)) {
|
||||
return true;
|
||||
}
|
||||
if (!child.isEffectiveLeaf() && hasAnySubtreeMatch(child, pool)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasDirectMatch(XsdConfigItem item, List<ResultNode> pool) {
|
||||
for (ResultNode node : pool) {
|
||||
if (matches(item, node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResultNode removeFirstMatch(List<ResultNode> pool, XsdConfigItem item) {
|
||||
for (int i = 0; i < pool.size(); i++) {
|
||||
ResultNode node = pool.get(i);
|
||||
if (matches(item, node)) {
|
||||
pool.remove(i);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean matches(XsdConfigItem item, ResultNode node) {
|
||||
if (item == null || node == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String expected = trimToNull(item.getXmlName());
|
||||
if (expected == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String actual = item.isAttribute()
|
||||
? firstNotNull(node.getLabelAttribute(), node.getLabel())
|
||||
: firstNotNull(node.getLabel(), node.getLabelAttribute());
|
||||
if (actual == null) {
|
||||
return false;
|
||||
}
|
||||
return expected.equals(actual) || expected.equalsIgnoreCase(actual);
|
||||
}
|
||||
|
||||
private void applyDirectConfig(XsdConfigItem item, ResultNode node) {
|
||||
String property = trimToNull(node.getProperty());
|
||||
item.setJavaProperty(property);
|
||||
|
||||
String customAttribute = trimToNull(node.getCustomAttribute());
|
||||
item.setCustomAttributeEnabled(customAttribute != null);
|
||||
item.setCustomAttributeValue(customAttribute);
|
||||
|
||||
String preProcessor = trimToNull(node.getPreProcessor());
|
||||
item.setPreProcessorEnabled(preProcessor != null);
|
||||
item.setPreProcessorValue(preProcessor);
|
||||
|
||||
if (item.isEffectiveLeaf()) {
|
||||
item.setConfigMode(XsdConfigMode.KEEP);
|
||||
} else if (property != null) {
|
||||
item.setConfigMode(XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES);
|
||||
} else {
|
||||
item.setConfigMode(XsdConfigMode.PRESERVE_HIERARCHY);
|
||||
}
|
||||
}
|
||||
|
||||
private File resolveXsdFile(String xsdPath, File mappingXmlFile, String projectBasePath) {
|
||||
if (xsdPath == null) {
|
||||
throw new IllegalArgumentException("XOM文件缺少schema.xsdPath配置");
|
||||
}
|
||||
|
||||
List<File> candidates = new ArrayList<File>();
|
||||
File declared = new File(xsdPath);
|
||||
if (declared.isAbsolute()) {
|
||||
candidates.add(declared);
|
||||
}
|
||||
|
||||
File mappingParent = mappingXmlFile == null ? null : mappingXmlFile.getParentFile();
|
||||
if (mappingParent != null) {
|
||||
candidates.add(new File(mappingParent, xsdPath));
|
||||
}
|
||||
|
||||
File resourcesRoot = findAncestorByName(mappingParent, "resources");
|
||||
if (resourcesRoot != null) {
|
||||
candidates.add(new File(resourcesRoot, xsdPath));
|
||||
}
|
||||
|
||||
String normalizedBasePath = trimToNull(projectBasePath);
|
||||
if (normalizedBasePath != null) {
|
||||
File projectBaseDir = new File(normalizedBasePath);
|
||||
candidates.add(new File(projectBaseDir, xsdPath));
|
||||
candidates.add(new File(new File(new File(new File(projectBaseDir, "src"), "main"), "resources"), xsdPath));
|
||||
}
|
||||
|
||||
Set<String> dedup = new LinkedHashSet<String>();
|
||||
for (File candidate : candidates) {
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
File normalized = candidate.getAbsoluteFile();
|
||||
String key = normalized.getAbsolutePath();
|
||||
if (!dedup.add(key)) {
|
||||
continue;
|
||||
}
|
||||
if (normalized.isFile()) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("根据xsdPath未找到XSD文件: " + xsdPath);
|
||||
}
|
||||
|
||||
private File findAncestorByName(File node, String directoryName) {
|
||||
if (directoryName == null) {
|
||||
return null;
|
||||
}
|
||||
String normalizedName = directoryName.toLowerCase(Locale.ROOT);
|
||||
File current = node;
|
||||
while (current != null) {
|
||||
if (normalizedName.equals(current.getName().toLowerCase(Locale.ROOT))) {
|
||||
return current;
|
||||
}
|
||||
current = current.getParentFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<ResultNode> parseResultChildren(Element parent,
|
||||
Set<String> customAttributeValues,
|
||||
Set<String> preProcessorValues) {
|
||||
List<ResultNode> nodes = new ArrayList<ResultNode>();
|
||||
if (parent == null) {
|
||||
return nodes;
|
||||
}
|
||||
NodeList children = parent.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element child = (Element) node;
|
||||
if (!"result".equals(localNameOf(child))) {
|
||||
continue;
|
||||
}
|
||||
ResultNode resultNode = parseResultNode(child, customAttributeValues, preProcessorValues);
|
||||
nodes.add(resultNode);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private ResultNode parseResultNode(Element element,
|
||||
Set<String> customAttributeValues,
|
||||
Set<String> preProcessorValues) {
|
||||
String label = trimToNull(element.getAttribute("label"));
|
||||
if (label == null) {
|
||||
label = trimToNull(element.getAttribute("lable"));
|
||||
}
|
||||
|
||||
String labelAttribute = trimToNull(element.getAttribute("label-attribute"));
|
||||
if (labelAttribute == null) {
|
||||
labelAttribute = trimToNull(element.getAttribute("lable-attribute"));
|
||||
}
|
||||
|
||||
String property = trimToNull(element.getAttribute("property"));
|
||||
String customAttribute = trimToNull(element.getAttribute("attribute"));
|
||||
String preProcessor = trimToNull(element.getAttribute("pre-processor"));
|
||||
|
||||
if (customAttribute != null) {
|
||||
customAttributeValues.add(customAttribute);
|
||||
}
|
||||
if (preProcessor != null) {
|
||||
preProcessorValues.add(preProcessor);
|
||||
}
|
||||
|
||||
List<ResultNode> children = parseResultChildren(element, customAttributeValues, preProcessorValues);
|
||||
return new ResultNode(label, labelAttribute, property, customAttribute, preProcessor, children);
|
||||
}
|
||||
|
||||
private Element findFirstDirectChildByLocalName(Element parent, String localName) {
|
||||
if (parent == null || localName == null) {
|
||||
return null;
|
||||
}
|
||||
NodeList children = parent.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element element = (Element) node;
|
||||
if (localName.equals(localNameOf(element))) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String localNameOf(Node node) {
|
||||
if (node == null) {
|
||||
return "";
|
||||
}
|
||||
String localName = node.getLocalName();
|
||||
if (localName != null) {
|
||||
return localName;
|
||||
}
|
||||
String nodeName = node.getNodeName();
|
||||
if (nodeName == null) {
|
||||
return "";
|
||||
}
|
||||
int index = nodeName.indexOf(':');
|
||||
return index >= 0 ? nodeName.substring(index + 1) : nodeName;
|
||||
}
|
||||
|
||||
private String attribute(Element element, String name) {
|
||||
if (element == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
return element.getAttribute(name);
|
||||
}
|
||||
|
||||
private String inferTopInput(Set<String> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (values.size() == 1) {
|
||||
return values.iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
private String firstNotNull(String first, String second) {
|
||||
String firstValue = trimToNull(first);
|
||||
if (firstValue != null) {
|
||||
return firstValue;
|
||||
}
|
||||
return trimToNull(second);
|
||||
}
|
||||
|
||||
private static final class MappingDocument {
|
||||
private final String schemaId;
|
||||
private final String resultMapType;
|
||||
private final String xsdPath;
|
||||
private final List<ResultNode> results;
|
||||
private final Set<String> customAttributeValues;
|
||||
private final Set<String> preProcessorValues;
|
||||
|
||||
private MappingDocument(String schemaId,
|
||||
String resultMapType,
|
||||
String xsdPath,
|
||||
List<ResultNode> results,
|
||||
Set<String> customAttributeValues,
|
||||
Set<String> preProcessorValues) {
|
||||
this.schemaId = schemaId;
|
||||
this.resultMapType = resultMapType;
|
||||
this.xsdPath = xsdPath;
|
||||
this.results = results;
|
||||
this.customAttributeValues = customAttributeValues;
|
||||
this.preProcessorValues = preProcessorValues;
|
||||
}
|
||||
|
||||
private String getSchemaId() {
|
||||
return schemaId;
|
||||
}
|
||||
|
||||
private String getResultMapType() {
|
||||
return resultMapType;
|
||||
}
|
||||
|
||||
private String getXsdPath() {
|
||||
return xsdPath;
|
||||
}
|
||||
|
||||
private List<ResultNode> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
private Set<String> getCustomAttributeValues() {
|
||||
return customAttributeValues;
|
||||
}
|
||||
|
||||
private Set<String> getPreProcessorValues() {
|
||||
return preProcessorValues;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResultNode {
|
||||
private final String label;
|
||||
private final String labelAttribute;
|
||||
private final String property;
|
||||
private final String customAttribute;
|
||||
private final String preProcessor;
|
||||
private final List<ResultNode> children;
|
||||
|
||||
private ResultNode(String label,
|
||||
String labelAttribute,
|
||||
String property,
|
||||
String customAttribute,
|
||||
String preProcessor,
|
||||
List<ResultNode> children) {
|
||||
this.label = label;
|
||||
this.labelAttribute = labelAttribute;
|
||||
this.property = property;
|
||||
this.customAttribute = customAttribute;
|
||||
this.preProcessor = preProcessor;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
private String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
private String getLabelAttribute() {
|
||||
return labelAttribute;
|
||||
}
|
||||
|
||||
private String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
private String getCustomAttribute() {
|
||||
return customAttribute;
|
||||
}
|
||||
|
||||
private String getPreProcessor() {
|
||||
return preProcessor;
|
||||
}
|
||||
|
||||
private List<ResultNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.dmiki.metaplugin.xom.reverse;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class XomReverseDisplayResult {
|
||||
private final XsdConfigItem root;
|
||||
private final File xsdFile;
|
||||
private final String schemaId;
|
||||
private final String resultMapType;
|
||||
private final String customAttribute;
|
||||
private final String preProcessor;
|
||||
|
||||
public XomReverseDisplayResult(XsdConfigItem root,
|
||||
File xsdFile,
|
||||
String schemaId,
|
||||
String resultMapType,
|
||||
String customAttribute,
|
||||
String preProcessor) {
|
||||
this.root = root;
|
||||
this.xsdFile = xsdFile;
|
||||
this.schemaId = schemaId;
|
||||
this.resultMapType = resultMapType;
|
||||
this.customAttribute = customAttribute;
|
||||
this.preProcessor = preProcessor;
|
||||
}
|
||||
|
||||
public XsdConfigItem getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public File getXsdFile() {
|
||||
return xsdFile;
|
||||
}
|
||||
|
||||
public String getSchemaId() {
|
||||
return schemaId;
|
||||
}
|
||||
|
||||
public String getResultMapType() {
|
||||
return resultMapType;
|
||||
}
|
||||
|
||||
public String getCustomAttribute() {
|
||||
return customAttribute;
|
||||
}
|
||||
|
||||
public String getPreProcessor() {
|
||||
return preProcessor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.dmiki.metaplugin.xom.reverse;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface XomReverseDisplayService {
|
||||
|
||||
XomReverseDisplayResult loadForDisplay(File mappingXmlFile, String projectBasePath) throws Exception;
|
||||
}
|
||||
149
src/main/java/com/dmiki/metaplugin/xsd/model/XsdConfigItem.java
Normal file
149
src/main/java/com/dmiki/metaplugin/xsd/model/XsdConfigItem.java
Normal file
@@ -0,0 +1,149 @@
|
||||
package com.dmiki.metaplugin.xsd.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class XsdConfigItem {
|
||||
private final String xmlName;
|
||||
private final boolean required;
|
||||
private final boolean repeated;
|
||||
private final boolean attribute;
|
||||
private final List<XsdConfigItem> children = new ArrayList<XsdConfigItem>();
|
||||
private XsdConfigMode configMode = XsdConfigMode.KEEP;
|
||||
private String javaProperty;
|
||||
private String javaType;
|
||||
private boolean choiceContainer;
|
||||
private boolean customAttributeEnabled;
|
||||
private boolean preProcessorEnabled;
|
||||
private String customAttributeValue;
|
||||
private String preProcessorValue;
|
||||
|
||||
public XsdConfigItem(String xmlName, boolean required, boolean repeated, boolean attribute) {
|
||||
this.xmlName = xmlName;
|
||||
this.required = required;
|
||||
this.repeated = repeated;
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
public String getXmlName() {
|
||||
return xmlName;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public boolean isRepeated() {
|
||||
return repeated;
|
||||
}
|
||||
|
||||
public boolean isAttribute() {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
public List<XsdConfigItem> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public XsdConfigMode getConfigMode() {
|
||||
return configMode;
|
||||
}
|
||||
|
||||
public void setConfigMode(XsdConfigMode configMode) {
|
||||
this.configMode = configMode;
|
||||
}
|
||||
|
||||
public String getJavaProperty() {
|
||||
return javaProperty;
|
||||
}
|
||||
|
||||
public void setJavaProperty(String javaProperty) {
|
||||
this.javaProperty = javaProperty;
|
||||
}
|
||||
|
||||
public String getJavaType() {
|
||||
return javaType;
|
||||
}
|
||||
|
||||
public void setJavaType(String javaType) {
|
||||
this.javaType = javaType;
|
||||
}
|
||||
|
||||
public boolean isChoiceContainer() {
|
||||
return choiceContainer;
|
||||
}
|
||||
|
||||
public void setChoiceContainer(boolean choiceContainer) {
|
||||
this.choiceContainer = choiceContainer;
|
||||
}
|
||||
|
||||
public boolean isCustomAttributeEnabled() {
|
||||
return customAttributeEnabled;
|
||||
}
|
||||
|
||||
public void setCustomAttributeEnabled(boolean customAttributeEnabled) {
|
||||
this.customAttributeEnabled = customAttributeEnabled;
|
||||
}
|
||||
|
||||
public String getCustomAttributeValue() {
|
||||
return customAttributeValue;
|
||||
}
|
||||
|
||||
public void setCustomAttributeValue(String customAttributeValue) {
|
||||
this.customAttributeValue = customAttributeValue;
|
||||
}
|
||||
|
||||
public boolean isPreProcessorEnabled() {
|
||||
return preProcessorEnabled;
|
||||
}
|
||||
|
||||
public void setPreProcessorEnabled(boolean preProcessorEnabled) {
|
||||
this.preProcessorEnabled = preProcessorEnabled;
|
||||
}
|
||||
|
||||
public String getPreProcessorValue() {
|
||||
return preProcessorValue;
|
||||
}
|
||||
|
||||
public void setPreProcessorValue(String preProcessorValue) {
|
||||
this.preProcessorValue = preProcessorValue;
|
||||
}
|
||||
|
||||
public boolean hasChildren() {
|
||||
return !children.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasNonAttributeChildren() {
|
||||
for (XsdConfigItem child : children) {
|
||||
if (!child.isAttribute()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasOnlyAttributeChildren() {
|
||||
return hasChildren() && !hasNonAttributeChildren();
|
||||
}
|
||||
|
||||
public boolean isEffectiveLeaf() {
|
||||
return !hasNonAttributeChildren();
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (required) {
|
||||
builder.append("* ");
|
||||
}
|
||||
if (attribute) {
|
||||
builder.append("@");
|
||||
}
|
||||
builder.append(xmlName);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getDisplayName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.dmiki.metaplugin.xsd.model;
|
||||
|
||||
public enum XsdConfigMode {
|
||||
PRESERVE_HIERARCHY_AND_ATTRIBUTES("保留层级保留属性", false, true),
|
||||
PRESERVE_HIERARCHY("保留层级不保留属性", false, true),
|
||||
FLATTEN("不保留层级", false, true),
|
||||
FLATTEN_AND_IGNORE_CHILDREN("不保留层级且不保留子项", false, true),
|
||||
KEEP("保留", true, false),
|
||||
IGNORE("不保留", true, false);
|
||||
|
||||
private final String label;
|
||||
private final boolean leafAvailable;
|
||||
private final boolean nonLeafAvailable;
|
||||
|
||||
XsdConfigMode(String label, boolean leafAvailable, boolean nonLeafAvailable) {
|
||||
this.label = label;
|
||||
this.leafAvailable = leafAvailable;
|
||||
this.nonLeafAvailable = nonLeafAvailable;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public boolean isLeafAvailable() {
|
||||
return leafAvailable;
|
||||
}
|
||||
|
||||
public boolean isNonLeafAvailable() {
|
||||
return nonLeafAvailable;
|
||||
}
|
||||
|
||||
public static XsdConfigMode fromLabel(String label) {
|
||||
if (label == null) {
|
||||
return KEEP;
|
||||
}
|
||||
String normalized = label.trim();
|
||||
for (XsdConfigMode mode : values()) {
|
||||
if (mode.label.equals(normalized)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
// Backward compatibility for historical label.
|
||||
if ("忽略".equals(normalized)) {
|
||||
return IGNORE;
|
||||
}
|
||||
return KEEP;
|
||||
}
|
||||
|
||||
public static String[] labels() {
|
||||
XsdConfigMode[] values = values();
|
||||
String[] labels = new String[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
labels[i] = values[i].label;
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
public static String[] nonLeafLabels() {
|
||||
return new String[]{
|
||||
PRESERVE_HIERARCHY_AND_ATTRIBUTES.getLabel(),
|
||||
PRESERVE_HIERARCHY.getLabel(),
|
||||
FLATTEN.getLabel(),
|
||||
FLATTEN_AND_IGNORE_CHILDREN.getLabel()
|
||||
};
|
||||
}
|
||||
|
||||
public static String[] leafLabels() {
|
||||
return new String[]{
|
||||
KEEP.getLabel(),
|
||||
IGNORE.getLabel()
|
||||
};
|
||||
}
|
||||
|
||||
public static XsdConfigMode normalizeForNode(boolean leaf, XsdConfigMode mode) {
|
||||
if (mode == null) {
|
||||
return leaf ? KEEP : PRESERVE_HIERARCHY_AND_ATTRIBUTES;
|
||||
}
|
||||
if (leaf) {
|
||||
return mode.isLeafAvailable() ? mode : KEEP;
|
||||
}
|
||||
if (mode == IGNORE) {
|
||||
return FLATTEN_AND_IGNORE_CHILDREN;
|
||||
}
|
||||
if (mode == KEEP) {
|
||||
return PRESERVE_HIERARCHY_AND_ATTRIBUTES;
|
||||
}
|
||||
return mode.isNonLeafAvailable() ? mode : PRESERVE_HIERARCHY_AND_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
package com.dmiki.metaplugin.xsd.parser;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class XsdSchemaParser {
|
||||
|
||||
private static final String DOCUMENT_ELEMENT_NAME = "Document";
|
||||
private static final String UNBOUNDED = "unbounded";
|
||||
|
||||
public XsdConfigItem parse(File xsdFile) throws Exception {
|
||||
if (xsdFile == null || !xsdFile.isFile()) {
|
||||
throw new IllegalArgumentException("XSD文件不存在: " + xsdFile);
|
||||
}
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
Document document = factory.newDocumentBuilder().parse(xsdFile);
|
||||
|
||||
Element schema = document.getDocumentElement();
|
||||
SchemaIndex index = buildIndex(schema);
|
||||
Element rootElement = resolveRootElement(index);
|
||||
|
||||
XsdConfigItem root = buildNodeFromElement(rootElement, index, new LinkedHashSet<String>());
|
||||
applyDefaultConfigMode(root);
|
||||
normalizeCcyAttributeProperties(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
private SchemaIndex buildIndex(Element schema) {
|
||||
SchemaIndex index = new SchemaIndex();
|
||||
NodeList children = schema.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element child = (Element) node;
|
||||
String localName = localNameOf(child);
|
||||
String name = child.getAttribute("name");
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if ("element".equals(localName)) {
|
||||
index.globalElements.put(name, child);
|
||||
} else if ("complexType".equals(localName)) {
|
||||
index.complexTypes.put(name, child);
|
||||
} else if ("simpleType".equals(localName)) {
|
||||
index.simpleTypes.put(name, child);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private Element resolveRootElement(SchemaIndex index) {
|
||||
Element root = index.globalElements.get(DOCUMENT_ELEMENT_NAME);
|
||||
if (root != null) {
|
||||
return root;
|
||||
}
|
||||
if (index.globalElements.isEmpty()) {
|
||||
throw new IllegalStateException("未找到全局xs:element定义,无法构建树结构");
|
||||
}
|
||||
return index.globalElements.values().iterator().next();
|
||||
}
|
||||
|
||||
private XsdConfigItem buildNodeFromElement(Element element, SchemaIndex index, LinkedHashSet<String> typeStack) {
|
||||
String name = element.getAttribute("name");
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "AnonymousElement";
|
||||
}
|
||||
int minOccurs = parseIntOrDefault(element.getAttribute("minOccurs"), 1);
|
||||
String maxOccursRaw = element.getAttribute("maxOccurs");
|
||||
boolean repeated = !maxOccursRaw.isEmpty() && (!"1".equals(maxOccursRaw));
|
||||
if (UNBOUNDED.equalsIgnoreCase(maxOccursRaw)) {
|
||||
repeated = true;
|
||||
}
|
||||
|
||||
XsdConfigItem item = new XsdConfigItem(name, minOccurs > 0, repeated, false);
|
||||
item.setJavaProperty(toLowerCamel(name));
|
||||
|
||||
String typeName = normalizeTypeName(element.getAttribute("type"));
|
||||
Element inlineComplexType = firstDirectChildByLocalName(element, "complexType");
|
||||
Element inlineSimpleType = firstDirectChildByLocalName(element, "simpleType");
|
||||
|
||||
if (inlineComplexType != null) {
|
||||
item.setJavaType(toUpperCamel(name));
|
||||
populateFromComplexType(item, inlineComplexType, index, typeStack);
|
||||
} else if (typeName != null && index.complexTypes.containsKey(typeName)) {
|
||||
item.setJavaType(cleanComplexJavaType(typeName, name));
|
||||
expandComplexTypeByName(item, typeName, index, typeStack);
|
||||
} else if (inlineSimpleType != null) {
|
||||
item.setJavaType(resolveSimpleTypeJavaType(inlineSimpleType, index));
|
||||
} else if (typeName != null) {
|
||||
item.setJavaType(resolveTypeByName(typeName, index, name));
|
||||
} else {
|
||||
item.setJavaType("String");
|
||||
}
|
||||
|
||||
if (item.isEffectiveLeaf() && item.getConfigMode() == null) {
|
||||
item.setConfigMode(XsdConfigMode.KEEP);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private void expandComplexTypeByName(XsdConfigItem item,
|
||||
String typeName,
|
||||
SchemaIndex index,
|
||||
LinkedHashSet<String> typeStack) {
|
||||
if (typeStack.contains(typeName)) {
|
||||
return;
|
||||
}
|
||||
Element complexType = index.complexTypes.get(typeName);
|
||||
if (complexType == null) {
|
||||
return;
|
||||
}
|
||||
typeStack.add(typeName);
|
||||
try {
|
||||
populateFromComplexType(item, complexType, index, typeStack);
|
||||
} finally {
|
||||
typeStack.remove(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateFromComplexType(XsdConfigItem parent,
|
||||
Element complexType,
|
||||
SchemaIndex index,
|
||||
LinkedHashSet<String> typeStack) {
|
||||
NodeList children = complexType.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element child = (Element) node;
|
||||
String localName = localNameOf(child);
|
||||
if ("sequence".equals(localName) || "choice".equals(localName) || "all".equals(localName)) {
|
||||
if ("choice".equals(localName)) {
|
||||
parent.setChoiceContainer(true);
|
||||
}
|
||||
collectParticleChildren(parent, child, index, typeStack, localName);
|
||||
} else if ("complexContent".equals(localName)) {
|
||||
collectFromComplexContent(parent, child, index, typeStack);
|
||||
} else if ("simpleContent".equals(localName)) {
|
||||
collectFromSimpleContent(parent, child, index, typeStack);
|
||||
} else if ("attribute".equals(localName)) {
|
||||
parent.getChildren().add(buildNodeFromAttribute(child, index));
|
||||
} else if ("element".equals(localName)) {
|
||||
parent.getChildren().add(buildNodeFromElement(child, index, typeStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectFromSimpleContent(XsdConfigItem parent,
|
||||
Element simpleContent,
|
||||
SchemaIndex index,
|
||||
LinkedHashSet<String> typeStack) {
|
||||
NodeList children = simpleContent.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element element = (Element) node;
|
||||
String localName = localNameOf(element);
|
||||
if (!"extension".equals(localName) && !"restriction".equals(localName)) {
|
||||
continue;
|
||||
}
|
||||
String baseType = normalizeTypeName(element.getAttribute("base"));
|
||||
if (baseType != null && index.complexTypes.containsKey(baseType)) {
|
||||
expandComplexTypeByName(parent, baseType, index, typeStack);
|
||||
}
|
||||
if (baseType != null && (isBuiltInXsdType(baseType) || index.simpleTypes.containsKey(baseType))) {
|
||||
parent.setJavaType(resolveTypeByName(baseType, index, parent.getXmlName()));
|
||||
}
|
||||
|
||||
NodeList extensionChildren = element.getChildNodes();
|
||||
for (int j = 0; j < extensionChildren.getLength(); j++) {
|
||||
Node extensionNode = extensionChildren.item(j);
|
||||
if (!(extensionNode instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element extensionElement = (Element) extensionNode;
|
||||
String extensionLocal = localNameOf(extensionElement);
|
||||
if ("attribute".equals(extensionLocal)) {
|
||||
parent.getChildren().add(buildNodeFromAttribute(extensionElement, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectFromComplexContent(XsdConfigItem parent,
|
||||
Element complexContent,
|
||||
SchemaIndex index,
|
||||
LinkedHashSet<String> typeStack) {
|
||||
NodeList children = complexContent.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element element = (Element) node;
|
||||
String localName = localNameOf(element);
|
||||
if (!"extension".equals(localName) && !"restriction".equals(localName)) {
|
||||
continue;
|
||||
}
|
||||
String baseType = normalizeTypeName(element.getAttribute("base"));
|
||||
if (baseType != null && index.complexTypes.containsKey(baseType)) {
|
||||
expandComplexTypeByName(parent, baseType, index, typeStack);
|
||||
}
|
||||
|
||||
NodeList extensionChildren = element.getChildNodes();
|
||||
for (int j = 0; j < extensionChildren.getLength(); j++) {
|
||||
Node extensionNode = extensionChildren.item(j);
|
||||
if (!(extensionNode instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element extensionElement = (Element) extensionNode;
|
||||
String extensionLocal = localNameOf(extensionElement);
|
||||
if ("sequence".equals(extensionLocal) || "choice".equals(extensionLocal) || "all".equals(extensionLocal)) {
|
||||
if ("choice".equals(extensionLocal)) {
|
||||
parent.setChoiceContainer(true);
|
||||
}
|
||||
collectParticleChildren(parent, extensionElement, index, typeStack, extensionLocal);
|
||||
} else if ("attribute".equals(extensionLocal)) {
|
||||
parent.getChildren().add(buildNodeFromAttribute(extensionElement, index));
|
||||
} else if ("element".equals(extensionLocal)) {
|
||||
parent.getChildren().add(buildNodeFromElement(extensionElement, index, typeStack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectParticleChildren(XsdConfigItem parent,
|
||||
Element particle,
|
||||
SchemaIndex index,
|
||||
LinkedHashSet<String> typeStack,
|
||||
String particleType) {
|
||||
NodeList children = particle.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element child = (Element) node;
|
||||
String localName = localNameOf(child);
|
||||
if ("element".equals(localName)) {
|
||||
XsdConfigItem item = buildNodeFromElement(child, index, typeStack);
|
||||
if ("choice".equals(particleType) && !item.isEffectiveLeaf()) {
|
||||
item.setChoiceContainer(true);
|
||||
}
|
||||
parent.getChildren().add(item);
|
||||
} else if ("choice".equals(localName) || "sequence".equals(localName) || "all".equals(localName)) {
|
||||
if ("choice".equals(localName)) {
|
||||
parent.setChoiceContainer(true);
|
||||
}
|
||||
collectParticleChildren(parent, child, index, typeStack, localName);
|
||||
} else if ("attribute".equals(localName)) {
|
||||
parent.getChildren().add(buildNodeFromAttribute(child, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private XsdConfigItem buildNodeFromAttribute(Element attribute, SchemaIndex index) {
|
||||
String name = attribute.getAttribute("name");
|
||||
if (name == null || name.isEmpty()) {
|
||||
name = "AnonymousAttribute";
|
||||
}
|
||||
boolean required = "required".equalsIgnoreCase(attribute.getAttribute("use"));
|
||||
XsdConfigItem item = new XsdConfigItem(name, required, false, true);
|
||||
item.setJavaProperty(toLowerCamel(name));
|
||||
|
||||
String typeName = normalizeTypeName(attribute.getAttribute("type"));
|
||||
if (typeName == null) {
|
||||
item.setJavaType("String");
|
||||
} else {
|
||||
item.setJavaType(resolveTypeByName(typeName, index, name));
|
||||
}
|
||||
item.setConfigMode(XsdConfigMode.KEEP);
|
||||
return item;
|
||||
}
|
||||
|
||||
private String resolveTypeByName(String typeName, SchemaIndex index, String fallbackName) {
|
||||
if (isBuiltInXsdType(typeName)) {
|
||||
return mapXsdBuiltInType(typeName);
|
||||
}
|
||||
Element simpleType = index.simpleTypes.get(typeName);
|
||||
if (simpleType != null) {
|
||||
return resolveSimpleTypeJavaType(simpleType, index);
|
||||
}
|
||||
if (index.complexTypes.containsKey(typeName)) {
|
||||
return cleanComplexJavaType(typeName, fallbackName);
|
||||
}
|
||||
return toUpperCamel(typeName);
|
||||
}
|
||||
|
||||
private String resolveSimpleTypeJavaType(Element simpleType, SchemaIndex index) {
|
||||
Element restriction = firstDirectChildByLocalName(simpleType, "restriction");
|
||||
if (restriction != null) {
|
||||
String base = normalizeTypeName(restriction.getAttribute("base"));
|
||||
if (base != null) {
|
||||
return resolveTypeByName(base, index, simpleType.getAttribute("name"));
|
||||
}
|
||||
}
|
||||
|
||||
Element union = firstDirectChildByLocalName(simpleType, "union");
|
||||
if (union != null) {
|
||||
return "String";
|
||||
}
|
||||
return "String";
|
||||
}
|
||||
|
||||
private boolean isBuiltInXsdType(String typeName) {
|
||||
if (typeName == null) {
|
||||
return false;
|
||||
}
|
||||
String lower = typeName.toLowerCase(Locale.ROOT);
|
||||
return lower.startsWith("xs:") || lower.startsWith("xsd:")
|
||||
|| "string".equals(lower)
|
||||
|| "int".equals(lower)
|
||||
|| "integer".equals(lower)
|
||||
|| "long".equals(lower)
|
||||
|| "short".equals(lower)
|
||||
|| "byte".equals(lower)
|
||||
|| "decimal".equals(lower)
|
||||
|| "double".equals(lower)
|
||||
|| "float".equals(lower)
|
||||
|| "boolean".equals(lower)
|
||||
|| "date".equals(lower)
|
||||
|| "datetime".equals(lower)
|
||||
|| "time".equals(lower)
|
||||
|| "base64binary".equals(lower);
|
||||
}
|
||||
|
||||
private String mapXsdBuiltInType(String rawTypeName) {
|
||||
String typeName = normalizeTypeName(rawTypeName);
|
||||
if (typeName == null) {
|
||||
return "String";
|
||||
}
|
||||
String lower = typeName.toLowerCase(Locale.ROOT);
|
||||
if ("string".equals(lower) || "token".equals(lower) || "normalizedstring".equals(lower) || "anyuri".equals(lower)) {
|
||||
return "String";
|
||||
}
|
||||
if ("boolean".equals(lower)) {
|
||||
return "Boolean";
|
||||
}
|
||||
if ("byte".equals(lower)) {
|
||||
return "Byte";
|
||||
}
|
||||
if ("short".equals(lower)) {
|
||||
return "Short";
|
||||
}
|
||||
if ("int".equals(lower) || "integer".equals(lower) || "nonnegativeinteger".equals(lower) || "positiveinteger".equals(lower)) {
|
||||
return "Integer";
|
||||
}
|
||||
if ("long".equals(lower)) {
|
||||
return "Long";
|
||||
}
|
||||
if ("float".equals(lower)) {
|
||||
return "Float";
|
||||
}
|
||||
if ("double".equals(lower)) {
|
||||
return "Double";
|
||||
}
|
||||
if ("decimal".equals(lower)) {
|
||||
return "BigDecimal";
|
||||
}
|
||||
if ("date".equals(lower)) {
|
||||
return "LocalDate";
|
||||
}
|
||||
if ("datetime".equals(lower)) {
|
||||
return "LocalDateTime";
|
||||
}
|
||||
if ("time".equals(lower)) {
|
||||
return "LocalTime";
|
||||
}
|
||||
if ("base64binary".equals(lower)) {
|
||||
return "byte[]";
|
||||
}
|
||||
return "String";
|
||||
}
|
||||
|
||||
private String cleanComplexJavaType(String rawTypeName, String fallback) {
|
||||
String normalized = normalizeTypeName(rawTypeName);
|
||||
if (normalized == null || normalized.isEmpty()) {
|
||||
return toUpperCamel(fallback);
|
||||
}
|
||||
int suffixIndex = normalized.indexOf("__");
|
||||
if (suffixIndex > 0) {
|
||||
normalized = normalized.substring(0, suffixIndex);
|
||||
}
|
||||
return toUpperCamel(normalized);
|
||||
}
|
||||
|
||||
private String normalizeTypeName(String typeName) {
|
||||
if (typeName == null || typeName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int idx = typeName.indexOf(':');
|
||||
if (idx >= 0 && idx < typeName.length() - 1) {
|
||||
return typeName.substring(idx + 1);
|
||||
}
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private int parseIntOrDefault(String text, int defaultValue) {
|
||||
if (text == null || text.trim().isEmpty()) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(text.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private Element firstDirectChildByLocalName(Element parent, String localName) {
|
||||
NodeList children = parent.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
if (!(node instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
Element element = (Element) node;
|
||||
if (localName.equals(localNameOf(element))) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String localNameOf(Node node) {
|
||||
String localName = node.getLocalName();
|
||||
if (localName != null) {
|
||||
return localName;
|
||||
}
|
||||
String nodeName = node.getNodeName();
|
||||
if (nodeName == null) {
|
||||
return "";
|
||||
}
|
||||
int idx = nodeName.indexOf(':');
|
||||
return idx >= 0 ? nodeName.substring(idx + 1) : nodeName;
|
||||
}
|
||||
|
||||
private void applyDefaultConfigMode(XsdConfigItem root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
if (!root.isEffectiveLeaf()) {
|
||||
root.setConfigMode(XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES);
|
||||
for (XsdConfigItem child : root.getChildren()) {
|
||||
applyDefaultConfigMode(child);
|
||||
}
|
||||
} else {
|
||||
root.setConfigMode(XsdConfigMode.KEEP);
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeCcyAttributeProperties(XsdConfigItem root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
normalizeCcyAttributePropertiesByEntityScope(root);
|
||||
}
|
||||
|
||||
private void normalizeCcyAttributePropertiesByEntityScope(XsdConfigItem entityNode) {
|
||||
List<XsdConfigItem> ccyAttributes = new ArrayList<XsdConfigItem>();
|
||||
collectCcyAttributesInCurrentEntity(entityNode, ccyAttributes);
|
||||
if (ccyAttributes.size() == 1) {
|
||||
ccyAttributes.get(0).setJavaProperty("ccy");
|
||||
} else if (ccyAttributes.size() > 1) {
|
||||
for (int i = 0; i < ccyAttributes.size(); i++) {
|
||||
ccyAttributes.get(i).setJavaProperty("ccy" + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
for (XsdConfigItem child : entityNode.getChildren()) {
|
||||
if (isNestedEntityNode(child)) {
|
||||
normalizeCcyAttributePropertiesByEntityScope(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectCcyAttributesInCurrentEntity(XsdConfigItem entityNode, List<XsdConfigItem> output) {
|
||||
if (entityNode == null) {
|
||||
return;
|
||||
}
|
||||
for (XsdConfigItem child : entityNode.getChildren()) {
|
||||
collectCcyFromInlinedNode(child, output);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectCcyFromInlinedNode(XsdConfigItem node, List<XsdConfigItem> output) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (node.isAttribute()) {
|
||||
if ("ccy".equalsIgnoreCase(node.getXmlName())) {
|
||||
output.add(node);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (shouldInlineSimpleContentNode(node) || shouldInlineAttributeCarrierWrapperNode(node)) {
|
||||
for (XsdConfigItem child : node.getChildren()) {
|
||||
collectCcyFromInlinedNode(child, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNestedEntityNode(XsdConfigItem node) {
|
||||
if (node == null || node.isAttribute() || !node.hasChildren()) {
|
||||
return false;
|
||||
}
|
||||
return !shouldInlineSimpleContentNode(node) && !shouldInlineAttributeCarrierWrapperNode(node);
|
||||
}
|
||||
|
||||
private boolean shouldInlineSimpleContentNode(XsdConfigItem node) {
|
||||
return isSimpleContentNode(node) && !node.isRepeated();
|
||||
}
|
||||
|
||||
private boolean isSimpleContentNode(XsdConfigItem node) {
|
||||
return node != null && !node.isAttribute() && node.hasOnlyAttributeChildren();
|
||||
}
|
||||
|
||||
private boolean shouldInlineAttributeCarrierWrapperNode(XsdConfigItem node) {
|
||||
if (node == null || node.isRepeated() || node.isAttribute() || !node.hasChildren()) {
|
||||
return false;
|
||||
}
|
||||
boolean hasAttributeCarrierChild = false;
|
||||
for (XsdConfigItem child : node.getChildren()) {
|
||||
if (!child.isEffectiveLeaf()) {
|
||||
return false;
|
||||
}
|
||||
if (child.hasOnlyAttributeChildren()) {
|
||||
hasAttributeCarrierChild = true;
|
||||
}
|
||||
}
|
||||
return hasAttributeCarrierChild;
|
||||
}
|
||||
|
||||
private String toLowerCamel(String source) {
|
||||
String upperCamel = toUpperCamel(source);
|
||||
if (upperCamel.isEmpty()) {
|
||||
return upperCamel;
|
||||
}
|
||||
if (isAllUpperCaseLetters(upperCamel)) {
|
||||
return upperCamel.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
if (upperCamel.length() == 1) {
|
||||
return upperCamel.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
return upperCamel.substring(0, 1).toLowerCase(Locale.ROOT) + upperCamel.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 String toUpperCamel(String source) {
|
||||
if (source == null || source.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
String cleaned = source.replaceAll("[^A-Za-z0-9]+", " ").trim();
|
||||
if (cleaned.isEmpty()) {
|
||||
return source;
|
||||
}
|
||||
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 static class SchemaIndex {
|
||||
private final Map<String, Element> globalElements = new LinkedHashMap<String, Element>();
|
||||
private final Map<String, Element> complexTypes = new LinkedHashMap<String, Element>();
|
||||
private final Map<String, Element> simpleTypes = new LinkedHashMap<String, Element>();
|
||||
}
|
||||
|
||||
public static String[] javaTypeOptions() {
|
||||
return new String[]{
|
||||
"String",
|
||||
"Integer",
|
||||
"Long",
|
||||
"BigDecimal",
|
||||
"Boolean",
|
||||
"LocalDate",
|
||||
"LocalDateTime",
|
||||
"LocalTime",
|
||||
"Double",
|
||||
"Float",
|
||||
"byte[]"
|
||||
};
|
||||
}
|
||||
}
|
||||
1886
src/main/java/com/dmiki/metaplugin/xsd/ui/XsdConfigDialog.java
Normal file
1886
src/main/java/com/dmiki/metaplugin/xsd/ui/XsdConfigDialog.java
Normal file
File diff suppressed because it is too large
Load Diff
215
src/main/java/com/dmiki/metaplugin/xsd/ui/XsdTreeTableModel.java
Normal file
215
src/main/java/com/dmiki/metaplugin/xsd/ui/XsdTreeTableModel.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package com.dmiki.metaplugin.xsd.ui;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
|
||||
import com.intellij.ui.treeStructure.treetable.TreeTableModel;
|
||||
import com.intellij.util.ui.ColumnInfo;
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import java.util.List;
|
||||
|
||||
public class XsdTreeTableModel extends ListTreeTableModelOnColumns {
|
||||
|
||||
private static final ColumnInfo[] COLUMNS = new ColumnInfo[]{
|
||||
new ColumnInfo<DefaultMutableTreeNode, String>("XML节点") {
|
||||
@Override
|
||||
public String valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
return item == null ? "" : item.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass() {
|
||||
return TreeTableModel.class;
|
||||
}
|
||||
},
|
||||
new ColumnInfo<DefaultMutableTreeNode, String>("配置策略") {
|
||||
@Override
|
||||
public String valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
return isConfigVisible(node) && item != null ? item.getConfigMode().getLabel() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(DefaultMutableTreeNode node) {
|
||||
return isConfigVisible(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DefaultMutableTreeNode node, String value) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (isConfigVisible(node) && item != null && value != null) {
|
||||
XsdConfigMode selected = XsdConfigMode.fromLabel(value);
|
||||
item.setConfigMode(XsdConfigMode.normalizeForNode(item.isEffectiveLeaf(), selected));
|
||||
}
|
||||
}
|
||||
},
|
||||
new ColumnInfo<DefaultMutableTreeNode, String>("JAVA属性名") {
|
||||
@Override
|
||||
public String valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
return isConfigVisible(node) && item != null ? item.getJavaProperty() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(DefaultMutableTreeNode node) {
|
||||
return isConfigVisible(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DefaultMutableTreeNode node, String value) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (isConfigVisible(node) && item != null) {
|
||||
item.setJavaProperty(value == null ? "" : value.trim());
|
||||
}
|
||||
}
|
||||
},
|
||||
new ColumnInfo<DefaultMutableTreeNode, String>("JAVA类型") {
|
||||
@Override
|
||||
public String valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
return isConfigVisible(node) && item != null ? item.getJavaType() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(DefaultMutableTreeNode node) {
|
||||
return isConfigVisible(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DefaultMutableTreeNode node, String value) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (isConfigVisible(node) && item != null) {
|
||||
item.setJavaType(value == null ? "" : value.trim());
|
||||
}
|
||||
}
|
||||
},
|
||||
new ColumnInfo<DefaultMutableTreeNode, ToggleInputValue>("自定义属性") {
|
||||
@Override
|
||||
public ToggleInputValue valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (!isConfigVisible(node) || item == null) {
|
||||
return ToggleInputValue.EMPTY;
|
||||
}
|
||||
return new ToggleInputValue(item.isCustomAttributeEnabled(), item.getCustomAttributeValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(DefaultMutableTreeNode node) {
|
||||
return isConfigVisible(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DefaultMutableTreeNode node, ToggleInputValue value) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (isConfigVisible(node) && item != null) {
|
||||
item.setCustomAttributeEnabled(value != null && value.isEnabled());
|
||||
item.setCustomAttributeValue(value == null ? null : value.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass() {
|
||||
return ToggleInputValue.class;
|
||||
}
|
||||
},
|
||||
new ColumnInfo<DefaultMutableTreeNode, ToggleInputValue>("预处理属性") {
|
||||
@Override
|
||||
public ToggleInputValue valueOf(DefaultMutableTreeNode node) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (!isConfigVisible(node) || item == null) {
|
||||
return ToggleInputValue.EMPTY;
|
||||
}
|
||||
return new ToggleInputValue(item.isPreProcessorEnabled(), item.getPreProcessorValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(DefaultMutableTreeNode node) {
|
||||
return isConfigVisible(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(DefaultMutableTreeNode node, ToggleInputValue value) {
|
||||
XsdConfigItem item = itemOf(node);
|
||||
if (isConfigVisible(node) && item != null) {
|
||||
item.setPreProcessorEnabled(value != null && value.isEnabled());
|
||||
item.setPreProcessorValue(value == null ? null : value.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass() {
|
||||
return ToggleInputValue.class;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public XsdTreeTableModel(DefaultMutableTreeNode root) {
|
||||
super(root, COLUMNS);
|
||||
}
|
||||
|
||||
public static DefaultMutableTreeNode toSwingTree(XsdConfigItem root) {
|
||||
DefaultMutableTreeNode swingRoot = new DefaultMutableTreeNode(root);
|
||||
appendChildren(swingRoot, root.getChildren());
|
||||
return swingRoot;
|
||||
}
|
||||
|
||||
private static void appendChildren(DefaultMutableTreeNode parent, List<XsdConfigItem> children) {
|
||||
XsdConfigItem parentItem = itemOf(parent);
|
||||
boolean inlineAttributeChildren = shouldInlineAttributeChildren(parentItem);
|
||||
for (XsdConfigItem child : children) {
|
||||
if (inlineAttributeChildren && child.isAttribute()) {
|
||||
continue;
|
||||
}
|
||||
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
|
||||
parent.add(childNode);
|
||||
appendChildren(childNode, child.getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldInlineAttributeChildren(XsdConfigItem item) {
|
||||
if (item == null || !item.hasChildren()) {
|
||||
return false;
|
||||
}
|
||||
return item.hasOnlyAttributeChildren();
|
||||
}
|
||||
|
||||
private static XsdConfigItem itemOf(DefaultMutableTreeNode node) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
Object userObject = node.getUserObject();
|
||||
if (userObject instanceof XsdConfigItem) {
|
||||
return (XsdConfigItem) userObject;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isConfigVisible(DefaultMutableTreeNode node) {
|
||||
return itemOf(node) != null && !isRootNode(node);
|
||||
}
|
||||
|
||||
private static boolean isRootNode(DefaultMutableTreeNode node) {
|
||||
return node != null && node.getParent() == null;
|
||||
}
|
||||
|
||||
public static final class ToggleInputValue {
|
||||
public static final ToggleInputValue EMPTY = new ToggleInputValue(false, null);
|
||||
private final boolean enabled;
|
||||
private final String text;
|
||||
|
||||
public ToggleInputValue(boolean enabled, String text) {
|
||||
this.enabled = enabled;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user