update
This commit is contained in:
@@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'com.dmiki'
|
||||
version '1.0.0'
|
||||
version '1.1.0'
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
@@ -7,7 +7,7 @@ MetaPlugin 是一个 IntelliJ IDEA 插件,用于把 XSD 结构转换为可配
|
||||
当前项目中的插件信息:
|
||||
- 插件名:`MetaPlugin`
|
||||
- 插件 ID:`com.dmiki.metaplugin`
|
||||
- 当前版本:`1.0.0`
|
||||
- 当前版本:`1.1.0`
|
||||
- IDE 兼容下限:`sinceBuild = 193`(IntelliJ IDEA 2019.3+)
|
||||
|
||||
## 2. 核心能力
|
||||
@@ -46,7 +46,7 @@ MetaPlugin 是一个 IntelliJ IDEA 插件,用于把 XSD 结构转换为可配
|
||||
|
||||
打包产物位于:
|
||||
|
||||
- `build/distributions/MetaPlugin-1.0.0.zip`
|
||||
- `build/distributions/MetaPlugin-1.1.0.zip`
|
||||
|
||||
在 IDEA 中通过 `Settings -> Plugins -> Install Plugin from Disk...` 选择该 ZIP 安装。
|
||||
|
||||
@@ -93,7 +93,7 @@ MetaPlugin 是一个 IntelliJ IDEA 插件,用于把 XSD 结构转换为可配
|
||||
- 包名:默认 `com.example.message`。
|
||||
- 注意这里填写的是“包名”,最终 `resultMap.type` 会自动拼接为:`包名 + 报文类型大写格式`。
|
||||
- 例如 `hvps.101.001.01` 会转为 `HVPS_101_001_01`。
|
||||
- 输出目录:支持绝对路径或相对项目根目录路径。
|
||||
- 输出目录:支持绝对路径或相对模块根目录路径。
|
||||
- 自定义属性:全局默认值,默认 `sign`。
|
||||
- 预处理属性:全局默认值,默认空。
|
||||
|
||||
@@ -147,11 +147,9 @@ MetaPlugin 是一个 IntelliJ IDEA 插件,用于把 XSD 结构转换为可配
|
||||
|
||||
### 8.3 输出目录与文件名
|
||||
|
||||
- 默认输出目录:`src/main/resources` 下第一个不存在的目录:
|
||||
- `msgmapper`
|
||||
- `msgmapper1`
|
||||
- `msgmapper2`
|
||||
- ... 依次类推
|
||||
- 默认输出目录分两种情况:
|
||||
- 如果 XSD 位于某个 `resources` 目录下,则输出到该 `resources` 目录下第一个不存在的 `msgmapperN`
|
||||
- 否则输出到 `XSD 父目录` 的同级第一个不存在的 `msgmapperN`
|
||||
- 默认输出文件名:`报文类型.xml`。
|
||||
- 右键“生成选中节点XOM文件”时,默认文件名:`报文类型_节点名.xml`。
|
||||
|
||||
|
||||
218
docs/hvps.101.001.01.xsd
Normal file
218
docs/hvps.101.001.01.xsd
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns="urn:cnaps:std:hvps:2010:tech:xsd:hvps.111.001.01"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="urn:cnaps:std:hvps:2010:tech:xsd:hvps.111.001.01"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<!-- 根节点 Document -->
|
||||
<xs:element name="Document" type="Document"/>
|
||||
|
||||
<!-- 1. 文档结构 -->
|
||||
<xs:complexType name="Document">
|
||||
<xs:sequence>
|
||||
<xs:element name="FIToFICstmrCdtTrf" type="FIToFICstmrCdtTrf"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 2. 金融机构间客户汇兑主体 (大额汇兑) -->
|
||||
<xs:complexType name="FIToFICstmrCdtTrf">
|
||||
<xs:sequence>
|
||||
<!-- 业务公共报头 -->
|
||||
<xs:element name="GrpHdr" type="GroupHeader"/>
|
||||
<!-- 资金交易明细 (大额报文通常只有一笔明细) -->
|
||||
<xs:element name="CdtTrfTxInf" type="CreditTransferTransactionInformation" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 3. 业务头 (Group Header) -->
|
||||
<xs:complexType name="GroupHeader">
|
||||
<xs:sequence>
|
||||
<xs:element name="MsgId" type="Max35Text"/> <!-- 报文标识号 -->
|
||||
<xs:element name="CreDtTm" type="ISODateTime"/> <!-- 报文创建时间 -->
|
||||
<xs:element name="InstgPty" type="InstructingParty"/> <!-- 发起参与机构 -->
|
||||
<xs:element name="InstdPty" type="InstructedParty"/> <!-- 接收参与机构 -->
|
||||
<xs:element name="SysCd" type="SystemCode"/> <!-- 系统代码 (如 HVPS) -->
|
||||
<xs:element name="Rmk" type="Max256Text" minOccurs="0"/> <!-- 备注 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- 4. 汇兑交易信息 (Transaction Information) -->
|
||||
<xs:complexType name="CreditTransferTransactionInformation">
|
||||
<xs:sequence>
|
||||
<xs:element name="PmtId" type="PaymentIdentification"/> <!-- 支付标识 -->
|
||||
<xs:element name="PmtTpInf" type="PaymentTypeInformation" minOccurs="0"/> <!-- 支付类型信息 (业务种类) -->
|
||||
<xs:element name="IntrBkSttlmAmt" type="ActiveCurrencyAndAmount"/> <!-- 银行间结算金额 -->
|
||||
<xs:element name="Dbtr" type="PartyIdentification"/> <!-- 付款人 -->
|
||||
<xs:element name="DbtrAcct" type="CashAccount" minOccurs="0"/> <!-- 付款人账户 -->
|
||||
<xs:element name="DbtrAgt" type="BranchAndFinancialInstitutionIdentification"/> <!-- 付款行 -->
|
||||
<xs:element name="CdtrAgt" type="BranchAndFinancialInstitutionIdentification"/> <!-- 收款行 -->
|
||||
<xs:element name="Cdtr" type="PartyIdentification"/> <!-- 收款人 -->
|
||||
<xs:element name="CdtrAcct" type="CashAccount"/> <!-- 收款人账户 -->
|
||||
<xs:element name="Purp" type="Purpose" minOccurs="0"/> <!-- 用途/附言 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- ==== 以下为子组件装配定义 ==== -->
|
||||
<xs:complexType name="PaymentIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="EndToEndId" type="Max35Text"/> <!-- 端到端标识号 -->
|
||||
<xs:element name="TxId" type="Max35Text"/> <!-- 交易标识号 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="InstructingParty">
|
||||
<xs:sequence>
|
||||
<xs:element name="InstgDrctPty" type="Max14Text"/> <!-- 发起直接参与机构 (14位行号) -->
|
||||
<xs:element name="InstgPty" type="Max14Text" minOccurs="0"/> <!-- 发起间接参与机构 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="InstructedParty">
|
||||
<xs:sequence>
|
||||
<xs:element name="InstdDrctPty" type="Max14Text"/> <!-- 接收直接参与机构 (14位行号) -->
|
||||
<xs:element name="InstdPty" type="Max14Text" minOccurs="0"/> <!-- 接收间接参与机构 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="PartyIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="Nm" type="Max60Text"/> <!-- 户名 -->
|
||||
<xs:element name="PstlAdr" type="PostalAddress" minOccurs="0"/> <!-- 地址 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CashAccount">
|
||||
<xs:sequence>
|
||||
<xs:element name="Id" type="AccountIdentification"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="AccountIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="Othr" type="GenericAccountIdentification"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="GenericAccountIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="Id" type="Max32Text"/> <!-- 账号 (一般最长32位) -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="BranchAndFinancialInstitutionIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="FinInstnId" type="FinancialInstitutionIdentification"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="FinancialInstitutionIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="ClrSysMmbId" type="ClearingSystemMemberIdentification"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ClearingSystemMemberIdentification">
|
||||
<xs:sequence>
|
||||
<xs:element name="MmbId" type="Max14Text"/> <!-- CNAPS 14位支付行号 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="PaymentTypeInformation">
|
||||
<xs:sequence>
|
||||
<xs:element name="CtgyPurp" type="CategoryPurpose"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CategoryPurpose">
|
||||
<xs:sequence>
|
||||
<xs:element name="Prtry" type="Max5Text"/> <!-- 二代支付中业务种类为最多5位数字,如大额A100 -->
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="Purpose">
|
||||
<xs:sequence>
|
||||
<xs:element name="Prtry" type="Max256Text"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="PostalAddress">
|
||||
<xs:sequence>
|
||||
<xs:element name="AdrLine" type="Max256Text" minOccurs="0" maxOccurs="2"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<!-- ==== 以下为基本数据类型 (Simple Types) ==== -->
|
||||
<xs:simpleType name="Max35Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="35"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="Max32Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="32"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="Max14Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:length value="14"/> <!-- 人行行号通常定长14位 -->
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="Max5Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="5"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="Max60Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="60"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="Max256Text">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1"/>
|
||||
<xs:maxLength value="256"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="SystemCode">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="HVPS"/> <!-- 单笔大额 -->
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="ISODateTime">
|
||||
<xs:restriction base="xs:dateTime"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<!-- 人民币金额类型定义 -->
|
||||
<xs:complexType name="ActiveCurrencyAndAmount">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="ActiveCurrencyAndAmount_SimpleType">
|
||||
<xs:attribute name="Ccy" type="ActiveCurrencyCode" use="required"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="ActiveCurrencyAndAmount_SimpleType">
|
||||
<xs:restriction base="xs:decimal">
|
||||
<xs:fractionDigits value="2"/>
|
||||
<xs:totalDigits value="18"/>
|
||||
<xs:minInclusive value="0.01"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="ActiveCurrencyCode">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern value="[A-Z]{3,3}"/> <!-- 绝大多数情况为 CNY -->
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@@ -65,16 +65,18 @@ public class XomGenerationService {
|
||||
|
||||
private String resolveOutputPath(XomGenerateOptions options) throws XomGenerationException {
|
||||
String outputPath = trimToNull(options.getOutputPath());
|
||||
File xsdFile = resolveXsdFile(options);
|
||||
File moduleBaseDir = XomPathStrategy.resolveModuleBaseDir(options.getProjectBasePath(), xsdFile);
|
||||
if (outputPath != null) {
|
||||
File outputFile = resolveOutputFile(outputPath, options);
|
||||
File outputFile = resolveOutputFile(outputPath, moduleBaseDir);
|
||||
if (outputFile.isDirectory()) {
|
||||
return new File(outputFile, safeName(options.getSchemaId()) + ".xml").getAbsolutePath();
|
||||
}
|
||||
return outputFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
File resourcesDir = resolveResourcesDirectory(options);
|
||||
File targetDirectory = resolveNextMsgMapperDirectory(resourcesDir);
|
||||
File targetDirectory = XomPathStrategy.resolveNextMsgMapperDirectory(
|
||||
XomPathStrategy.resolveDefaultOutputBaseDir(xsdFile));
|
||||
String schemaId = trimToNull(options.getSchemaId());
|
||||
if (schemaId == null) {
|
||||
schemaId = nameFromXsd(options);
|
||||
@@ -82,46 +84,16 @@ public class XomGenerationService {
|
||||
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");
|
||||
private File resolveOutputFile(String outputPath, File moduleBaseDir) {
|
||||
return XomPathStrategy.resolvePath(outputPath, moduleBaseDir);
|
||||
}
|
||||
|
||||
private File resolveXsdFile(XomGenerateOptions options) throws XomGenerationException {
|
||||
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++;
|
||||
}
|
||||
return new File(xsdPath);
|
||||
}
|
||||
|
||||
private String nameFromXsd(XomGenerateOptions options) {
|
||||
|
||||
180
src/main/java/com/dmiki/metaplugin/xom/XomPathStrategy.java
Normal file
180
src/main/java/com/dmiki/metaplugin/xom/XomPathStrategy.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.dmiki.metaplugin.xom;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class XomPathStrategy {
|
||||
private static final String[] MODULE_MARKERS = {
|
||||
"build.gradle",
|
||||
"build.gradle.kts",
|
||||
"pom.xml",
|
||||
".idea"
|
||||
};
|
||||
|
||||
private XomPathStrategy() {
|
||||
}
|
||||
|
||||
public static File resolveModuleBaseDir(String basePath, File anchorFile) {
|
||||
String normalizedBasePath = trimToNull(basePath);
|
||||
if (normalizedBasePath != null) {
|
||||
return new File(normalizedBasePath);
|
||||
}
|
||||
return inferModuleBaseDir(anchorFile);
|
||||
}
|
||||
|
||||
public static File inferModuleBaseDir(File anchorFile) {
|
||||
File current = toDirectory(anchorFile);
|
||||
while (current != null) {
|
||||
if (containsModuleMarker(current)) {
|
||||
return current;
|
||||
}
|
||||
current = current.getParentFile();
|
||||
}
|
||||
|
||||
File srcDir = findAncestorByName(anchorFile, "src");
|
||||
if (srcDir != null && srcDir.getParentFile() != null) {
|
||||
return srcDir.getParentFile();
|
||||
}
|
||||
|
||||
File anchorDir = toDirectory(anchorFile);
|
||||
if (anchorDir == null) {
|
||||
return null;
|
||||
}
|
||||
File parent = anchorDir.getParentFile();
|
||||
return parent == null ? anchorDir : parent;
|
||||
}
|
||||
|
||||
public static File resolveDefaultOutputBaseDir(File xsdFile) {
|
||||
File resourcesDir = findAncestorByName(xsdFile, "resources");
|
||||
if (resourcesDir != null) {
|
||||
return resourcesDir;
|
||||
}
|
||||
|
||||
File xsdParent = xsdFile == null ? null : xsdFile.getParentFile();
|
||||
if (xsdParent == null) {
|
||||
return null;
|
||||
}
|
||||
File siblingBase = xsdParent.getParentFile();
|
||||
return siblingBase == null ? xsdParent : siblingBase;
|
||||
}
|
||||
|
||||
public static File resolveNextMsgMapperDirectory(File baseDir) {
|
||||
if (baseDir == null) {
|
||||
return new File("msgmapper");
|
||||
}
|
||||
int suffix = 0;
|
||||
while (true) {
|
||||
String dirName = suffix == 0 ? "msgmapper" : "msgmapper" + suffix;
|
||||
File candidate = new File(baseDir, dirName);
|
||||
if (!candidate.exists()) {
|
||||
return candidate;
|
||||
}
|
||||
suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
public static File resolvePath(String path, File baseDir) {
|
||||
String normalizedPath = trimToNull(path);
|
||||
if (normalizedPath == null) {
|
||||
return null;
|
||||
}
|
||||
File candidate = new File(normalizedPath);
|
||||
if (candidate.isAbsolute() || baseDir == null) {
|
||||
return candidate;
|
||||
}
|
||||
return new File(baseDir, normalizedPath);
|
||||
}
|
||||
|
||||
public static String toRelativePath(File baseDir, File file) {
|
||||
if (file == null) {
|
||||
return "";
|
||||
}
|
||||
if (baseDir == null) {
|
||||
return normalizeSeparators(file.getAbsolutePath());
|
||||
}
|
||||
try {
|
||||
Path normalizedBase = baseDir.toPath().toAbsolutePath().normalize();
|
||||
Path normalizedTarget = file.toPath().toAbsolutePath().normalize();
|
||||
if (!normalizedTarget.startsWith(normalizedBase)) {
|
||||
return normalizeSeparators(normalizedTarget.toString());
|
||||
}
|
||||
String relative = normalizedBase.relativize(normalizedTarget).toString();
|
||||
return relative.isEmpty() ? "." : normalizeSeparators(relative);
|
||||
} catch (Exception ex) {
|
||||
return normalizeSeparators(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
public static String resolveRenderedXsdPath(File xsdFile, File moduleBaseDir) {
|
||||
if (xsdFile == null) {
|
||||
return "";
|
||||
}
|
||||
File resourcesDir = findAncestorByName(xsdFile, "resources");
|
||||
File relativeBase = resourcesDir != null ? resourcesDir : moduleBaseDir;
|
||||
if (relativeBase == null) {
|
||||
return normalizeSeparators(xsdFile.getPath());
|
||||
}
|
||||
return toRelativePath(relativeBase, xsdFile);
|
||||
}
|
||||
|
||||
public static File findAncestorByName(File node, String directoryName) {
|
||||
String normalizedName = trimToNull(directoryName);
|
||||
if (normalizedName == null) {
|
||||
return null;
|
||||
}
|
||||
File current = toDirectory(node);
|
||||
String targetName = normalizedName.toLowerCase(Locale.ROOT);
|
||||
while (current != null) {
|
||||
if (targetName.equals(current.getName().toLowerCase(Locale.ROOT))) {
|
||||
return current;
|
||||
}
|
||||
current = current.getParentFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isXmlFilePath(File file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
return file.getName().toLowerCase(Locale.ROOT).endsWith(".xml");
|
||||
}
|
||||
|
||||
private static File toDirectory(File file) {
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
return file;
|
||||
}
|
||||
return file.getParentFile();
|
||||
}
|
||||
|
||||
private static boolean containsModuleMarker(File directory) {
|
||||
for (String marker : MODULE_MARKERS) {
|
||||
if (new File(directory, marker).exists()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String normalizeSeparators(String path) {
|
||||
if (path == null) {
|
||||
return "";
|
||||
}
|
||||
return path.replace(File.separatorChar, '/');
|
||||
}
|
||||
|
||||
private static String trimToNull(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = text.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ 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 {
|
||||
|
||||
@@ -250,22 +248,8 @@ public class XomXmlRenderer {
|
||||
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;
|
||||
}
|
||||
File xsdFile = new File(rawXsdPath);
|
||||
File moduleBaseDir = XomPathStrategy.resolveModuleBaseDir(options.getProjectBasePath(), xsdFile);
|
||||
return XomPathStrategy.resolveRenderedXsdPath(xsdFile, moduleBaseDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.dmiki.metaplugin.xom.reverse;
|
||||
|
||||
import com.dmiki.metaplugin.xom.XomPathStrategy;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
import com.dmiki.metaplugin.xsd.parser.XsdSchemaParser;
|
||||
@@ -14,7 +15,6 @@ 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 {
|
||||
@@ -29,14 +29,32 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
prepareForReplay(root);
|
||||
applyReplay(root, mappingDocument.getResults());
|
||||
|
||||
return new XomReverseDisplayResult(
|
||||
root,
|
||||
xsdFile,
|
||||
mappingDocument.getSchemaId(),
|
||||
mappingDocument.getResultMapType(),
|
||||
inferTopInput(mappingDocument.getCustomAttributeValues()),
|
||||
inferTopInput(mappingDocument.getPreProcessorValues())
|
||||
);
|
||||
return buildDisplayResult(root, xsdFile, mappingDocument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XomReverseDisplayResult applyToCurrentTree(File mappingXmlFile,
|
||||
String projectBasePath,
|
||||
XsdConfigItem currentRoot,
|
||||
XsdConfigItem targetNode) throws Exception {
|
||||
if (currentRoot == null) {
|
||||
throw new IllegalArgumentException("当前UI未加载XSD结构");
|
||||
}
|
||||
if (targetNode == null) {
|
||||
throw new IllegalArgumentException("未选择要应用XOM的节点");
|
||||
}
|
||||
|
||||
MappingDocument mappingDocument = parseMappingDocument(mappingXmlFile);
|
||||
File xsdFile = resolveXsdFile(mappingDocument.getXsdPath(), mappingXmlFile, projectBasePath);
|
||||
|
||||
List<XsdConfigItem> targetPath = resolvePath(currentRoot, targetNode);
|
||||
if (targetPath == null) {
|
||||
throw new IllegalArgumentException("选中节点不属于当前XSD结构");
|
||||
}
|
||||
|
||||
prepareForReplay(targetNode);
|
||||
applyReplay(targetNode, resolveReplayPool(mappingDocument.getResults(), targetPath));
|
||||
return buildDisplayResult(currentRoot, xsdFile, mappingDocument);
|
||||
}
|
||||
|
||||
private MappingDocument parseMappingDocument(File mappingXmlFile) throws Exception {
|
||||
@@ -76,6 +94,7 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
return;
|
||||
}
|
||||
item.setConfigMode(item.isEffectiveLeaf() ? XsdConfigMode.IGNORE : XsdConfigMode.FLATTEN_AND_IGNORE_CHILDREN);
|
||||
item.setJavaProperty(null);
|
||||
item.setCustomAttributeEnabled(false);
|
||||
item.setCustomAttributeValue(null);
|
||||
item.setPreProcessorEnabled(false);
|
||||
@@ -85,13 +104,24 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
}
|
||||
}
|
||||
|
||||
private XomReverseDisplayResult buildDisplayResult(XsdConfigItem root, File xsdFile, MappingDocument mappingDocument) {
|
||||
return new XomReverseDisplayResult(
|
||||
root,
|
||||
xsdFile,
|
||||
mappingDocument.getSchemaId(),
|
||||
mappingDocument.getResultMapType(),
|
||||
inferTopInput(mappingDocument.getCustomAttributeValues()),
|
||||
inferTopInput(mappingDocument.getPreProcessorValues())
|
||||
);
|
||||
}
|
||||
|
||||
private void applyReplay(XsdConfigItem root, List<ResultNode> topResults) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<ResultNode> pool = new ArrayList<ResultNode>(topResults);
|
||||
ResultNode direct = removeFirstMatch(pool, root);
|
||||
ResultNode direct = removeFirstDirectMatch(pool, root);
|
||||
if (direct != null) {
|
||||
applyDirectConfig(root, direct);
|
||||
applyChildrenReplay(root, new ArrayList<ResultNode>(direct.getChildren()));
|
||||
@@ -114,7 +144,7 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
|
||||
private void applyChildrenReplay(XsdConfigItem parent, List<ResultNode> pool) {
|
||||
for (XsdConfigItem child : parent.getChildren()) {
|
||||
ResultNode direct = removeFirstMatch(pool, child);
|
||||
ResultNode direct = removeFirstDirectMatch(pool, child);
|
||||
if (direct != null) {
|
||||
applyDirectConfig(child, direct);
|
||||
applyChildrenReplay(child, new ArrayList<ResultNode>(direct.getChildren()));
|
||||
@@ -156,10 +186,10 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResultNode removeFirstMatch(List<ResultNode> pool, XsdConfigItem item) {
|
||||
private ResultNode removeFirstDirectMatch(List<ResultNode> pool, XsdConfigItem item) {
|
||||
for (int i = 0; i < pool.size(); i++) {
|
||||
ResultNode node = pool.get(i);
|
||||
if (matches(item, node)) {
|
||||
if (matchesDirect(item, node)) {
|
||||
pool.remove(i);
|
||||
return node;
|
||||
}
|
||||
@@ -168,6 +198,14 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
}
|
||||
|
||||
private boolean matches(XsdConfigItem item, ResultNode node) {
|
||||
return matches(item, node, true);
|
||||
}
|
||||
|
||||
private boolean matchesDirect(XsdConfigItem item, ResultNode node) {
|
||||
return matches(item, node, item != null && item.isEffectiveLeaf());
|
||||
}
|
||||
|
||||
private boolean matches(XsdConfigItem item, ResultNode node, boolean allowTailMatch) {
|
||||
if (item == null || node == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -183,7 +221,101 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
if (actual == null) {
|
||||
return false;
|
||||
}
|
||||
return expected.equals(actual) || expected.equalsIgnoreCase(actual);
|
||||
return matchesName(expected, actual, allowTailMatch);
|
||||
}
|
||||
|
||||
private boolean matchesName(String expected, String actual, boolean allowTailMatch) {
|
||||
String normalizedExpected = trimToNull(expected);
|
||||
String normalizedActual = trimToNull(actual);
|
||||
if (normalizedExpected == null || normalizedActual == null) {
|
||||
return false;
|
||||
}
|
||||
if (normalizedExpected.equals(normalizedActual) || normalizedExpected.equalsIgnoreCase(normalizedActual)) {
|
||||
return true;
|
||||
}
|
||||
if (!allowTailMatch) {
|
||||
return false;
|
||||
}
|
||||
int index = normalizedActual.lastIndexOf(':');
|
||||
if (index < 0 || index >= normalizedActual.length() - 1) {
|
||||
return false;
|
||||
}
|
||||
String tail = trimToNull(normalizedActual.substring(index + 1));
|
||||
return tail != null && (normalizedExpected.equals(tail) || normalizedExpected.equalsIgnoreCase(tail));
|
||||
}
|
||||
|
||||
private List<ResultNode> resolveReplayPool(List<ResultNode> topResults, List<XsdConfigItem> targetPath) {
|
||||
List<XsdConfigItem> ancestors = new ArrayList<XsdConfigItem>();
|
||||
if (targetPath != null && targetPath.size() > 1) {
|
||||
ancestors.addAll(targetPath.subList(0, targetPath.size() - 1));
|
||||
}
|
||||
|
||||
List<ReplayPool> pools = new ArrayList<ReplayPool>();
|
||||
collectReplayPools(topResults, new ArrayList<ResultNode>(), pools);
|
||||
|
||||
ReplayPool best = null;
|
||||
int bestScore = -1;
|
||||
for (ReplayPool pool : pools) {
|
||||
int score = matchAncestorSuffix(ancestors, pool.getAncestorPath());
|
||||
if (score > bestScore) {
|
||||
best = pool;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
if (best == null) {
|
||||
return new ArrayList<ResultNode>();
|
||||
}
|
||||
return new ArrayList<ResultNode>(best.getNodes());
|
||||
}
|
||||
|
||||
private void collectReplayPools(List<ResultNode> nodes, List<ResultNode> ancestors, List<ReplayPool> pools) {
|
||||
pools.add(new ReplayPool(nodes, ancestors));
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (ResultNode node : nodes) {
|
||||
List<ResultNode> nextAncestors = new ArrayList<ResultNode>(ancestors);
|
||||
nextAncestors.add(node);
|
||||
collectReplayPools(node.getChildren(), nextAncestors, pools);
|
||||
}
|
||||
}
|
||||
|
||||
private int matchAncestorSuffix(List<XsdConfigItem> ancestors, List<ResultNode> candidatePath) {
|
||||
if (candidatePath == null || candidatePath.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
if (ancestors == null || candidatePath.size() > ancestors.size()) {
|
||||
return -1;
|
||||
}
|
||||
int offset = ancestors.size() - candidatePath.size();
|
||||
for (int i = 0; i < candidatePath.size(); i++) {
|
||||
if (!matches(ancestors.get(offset + i), candidatePath.get(i))) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return candidatePath.size();
|
||||
}
|
||||
|
||||
private List<XsdConfigItem> resolvePath(XsdConfigItem root, XsdConfigItem targetNode) {
|
||||
List<XsdConfigItem> path = new ArrayList<XsdConfigItem>();
|
||||
return collectPath(root, targetNode, path) ? path : null;
|
||||
}
|
||||
|
||||
private boolean collectPath(XsdConfigItem current, XsdConfigItem target, List<XsdConfigItem> path) {
|
||||
if (current == null || target == null || path == null) {
|
||||
return false;
|
||||
}
|
||||
path.add(current);
|
||||
if (current == target) {
|
||||
return true;
|
||||
}
|
||||
for (XsdConfigItem child : current.getChildren()) {
|
||||
if (collectPath(child, target, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
path.remove(path.size() - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void applyDirectConfig(XsdConfigItem item, ResultNode node) {
|
||||
@@ -213,6 +345,7 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
}
|
||||
|
||||
List<File> candidates = new ArrayList<File>();
|
||||
File moduleBaseDir = XomPathStrategy.resolveModuleBaseDir(projectBasePath, mappingXmlFile);
|
||||
File declared = new File(xsdPath);
|
||||
if (declared.isAbsolute()) {
|
||||
candidates.add(declared);
|
||||
@@ -223,16 +356,14 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
candidates.add(new File(mappingParent, xsdPath));
|
||||
}
|
||||
|
||||
File resourcesRoot = findAncestorByName(mappingParent, "resources");
|
||||
File resourcesRoot = XomPathStrategy.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));
|
||||
if (moduleBaseDir != null) {
|
||||
candidates.add(new File(moduleBaseDir, xsdPath));
|
||||
candidates.add(new File(new File(new File(new File(moduleBaseDir, "src"), "main"), "resources"), xsdPath));
|
||||
}
|
||||
|
||||
Set<String> dedup = new LinkedHashSet<String>();
|
||||
@@ -253,21 +384,6 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
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) {
|
||||
@@ -482,4 +598,22 @@ public class ReservedXomReverseDisplayService implements XomReverseDisplayServic
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReplayPool {
|
||||
private final List<ResultNode> nodes;
|
||||
private final List<ResultNode> ancestorPath;
|
||||
|
||||
private ReplayPool(List<ResultNode> nodes, List<ResultNode> ancestorPath) {
|
||||
this.nodes = nodes == null ? new ArrayList<ResultNode>() : new ArrayList<ResultNode>(nodes);
|
||||
this.ancestorPath = ancestorPath == null ? new ArrayList<ResultNode>() : new ArrayList<ResultNode>(ancestorPath);
|
||||
}
|
||||
|
||||
private List<ResultNode> getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private List<ResultNode> getAncestorPath() {
|
||||
return ancestorPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package com.dmiki.metaplugin.xom.reverse;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface XomReverseDisplayService {
|
||||
|
||||
XomReverseDisplayResult loadForDisplay(File mappingXmlFile, String projectBasePath) throws Exception;
|
||||
|
||||
XomReverseDisplayResult applyToCurrentTree(File mappingXmlFile,
|
||||
String projectBasePath,
|
||||
XsdConfigItem currentRoot,
|
||||
XsdConfigItem targetNode) throws Exception;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.dmiki.metaplugin.xsd.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class JavaPackageResolver {
|
||||
private static final String[] PREFERRED_PACKAGES = {"entity", "domain", "message"};
|
||||
|
||||
private JavaPackageResolver() {
|
||||
}
|
||||
|
||||
public static String resolveDefaultPackage(File moduleBaseDir) {
|
||||
List<File> sourceRoots = collectJavaSourceRoots(moduleBaseDir);
|
||||
if (sourceRoots.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String preferredPackage = resolvePreferredPackage(sourceRoots);
|
||||
if (preferredPackage != null) {
|
||||
return preferredPackage;
|
||||
}
|
||||
|
||||
return inferModulePackage(sourceRoots);
|
||||
}
|
||||
|
||||
static List<File> collectJavaSourceRoots(File moduleBaseDir) {
|
||||
if (moduleBaseDir == null || !moduleBaseDir.isDirectory()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<String, File> sourceRoots = new LinkedHashMap<String, File>();
|
||||
registerSourceRoot(sourceRoots, new File(moduleBaseDir, "src/main/java"));
|
||||
registerSourceRoot(sourceRoots, new File(moduleBaseDir, "src/test/java"));
|
||||
registerSourceRoot(sourceRoots, new File(moduleBaseDir, "src/java"));
|
||||
|
||||
File srcDir = new File(moduleBaseDir, "src");
|
||||
for (File child : listDirectories(srcDir)) {
|
||||
registerSourceRoot(sourceRoots, new File(child, "java"));
|
||||
}
|
||||
|
||||
return new ArrayList<File>(sourceRoots.values());
|
||||
}
|
||||
|
||||
private static void registerSourceRoot(Map<String, File> sourceRoots, File sourceRoot) {
|
||||
if (sourceRoot == null || !sourceRoot.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
String key;
|
||||
try {
|
||||
key = sourceRoot.getCanonicalPath();
|
||||
sourceRoot = sourceRoot.getCanonicalFile();
|
||||
} catch (IOException ex) {
|
||||
key = sourceRoot.getAbsolutePath();
|
||||
sourceRoot = sourceRoot.getAbsoluteFile();
|
||||
}
|
||||
sourceRoots.put(key, sourceRoot);
|
||||
}
|
||||
|
||||
private static String resolvePreferredPackage(List<File> sourceRoots) {
|
||||
for (String preferredPackageName : PREFERRED_PACKAGES) {
|
||||
List<String> candidates = new ArrayList<String>();
|
||||
for (File sourceRoot : sourceRoots) {
|
||||
collectPackageDirectoriesByName(sourceRoot, preferredPackageName, candidates);
|
||||
}
|
||||
String bestMatch = chooseBestPackage(candidates);
|
||||
if (bestMatch != null) {
|
||||
return bestMatch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void collectPackageDirectoriesByName(File sourceRoot, String directoryName, List<String> candidates) {
|
||||
if (sourceRoot == null || directoryName == null) {
|
||||
return;
|
||||
}
|
||||
Deque<File> stack = new ArrayDeque<File>();
|
||||
stack.push(sourceRoot);
|
||||
while (!stack.isEmpty()) {
|
||||
File current = stack.pop();
|
||||
List<File> children = listDirectories(current);
|
||||
for (int i = children.size() - 1; i >= 0; i--) {
|
||||
File child = children.get(i);
|
||||
if (directoryName.equals(child.getName())) {
|
||||
String packageName = toPackageName(sourceRoot, child);
|
||||
if (packageName != null) {
|
||||
candidates.add(packageName);
|
||||
}
|
||||
}
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferModulePackage(List<File> sourceRoots) {
|
||||
Set<String> packageNames = new LinkedHashSet<String>();
|
||||
for (File sourceRoot : sourceRoots) {
|
||||
collectJavaFilePackages(sourceRoot, packageNames);
|
||||
String chainedPackage = inferSinglePathPackage(sourceRoot);
|
||||
if (chainedPackage != null) {
|
||||
packageNames.add(chainedPackage);
|
||||
}
|
||||
}
|
||||
|
||||
if (packageNames.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String commonPrefix = longestCommonPackagePrefix(packageNames);
|
||||
if (commonPrefix != null) {
|
||||
return commonPrefix;
|
||||
}
|
||||
return chooseBestPackage(new ArrayList<String>(packageNames));
|
||||
}
|
||||
|
||||
private static void collectJavaFilePackages(File sourceRoot, Set<String> packageNames) {
|
||||
Deque<File> stack = new ArrayDeque<File>();
|
||||
stack.push(sourceRoot);
|
||||
while (!stack.isEmpty()) {
|
||||
File current = stack.pop();
|
||||
for (File child : listChildren(current)) {
|
||||
if (child.isDirectory()) {
|
||||
if (isSkippableDirectory(child)) {
|
||||
continue;
|
||||
}
|
||||
stack.push(child);
|
||||
continue;
|
||||
}
|
||||
if (!child.isFile() || !child.getName().endsWith(".java")) {
|
||||
continue;
|
||||
}
|
||||
String packageName = toPackageName(sourceRoot, child.getParentFile());
|
||||
if (packageName != null) {
|
||||
packageNames.add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferSinglePathPackage(File sourceRoot) {
|
||||
List<String> segments = new ArrayList<String>();
|
||||
File current = sourceRoot;
|
||||
while (current != null && current.isDirectory()) {
|
||||
if (containsJavaFiles(current)) {
|
||||
break;
|
||||
}
|
||||
List<File> children = listPackageDirectories(current);
|
||||
if (children.size() != 1) {
|
||||
break;
|
||||
}
|
||||
File child = children.get(0);
|
||||
segments.add(child.getName());
|
||||
current = child;
|
||||
}
|
||||
if (segments.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return joinSegments(segments);
|
||||
}
|
||||
|
||||
private static boolean containsJavaFiles(File directory) {
|
||||
for (File child : listChildren(directory)) {
|
||||
if (child.isFile() && child.getName().endsWith(".java")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String longestCommonPackagePrefix(Set<String> packageNames) {
|
||||
if (packageNames.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] commonSegments = null;
|
||||
for (String packageName : packageNames) {
|
||||
if (packageName == null) {
|
||||
continue;
|
||||
}
|
||||
String[] segments = packageName.split("\\.");
|
||||
if (commonSegments == null) {
|
||||
commonSegments = segments;
|
||||
continue;
|
||||
}
|
||||
int length = Math.min(commonSegments.length, segments.length);
|
||||
int index = 0;
|
||||
while (index < length && commonSegments[index].equals(segments[index])) {
|
||||
index++;
|
||||
}
|
||||
if (index == 0) {
|
||||
return null;
|
||||
}
|
||||
commonSegments = Arrays.copyOf(commonSegments, index);
|
||||
}
|
||||
if (commonSegments == null || commonSegments.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return joinSegments(Arrays.asList(commonSegments));
|
||||
}
|
||||
|
||||
private static String chooseBestPackage(List<String> packageNames) {
|
||||
if (packageNames == null || packageNames.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Collections.sort(packageNames, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String left, String right) {
|
||||
int leftDepth = left.split("\\.").length;
|
||||
int rightDepth = right.split("\\.").length;
|
||||
if (leftDepth != rightDepth) {
|
||||
return Integer.compare(leftDepth, rightDepth);
|
||||
}
|
||||
if (left.length() != right.length()) {
|
||||
return Integer.compare(left.length(), right.length());
|
||||
}
|
||||
return left.compareTo(right);
|
||||
}
|
||||
});
|
||||
return packageNames.get(0);
|
||||
}
|
||||
|
||||
private static String toPackageName(File sourceRoot, File directory) {
|
||||
if (sourceRoot == null || directory == null) {
|
||||
return null;
|
||||
}
|
||||
String relativePath;
|
||||
try {
|
||||
relativePath = sourceRoot.toPath().relativize(directory.toPath()).toString();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
if (relativePath.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] segments = relativePath.replace('\\', '/').split("/");
|
||||
for (String segment : segments) {
|
||||
if (!isValidPackageSegment(segment)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return joinSegments(Arrays.asList(segments));
|
||||
}
|
||||
|
||||
private static List<File> listDirectories(File directory) {
|
||||
List<File> directories = new ArrayList<File>();
|
||||
for (File child : listChildren(directory)) {
|
||||
if (child.isDirectory() && !isSkippableDirectory(child)) {
|
||||
directories.add(child);
|
||||
}
|
||||
}
|
||||
Collections.sort(directories, new Comparator<File>() {
|
||||
@Override
|
||||
public int compare(File left, File right) {
|
||||
return left.getName().compareTo(right.getName());
|
||||
}
|
||||
});
|
||||
return directories;
|
||||
}
|
||||
|
||||
private static List<File> listPackageDirectories(File directory) {
|
||||
List<File> directories = new ArrayList<File>();
|
||||
for (File child : listDirectories(directory)) {
|
||||
if (isValidPackageSegment(child.getName())) {
|
||||
directories.add(child);
|
||||
}
|
||||
}
|
||||
return directories;
|
||||
}
|
||||
|
||||
private static File[] listChildren(File directory) {
|
||||
if (directory == null || !directory.isDirectory()) {
|
||||
return new File[0];
|
||||
}
|
||||
File[] children = directory.listFiles();
|
||||
return children == null ? new File[0] : children;
|
||||
}
|
||||
|
||||
private static boolean isSkippableDirectory(File directory) {
|
||||
String name = directory.getName();
|
||||
return name.startsWith(".")
|
||||
|| "build".equals(name)
|
||||
|| "out".equals(name)
|
||||
|| "target".equals(name);
|
||||
}
|
||||
|
||||
private static boolean isValidPackageSegment(String segment) {
|
||||
if (segment == null || segment.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!Character.isJavaIdentifierStart(segment.charAt(0))) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 1; i < segment.length(); i++) {
|
||||
if (!Character.isJavaIdentifierPart(segment.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String joinSegments(List<String> segments) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String segment : segments) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append('.');
|
||||
}
|
||||
builder.append(segment);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,23 @@ import com.dmiki.metaplugin.xom.XomGenerationDefaults;
|
||||
import com.dmiki.metaplugin.xom.XomGenerationException;
|
||||
import com.dmiki.metaplugin.xom.XomGenerationResult;
|
||||
import com.dmiki.metaplugin.xom.XomGenerationService;
|
||||
import com.dmiki.metaplugin.xom.XomPathStrategy;
|
||||
import com.dmiki.metaplugin.xom.reverse.ReservedXomReverseDisplayService;
|
||||
import com.dmiki.metaplugin.xom.reverse.XomReverseDisplayResult;
|
||||
import com.dmiki.metaplugin.xom.reverse.XomReverseDisplayService;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigMode;
|
||||
import com.dmiki.metaplugin.xsd.parser.XsdSchemaParser;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
@@ -76,15 +81,19 @@ import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class XsdConfigDialog extends DialogWrapper {
|
||||
private static final int TOGGLE_INPUT_GAP = 14;
|
||||
private static final int TOGGLE_INPUT_GAP = 10;
|
||||
private static final int TOGGLE_INPUT_OUTER_HORIZONTAL_PADDING = 4;
|
||||
private static final int TOGGLE_INPUT_INNER_HORIZONTAL_PADDING = 3;
|
||||
private static final int TOGGLE_INPUT_TEXT_HORIZONTAL_PADDING = 2;
|
||||
private static final int TOGGLE_INPUT_TEXT_WIDTH = 60;
|
||||
private static final int TOGGLE_INPUT_CHECKBOX_MIN_SIZE = 20;
|
||||
private static final String DEFAULT_RESULT_MAP_PACKAGE = "com.example.message";
|
||||
private final Project project;
|
||||
private final XsdSchemaParser parser = new XsdSchemaParser();
|
||||
private final XomGenerationService xomGenerationService = new XomGenerationService();
|
||||
@@ -109,6 +118,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
private MouseAdapter outsideClickEditStopper;
|
||||
private XsdConfigItem currentRoot;
|
||||
private File currentXsdFile;
|
||||
private String currentModuleBasePath;
|
||||
private String lastAutoResultMapType;
|
||||
|
||||
public XsdConfigDialog(@Nullable Project project) {
|
||||
@@ -154,7 +164,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
|
||||
installOutsideClickEditTermination(panel);
|
||||
|
||||
panel.setPreferredSize(new Dimension(1220, 720));
|
||||
panel.setPreferredSize(new Dimension(1280, 720));
|
||||
setActionButtonsEnabled(false);
|
||||
return panel;
|
||||
}
|
||||
@@ -213,7 +223,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
row1.setOpaque(false);
|
||||
|
||||
row1.add(createOptionLabel("报文类型:"));
|
||||
msgTypeField = createOptionField(180);
|
||||
msgTypeField = createOptionField(130);
|
||||
row1.add(msgTypeField);
|
||||
|
||||
row1.add(createOptionLabel("包名:"));
|
||||
@@ -222,6 +232,16 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
installResultMapTypeSync();
|
||||
syncResultMapTypeFromMsgType(true);
|
||||
|
||||
row1.add(createOptionLabel("自定义属性:"));
|
||||
customAttributeField = createOptionField(120);
|
||||
customAttributeField.setText("sign");
|
||||
row1.add(customAttributeField);
|
||||
|
||||
row1.add(createOptionLabel("预处理属性:"));
|
||||
preProcessorField = createOptionField(150);
|
||||
preProcessorField.setText("");
|
||||
row1.add(preProcessorField);
|
||||
|
||||
row1.add(createOptionLabel("输出目录:"));
|
||||
outputPathField = new TextFieldWithBrowseButton();
|
||||
outputPathField.setPreferredSize(new Dimension(340, 32));
|
||||
@@ -236,22 +256,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
});
|
||||
row1.add(outputPathField);
|
||||
|
||||
JBPanel row2 = new JBPanel(new HorizontalLayout(8));
|
||||
row2.setOpaque(false);
|
||||
row2.setBorder(new EmptyBorder(6, 0, 0, 0));
|
||||
|
||||
row2.add(createOptionLabel("自定义属性:"));
|
||||
customAttributeField = createOptionField(180);
|
||||
customAttributeField.setText("sign");
|
||||
row2.add(customAttributeField);
|
||||
|
||||
row2.add(createOptionLabel("预处理属性:"));
|
||||
preProcessorField = createOptionField(220);
|
||||
preProcessorField.setText("");
|
||||
row2.add(preProcessorField);
|
||||
|
||||
container.add(row1);
|
||||
container.add(row2);
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -304,7 +309,8 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
}
|
||||
|
||||
private String buildDefaultResultMapType() {
|
||||
return "com.example.message";
|
||||
String packageName = JavaPackageResolver.resolveDefaultPackage(moduleBaseDir());
|
||||
return packageName == null ? DEFAULT_RESULT_MAP_PACKAGE : packageName;
|
||||
}
|
||||
|
||||
private void styleTextField(JTextField textField) {
|
||||
@@ -436,7 +442,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
try {
|
||||
XomReverseDisplayResult reverseResult = xomReverseDisplayService.loadForDisplay(
|
||||
xomFile,
|
||||
project == null ? null : project.getBasePath());
|
||||
resolveModuleBasePath(xomFile));
|
||||
File xsdFile = reverseResult.getXsdFile();
|
||||
XsdConfigItem root = reverseResult.getRoot();
|
||||
|
||||
@@ -453,6 +459,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
private void displayTree(File xsdFile, XsdConfigItem root) {
|
||||
currentRoot = root;
|
||||
currentXsdFile = xsdFile;
|
||||
currentModuleBasePath = resolveModuleBasePath(xsdFile);
|
||||
applyGenerationDefaults(xsdFile);
|
||||
|
||||
DefaultMutableTreeNode swingRoot = XsdTreeTableModel.toSwingTree(root);
|
||||
@@ -483,7 +490,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
}
|
||||
if (outputPathField != null && xomFile != null) {
|
||||
File parent = xomFile.getParentFile();
|
||||
outputPathField.setText(toProjectRelativePath(parent == null ? xomFile : parent));
|
||||
outputPathField.setText(toModuleRelativePath(parent == null ? xomFile : parent));
|
||||
}
|
||||
if (customAttributeField != null) {
|
||||
customAttributeField.setText(trimToEmpty(reverseResult.getCustomAttribute()));
|
||||
@@ -610,6 +617,12 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
private JPopupMenu createNodeContextMenu(XsdConfigItem selectedItem) {
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
|
||||
JMenuItem applyItem = new JMenuItem("应用已存在的XOM");
|
||||
applyItem.addActionListener(event -> applyExistingXomToNode(selectedItem));
|
||||
menu.add(applyItem);
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
JMenuItem previewItem = new JMenuItem("预览选中节点XOM");
|
||||
previewItem.addActionListener(event -> previewXomForNode(selectedItem));
|
||||
menu.add(previewItem);
|
||||
@@ -621,6 +634,86 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void applyExistingXomToNode(XsdConfigItem selectedItem) {
|
||||
if (currentRoot == null || currentXsdFile == null || selectedItem == null) {
|
||||
Messages.showWarningDialog(project, "请先解析XSD并选择节点", "提示");
|
||||
return;
|
||||
}
|
||||
|
||||
stopTreeTableEditing();
|
||||
File xomFile = chooseExistingXomFile();
|
||||
if (xomFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
XomReverseDisplayResult reverseResult = xomReverseDisplayService.applyToCurrentTree(
|
||||
xomFile,
|
||||
resolveModuleBasePath(xomFile),
|
||||
currentRoot,
|
||||
selectedItem);
|
||||
if (selectedItem == currentRoot) {
|
||||
applyReplayedOptions(reverseResult, xomFile);
|
||||
}
|
||||
refreshTreeTableAfterReplay();
|
||||
|
||||
String nodeName = trimToNull(selectedItem.getXmlName());
|
||||
Messages.showInfoMessage(project,
|
||||
"XOM配置已应用到节点: " + (nodeName == null ? "(未命名节点)" : nodeName),
|
||||
"完成");
|
||||
} catch (Exception ex) {
|
||||
Messages.showErrorDialog(project, "应用XOM失败: " + ex.getMessage(), "错误");
|
||||
}
|
||||
}
|
||||
|
||||
private File chooseExistingXomFile() {
|
||||
FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor();
|
||||
descriptor.withFileFilter(virtualFile -> {
|
||||
String extension = virtualFile.getExtension();
|
||||
return "xml".equalsIgnoreCase(extension) || "xom".equalsIgnoreCase(extension);
|
||||
});
|
||||
descriptor.setTitle("选择已存在的XOM文件");
|
||||
descriptor.setDescription("选择一个 .xml 或 .xom 文件,并将配置应用到当前选中节点");
|
||||
|
||||
File initialFile = resolveInitialXomSelection();
|
||||
VirtualFile initialVirtualFile = initialFile == null
|
||||
? null
|
||||
: LocalFileSystem.getInstance().refreshAndFindFileByIoFile(initialFile);
|
||||
VirtualFile selected = FileChooser.chooseFile(descriptor, project, initialVirtualFile);
|
||||
return selected == null ? null : new File(selected.getPath());
|
||||
}
|
||||
|
||||
private File resolveInitialXomSelection() {
|
||||
String outputPath = valueOf(outputPathField);
|
||||
if (outputPath != null) {
|
||||
File outputDirectory = resolveOutputDirectory(outputPath);
|
||||
if (outputDirectory != null && outputDirectory.exists()) {
|
||||
return outputDirectory;
|
||||
}
|
||||
}
|
||||
if (currentXsdFile != null) {
|
||||
File parent = currentXsdFile.getParentFile();
|
||||
if (parent != null && parent.exists()) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
File moduleBaseDir = moduleBaseDir();
|
||||
if (moduleBaseDir != null && moduleBaseDir.exists()) {
|
||||
return moduleBaseDir;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void refreshTreeTableAfterReplay() {
|
||||
if (treeTable != null) {
|
||||
treeTable.revalidate();
|
||||
treeTable.repaint();
|
||||
}
|
||||
if (currentRoot != null) {
|
||||
refreshStats(currentRoot);
|
||||
}
|
||||
}
|
||||
|
||||
private void installOutsideClickEditTermination(JComponent rootComponent) {
|
||||
if (outsideClickEditStopper != null) {
|
||||
return;
|
||||
@@ -701,6 +794,23 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
static Dimension toggleCheckBoxSize(@Nullable Dimension preferredSize) {
|
||||
int width = preferredSize == null ? 0 : preferredSize.width;
|
||||
int height = preferredSize == null ? 0 : preferredSize.height;
|
||||
int size = Math.max(TOGGLE_INPUT_CHECKBOX_MIN_SIZE, Math.max(width, height));
|
||||
return new Dimension(size, size);
|
||||
}
|
||||
|
||||
private static void applyToggleCheckBoxSizing(JCheckBox checkBox) {
|
||||
if (checkBox == null) {
|
||||
return;
|
||||
}
|
||||
Dimension size = toggleCheckBoxSize(checkBox.getPreferredSize());
|
||||
checkBox.setPreferredSize(size);
|
||||
checkBox.setMinimumSize(size);
|
||||
checkBox.setMaximumSize(size);
|
||||
}
|
||||
|
||||
private XsdConfigItem itemOfRow(JTable table, int row) {
|
||||
if (!(table instanceof TreeTable)) {
|
||||
return null;
|
||||
@@ -713,6 +823,22 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
return itemOf(path.getLastPathComponent());
|
||||
}
|
||||
|
||||
static boolean shouldRenderConfigField(JTable table, int row) {
|
||||
if (!(table instanceof TreeTable)) {
|
||||
return true;
|
||||
}
|
||||
JTree tree = ((TreeTable) table).getTree();
|
||||
TreePath path = tree.getPathForRow(row);
|
||||
if (path == null) {
|
||||
return true;
|
||||
}
|
||||
Object node = path.getLastPathComponent();
|
||||
if (!(node instanceof DefaultMutableTreeNode)) {
|
||||
return true;
|
||||
}
|
||||
return ((DefaultMutableTreeNode) node).getParent() != null;
|
||||
}
|
||||
|
||||
private void installTextEditor(TableColumn column) {
|
||||
column.setCellEditor(new InputLikeTextCellEditor());
|
||||
}
|
||||
@@ -794,25 +920,9 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
}
|
||||
|
||||
private String resolveDefaultOutputPath(File xsdFile) {
|
||||
if (project == null || project.getBasePath() == null) {
|
||||
File parent = xsdFile.getParentFile();
|
||||
return parent == null ? xsdFile.getAbsolutePath() : parent.getAbsolutePath();
|
||||
}
|
||||
File resourcesDir = new File(project.getBasePath(), "src/main/resources");
|
||||
File outputDir = resolveNextMsgMapperDirectory(resourcesDir);
|
||||
return toProjectRelativePath(outputDir);
|
||||
}
|
||||
|
||||
private File resolveNextMsgMapperDirectory(File resourcesDir) {
|
||||
int suffix = 0;
|
||||
while (true) {
|
||||
String dirName = suffix == 0 ? "msgmapper" : "msgmapper" + suffix;
|
||||
File candidate = new File(resourcesDir, dirName);
|
||||
if (!candidate.exists()) {
|
||||
return candidate;
|
||||
}
|
||||
suffix++;
|
||||
}
|
||||
File outputBaseDir = XomPathStrategy.resolveDefaultOutputBaseDir(xsdFile);
|
||||
File outputDir = XomPathStrategy.resolveNextMsgMapperDirectory(outputBaseDir);
|
||||
return toModuleRelativePath(outputDir);
|
||||
}
|
||||
|
||||
private void generateXomFile() {
|
||||
@@ -830,8 +940,8 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
File javaEntityFile = result.getJavaEntityFile();
|
||||
Messages.showInfoMessage(project,
|
||||
"XOM生成成功:\n"
|
||||
+ "XML: " + toProjectRelativePath(result.getMappingXmlFile())
|
||||
+ "\nJAVA: " + (javaEntityFile == null ? "未生成(包名为空)" : toProjectRelativePath(javaEntityFile)),
|
||||
+ "XML: " + toModuleRelativePath(result.getMappingXmlFile())
|
||||
+ "\nJAVA: " + (javaEntityFile == null ? "未生成(包名为空)" : toModuleRelativePath(javaEntityFile)),
|
||||
"完成");
|
||||
} catch (XomGenerationException ex) {
|
||||
Messages.showErrorDialog(project, "XOM生成失败: " + ex.getMessage(), "错误");
|
||||
@@ -886,8 +996,8 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
Messages.showInfoMessage(project,
|
||||
"节点XOM生成成功:\n"
|
||||
+ "节点: " + (nodeName == null ? "(未命名节点)" : nodeName)
|
||||
+ "\nXML: " + toProjectRelativePath(result.getMappingXmlFile())
|
||||
+ "\nJAVA: " + (javaEntityFile == null ? "未生成(包名为空)" : toProjectRelativePath(javaEntityFile)),
|
||||
+ "\nXML: " + toModuleRelativePath(result.getMappingXmlFile())
|
||||
+ "\nJAVA: " + (javaEntityFile == null ? "未生成(包名为空)" : toModuleRelativePath(javaEntityFile)),
|
||||
"完成");
|
||||
} catch (XomGenerationException ex) {
|
||||
Messages.showErrorDialog(project, "节点XOM生成失败: " + ex.getMessage(), "错误");
|
||||
@@ -914,7 +1024,7 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
boolean hasJavaPackage = hasPackageName(packageName);
|
||||
String outputPath = resolveOutputFilePath(outputDirectoryPath, outputFileName);
|
||||
return new XomGenerateOptions(
|
||||
project == null ? null : project.getBasePath(),
|
||||
currentModuleBasePath,
|
||||
null,
|
||||
valueOf(msgTypeField),
|
||||
resultMapType,
|
||||
@@ -998,13 +1108,13 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
if (fileName == null) {
|
||||
fileName = defaultOutputName();
|
||||
}
|
||||
return toProjectRelativePath(new File(directory, fileName));
|
||||
return toModuleRelativePath(new File(directory, fileName));
|
||||
}
|
||||
|
||||
private File resolveOutputDirectory(String outputDirectoryPath) {
|
||||
File candidate = resolvePathAgainstProjectBase(outputDirectoryPath);
|
||||
File candidate = resolvePathAgainstModuleBase(outputDirectoryPath);
|
||||
if (candidate != null) {
|
||||
if (isXmlFilePath(candidate)) {
|
||||
if (XomPathStrategy.isXmlFilePath(candidate)) {
|
||||
File parent = candidate.getParentFile();
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
@@ -1013,25 +1123,14 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
return candidate;
|
||||
}
|
||||
if (currentXsdFile != null) {
|
||||
if (project != null && trimToNull(project.getBasePath()) != null) {
|
||||
File resourcesDir = new File(project.getBasePath(), "src/main/resources");
|
||||
return resolveNextMsgMapperDirectory(resourcesDir);
|
||||
}
|
||||
File parent = currentXsdFile.getParentFile();
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
File outputBaseDir = XomPathStrategy.resolveDefaultOutputBaseDir(currentXsdFile);
|
||||
if (outputBaseDir != null) {
|
||||
return XomPathStrategy.resolveNextMsgMapperDirectory(outputBaseDir);
|
||||
}
|
||||
}
|
||||
return new File("msgmapper");
|
||||
}
|
||||
|
||||
private boolean isXmlFilePath(File file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
return file.getName().toLowerCase(Locale.ROOT).endsWith(".xml");
|
||||
}
|
||||
|
||||
private String valueOf(TextFieldWithBrowseButton field) {
|
||||
if (field == null) {
|
||||
return null;
|
||||
@@ -1054,46 +1153,57 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
outputPathField.setText(toProjectRelativePath(resolveOutputDirectory(path)));
|
||||
outputPathField.setText(toModuleRelativePath(resolveOutputDirectory(path)));
|
||||
}
|
||||
|
||||
private File resolvePathAgainstProjectBase(String path) {
|
||||
String normalizedPath = trimToNull(path);
|
||||
if (normalizedPath == null) {
|
||||
return null;
|
||||
}
|
||||
File candidate = new File(normalizedPath);
|
||||
if (candidate.isAbsolute()) {
|
||||
return candidate;
|
||||
}
|
||||
String projectBasePath = project == null ? null : trimToNull(project.getBasePath());
|
||||
if (projectBasePath == null) {
|
||||
return candidate;
|
||||
}
|
||||
return new File(projectBasePath, normalizedPath);
|
||||
private File resolvePathAgainstModuleBase(String path) {
|
||||
return XomPathStrategy.resolvePath(path, moduleBaseDir());
|
||||
}
|
||||
|
||||
private String toProjectRelativePath(File file) {
|
||||
private String toModuleRelativePath(File file) {
|
||||
if (file == null) {
|
||||
return "";
|
||||
}
|
||||
String projectBasePath = project == null ? null : trimToNull(project.getBasePath());
|
||||
if (projectBasePath == null) {
|
||||
File moduleBaseDir = moduleBaseDir();
|
||||
if (moduleBaseDir == null) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
try {
|
||||
Path basePath = Paths.get(projectBasePath).toAbsolutePath().normalize();
|
||||
Path targetPath = file.toPath().toAbsolutePath().normalize();
|
||||
if (targetPath.startsWith(basePath)) {
|
||||
String relativePath = basePath.relativize(targetPath).toString();
|
||||
if (!relativePath.isEmpty()) {
|
||||
return relativePath.replace(File.separatorChar, '/');
|
||||
return XomPathStrategy.toRelativePath(moduleBaseDir, file);
|
||||
}
|
||||
|
||||
private File moduleBaseDir() {
|
||||
String basePath = trimToNull(currentModuleBasePath);
|
||||
return basePath == null ? null : new File(basePath);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
return file.getAbsolutePath();
|
||||
|
||||
private String resolveModuleBasePath(File file) {
|
||||
File moduleBaseDir = resolveModuleBaseDir(file);
|
||||
return moduleBaseDir == null ? null : moduleBaseDir.getAbsolutePath();
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
|
||||
private File resolveModuleBaseDir(File file) {
|
||||
File contentRoot = findContentRoot(file);
|
||||
if (contentRoot != null) {
|
||||
return contentRoot;
|
||||
}
|
||||
return XomPathStrategy.inferModuleBaseDir(file);
|
||||
}
|
||||
|
||||
private File findContentRoot(File file) {
|
||||
if (project == null || file == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
|
||||
if (virtualFile == null) {
|
||||
return null;
|
||||
}
|
||||
VirtualFile contentRoot = ProjectRootManager.getInstance(project)
|
||||
.getFileIndex()
|
||||
.getContentRootForFile(virtualFile);
|
||||
if (contentRoot == null) {
|
||||
return null;
|
||||
}
|
||||
return new File(contentRoot.getPath());
|
||||
}
|
||||
|
||||
private String removeExtension(String fileName) {
|
||||
@@ -1388,6 +1498,12 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
int row,
|
||||
int column) {
|
||||
setBackground(rowBackground(row, isSelected));
|
||||
boolean showField = shouldRenderConfigField(table, row);
|
||||
fieldPanel.setVisible(showField);
|
||||
if (!showField) {
|
||||
textLabel.setText("");
|
||||
return this;
|
||||
}
|
||||
String text = value == null ? "" : value.toString();
|
||||
textLabel.setText(text);
|
||||
|
||||
@@ -1441,6 +1557,12 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
int row,
|
||||
int column) {
|
||||
setBackground(rowBackground(row, isSelected));
|
||||
boolean showField = shouldRenderConfigField(table, row);
|
||||
fieldPanel.setVisible(showField);
|
||||
if (!showField) {
|
||||
textLabel.setText("");
|
||||
return this;
|
||||
}
|
||||
fieldPanel.setBackground(isSelected ? Palette.FIELD_BG_SELECTED : Palette.FIELD_BG);
|
||||
textLabel.setText(value == null ? "" : value.toString());
|
||||
return this;
|
||||
@@ -1627,14 +1749,14 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
ToggleInputCellRenderer() {
|
||||
setLayout(new BorderLayout());
|
||||
setOpaque(true);
|
||||
setBorder(new EmptyBorder(4, 8, 4, 8));
|
||||
setBorder(new EmptyBorder(4, TOGGLE_INPUT_OUTER_HORIZONTAL_PADDING, 4, TOGGLE_INPUT_OUTER_HORIZONTAL_PADDING));
|
||||
|
||||
fieldPanel = new JPanel(new GridBagLayout());
|
||||
fieldPanel.setOpaque(true);
|
||||
fieldPanel.setBackground(Palette.FIELD_BG);
|
||||
fieldPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||
new LineBorder(Palette.FIELD_BORDER, 1, true),
|
||||
new EmptyBorder(0, 6, 0, 6)));
|
||||
new EmptyBorder(0, TOGGLE_INPUT_INNER_HORIZONTAL_PADDING, 0, TOGGLE_INPUT_INNER_HORIZONTAL_PADDING)));
|
||||
|
||||
checkBox = new JCheckBox();
|
||||
checkBox.setEnabled(true);
|
||||
@@ -1645,19 +1767,16 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
checkBox.setRolloverEnabled(false);
|
||||
checkBox.setBorderPainted(false);
|
||||
checkBox.setMargin(new Insets(0, 0, 0, 0));
|
||||
checkBox.setPreferredSize(new Dimension(18, 18));
|
||||
checkBox.setMinimumSize(new Dimension(18, 18));
|
||||
checkBox.setMaximumSize(new Dimension(18, 18));
|
||||
applyToggleCheckBoxSizing(checkBox);
|
||||
|
||||
textField = new JTextField();
|
||||
textField.setEditable(false);
|
||||
textField.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
|
||||
textField.setBorder(BorderFactory.createEmptyBorder(0, TOGGLE_INPUT_TEXT_HORIZONTAL_PADDING, 0, TOGGLE_INPUT_TEXT_HORIZONTAL_PADDING));
|
||||
textField.setBackground(Palette.FIELD_BG);
|
||||
textField.setForeground(Palette.TEXT_PRIMARY);
|
||||
textField.setFont(textField.getFont().deriveFont(Font.PLAIN, 13f));
|
||||
textField.setPreferredSize(new Dimension(50, 20));
|
||||
textField.setMinimumSize(new Dimension(50, 20));
|
||||
textField.setMaximumSize(new Dimension(50, 20));
|
||||
textField.setPreferredSize(new Dimension(TOGGLE_INPUT_TEXT_WIDTH, 20));
|
||||
textField.setMinimumSize(new Dimension(TOGGLE_INPUT_TEXT_WIDTH, 20));
|
||||
|
||||
GridBagConstraints checkBoxConstraints = new GridBagConstraints();
|
||||
checkBoxConstraints.gridx = 0;
|
||||
@@ -1671,19 +1790,11 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
textFieldConstraints.gridx = 1;
|
||||
textFieldConstraints.gridy = 0;
|
||||
textFieldConstraints.anchor = GridBagConstraints.WEST;
|
||||
textFieldConstraints.weightx = 1.0;
|
||||
textFieldConstraints.weighty = 1.0;
|
||||
textFieldConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
textFieldConstraints.insets = new Insets(0, 0, 0, 0);
|
||||
fieldPanel.add(textField, textFieldConstraints);
|
||||
|
||||
JPanel filler = new JPanel();
|
||||
filler.setOpaque(false);
|
||||
GridBagConstraints fillerConstraints = new GridBagConstraints();
|
||||
fillerConstraints.gridx = 2;
|
||||
fillerConstraints.gridy = 0;
|
||||
fillerConstraints.weightx = 1.0;
|
||||
fillerConstraints.weighty = 1.0;
|
||||
fillerConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
fieldPanel.add(filler, fillerConstraints);
|
||||
add(fieldPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -1694,6 +1805,14 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
boolean hasFocus,
|
||||
int row,
|
||||
int column) {
|
||||
boolean showField = shouldRenderConfigField(table, row);
|
||||
fieldPanel.setVisible(showField);
|
||||
if (!showField) {
|
||||
checkBox.setSelected(false);
|
||||
textField.setText("");
|
||||
setBackground(rowBackground(row, isSelected));
|
||||
return this;
|
||||
}
|
||||
XsdTreeTableModel.ToggleInputValue toggleInputValue = value instanceof XsdTreeTableModel.ToggleInputValue
|
||||
? (XsdTreeTableModel.ToggleInputValue) value
|
||||
: XsdTreeTableModel.ToggleInputValue.EMPTY;
|
||||
@@ -1715,14 +1834,14 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
ToggleInputCellEditor() {
|
||||
wrapper = new JPanel(new BorderLayout());
|
||||
wrapper.setOpaque(true);
|
||||
wrapper.setBorder(new EmptyBorder(4, 8, 4, 8));
|
||||
wrapper.setBorder(new EmptyBorder(4, TOGGLE_INPUT_OUTER_HORIZONTAL_PADDING, 4, TOGGLE_INPUT_OUTER_HORIZONTAL_PADDING));
|
||||
|
||||
fieldPanel = new JPanel(new GridBagLayout());
|
||||
fieldPanel.setOpaque(true);
|
||||
fieldPanel.setBackground(Palette.FIELD_BG);
|
||||
fieldPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||
new LineBorder(Palette.FIELD_BORDER, 1, true),
|
||||
new EmptyBorder(0, 6, 0, 6)));
|
||||
new EmptyBorder(0, TOGGLE_INPUT_INNER_HORIZONTAL_PADDING, 0, TOGGLE_INPUT_INNER_HORIZONTAL_PADDING)));
|
||||
|
||||
checkBox = new JCheckBox();
|
||||
checkBox.setOpaque(false);
|
||||
@@ -1732,18 +1851,15 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
checkBox.setRolloverEnabled(false);
|
||||
checkBox.setBorderPainted(false);
|
||||
checkBox.setMargin(new Insets(0, 0, 0, 0));
|
||||
checkBox.setPreferredSize(new Dimension(18, 18));
|
||||
checkBox.setMinimumSize(new Dimension(18, 18));
|
||||
checkBox.setMaximumSize(new Dimension(18, 18));
|
||||
applyToggleCheckBoxSizing(checkBox);
|
||||
|
||||
textField = new JTextField();
|
||||
textField.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
|
||||
textField.setBorder(BorderFactory.createEmptyBorder(0, TOGGLE_INPUT_TEXT_HORIZONTAL_PADDING, 0, TOGGLE_INPUT_TEXT_HORIZONTAL_PADDING));
|
||||
textField.setBackground(Palette.FIELD_BG);
|
||||
textField.setForeground(Palette.TEXT_PRIMARY);
|
||||
textField.setFont(textField.getFont().deriveFont(Font.PLAIN, 13f));
|
||||
textField.setPreferredSize(new Dimension(50, 20));
|
||||
textField.setMinimumSize(new Dimension(50, 20));
|
||||
textField.setMaximumSize(new Dimension(50, 20));
|
||||
textField.setPreferredSize(new Dimension(TOGGLE_INPUT_TEXT_WIDTH, 20));
|
||||
textField.setMinimumSize(new Dimension(TOGGLE_INPUT_TEXT_WIDTH, 20));
|
||||
|
||||
GridBagConstraints checkBoxConstraints = new GridBagConstraints();
|
||||
checkBoxConstraints.gridx = 0;
|
||||
@@ -1757,19 +1873,11 @@ public class XsdConfigDialog extends DialogWrapper {
|
||||
textFieldConstraints.gridx = 1;
|
||||
textFieldConstraints.gridy = 0;
|
||||
textFieldConstraints.anchor = GridBagConstraints.WEST;
|
||||
textFieldConstraints.weightx = 1.0;
|
||||
textFieldConstraints.weighty = 1.0;
|
||||
textFieldConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
textFieldConstraints.insets = new Insets(0, 0, 0, 0);
|
||||
fieldPanel.add(textField, textFieldConstraints);
|
||||
|
||||
JPanel filler = new JPanel();
|
||||
filler.setOpaque(false);
|
||||
GridBagConstraints fillerConstraints = new GridBagConstraints();
|
||||
fillerConstraints.gridx = 2;
|
||||
fillerConstraints.gridy = 0;
|
||||
fillerConstraints.weightx = 1.0;
|
||||
fillerConstraints.weighty = 1.0;
|
||||
fillerConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
fieldPanel.add(filler, fillerConstraints);
|
||||
wrapper.add(fieldPanel, BorderLayout.CENTER);
|
||||
installCellEditorKeyBindings(wrapper, this);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<idea-plugin>
|
||||
<id>com.dmiki.metaplugin</id>
|
||||
<name>MetaPlugin</name>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
<vendor email="support@example.com" url="https://example.com">RD5</vendor>
|
||||
|
||||
<description><![CDATA[
|
||||
|
||||
@@ -51,7 +51,9 @@
|
||||
### 目标
|
||||
|
||||
将 XSD 结构转为“可执行的自定义 XML 映射配置”,用于后续 生成Java 实体与 XML 的双向/单向序列化流程。
|
||||
1、支持生成xml映射文件,默认存放在resources下msgmapper目录下,如果msgmapper存在,则放在msgmapper1下,如果msgmapper1存在,则放在msgmapper2下,以此类推。
|
||||
1、支持生成xml映射文件:
|
||||
- 如果XSD位于某个resources目录下,则默认存放在该resources下的msgmapper目录;如果msgmapper存在,则放在msgmapper1下,如果msgmapper1存在,则放在msgmapper2下,以此类推。
|
||||
- 如果XSD不在resources目录下,则默认存放在XSD父级目录的同级msgmapper目录;命名冲突时同样按msgmapper1、msgmapper2递增。
|
||||
2、支持生成java实体类,与上一步的xml文件在同一个目录
|
||||
3、预留后续根据xml映射文件和xsd反向展示能里
|
||||
|
||||
|
||||
@@ -80,6 +80,70 @@ public class XomGenerationServiceTest {
|
||||
assertTrue(expected.isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generate_defaultsToResourcesMsgMapperWhenXsdIsUnderResources() throws Exception {
|
||||
File moduleBase = Files.createTempDirectory("xom-service-module-resources").toFile();
|
||||
File xsdFile = new File(moduleBase, "src/main/resources/xsd/demo.xsd");
|
||||
File existingMsgmapper = new File(moduleBase, "src/main/resources/msgmapper");
|
||||
assertTrue(xsdFile.getParentFile().mkdirs() || xsdFile.getParentFile().isDirectory());
|
||||
assertTrue(existingMsgmapper.mkdirs() || existingMsgmapper.isDirectory());
|
||||
assertTrue(xsdFile.createNewFile() || xsdFile.isFile());
|
||||
|
||||
XomGenerateOptions options = new XomGenerateOptions(
|
||||
moduleBase.getAbsolutePath(),
|
||||
"message",
|
||||
"demo",
|
||||
null,
|
||||
xsdFile.getAbsolutePath(),
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
8
|
||||
);
|
||||
|
||||
XomGenerationResult result = service.generate(simpleRoot(), options);
|
||||
File expected = new File(moduleBase, "src/main/resources/msgmapper1/demo.xml");
|
||||
|
||||
assertEquals(expected.getCanonicalFile(), result.getMappingXmlFile().getCanonicalFile());
|
||||
assertTrue(expected.isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generate_defaultsToSiblingMsgMapperWhenXsdIsOutsideResources() throws Exception {
|
||||
File moduleBase = Files.createTempDirectory("xom-service-module-sibling").toFile();
|
||||
File xsdFile = new File(moduleBase, "schema/inbound/demo.xsd");
|
||||
File existingMsgmapper = new File(moduleBase, "schema/msgmapper");
|
||||
assertTrue(xsdFile.getParentFile().mkdirs() || xsdFile.getParentFile().isDirectory());
|
||||
assertTrue(existingMsgmapper.mkdirs() || existingMsgmapper.isDirectory());
|
||||
assertTrue(xsdFile.createNewFile() || xsdFile.isFile());
|
||||
|
||||
XomGenerateOptions options = new XomGenerateOptions(
|
||||
moduleBase.getAbsolutePath(),
|
||||
"message",
|
||||
"demo",
|
||||
null,
|
||||
xsdFile.getAbsolutePath(),
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
8
|
||||
);
|
||||
|
||||
XomGenerationResult result = service.generate(simpleRoot(), options);
|
||||
File expected = new File(moduleBase, "schema/msgmapper1/demo.xml");
|
||||
|
||||
assertEquals(expected.getCanonicalFile(), result.getMappingXmlFile().getCanonicalFile());
|
||||
assertTrue(expected.isFile());
|
||||
}
|
||||
|
||||
private XomGenerateOptions options(File xsdFile, File outputFile, String resultMapType, boolean strictMode) {
|
||||
return new XomGenerateOptions(
|
||||
null,
|
||||
|
||||
@@ -106,10 +106,11 @@ public class XomXmlRendererTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void render_schemaUsesResourceRelativeXsdPathWhenProjectBaseProvided() throws Exception {
|
||||
public void render_schemaUsesModuleRelativeXsdPathWhenXsdIsOutsideResources() throws Exception {
|
||||
File projectBase = Files.createTempDirectory("xom-render-project").toFile();
|
||||
File xsdFile = new File(projectBase, "xsd/camt.053.001.01.xsd");
|
||||
xsdFile.getParentFile().mkdirs();
|
||||
assertTrue(xsdFile.createNewFile() || xsdFile.isFile());
|
||||
|
||||
XsdConfigItem root = new XsdConfigItem("Root", false, false, false);
|
||||
root.setJavaProperty("root");
|
||||
@@ -130,8 +131,7 @@ public class XomXmlRendererTest {
|
||||
8
|
||||
);
|
||||
String xml = renderer.render(root, options);
|
||||
Path resourcePath = projectBase.toPath().resolve("src/main/resources");
|
||||
String expectedRelative = resourcePath.relativize(xsdFile.toPath())
|
||||
String expectedRelative = projectBase.toPath().relativize(xsdFile.toPath())
|
||||
.toString()
|
||||
.replace(File.separatorChar, '/');
|
||||
|
||||
@@ -139,6 +139,40 @@ public class XomXmlRendererTest {
|
||||
assertFalse(xml.contains("reverse-reserved"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void render_schemaUsesResourcesRelativeXsdPathWhenXsdIsUnderResources() throws Exception {
|
||||
File projectBase = Files.createTempDirectory("xom-render-project-resources").toFile();
|
||||
File xsdFile = new File(projectBase, "src/main/resources/xsd/demo.xsd");
|
||||
xsdFile.getParentFile().mkdirs();
|
||||
assertTrue(xsdFile.createNewFile() || xsdFile.isFile());
|
||||
|
||||
XsdConfigItem root = new XsdConfigItem("Root", false, false, false);
|
||||
root.setJavaProperty("root");
|
||||
|
||||
XomGenerateOptions options = new XomGenerateOptions(
|
||||
projectBase.getAbsolutePath(),
|
||||
"message",
|
||||
"schema",
|
||||
"com.demo.Root",
|
||||
xsdFile.getAbsolutePath(),
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
8
|
||||
);
|
||||
String xml = renderer.render(root, options);
|
||||
Path resourcesPath = projectBase.toPath().resolve("src/main/resources");
|
||||
String expectedRelative = resourcesPath.relativize(xsdFile.toPath())
|
||||
.toString()
|
||||
.replace(File.separatorChar, '/');
|
||||
|
||||
assertTrue(xml.contains("xsdPath=\"" + expectedRelative + "\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void render_rendersAttributeCarrierNodeWithAttributeChild() {
|
||||
XsdConfigItem root = new XsdConfigItem("Document", false, false, false);
|
||||
|
||||
@@ -2,6 +2,7 @@ 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.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
@@ -16,6 +17,7 @@ import static org.junit.Assert.fail;
|
||||
|
||||
public class ReservedXomReverseDisplayServiceTest {
|
||||
private final ReservedXomReverseDisplayService service = new ReservedXomReverseDisplayService();
|
||||
private final XsdSchemaParser parser = new XsdSchemaParser();
|
||||
|
||||
@Test
|
||||
public void loadForDisplay_replaysNodeConfigAndTopFields() throws Exception {
|
||||
@@ -112,6 +114,140 @@ public class ReservedXomReverseDisplayServiceTest {
|
||||
assertEquals("id", id.getJavaProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadForDisplay_resolvesModuleRelativeXsdPathOutsideResources() throws Exception {
|
||||
File moduleDir = Files.createTempDirectory("reverse-project-module-relative").toFile();
|
||||
File xsdFile = new File(moduleDir, "schema/inbound/demo.xsd");
|
||||
File xomFile = new File(moduleDir, "schema/msgmapper/demo.xml");
|
||||
|
||||
writeFile(xsdFile, simpleSchema());
|
||||
writeFile(xomFile, moduleRelativeMapping());
|
||||
|
||||
XomReverseDisplayResult result = service.loadForDisplay(xomFile, moduleDir.getAbsolutePath());
|
||||
|
||||
assertEquals(xsdFile.getAbsolutePath(), result.getXsdFile().getAbsolutePath());
|
||||
XsdConfigItem root = result.getRoot();
|
||||
XsdConfigItem id = childByName(root, "Id");
|
||||
assertNotNull(id);
|
||||
assertEquals(XsdConfigMode.KEEP, id.getConfigMode());
|
||||
assertEquals("id", id.getJavaProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyToCurrentTree_replaysSelectedSubtreeFromPartialMapping() throws Exception {
|
||||
File projectDir = Files.createTempDirectory("reverse-apply-subtree").toFile();
|
||||
File resourcesDir = new File(projectDir, "src/main/resources");
|
||||
File xsdFile = new File(resourcesDir, "xsd/demo.xsd");
|
||||
File xomFile = new File(resourcesDir, "msgmapper/group.xml");
|
||||
|
||||
writeFile(xsdFile, schemaForReplay());
|
||||
writeFile(xomFile, groupOnlyMapping());
|
||||
|
||||
XsdConfigItem currentRoot = parser.parse(xsdFile);
|
||||
XsdConfigItem group = childByName(currentRoot, "Group");
|
||||
XsdConfigItem msgId = childByName(group, "MsgId");
|
||||
XsdConfigItem creDtTm = childByName(group, "CreDtTm");
|
||||
group.setJavaProperty("staleGroup");
|
||||
creDtTm.setJavaProperty("staleCreDtTm");
|
||||
creDtTm.setConfigMode(XsdConfigMode.KEEP);
|
||||
|
||||
XomReverseDisplayResult result = service.applyToCurrentTree(
|
||||
xomFile,
|
||||
projectDir.getAbsolutePath(),
|
||||
currentRoot,
|
||||
group);
|
||||
|
||||
assertEquals(xsdFile.getAbsolutePath(), result.getXsdFile().getAbsolutePath());
|
||||
assertEquals(XsdConfigMode.PRESERVE_HIERARCHY, group.getConfigMode());
|
||||
assertNull(group.getJavaProperty());
|
||||
assertEquals(XsdConfigMode.KEEP, msgId.getConfigMode());
|
||||
assertEquals("msgId", msgId.getJavaProperty());
|
||||
assertTrue(msgId.isCustomAttributeEnabled());
|
||||
assertTrue(msgId.isPreProcessorEnabled());
|
||||
assertEquals(XsdConfigMode.IGNORE, creDtTm.getConfigMode());
|
||||
assertNull(creDtTm.getJavaProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyToCurrentTree_usesClosestAncestorPoolForNestedSelection() throws Exception {
|
||||
File projectDir = Files.createTempDirectory("reverse-apply-nested").toFile();
|
||||
File resourcesDir = new File(projectDir, "src/main/resources");
|
||||
File xsdFile = new File(resourcesDir, "xsd/demo.xsd");
|
||||
File xomFile = new File(resourcesDir, "msgmapper/group-nested.xml");
|
||||
|
||||
writeFile(xsdFile, schemaForReplay());
|
||||
writeFile(xomFile, nestedGroupMapping());
|
||||
|
||||
XsdConfigItem currentRoot = parser.parse(xsdFile);
|
||||
XsdConfigItem group = childByName(currentRoot, "Group");
|
||||
XsdConfigItem msgId = childByName(group, "MsgId");
|
||||
msgId.setJavaProperty("staleMsgId");
|
||||
msgId.setConfigMode(XsdConfigMode.IGNORE);
|
||||
|
||||
service.applyToCurrentTree(xomFile, projectDir.getAbsolutePath(), currentRoot, msgId);
|
||||
|
||||
assertEquals(XsdConfigMode.KEEP, msgId.getConfigMode());
|
||||
assertEquals("msgId", msgId.getJavaProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyToCurrentTree_supportsFlattenedLabelsGeneratedForSelectedNode() throws Exception {
|
||||
File projectDir = Files.createTempDirectory("reverse-apply-flatten").toFile();
|
||||
File resourcesDir = new File(projectDir, "src/main/resources");
|
||||
File xsdFile = new File(resourcesDir, "xsd/demo.xsd");
|
||||
File xomFile = new File(resourcesDir, "msgmapper/flat-parent.xml");
|
||||
|
||||
writeFile(xsdFile, schemaForReplay());
|
||||
writeFile(xomFile, flattenedFlatParentMapping());
|
||||
|
||||
XsdConfigItem currentRoot = parser.parse(xsdFile);
|
||||
XsdConfigItem flatParent = childByName(currentRoot, "FlatParent");
|
||||
XsdConfigItem a = childByName(flatParent, "A");
|
||||
XsdConfigItem b = childByName(flatParent, "B");
|
||||
a.setJavaProperty("staleA");
|
||||
b.setJavaProperty("staleB");
|
||||
|
||||
service.applyToCurrentTree(xomFile, projectDir.getAbsolutePath(), currentRoot, flatParent);
|
||||
|
||||
assertEquals(XsdConfigMode.FLATTEN, flatParent.getConfigMode());
|
||||
assertEquals(XsdConfigMode.KEEP, a.getConfigMode());
|
||||
assertEquals("a", a.getJavaProperty());
|
||||
assertEquals(XsdConfigMode.KEEP, b.getConfigMode());
|
||||
assertEquals("b", b.getJavaProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyToCurrentTree_preservesFlattenModeForNestedSelectedNodeInFullMapping() throws Exception {
|
||||
File projectDir = Files.createTempDirectory("reverse-apply-nested-flatten").toFile();
|
||||
File resourcesDir = new File(projectDir, "src/main/resources");
|
||||
File xsdFile = new File(resourcesDir, "xsd/demo.xsd");
|
||||
File xomFile = new File(resourcesDir, "msgmapper/nested-flat-parent.xml");
|
||||
|
||||
writeFile(xsdFile, schemaForReplay());
|
||||
writeFile(xomFile, fullMappingWithFlattenedFlatParent());
|
||||
|
||||
XsdConfigItem currentRoot = parser.parse(xsdFile);
|
||||
XsdConfigItem flatParent = childByName(currentRoot, "FlatParent");
|
||||
XsdConfigItem a = childByName(flatParent, "A");
|
||||
XsdConfigItem b = childByName(flatParent, "B");
|
||||
|
||||
flatParent.setJavaProperty("staleFlatParent");
|
||||
flatParent.setConfigMode(XsdConfigMode.PRESERVE_HIERARCHY_AND_ATTRIBUTES);
|
||||
a.setJavaProperty("staleA");
|
||||
a.setConfigMode(XsdConfigMode.IGNORE);
|
||||
b.setJavaProperty("staleB");
|
||||
b.setConfigMode(XsdConfigMode.IGNORE);
|
||||
|
||||
service.applyToCurrentTree(xomFile, projectDir.getAbsolutePath(), currentRoot, flatParent);
|
||||
|
||||
assertEquals(XsdConfigMode.FLATTEN, flatParent.getConfigMode());
|
||||
assertNull(flatParent.getJavaProperty());
|
||||
assertEquals(XsdConfigMode.KEEP, a.getConfigMode());
|
||||
assertEquals("a", a.getJavaProperty());
|
||||
assertEquals(XsdConfigMode.KEEP, b.getConfigMode());
|
||||
assertEquals("b", b.getJavaProperty());
|
||||
}
|
||||
|
||||
private void writeFile(File file, String content) throws Exception {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
@@ -223,4 +359,70 @@ public class ReservedXomReverseDisplayServiceTest {
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
|
||||
private String moduleRelativeMapping() {
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<message>\n"
|
||||
+ " <schema id=\"demo\" xsdPath=\"schema/inbound/demo.xsd\"/>\n"
|
||||
+ " <resultMap id=\"demo\" type=\"com.demo.Document\">\n"
|
||||
+ " <result label=\"Document\">\n"
|
||||
+ " <result property=\"id\" label=\"Id\"/>\n"
|
||||
+ " </result>\n"
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
|
||||
private String groupOnlyMapping() {
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<message>\n"
|
||||
+ " <schema id=\"demo\" xsdPath=\"xsd/demo.xsd\"/>\n"
|
||||
+ " <resultMap id=\"demo\" type=\"com.demo.Document\">\n"
|
||||
+ " <result lable=\"Group\">\n"
|
||||
+ " <result property=\"msgId\" lable=\"MsgId\" attribute=\"sign\" pre-processor=\"trim\"/>\n"
|
||||
+ " </result>\n"
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
|
||||
private String nestedGroupMapping() {
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<message>\n"
|
||||
+ " <schema id=\"demo\" xsdPath=\"xsd/demo.xsd\"/>\n"
|
||||
+ " <resultMap id=\"demo\" type=\"com.demo.Document\">\n"
|
||||
+ " <result lable=\"Group\">\n"
|
||||
+ " <result property=\"msgId\" lable=\"MsgId\"/>\n"
|
||||
+ " </result>\n"
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
|
||||
private String flattenedFlatParentMapping() {
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<message>\n"
|
||||
+ " <schema id=\"demo\" xsdPath=\"xsd/demo.xsd\"/>\n"
|
||||
+ " <resultMap id=\"demo\" type=\"com.demo.Document\">\n"
|
||||
+ " <result property=\"a\" lable=\"FlatParent:A\"/>\n"
|
||||
+ " <result property=\"b\" lable=\"FlatParent:B\"/>\n"
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
|
||||
private String fullMappingWithFlattenedFlatParent() {
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
+ "<message>\n"
|
||||
+ " <schema id=\"demo\" xsdPath=\"xsd/demo.xsd\"/>\n"
|
||||
+ " <resultMap id=\"demo\" type=\"com.demo.Document\">\n"
|
||||
+ " <result property=\"document\" lable=\"Document\">\n"
|
||||
+ " <result label=\"Group\">\n"
|
||||
+ " <result property=\"msgId\" label=\"MsgId\"/>\n"
|
||||
+ " </result>\n"
|
||||
+ " <result property=\"a\" lable=\"FlatParent:A\"/>\n"
|
||||
+ " <result property=\"b\" lable=\"FlatParent:B\"/>\n"
|
||||
+ " <result property=\"amt\" label=\"Amt\">\n"
|
||||
+ " <result property=\"ccy\" label-attribute=\"Ccy\"/>\n"
|
||||
+ " </result>\n"
|
||||
+ " </result>\n"
|
||||
+ " </resultMap>\n"
|
||||
+ "</message>\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.dmiki.metaplugin.xsd.ui;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class JavaPackageResolverTest {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void resolveDefaultPackage_prefersEntityThenDomainThenMessage() throws Exception {
|
||||
File moduleDir = temporaryFolder.newFolder("module-entity-priority");
|
||||
new File(moduleDir, "src/main/java/com/demo/message").mkdirs();
|
||||
new File(moduleDir, "src/main/java/com/demo/domain").mkdirs();
|
||||
new File(moduleDir, "src/main/java/com/demo/entity").mkdirs();
|
||||
|
||||
assertEquals("com.demo.entity", JavaPackageResolver.resolveDefaultPackage(moduleDir));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDefaultPackage_infersCommonPackageWhenPreferredPackagesAreMissing() throws Exception {
|
||||
File moduleDir = temporaryFolder.newFolder("module-common-package");
|
||||
writeJavaFile(moduleDir, "src/main/java/com/acme/payment/service/PaymentService.java");
|
||||
writeJavaFile(moduleDir, "src/main/java/com/acme/payment/model/PaymentModel.java");
|
||||
|
||||
assertEquals("com.acme.payment", JavaPackageResolver.resolveDefaultPackage(moduleDir));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDefaultPackage_returnsNullWhenModuleIsNotJavaProject() throws Exception {
|
||||
File moduleDir = temporaryFolder.newFolder("module-no-java");
|
||||
new File(moduleDir, "src/main/resources").mkdirs();
|
||||
|
||||
assertNull(JavaPackageResolver.resolveDefaultPackage(moduleDir));
|
||||
}
|
||||
|
||||
private void writeJavaFile(File moduleDir, String relativePath) throws Exception {
|
||||
File javaFile = new File(moduleDir, relativePath);
|
||||
File parent = javaFile.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
Files.write(javaFile.toPath(), "class Test {}".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.dmiki.metaplugin.xsd.ui;
|
||||
|
||||
import com.dmiki.metaplugin.xsd.model.XsdConfigItem;
|
||||
import com.intellij.ui.treeStructure.treetable.TreeTable;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.Dimension;
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class XsdConfigDialogTest {
|
||||
|
||||
@Test
|
||||
public void shouldRenderConfigField_hidesRendererShellForRootRow() {
|
||||
XsdConfigItem root = new XsdConfigItem("Document", false, false, false);
|
||||
root.getChildren().add(new XsdConfigItem("GrpHdr", true, false, false));
|
||||
|
||||
DefaultMutableTreeNode swingRoot = XsdTreeTableModel.toSwingTree(root);
|
||||
TreeTable table = new TreeTable(new XsdTreeTableModel(swingRoot));
|
||||
table.getTree().setRootVisible(true);
|
||||
table.getTree().expandRow(0);
|
||||
|
||||
assertFalse(XsdConfigDialog.shouldRenderConfigField(table, 0));
|
||||
assertTrue(XsdConfigDialog.shouldRenderConfigField(table, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toggleCheckBoxSize_usesLargestAxisAndMinimumFloor() {
|
||||
assertEquals(new Dimension(20, 20), XsdConfigDialog.toggleCheckBoxSize(new Dimension(18, 18)));
|
||||
assertEquals(new Dimension(22, 22), XsdConfigDialog.toggleCheckBoxSize(new Dimension(22, 18)));
|
||||
assertEquals(new Dimension(20, 20), XsdConfigDialog.toggleCheckBoxSize(null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user